@byyuurin/ui 0.0.2 → 0.0.4

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 (109) hide show
  1. package/README.md +76 -4
  2. package/dist/index.d.ts +3 -3
  3. package/dist/index.mjs +3 -3
  4. package/dist/nuxt.d.mts +13 -0
  5. package/dist/nuxt.d.ts +6 -3
  6. package/dist/nuxt.mjs +6 -3
  7. package/dist/resolver.d.mts +13 -0
  8. package/dist/resolver.d.ts +6 -3
  9. package/dist/resolver.mjs +5 -2
  10. package/dist/{components → runtime/components}/Accordion.vue +19 -3
  11. package/dist/{components → runtime/components}/App.vue +5 -1
  12. package/dist/{components → runtime/components}/Button.vue +92 -94
  13. package/dist/{components → runtime/components}/Checkbox.vue +101 -104
  14. package/dist/{components → runtime/components}/Input.vue +5 -1
  15. package/dist/{components → runtime/components}/ModalProvider.vue +1 -1
  16. package/dist/{components → runtime/components}/RadioGroup.vue +174 -180
  17. package/dist/runtime/components/ScrollArea.vue +72 -0
  18. package/dist/{components → runtime/components}/Select.vue +262 -258
  19. package/dist/{components → runtime/components}/Switch.vue +98 -99
  20. package/dist/{components → runtime/components}/Tabs.vue +117 -117
  21. package/dist/runtime/components/Textarea.vue +173 -0
  22. package/dist/{components → runtime/components}/Toast.vue +1 -1
  23. package/dist/{components → runtime/components}/Toaster.vue +35 -1
  24. package/dist/{components → runtime/components}/index.d.ts +2 -0
  25. package/dist/{components → runtime/components}/index.mjs +2 -0
  26. package/dist/runtime/composables/defineInjection.d.ts +11 -0
  27. package/dist/runtime/composables/defineInjection.mjs +9 -0
  28. package/dist/{composables → runtime/composables}/index.d.ts +1 -0
  29. package/dist/{composables → runtime/composables}/index.mjs +1 -0
  30. package/dist/{composables → runtime/composables}/useModal.d.ts +1 -1
  31. package/dist/{composables → runtime/composables}/useModal.mjs +3 -2
  32. package/dist/{composables → runtime/composables}/useTheme.d.ts +3 -2
  33. package/dist/runtime/composables/useTheme.mjs +26 -0
  34. package/dist/{composables → runtime/composables}/useToast.d.ts +1 -1
  35. package/dist/{theme → runtime/theme}/button.d.ts +0 -8
  36. package/dist/{theme → runtime/theme}/button.mjs +1 -9
  37. package/dist/{theme → runtime/theme}/checkbox.d.ts +0 -12
  38. package/dist/{theme → runtime/theme}/checkbox.mjs +4 -9
  39. package/dist/{theme → runtime/theme}/index.d.ts +2 -0
  40. package/dist/{theme → runtime/theme}/index.mjs +2 -0
  41. package/dist/{theme → runtime/theme}/radioGroup.d.ts +0 -24
  42. package/dist/{theme → runtime/theme}/radioGroup.mjs +8 -20
  43. package/dist/runtime/theme/scrollArea.d.ts +51 -0
  44. package/dist/runtime/theme/scrollArea.mjs +30 -0
  45. package/dist/{theme → runtime/theme}/switch.d.ts +0 -12
  46. package/dist/{theme → runtime/theme}/switch.mjs +2 -8
  47. package/dist/{theme → runtime/theme}/tabs.d.ts +32 -11
  48. package/dist/{theme → runtime/theme}/tabs.mjs +19 -12
  49. package/dist/runtime/theme/textarea.d.ts +90 -0
  50. package/dist/runtime/theme/textarea.mjs +100 -0
  51. package/dist/{theme → runtime/theme}/toast.mjs +1 -1
  52. package/dist/{types → runtime/types}/components.d.ts +2 -0
  53. package/dist/{types → runtime/types}/index.d.ts +1 -1
  54. package/dist/{utils → runtime/utils}/index.d.ts +3 -12
  55. package/dist/{utils → runtime/utils}/index.mjs +3 -12
  56. package/dist/{internal → runtime/utils}/styler.d.ts +0 -1
  57. package/dist/runtime/utils/styler.mjs +10 -0
  58. package/dist/{internal/constants.mjs → shared/ui.Cmq14xN9.mjs} +6 -2
  59. package/dist/unocss-preset.d.mts +57 -0
  60. package/dist/unocss-preset.d.ts +29 -9
  61. package/dist/unocss-preset.mjs +164 -110
  62. package/package.json +18 -12
  63. package/dist/composables/useTheme.mjs +0 -18
  64. package/dist/internal/constants.d.ts +0 -3
  65. package/dist/internal/index.d.ts +0 -4
  66. package/dist/internal/index.mjs +0 -4
  67. package/dist/internal/styler.mjs +0 -236
  68. package/dist/utils/unocss.d.ts +0 -3
  69. package/dist/utils/unocss.mjs +0 -50
  70. /package/dist/{components → runtime/components}/Card.vue +0 -0
  71. /package/dist/{components → runtime/components}/Drawer.vue +0 -0
  72. /package/dist/{components → runtime/components}/Link.vue +0 -0
  73. /package/dist/{components → runtime/components}/Modal.vue +0 -0
  74. /package/dist/{components → runtime/components}/Popover.vue +0 -0
  75. /package/dist/{components → runtime/components}/Tooltip.vue +0 -0
  76. /package/dist/{composables → runtime/composables}/useComponentIcons.d.ts +0 -0
  77. /package/dist/{composables → runtime/composables}/useComponentIcons.mjs +0 -0
  78. /package/dist/{composables → runtime/composables}/useToast.mjs +0 -0
  79. /package/dist/{theme → runtime/theme}/accordion.d.ts +0 -0
  80. /package/dist/{theme → runtime/theme}/accordion.mjs +0 -0
  81. /package/dist/{theme → runtime/theme}/app.d.ts +0 -0
  82. /package/dist/{theme → runtime/theme}/app.mjs +0 -0
  83. /package/dist/{theme → runtime/theme}/card.d.ts +0 -0
  84. /package/dist/{theme → runtime/theme}/card.mjs +0 -0
  85. /package/dist/{theme → runtime/theme}/drawer.d.ts +0 -0
  86. /package/dist/{theme → runtime/theme}/drawer.mjs +0 -0
  87. /package/dist/{theme → runtime/theme}/input.d.ts +0 -0
  88. /package/dist/{theme → runtime/theme}/input.mjs +0 -0
  89. /package/dist/{theme → runtime/theme}/link.d.ts +0 -0
  90. /package/dist/{theme → runtime/theme}/link.mjs +0 -0
  91. /package/dist/{theme → runtime/theme}/modal.d.ts +0 -0
  92. /package/dist/{theme → runtime/theme}/modal.mjs +0 -0
  93. /package/dist/{theme → runtime/theme}/popover.d.ts +0 -0
  94. /package/dist/{theme → runtime/theme}/popover.mjs +0 -0
  95. /package/dist/{theme → runtime/theme}/select.d.ts +0 -0
  96. /package/dist/{theme → runtime/theme}/select.mjs +0 -0
  97. /package/dist/{theme → runtime/theme}/toast.d.ts +0 -0
  98. /package/dist/{theme → runtime/theme}/toaster.d.ts +0 -0
  99. /package/dist/{theme → runtime/theme}/toaster.mjs +0 -0
  100. /package/dist/{theme → runtime/theme}/tooltip.d.ts +0 -0
  101. /package/dist/{theme → runtime/theme}/tooltip.mjs +0 -0
  102. /package/dist/{types → runtime/types}/components.mjs +0 -0
  103. /package/dist/{types → runtime/types}/index.mjs +0 -0
  104. /package/dist/{types → runtime/types}/utils.d.ts +0 -0
  105. /package/dist/{types → runtime/types}/utils.mjs +0 -0
  106. /package/dist/{internal → runtime/utils}/extend-theme.d.ts +0 -0
  107. /package/dist/{internal → runtime/utils}/extend-theme.mjs +0 -0
  108. /package/dist/{internal → runtime/utils}/link.d.ts +0 -0
  109. /package/dist/{internal → runtime/utils}/link.mjs +0 -0
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ import type { ScrollAreaRootProps } from 'reka-ui'
3
+ import type { scrollArea } from '../theme'
4
+ import { transitionProps } from '../theme/scrollArea'
5
+ import type { ComponentAttrs } from '../types'
6
+
7
+ export interface ScrollAreaProps extends ComponentAttrs<typeof scrollArea>, Pick<ScrollAreaRootProps, 'type' | 'dir' | 'scrollHideDelay'> {}
8
+ </script>
9
+
10
+ <script setup lang="ts">
11
+ import { reactivePick } from '@vueuse/core'
12
+ import { ScrollAreaCorner, ScrollAreaRoot, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport } from 'reka-ui'
13
+ import { computed, ref } from 'vue'
14
+ import { useTheme } from '../composables'
15
+
16
+ const props = withDefaults(defineProps<ScrollAreaProps>(), {})
17
+ const rootRef = ref<InstanceType<typeof ScrollAreaRoot>>()
18
+ const rootProps = reactivePick(props, 'type', 'dir', 'scrollHideDelay')
19
+
20
+ const { theme, createStyler } = useTheme()
21
+
22
+ const style = computed(() => {
23
+ const styler = createStyler(theme.value.scrollArea)
24
+ return styler(props)
25
+ })
26
+
27
+ defineExpose({
28
+ scrollTop,
29
+ scrollTopLeft,
30
+ })
31
+
32
+ function scrollTop() {
33
+ rootRef.value?.scrollTop()
34
+ }
35
+
36
+ function scrollTopLeft() {
37
+ rootRef.value?.scrollTopLeft()
38
+ }
39
+ </script>
40
+
41
+ <template>
42
+ <ScrollAreaRoot
43
+ ref="rootRef"
44
+ v-bind="rootProps"
45
+ :class="style.root({ class: [props.class, props.ui?.root] })"
46
+ >
47
+ <ScrollAreaViewport :class="style.viewport({ class: props.ui?.viewport })">
48
+ <slot></slot>
49
+ </ScrollAreaViewport>
50
+
51
+ <TransitionGroup v-bind="transitionProps">
52
+ <ScrollAreaScrollbar
53
+ key="scrollbar-horizontal"
54
+ :class="style.scrollbar({ class: props.ui?.scrollbar })"
55
+ orientation="horizontal"
56
+ >
57
+ <ScrollAreaThumb :class="style.thumb({ class: props.ui?.thumb })" />
58
+ </ScrollAreaScrollbar>
59
+ <ScrollAreaScrollbar
60
+ key="scrollbar-vertical"
61
+ :class="style.scrollbar({ class: props.ui?.scrollbar })"
62
+ orientation="vertical"
63
+ >
64
+ <ScrollAreaThumb :class="style.thumb({ class: props.ui?.thumb })" />
65
+ </ScrollAreaScrollbar>
66
+ <ScrollAreaCorner
67
+ key="corner"
68
+ :class="style.corner({ class: props.ui?.corner })"
69
+ />
70
+ </TransitionGroup>
71
+ </ScrollAreaRoot>
72
+ </template>
@@ -1,258 +1,262 @@
1
- <script lang="ts">
2
- import type { VariantProps } from '@byyuurin/ui-kit'
3
- import type { AcceptableValue, SelectArrowProps, SelectContentProps, SelectRootProps } from 'reka-ui'
4
- import type { UseComponentIconsProps } from '../composables/useComponentIcons'
5
- import type { select } from '../theme'
6
- import type { ComponentAttrs, MaybeArray, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectOptionKey } from '../types'
7
-
8
- type SelectVariants = VariantProps<typeof select>
9
-
10
- export interface SelectOption {
11
- label?: string
12
- icon?: string
13
- /**
14
- * The option type.
15
- * @default 'option'
16
- */
17
- type?: 'label' | 'separator' | 'option'
18
- value?: string
19
- disabled?: boolean
20
- }
21
-
22
- export type SelectOptionValue = SelectOption | AcceptableValue | boolean
23
-
24
- export interface SelectProps<
25
- T extends MaybeArrayOfArrayItem<I>,
26
- I extends MaybeArrayOfArray<SelectOptionValue> = MaybeArrayOfArray<SelectOptionValue>,
27
- V extends SelectOptionKey<T> | undefined = undefined,
28
- M extends boolean = false,
29
- > extends Omit<SelectRootProps<T>, 'dir' | 'multiple' | 'modelValue' | 'defaultValue' | 'by'>, ComponentAttrs<typeof select>, UseComponentIconsProps {
30
- id?: string
31
- /** The placeholder text when the select is empty. */
32
- placeholder?: string
33
- variant?: SelectVariants['variant']
34
- size?: SelectVariants['size']
35
- /**
36
- * The icon displayed to open the menu.
37
- * @default app.icons.down
38
- */
39
- suffixIcon?: string
40
- /**
41
- * The icon displayed when an item is selected.
42
- * @default app.icons.check
43
- */
44
- selectedIcon?: string
45
- /**
46
- * The content of the menu.
47
- * @default { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
48
- */
49
- content?: Omit<SelectContentProps, 'as' | 'asChild' | 'forceMount'>
50
- /**
51
- * Display an arrow alongside the menu.
52
- * @default false
53
- */
54
- arrow?: boolean | Omit<SelectArrowProps, 'as' | 'asChild'>
55
- /**
56
- * Render the menu in a portal.
57
- * @default true
58
- */
59
- portal?: boolean
60
- /**
61
- * When `options` is an array of objects, select the field to use as the value.
62
- * @default 'value'
63
- */
64
- valueKey?: V
65
- /**
66
- * When `options` is an array of objects, select the field to use as the label.
67
- * @default "label"
68
- */
69
- labelKey?: V
70
- options?: I
71
- /** The value of the Select when initially rendered. Use when you do not need to control the state of the Select. */
72
- defaultValue?: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>
73
- /** The controlled value of the Select. Can be bind as `v-model`. */
74
- modelValue?: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>
75
- /** Whether multiple options can be selected or not. */
76
- multiple?: M & boolean
77
- /** Highlight the ring color like a focus state. */
78
- highlight?: boolean
79
- underline?: boolean
80
- }
81
-
82
- export interface SelectEmits<T, V, M extends boolean> {
83
- (event: 'update:open', value: boolean): void
84
- (event: 'update:modelValue', payload: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>): void
85
- (event: 'change', payload: Event): void
86
- (event: 'blur', payload: FocusEvent): void
87
- (event: 'focus', payload: FocusEvent): void
88
- }
89
-
90
- type SlotProps<T> = (props: { item: T, index: number }) => any
91
-
92
- export interface SelectSlots<T, M extends boolean> {
93
- prefix?: (props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean, ui: ComponentAttrs<typeof select>['ui'] }) => any
94
- default?: (props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean }) => any
95
- suffix?: (props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean, ui: ComponentAttrs<typeof select>['ui'] }) => any
96
- item?: SlotProps<T>
97
- itemPrefix?: SlotProps<T>
98
- itemLabel?: SlotProps<T>
99
- itemSuffix?: SlotProps<T>
100
- }
101
- </script>
102
-
103
- <script lang="ts" setup generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectOption | AcceptableValue | boolean> = MaybeArrayOfArray<SelectOption | AcceptableValue | boolean>, V extends SelectOptionKey<T> | undefined = undefined, M extends boolean = false">
104
- import { reactivePick } from '@vueuse/core'
105
- import { defu } from 'defu'
106
- import { SelectArrow, SelectContent, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectPortal, SelectRoot, SelectSeparator, SelectTrigger, SelectViewport, useForwardPropsEmits } from 'reka-ui'
107
- import { computed, toRef } from 'vue'
108
- import { useComponentIcons, useTheme } from '../composables'
109
- import { compare, get } from '../utils'
110
-
111
- const props = withDefaults(defineProps<SelectProps<T, I, V, M>>(), {
112
- variant: 'outline',
113
- size: 'md',
114
- valueKey: 'value' as never,
115
- labelKey: 'label' as never,
116
- portal: true,
117
- })
118
-
119
- const emit = defineEmits<SelectEmits<T, V, M>>()
120
- const slots = defineSlots<SelectSlots<T, M>>()
121
-
122
- const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required', 'multiple'), emit)
123
- const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as SelectContentProps)
124
- const arrowProps = toRef(() => props.arrow as SelectArrowProps)
125
-
126
- const { theme, createStyler } = useTheme()
127
- const { isPrefix, isSuffix, prefixIconName, suffixIconName } = useComponentIcons(toRef(() => defu(props, {
128
- suffixIcon: theme.value.app.icons.down,
129
- })))
130
-
131
- const groups = computed(() => props.options?.length ? (Array.isArray(props.options[0]) ? props.options : [props.options]) as SelectOption[][] : [])
132
- const items = computed(() => groups.value.flat() as T[])
133
-
134
- const style = computed(() => {
135
- const styler = createStyler(theme.value.select)
136
- return styler({
137
- ...props,
138
- prefix: isPrefix.value,
139
- suffix: isSuffix.value,
140
- })
141
- })
142
-
143
- function typedItem(item: SelectOption) {
144
- return item as unknown as T
145
- }
146
-
147
- function typedValue<V extends MaybeArray<AcceptableValue>>(value?: V) {
148
- return value as unknown as M extends true ? AcceptableValue[] : AcceptableValue
149
- }
150
-
151
- function displayValue(value?: MaybeArray<AcceptableValue>): string | undefined {
152
- if (props.multiple && Array.isArray(value))
153
- return value.map((v) => displayValue(v)).filter(Boolean).join(', ')
154
-
155
- const item = items.value.find((item) => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
156
- return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
157
- }
158
-
159
- function onUpdate(value: any) {
160
- // @ts-expect-error - 'target' does not exist in type 'EventInit'
161
- const event = new Event('change', { target: { value } })
162
- emit('change', event)
163
- }
164
-
165
- function onUpdateOpen(value: boolean) {
166
- if (value) {
167
- const event = new FocusEvent('focus')
168
- emit('focus', event)
169
- }
170
- else {
171
- const event = new FocusEvent('blur')
172
- emit('blur', event)
173
- }
174
- }
175
- </script>
176
-
177
- <template>
178
- <SelectRoot
179
- v-slot="{ modelValue: innerValue, open }"
180
- :name="props.name"
181
- v-bind="rootProps"
182
- :autocomplete="props.autocomplete"
183
- :disabled="props.disabled"
184
- :default-value="(props.defaultValue as MaybeArray<AcceptableValue> | undefined)"
185
- :model-value="(props.modelValue as MaybeArray<AcceptableValue> | undefined)"
186
- @update:model-value="onUpdate"
187
- @update:open="onUpdateOpen"
188
- >
189
- <SelectTrigger :id="props.id" :class="style.base({ class: [props.class, props.ui?.base] })">
190
- <span v-if="isPrefix || slots.prefix" :class="style.prefix({ class: props.ui?.prefix })">
191
- <slot name="prefix" :model-value="typedValue(innerValue)" :open="open" :ui="props.ui">
192
- <i v-if="isPrefix && prefixIconName" :class="style.prefixIcon({ class: [prefixIconName, props.ui?.prefixIcon] })"></i>
193
- </slot>
194
- </span>
195
-
196
- <slot :model-value="typedValue(innerValue)" :open="open">
197
- <template v-for="displayedModelValue in [displayValue(innerValue)]" :key="displayedModelValue">
198
- <span v-if="displayedModelValue" :class="style.value({ class: props.ui?.value })">
199
- {{ displayedModelValue }}
200
- </span>
201
- <span v-else :class="style.placeholder({ class: props.ui?.placeholder })">
202
- {{ placeholder }}&nbsp;
203
- </span>
204
- </template>
205
- </slot>
206
-
207
- <span v-if="isSuffix || !!slots.suffix" :class="style.suffix({ class: props.ui?.suffix })">
208
- <slot name="suffix" :model-value="typedValue(innerValue)" :open="open" :ui="props.ui">
209
- <i v-if="suffixIconName" :class="style.suffixIcon({ class: [suffixIconName, props.ui?.suffixIcon] })"></i>
210
- </slot>
211
- </span>
212
- </SelectTrigger>
213
-
214
- <SelectPortal :disabled="!props.portal">
215
- <SelectContent v-bind="contentProps" :class="style.content({ class: props.ui?.content })">
216
- <SelectViewport :class="style.viewport({ class: props.ui?.viewport })">
217
- <SelectGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="style.group({ class: props.ui?.group })">
218
- <template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
219
- <SelectLabel v-if="item.type === 'label'" :class="style.label({ class: props.ui?.label })">
220
- {{ get(item, props.labelKey as string) }}
221
- </SelectLabel>
222
- <SelectSeparator v-else-if="item.type === 'separator'" :class="style.separator({ class: props.ui?.separator })" />
223
-
224
- <SelectItem
225
- v-else
226
- :class="style.item({ class: props.ui?.item })"
227
- :disabled="item.disabled"
228
- :value="typeof item === 'object' ? get(item, props.valueKey as string) : item"
229
- >
230
- <slot name="item" :item="typedItem(item)" :index="index">
231
- <slot name="itemPrefix" :item="typedItem(item)" :index="index">
232
- <i v-if="item.icon" :class="style.itemPrefixIcon({ class: [item.icon, props.ui?.itemPrefixIcon] })"></i>
233
- </slot>
234
-
235
- <SelectItemText :class="style.itemLabel({ class: props.ui?.itemLabel })">
236
- <slot name="itemLabel" :item="typedItem(item)" :index="index">
237
- {{ typeof item === 'object' ? get(item, props.labelKey as string) : item }}
238
- </slot>
239
- </SelectItemText>
240
-
241
- <span :class="style.itemSuffix({ class: props.ui?.itemSuffix })">
242
- <slot name="itemSuffix" :item="typedItem(item)" :index="index"></slot>
243
-
244
- <SelectItemIndicator as-child>
245
- <i :class="style.itemSuffixIcon({ class: [props.selectedIcon || theme.app.icons.check, props.ui?.itemSuffixIcon] })"></i>
246
- </SelectItemIndicator>
247
- </span>
248
- </slot>
249
- </SelectItem>
250
- </template>
251
- </SelectGroup>
252
- </SelectViewport>
253
-
254
- <SelectArrow v-if="!!props.arrow" v-bind="arrowProps" :class="style.arrow({ class: props.ui?.arrow })" />
255
- </SelectContent>
256
- </SelectPortal>
257
- </SelectRoot>
258
- </template>
1
+ <script lang="ts">
2
+ import type { VariantProps } from '@byyuurin/ui-kit'
3
+ import type { AcceptableValue, SelectArrowProps, SelectContentProps, SelectRootProps } from 'reka-ui'
4
+ import type { UseComponentIconsProps } from '../composables/useComponentIcons'
5
+ import type { select } from '../theme'
6
+ import type { ComponentAttrs, MaybeArray, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectOptionKey } from '../types'
7
+
8
+ type SelectVariants = VariantProps<typeof select>
9
+
10
+ export interface SelectOption {
11
+ label?: string
12
+ icon?: string
13
+ /**
14
+ * The option type.
15
+ * @default 'option'
16
+ */
17
+ type?: 'label' | 'separator' | 'option'
18
+ value?: string
19
+ disabled?: boolean
20
+ }
21
+
22
+ export type SelectOptionValue = SelectOption | AcceptableValue | boolean
23
+
24
+ export interface SelectProps<
25
+ T extends MaybeArrayOfArrayItem<I>,
26
+ I extends MaybeArrayOfArray<SelectOptionValue> = MaybeArrayOfArray<SelectOptionValue>,
27
+ V extends SelectOptionKey<T> | undefined = undefined,
28
+ M extends boolean = false,
29
+ > extends Omit<SelectRootProps<T>, 'dir' | 'multiple' | 'modelValue' | 'defaultValue' | 'by'>, ComponentAttrs<typeof select>, UseComponentIconsProps {
30
+ id?: string
31
+ /** The placeholder text when the select is empty. */
32
+ placeholder?: string
33
+ variant?: SelectVariants['variant']
34
+ size?: SelectVariants['size']
35
+ /**
36
+ * The icon displayed to open the menu.
37
+ * @default app.icons.down
38
+ */
39
+ suffixIcon?: string
40
+ /**
41
+ * The icon displayed when an item is selected.
42
+ * @default app.icons.check
43
+ */
44
+ selectedIcon?: string
45
+ /**
46
+ * The content of the menu.
47
+ * @default { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
48
+ */
49
+ content?: Omit<SelectContentProps, 'as' | 'asChild' | 'forceMount'>
50
+ /**
51
+ * Display an arrow alongside the menu.
52
+ * @default false
53
+ */
54
+ arrow?: boolean | Omit<SelectArrowProps, 'as' | 'asChild'>
55
+ /**
56
+ * Render the menu in a portal.
57
+ * @default true
58
+ */
59
+ portal?: boolean
60
+ /**
61
+ * When `options` is an array of objects, select the field to use as the value.
62
+ * @default 'value'
63
+ */
64
+ valueKey?: V
65
+ /**
66
+ * When `options` is an array of objects, select the field to use as the label.
67
+ * @default "label"
68
+ */
69
+ labelKey?: V
70
+ options?: I
71
+ /** The value of the Select when initially rendered. Use when you do not need to control the state of the Select. */
72
+ defaultValue?: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>
73
+ /** The controlled value of the Select. Can be bind as `v-model`. */
74
+ modelValue?: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>
75
+ /** Whether multiple options can be selected or not. */
76
+ multiple?: M & boolean
77
+ /** Highlight the ring color like a focus state. */
78
+ highlight?: boolean
79
+ underline?: boolean
80
+ }
81
+
82
+ export interface SelectEmits<T, V, M extends boolean> {
83
+ (event: 'update:open', value: boolean): void
84
+ (event: 'update:modelValue', payload: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>): void
85
+ (event: 'change', payload: Event): void
86
+ (event: 'blur', payload: FocusEvent): void
87
+ (event: 'focus', payload: FocusEvent): void
88
+ }
89
+
90
+ type SlotProps<T> = (props: { item: T, index: number }) => any
91
+
92
+ export interface SelectSlots<T, M extends boolean> {
93
+ prefix?: (props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean, ui: ComponentAttrs<typeof select>['ui'] }) => any
94
+ default?: (props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean }) => any
95
+ suffix?: (props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean, ui: ComponentAttrs<typeof select>['ui'] }) => any
96
+ item?: SlotProps<T>
97
+ itemPrefix?: SlotProps<T>
98
+ itemLabel?: SlotProps<T>
99
+ itemSuffix?: SlotProps<T>
100
+ }
101
+ </script>
102
+
103
+ <script lang="ts" setup generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectOption | AcceptableValue | boolean> = MaybeArrayOfArray<SelectOption | AcceptableValue | boolean>, V extends SelectOptionKey<T> | undefined = undefined, M extends boolean = false">
104
+ import { reactivePick } from '@vueuse/core'
105
+ import { defu } from 'defu'
106
+ import { SelectArrow, SelectContent, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectPortal, SelectRoot, SelectSeparator, SelectTrigger, SelectViewport, useForwardPropsEmits } from 'reka-ui'
107
+ import { computed, toRef } from 'vue'
108
+ import { useComponentIcons, useTheme } from '../composables'
109
+ import { compare, get } from '../utils'
110
+
111
+ const props = withDefaults(defineProps<SelectProps<T, I, V, M>>(), {
112
+ variant: 'outline',
113
+ size: 'md',
114
+ valueKey: 'value' as never,
115
+ labelKey: 'label' as never,
116
+ portal: true,
117
+ })
118
+
119
+ const emit = defineEmits<SelectEmits<T, V, M>>()
120
+ const slots = defineSlots<SelectSlots<T, M>>()
121
+
122
+ const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required', 'multiple'), emit)
123
+ const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as SelectContentProps)
124
+ const arrowProps = toRef(() => props.arrow as SelectArrowProps)
125
+
126
+ const { theme, createStyler } = useTheme()
127
+ const { isPrefix, isSuffix, prefixIconName, suffixIconName } = useComponentIcons(toRef(() => defu(props, {
128
+ suffixIcon: theme.value.app.icons.down,
129
+ })))
130
+
131
+ const groups = computed(() => props.options?.length ? (Array.isArray(props.options[0]) ? props.options : [props.options]) as SelectOption[][] : [])
132
+ const items = computed(() => groups.value.flat() as T[])
133
+
134
+ const style = computed(() => {
135
+ const styler = createStyler(theme.value.select)
136
+ return styler({
137
+ ...props,
138
+ prefix: isPrefix.value,
139
+ suffix: isSuffix.value,
140
+ })
141
+ })
142
+
143
+ function typedItem(item: SelectOption) {
144
+ return item as unknown as T
145
+ }
146
+
147
+ function typedValue<V extends MaybeArray<AcceptableValue>>(value?: V) {
148
+ return value as unknown as M extends true ? AcceptableValue[] : AcceptableValue
149
+ }
150
+
151
+ function typedModelValue(value: any) {
152
+ return value as MaybeArray<AcceptableValue> | undefined
153
+ }
154
+
155
+ function displayValue(value?: MaybeArray<AcceptableValue>): string | undefined {
156
+ if (props.multiple && Array.isArray(value))
157
+ return value.map((v) => displayValue(v)).filter(Boolean).join(', ')
158
+
159
+ const item = items.value.find((item) => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
160
+ return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
161
+ }
162
+
163
+ function onUpdate(value: any) {
164
+ // @ts-expect-error - 'target' does not exist in type 'EventInit'
165
+ const event = new Event('change', { target: { value } })
166
+ emit('change', event)
167
+ }
168
+
169
+ function onUpdateOpen(value: boolean) {
170
+ if (value) {
171
+ const event = new FocusEvent('focus')
172
+ emit('focus', event)
173
+ }
174
+ else {
175
+ const event = new FocusEvent('blur')
176
+ emit('blur', event)
177
+ }
178
+ }
179
+ </script>
180
+
181
+ <template>
182
+ <SelectRoot
183
+ v-slot="{ modelValue: innerValue, open }"
184
+ :name="props.name"
185
+ v-bind="rootProps"
186
+ :autocomplete="props.autocomplete"
187
+ :disabled="props.disabled"
188
+ :default-value="typedModelValue(props.defaultValue)"
189
+ :model-value="typedModelValue(props.modelValue)"
190
+ @update:model-value="onUpdate"
191
+ @update:open="onUpdateOpen"
192
+ >
193
+ <SelectTrigger :id="props.id" :class="style.base({ class: [props.class, props.ui?.base] })">
194
+ <span v-if="isPrefix || slots.prefix" :class="style.prefix({ class: props.ui?.prefix })">
195
+ <slot name="prefix" :model-value="typedValue(innerValue)" :open="open" :ui="props.ui">
196
+ <i v-if="isPrefix && prefixIconName" :class="style.prefixIcon({ class: [prefixIconName, props.ui?.prefixIcon] })"></i>
197
+ </slot>
198
+ </span>
199
+
200
+ <slot :model-value="typedValue(innerValue)" :open="open">
201
+ <template v-for="displayedModelValue in [displayValue(innerValue)]" :key="displayedModelValue">
202
+ <span v-if="displayedModelValue" :class="style.value({ class: props.ui?.value })">
203
+ {{ displayedModelValue }}
204
+ </span>
205
+ <span v-else :class="style.placeholder({ class: props.ui?.placeholder })">
206
+ {{ placeholder }}&nbsp;
207
+ </span>
208
+ </template>
209
+ </slot>
210
+
211
+ <span v-if="isSuffix || !!slots.suffix" :class="style.suffix({ class: props.ui?.suffix })">
212
+ <slot name="suffix" :model-value="typedValue(innerValue)" :open="open" :ui="props.ui">
213
+ <i v-if="suffixIconName" :class="style.suffixIcon({ class: [suffixIconName, props.ui?.suffixIcon] })"></i>
214
+ </slot>
215
+ </span>
216
+ </SelectTrigger>
217
+
218
+ <SelectPortal :disabled="!props.portal">
219
+ <SelectContent v-bind="contentProps" :class="style.content({ class: props.ui?.content })">
220
+ <SelectViewport :class="style.viewport({ class: props.ui?.viewport })">
221
+ <SelectGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="style.group({ class: props.ui?.group })">
222
+ <template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
223
+ <SelectLabel v-if="item.type === 'label'" :class="style.label({ class: props.ui?.label })">
224
+ {{ get(item, props.labelKey as string) }}
225
+ </SelectLabel>
226
+ <SelectSeparator v-else-if="item.type === 'separator'" :class="style.separator({ class: props.ui?.separator })" />
227
+
228
+ <SelectItem
229
+ v-else
230
+ :class="style.item({ class: props.ui?.item })"
231
+ :disabled="item.disabled"
232
+ :value="typeof item === 'object' ? get(item, props.valueKey as string) : item"
233
+ >
234
+ <slot name="item" :item="typedItem(item)" :index="index">
235
+ <slot name="itemPrefix" :item="typedItem(item)" :index="index">
236
+ <i v-if="item.icon" :class="style.itemPrefixIcon({ class: [item.icon, props.ui?.itemPrefixIcon] })"></i>
237
+ </slot>
238
+
239
+ <SelectItemText :class="style.itemLabel({ class: props.ui?.itemLabel })">
240
+ <slot name="itemLabel" :item="typedItem(item)" :index="index">
241
+ {{ typeof item === 'object' ? get(item, props.labelKey as string) : item }}
242
+ </slot>
243
+ </SelectItemText>
244
+
245
+ <span :class="style.itemSuffix({ class: props.ui?.itemSuffix })">
246
+ <slot name="itemSuffix" :item="typedItem(item)" :index="index"></slot>
247
+
248
+ <SelectItemIndicator as-child>
249
+ <i :class="style.itemSuffixIcon({ class: [props.selectedIcon || theme.app.icons.check, props.ui?.itemSuffixIcon] })"></i>
250
+ </SelectItemIndicator>
251
+ </span>
252
+ </slot>
253
+ </SelectItem>
254
+ </template>
255
+ </SelectGroup>
256
+ </SelectViewport>
257
+
258
+ <SelectArrow v-if="!!props.arrow" v-bind="arrowProps" :class="style.arrow({ class: props.ui?.arrow })" />
259
+ </SelectContent>
260
+ </SelectPortal>
261
+ </SelectRoot>
262
+ </template>