@energie360/ui-library 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/base/_input-resets.scss +9 -3
  2. package/base/_resets.scss +5 -0
  3. package/base/abstracts/_mixins.scss +11 -5
  4. package/base/main-base.scss +1 -0
  5. package/components/accordion-item/accordion-item.scss +62 -0
  6. package/components/accordion-item/u-accordion-item.vue +44 -0
  7. package/components/card/card.scss +58 -0
  8. package/components/card/u-card.vue +26 -0
  9. package/components/card-header/card-header.scss +102 -0
  10. package/components/card-header/u-card-header.vue +51 -0
  11. package/components/card-hint/card-hint.scss +13 -0
  12. package/components/card-hint/u-card-hint.vue +23 -0
  13. package/components/card-price/card-price.scss +110 -0
  14. package/components/card-price/u-card-price.vue +47 -0
  15. package/components/card-table/card-table.scss +76 -0
  16. package/components/card-table/u-card-table.vue +77 -0
  17. package/components/card-toggle-switches/card-toggle-switches.scss +13 -0
  18. package/components/card-toggle-switches/u-card-toggle-switches.vue +30 -0
  19. package/components/collapsible/collapsible.scss +14 -0
  20. package/components/collapsible/u-collapsible.vue +81 -0
  21. package/components/collapsible-group/u-collapsible-group.vue +14 -0
  22. package/components/icon-teaser/icon-teaser.scss +58 -0
  23. package/components/icon-teaser/u-icon-teaser.vue +35 -0
  24. package/components/icon-teaser-group/icon-teaser-group.scss +10 -0
  25. package/components/icon-teaser-group/u-icon-teaser-group.vue +19 -0
  26. package/components/icon-text-block/{icon-text-block.vue → u-icon-text-block.vue} +11 -12
  27. package/components/index.js +14 -0
  28. package/components/language-nav/language-nav.scss +32 -0
  29. package/components/language-nav/u-language-nav.vue +27 -0
  30. package/components/panel/panel.scss +107 -0
  31. package/components/panel/u-panel.vue +48 -0
  32. package/components/progress-bar/progress-bar.scss +37 -0
  33. package/components/progress-bar/u-progress-bar.vue +21 -0
  34. package/components/richtext/richtext.scss +208 -0
  35. package/components/richtext/u-richtext.vue +21 -0
  36. package/components/table/cell-ctas.scss +12 -0
  37. package/components/table/cell-icon-group.scss +12 -0
  38. package/components/table/cell-icon-text.scss +22 -0
  39. package/components/table/cell-progress-bar.scss +23 -0
  40. package/components/table/table-cell.mixins.scss +60 -0
  41. package/components/table/table-cell.scss +24 -0
  42. package/components/table/table-header.scss +5 -0
  43. package/components/table/table-heading.scss +8 -0
  44. package/components/table/table-row.scss +20 -0
  45. package/components/table/table.scss +12 -0
  46. package/components/table/table.type.ts +31 -0
  47. package/components/table/u-cell-ctas.vue +28 -0
  48. package/components/table/u-cell-icon-group.vue +31 -0
  49. package/components/table/u-cell-icon-text.vue +23 -0
  50. package/components/table/u-cell-progress-bar.vue +22 -0
  51. package/components/table/u-table-cell.vue +37 -0
  52. package/components/table/u-table-header.vue +9 -0
  53. package/components/table/u-table-heading.vue +21 -0
  54. package/components/table/u-table-row.vue +17 -0
  55. package/components/table/u-table.vue +11 -0
  56. package/components/tooltip/dom.js +167 -0
  57. package/components/tooltip/popover.ts +208 -0
  58. package/components/tooltip/tooltip.scss +75 -0
  59. package/components/tooltip/u-tooltip.vue +72 -0
  60. package/components/tooltip/viewport.js +21 -0
  61. package/custom-elements.js +1 -0
  62. package/dist/base-style.css +409 -2
  63. package/dist/base-style.css.map +1 -0
  64. package/dist/elements/text-link.css +40 -0
  65. package/dist/elements/text-link.css.map +1 -0
  66. package/dist/layout/split.css +124 -0
  67. package/dist/layout/split.css.map +1 -0
  68. package/elements/button/_button-base.scss +1 -1
  69. package/elements/button/_button-filled-inverted.scss +3 -3
  70. package/elements/button/_button-filled.scss +3 -3
  71. package/elements/button/_button-outlined-inverted.scss +3 -3
  72. package/elements/button/_button-outlined.scss +3 -3
  73. package/elements/button/_button-plain.scss +3 -3
  74. package/elements/button/_button-secondary-outlined.scss +3 -3
  75. package/elements/button/button.js +2 -2
  76. package/elements/button/button.scss +1 -1
  77. package/elements/button/u-button.vue +41 -0
  78. package/elements/button-chip/button-chip.scss +83 -0
  79. package/elements/button-chip/u-button-chip.vue +45 -0
  80. package/elements/elements.js +35 -0
  81. package/elements/form-field/form-field-base.scss +141 -0
  82. package/elements/form-field/form-field-error.scss +20 -0
  83. package/elements/form-field/form-field-prefix-suffix.scss +80 -0
  84. package/elements/form-field/form-field-states.scss +59 -0
  85. package/elements/form-field/form-field.types.ts +8 -0
  86. package/elements/form-field/index.scss +4 -0
  87. package/elements/icon/icon.js +2 -2
  88. package/elements/icon/{icon.vue → u-icon.vue} +12 -18
  89. package/elements/icon-button/icon-button.js +2 -2
  90. package/elements/icon-button/{icon-button.vue → u-icon-button.vue} +14 -15
  91. package/elements/image/image.scss +3 -0
  92. package/elements/image/u-image.vue +17 -0
  93. package/elements/index.js +6 -31
  94. package/elements/loader/loader.js +2 -2
  95. package/elements/loader/{loader.vue → u-loader.vue} +6 -7
  96. package/elements/numeric-stepper/numeric-stepper.scss +110 -0
  97. package/elements/numeric-stepper/u-numeric-stepper.vue +135 -0
  98. package/elements/select/select.scss +32 -0
  99. package/elements/select/u-select.vue +130 -0
  100. package/elements/select-chip/select-chip.scss +18 -0
  101. package/elements/select-chip/u-select-chip.vue +50 -0
  102. package/elements/select-chips/select-chips.scss +5 -0
  103. package/elements/select-chips/u-select-chips.vue +23 -0
  104. package/elements/spectro/spectro.scss +10 -0
  105. package/elements/spectro/u-spectro.vue +11 -0
  106. package/elements/text-field/text-field.scss +30 -0
  107. package/elements/text-field/text-field.types.ts +6 -0
  108. package/elements/text-field/u-text-field.vue +180 -0
  109. package/elements/text-link/text-link.scss +57 -0
  110. package/elements/toggle-switch/toggle-switch-small.scss +40 -0
  111. package/elements/toggle-switch/toggle-switch.scss +149 -0
  112. package/elements/toggle-switch/u-toggle-switch.vue +68 -0
  113. package/elements/types.ts +19 -0
  114. package/env.d.ts +1 -0
  115. package/globals.js +1 -2
  116. package/helpers/transition-height.vue +39 -0
  117. package/i18n/i18n.ts +40 -0
  118. package/layout/grid/grid.mixin.scss +4 -11
  119. package/layout/grid/grid.scss +6 -7
  120. package/layout/split/split.scss +96 -0
  121. package/modules/footer/footer.scss +161 -0
  122. package/modules/footer/u-footer.vue +59 -0
  123. package/package.json +33 -13
  124. package/tsconfig.app.json +12 -0
  125. package/tsconfig.json +11 -0
  126. package/tsconfig.node.json +19 -0
  127. package/utility/elements/text-link.scss +1 -0
  128. package/utility/layout/split.scss +1 -0
  129. package/utility/utility-text.js +1 -0
  130. package/utils/object/deep-get.js +1 -2
  131. package/utils/translations/translate.js +13 -0
  132. package/{vite.config.js → vite.config.ts} +2 -1
  133. package/watch.js +27 -0
  134. package/wizard/index.js +4 -0
  135. package/wizard/wizard-intro/{wizard-intro.vue → u-wizard-intro.vue} +12 -9
  136. package/wizard/wizard-intro/wizard-intro.scss +4 -0
  137. package/wizard/wizard-layout/{wizard-layout-block.vue → u-wizard-layout-block.vue} +7 -5
  138. package/wizard/wizard-layout/{wizard-layout-element.vue → u-wizard-layout-element.vue} +1 -1
  139. package/wizard/wizard-layout/{wizard-layout.vue → u-wizard-layout.vue} +1 -1
  140. package/wizard/wizard-layout/wizard-layout.scss +6 -6
  141. package/dist/base-style.js +0 -2
  142. package/dist/base-style.js.map +0 -1
  143. package/dist/index.css +0 -1
  144. package/dist/index.js +0 -5194
  145. package/dist/index.js.map +0 -1
  146. package/elements/button/button.vue +0 -42
  147. package/index.js +0 -1
  148. /package/components/icon-text-block-group/{icon-text-block-group.vue → u-icon-text-block-group.vue} +0 -0
@@ -1,23 +1,22 @@
1
- <script setup>
2
- import Icon from '../icon/icon.vue'
1
+ <script setup lang="ts">
2
+ import UIcon from '../icon/u-icon.vue'
3
3
 
4
- defineProps({
5
- label: String,
6
- icon: String,
7
- variant: String,
8
- disabled: Boolean,
9
- href: String,
10
- target: {
11
- type: String,
12
- default: '_blank',
13
- },
14
- })
4
+ interface Props {
5
+ label?: string
6
+ icon: string
7
+ variant?: string
8
+ disabled?: boolean
9
+ href?: string
10
+ target?: string
11
+ }
12
+
13
+ const { target = '_blank', variant = 'filled' } = defineProps<Props>()
15
14
  </script>
16
15
 
17
16
  <template>
18
17
  <template v-if="href">
19
18
  <a :class="['icon-button', variant]" :href="href" :target="target">
20
- <Icon v-if="icon" :name="icon" />
19
+ <UIcon v-if="icon" :name="icon" />
21
20
 
22
21
  <span class="visually-hidden">
23
22
  <slot>{{ label }}</slot>
@@ -27,7 +26,7 @@ defineProps({
27
26
 
28
27
  <template v-else>
29
28
  <button :class="['icon-button', variant]" :disabled="disabled || null">
30
- <Icon v-if="icon" :name="icon" />
29
+ <UIcon v-if="icon" :name="icon" />
31
30
 
32
31
  <span class="visually-hidden">
33
32
  <slot>{{ label }}</slot>
@@ -0,0 +1,3 @@
1
+ .image {
2
+ // TODO: migrate `img` element.
3
+ }
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ import { Image } from '../types'
3
+
4
+ // interface Props extends Image {}
5
+
6
+ defineProps<Image>()
7
+ </script>
8
+
9
+ <template>
10
+ <picture>
11
+ <img class="image" :src :alt />
12
+ </picture>
13
+ </template>
14
+
15
+ <style lang="scss" scoped>
16
+ @use './image.scss';
17
+ </style>
package/elements/index.js CHANGED
@@ -1,33 +1,8 @@
1
- import { ButtonElement as Button } from './button/button.js'
2
- import { IconButtonElement as IconButton } from './icon-button/icon-button.js'
3
- import { IconElement as Icon } from './icon/icon.js'
4
- import { LoaderElement as Loader } from './loader/loader.js'
5
-
6
1
  /**
7
- * These exports allow best tree-shaking behaviour.
8
- * Obviously you'll have to import 'manually' all the elements you need.
9
- *
10
- * @example
11
- * import { IconButton } from '@energie360/ui-library'
12
- * IconButton.register()
2
+ * It's not recommended to import components from this file, because tree-shaking won't work then.
3
+ * -> https://vite.dev/guide/performance#avoid-barrel-files
13
4
  */
14
- export { Button, IconButton, Icon, Loader }
15
-
16
- /**
17
- * With this export you get all the elements. Meaning all the elements will be inlcuded in your bundle, even if you register only one element.
18
- *
19
- * @example
20
- * import { Elements } from '@energie360/ui-library'
21
- * Elements.Button.register()
22
- */
23
-
24
- /**
25
- *
26
- * @type {{IconButton: {register: function(): void}, Button: {register: function(): void}, Loader: {register: function(): void}, Icon: {register: function(): void}}}
27
- */
28
- export const Elements = {
29
- Button,
30
- IconButton,
31
- Icon,
32
- Loader,
33
- }
5
+ export { default as UButton } from './button/u-button.vue'
6
+ export { default as UIcon } from './icon/u-icon.vue'
7
+ export { default as UIconButton } from './icon-button/u-icon-button.vue'
8
+ export { default as ULoader } from './loader/u-loader.vue'
@@ -1,5 +1,5 @@
1
1
  import { defineCustomElement } from 'vue'
2
- import Loader from './loader.vue'
2
+ import Loader from './u-loader.vue'
3
3
  import customElementBaseStyle from '../../base/custom-element-base.scss?inline'
4
4
  import ceStyles from './loader.ce.scss?inline'
5
5
  import styles from './loader.scss?inline'
@@ -8,6 +8,6 @@ Loader.styles = [customElementBaseStyle, ceStyles, styles]
8
8
 
9
9
  export const LoaderElement = {
10
10
  register: () => {
11
- customElements.define('e-loader', defineCustomElement(Loader))
11
+ customElements.define('u-loader', defineCustomElement(Loader))
12
12
  },
13
13
  }
@@ -1,10 +1,9 @@
1
- <script setup>
2
- defineProps({
3
- anim: {
4
- type: String,
5
- default: 'horizontal',
6
- },
7
- })
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ anim?: string
4
+ }
5
+
6
+ const { anim = 'horizontal' } = defineProps<Props>()
8
7
  </script>
9
8
 
10
9
  <template>
@@ -0,0 +1,110 @@
1
+ @use '../../base/abstracts' as a;
2
+
3
+ .numeric-stepper {
4
+ display: flex;
5
+ width: a.rem(186);
6
+ height: a.rem(58);
7
+ padding: var(--e-space-2);
8
+ background-color: var(--e-c-mono-00);
9
+ box-shadow: var(--e-elevation-md);
10
+ border-radius: a.rem(28);
11
+ border: 1px solid var(--e-c-mono-100);
12
+ gap: var(--e-space-2);
13
+
14
+ // States
15
+ &.is-hovering,
16
+ &.is-focused {
17
+ .control {
18
+ border: 2px solid var(--e-c-primary-01-900);
19
+ }
20
+ }
21
+
22
+ &.readonly {
23
+ .control {
24
+ background-color: var(--e-c-mono-50);
25
+ }
26
+ }
27
+
28
+ &.disabled {
29
+ .control input {
30
+ color: var(--e-c-mono-500);
31
+ }
32
+ }
33
+
34
+ &.has-error {
35
+ .control {
36
+ border: 2px solid var(--e-c-signal-03-700);
37
+ }
38
+
39
+ ~ .error-message-container {
40
+ display: block;
41
+ }
42
+ }
43
+ }
44
+
45
+ .control {
46
+ order: 2;
47
+ border: 2px solid transparent;
48
+ border-radius: var(--e-brd-radius-1);
49
+ width: a.rem(74);
50
+ height: a.rem(40);
51
+ transition: border var(--e-trs-duration-faster) var(--e-trs-easing-default);
52
+
53
+ input {
54
+ padding: 0;
55
+ width: 100%;
56
+ height: 100%;
57
+ text-align: center;
58
+ color: var(--e-c-mono-900);
59
+ border-radius: var(--e-brd-radius-1);
60
+ }
61
+ }
62
+
63
+ .stepper-button {
64
+ display: flex;
65
+ width: a.rem(40);
66
+ height: a.rem(40);
67
+ border-radius: 100%;
68
+ background-color: transparent;
69
+ align-items: center;
70
+ justify-content: center;
71
+ color: var(--e-c-mono-900);
72
+ transition:
73
+ background-color var(--e-trs-duration-faster) var(--e-trs-easing-default),
74
+ color var(--e-trs-duration-faster) var(--e-trs-easing-default);
75
+
76
+ &:hover {
77
+ background-color: var(--e-c-primary-01-50);
78
+ color: var(--e-c-primary-01-700);
79
+ }
80
+
81
+ &:active {
82
+ background-color: var(--e-c-primary-01-100);
83
+ color: var(--e-c-primary-01-700);
84
+ }
85
+
86
+ &:disabled {
87
+ color: var(--e-c-mono-500);
88
+ background-color: transparent;
89
+ }
90
+
91
+ &.dec {
92
+ order: 1;
93
+ }
94
+
95
+ &.inc {
96
+ order: 3;
97
+ }
98
+ }
99
+
100
+ .error-message-container {
101
+ @include a.type(50);
102
+
103
+ display: none;
104
+ margin-top: var(--e-space-1);
105
+ color: var(--e-c-signal-03-700);
106
+ }
107
+
108
+ .visually-hidden {
109
+ @include a.visually-hidden;
110
+ }
@@ -0,0 +1,135 @@
1
+ <script setup lang="ts">
2
+ import UIcon from '../icon/u-icon.vue'
3
+ import { computed, ref, useTemplateRef, useId, nextTick } from 'vue'
4
+ import { getTranslation } from '../../utils/translations/translate'
5
+
6
+ interface Props {
7
+ label?: string
8
+ value?: string | number
9
+ name: string
10
+ disabled?: boolean
11
+ readonly?: boolean
12
+ error?: boolean
13
+ errorMessage?: string
14
+ min?: number | string
15
+ max?: number | string
16
+ step?: number | string
17
+ }
18
+
19
+ const {
20
+ label = '',
21
+ disabled = false,
22
+ readonly = false,
23
+ min = '',
24
+ max = '',
25
+ step = '1',
26
+ error = false,
27
+ } = defineProps<Props>()
28
+
29
+ const cId = `numeric-stepper-${useId()}`
30
+
31
+ const model = defineModel<string | number>()
32
+
33
+ const input = useTemplateRef('input')
34
+
35
+ const isFocused = ref(false)
36
+ const isHovering = ref(false)
37
+ const isMax = ref(false)
38
+ const isMin = ref(false)
39
+
40
+ const classes = computed(() => ({
41
+ 'is-focused': isFocused.value,
42
+ 'is-hovering': isHovering.value,
43
+ 'has-error': error,
44
+ disabled,
45
+ readonly,
46
+ }))
47
+
48
+ const onHover = () => {
49
+ if (readonly || disabled) {
50
+ return
51
+ }
52
+
53
+ isHovering.value = true
54
+ }
55
+ const onHoverOut = () => {
56
+ isHovering.value = false
57
+ }
58
+
59
+ const onStepDown = () => {
60
+ model.value = (Number(model.value) - Number(step)).toString()
61
+ nextTick(() => {
62
+ input.value?.dispatchEvent(new Event('input'))
63
+ })
64
+ }
65
+
66
+ const onStepUp = () => {
67
+ model.value = (Number(model.value) + Number(step)).toString()
68
+
69
+ nextTick(() => {
70
+ input.value?.dispatchEvent(new Event('input'))
71
+ })
72
+ }
73
+
74
+ const onInput = (e) => {
75
+ e.preventDefault()
76
+ e.stopPropagation()
77
+
78
+ if (min !== '') {
79
+ isMin.value = Number(model.value) <= Number(min)
80
+ }
81
+
82
+ if (max !== '') {
83
+ isMax.value = Number(model.value) >= Number(max)
84
+ }
85
+ }
86
+
87
+ const stepButtonUpDisabled = computed(() => disabled || readonly || isMax.value)
88
+ const stepButtonDownDisabled = computed(() => disabled || readonly || isMin.value)
89
+ </script>
90
+
91
+ <template>
92
+ <div :class="['numeric-stepper', classes]" @mouseenter="onHover" @mouseleave="onHoverOut">
93
+ <label class="visually-hidden" :for="cId">{{ label }}</label>
94
+ <div class="control">
95
+ <slot name="input"
96
+ ><input
97
+ :id="cId"
98
+ ref="input"
99
+ v-model="model"
100
+ type="number"
101
+ :name
102
+ :min
103
+ :max
104
+ :step
105
+ @input="onInput"
106
+ /></slot>
107
+ </div>
108
+
109
+ <button
110
+ ref="stepDownButton"
111
+ class="stepper-button dec"
112
+ :aria-disabled="stepButtonDownDisabled ? 'true' : 'false'"
113
+ :disabled="stepButtonDownDisabled"
114
+ @click="onStepDown"
115
+ >
116
+ <span class="visually-hidden">{{ getTranslation('decreaseValue') }}</span>
117
+ <UIcon name="remove"></UIcon>
118
+ </button>
119
+
120
+ <button
121
+ ref="stepUpButton"
122
+ class="stepper-button inc"
123
+ :aria-disabled="stepButtonUpDisabled ? 'true' : 'false'"
124
+ :disabled="stepButtonUpDisabled"
125
+ @click="onStepUp"
126
+ >
127
+ <span class="visually-hidden">{{ getTranslation('increaseValue') }}</span>
128
+ <UIcon name="add"></UIcon>
129
+ </button>
130
+ </div>
131
+ </template>
132
+
133
+ <style lang="scss" scoped>
134
+ @use './numeric-stepper.scss';
135
+ </style>
@@ -0,0 +1,32 @@
1
+ @use '../../base/abstracts' as a;
2
+ @use '../form-field' as f;
3
+
4
+ .select {
5
+ @include f.form-field-base(select);
6
+ @include f.form-field-states-all(select);
7
+ @include f.form-field-error;
8
+
9
+ .control {
10
+ &::after {
11
+ content: '';
12
+ pointer-events: none;
13
+ position: absolute;
14
+ top: 50%;
15
+ right: var(--e-space-4);
16
+ width: a.rem(16);
17
+ height: a.rem(16);
18
+ transform: translateY(-50%);
19
+
20
+ // TODO: find a better solution for this.
21
+ background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11.06 5.53 8 8.583 4.94 5.53 4 6.47l4 4 4-4-.94-.94Z' fill='%23333'/%3E%3C/svg%3E");
22
+ }
23
+
24
+ select {
25
+ padding-right: var(--e-space-12);
26
+
27
+ &:invalid {
28
+ color: var(--e-c-mono-700);
29
+ }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,130 @@
1
+ <script setup lang="ts">
2
+ import { FormFieldBase } from '../form-field/form-field.types'
3
+ import { computed, ref, useId, useSlots } from 'vue'
4
+ import { SelectOption } from '../types'
5
+ import { translate } from '../../utils/translations/translate'
6
+
7
+ interface Props extends FormFieldBase {
8
+ name: string
9
+ options: SelectOption[]
10
+ }
11
+
12
+ const model = defineModel<string>()
13
+
14
+ const {
15
+ disabled = false,
16
+ required = false,
17
+ helpText = '',
18
+ errorMessage = '',
19
+ } = defineProps<Props>()
20
+
21
+ const slots = useSlots()
22
+ const selectId = useId()
23
+
24
+ const isFocused = ref(false)
25
+ const isHovering = ref(false)
26
+
27
+ const spacer = '.&nbsp;'
28
+
29
+ const onFocus = () => {
30
+ if (disabled) {
31
+ return
32
+ }
33
+
34
+ isFocused.value = true
35
+ }
36
+
37
+ const onBlur = () => {
38
+ isFocused.value = false
39
+ }
40
+
41
+ const onHover = () => {
42
+ if (disabled) {
43
+ return
44
+ }
45
+
46
+ isHovering.value = true
47
+ }
48
+
49
+ const onHoverOut = () => {
50
+ isHovering.value = false
51
+ }
52
+
53
+ const hasHelpText = computed(() => {
54
+ return !!helpText || !!slots.helpText || required
55
+ })
56
+
57
+ const needsHelpTextSpacer = computed(() => {
58
+ return !!helpText || !!slots.helpText
59
+ })
60
+ </script>
61
+
62
+ <template>
63
+ <div
64
+ :class="[
65
+ 'select',
66
+ {
67
+ required,
68
+ disabled,
69
+ focus: isFocused,
70
+ hover: isHovering,
71
+ 'float-label': true,
72
+ 'has-error': error,
73
+ 'show-help-text': !required || hasHelpText,
74
+ },
75
+ ]"
76
+ @mouseenter="onHover"
77
+ @mouseleave="onHoverOut"
78
+ >
79
+ <div class="wrapper">
80
+ <div class="label">
81
+ <label :for="selectId">
82
+ <slot name="label">{{ label }}</slot>
83
+ </label>
84
+ </div>
85
+
86
+ <div class="control">
87
+ <slot>
88
+ <select
89
+ :id="selectId"
90
+ v-model="model"
91
+ :disabled
92
+ :required
93
+ :name
94
+ @focus="onFocus"
95
+ @blur="onBlur"
96
+ >
97
+ <option
98
+ v-for="(option, idx) in options"
99
+ :key="idx"
100
+ :value="option.value"
101
+ :disabled="option.disabled"
102
+ :selected="option.selected"
103
+ >
104
+ {{ option.label }}
105
+ </option>
106
+ </select>
107
+ </slot>
108
+
109
+ <div class="control-border"></div>
110
+ </div>
111
+ </div>
112
+
113
+ <div class="help-text">
114
+ <span class="optional-text"
115
+ >{{ translate('common.optional')
116
+ }}<span v-if="needsHelpTextSpacer" v-html="spacer"></span></span
117
+ ><slot name="helpText">{{ helpText }}</slot>
118
+ </div>
119
+
120
+ <div class="error-messages-container">
121
+ <slot name="error-message"
122
+ ><span>{{ errorMessage }}</span></slot
123
+ >
124
+ </div>
125
+ </div>
126
+ </template>
127
+
128
+ <style lang="scss" scoped>
129
+ @use './select.scss';
130
+ </style>
@@ -0,0 +1,18 @@
1
+ @use '../../base/abstracts' as a;
2
+ @use '../button/button';
3
+
4
+ .select-chip {
5
+ display: inline-flex;
6
+ }
7
+
8
+ .select-chip__button {
9
+ text-align: left;
10
+ width: 100%;
11
+
12
+ input {
13
+ // So that the focus works nicely.
14
+ position: absolute;
15
+ inset: 0;
16
+ cursor: pointer;
17
+ }
18
+ }
@@ -0,0 +1,50 @@
1
+ <script setup lang="ts">
2
+ import UIcon from '../icon/u-icon.vue'
3
+ import { computed, inject, useId, watch } from 'vue'
4
+
5
+ interface Props {
6
+ name: string
7
+ label?: string
8
+ disabled?: boolean
9
+ value?: string
10
+ }
11
+ const { name, disabled = false, value = '' } = defineProps<Props>()
12
+ const cId = useId()
13
+
14
+ const model = defineModel<boolean>({ default: false })
15
+
16
+ const { activeSelectChip, updateActiveSelectChip } = inject('select-chip-group', {})
17
+
18
+ const onChange = () => {
19
+ if (updateActiveSelectChip) {
20
+ updateActiveSelectChip(cId)
21
+ }
22
+ }
23
+
24
+ if (activeSelectChip) {
25
+ watch(activeSelectChip, (newV) => {
26
+ if (newV !== cId) {
27
+ model.value = false
28
+ }
29
+ })
30
+ }
31
+
32
+ const classes = computed(() => (disabled ? 'disabled' : model.value ? 'filled' : 'outlined'))
33
+ </script>
34
+
35
+ <template>
36
+ <div class="select-chip">
37
+ <label :class="classes" class="select-chip__button button">
38
+ <UIcon v-if="model" name="mini-check"></UIcon>
39
+ {{ label }}
40
+
41
+ <slot>
42
+ <input v-model="model" type="radio" :name :value :disabled @change="onChange" />
43
+ </slot>
44
+ </label>
45
+ </div>
46
+ </template>
47
+
48
+ <style lang="scss">
49
+ @use './select-chip.scss';
50
+ </style>
@@ -0,0 +1,5 @@
1
+ .select-chips {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ gap: var(--e-space-6);
5
+ }
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ import { provide, ref } from 'vue'
3
+
4
+ const activeSelectChip = ref(null)
5
+ const updateActiveSelectChip = (id: string): void => {
6
+ activeSelectChip.value = id
7
+ }
8
+
9
+ provide('select-chip-group', {
10
+ activeSelectChip,
11
+ updateActiveSelectChip,
12
+ })
13
+ </script>
14
+
15
+ <template>
16
+ <div class="select-chips">
17
+ <slot></slot>
18
+ </div>
19
+ </template>
20
+
21
+ <style lang="scss">
22
+ @use './select-chips.scss';
23
+ </style>
@@ -0,0 +1,10 @@
1
+ .spectro {
2
+ position: relative;
3
+ aspect-ratio: 1;
4
+ overflow: hidden;
5
+ }
6
+
7
+ .inner {
8
+ position: absolute;
9
+ inset: 0;
10
+ }
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <div class="spectro">
3
+ <div class="inner">
4
+ <img src="/static/ui-assets/images/spectro.svg" alt="" aria-hidden="true" />
5
+ </div>
6
+ </div>
7
+ </template>
8
+
9
+ <style scoped lang="scss">
10
+ @use './spectro.scss';
11
+ </style>