@byyuurin/ui 0.0.8 → 0.0.10

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 (151) hide show
  1. package/README.md +0 -3
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +4 -3
  4. package/dist/module.mjs.map +1 -1
  5. package/dist/runtime/app/injections.d.ts +9299 -8
  6. package/dist/runtime/app/injections.js +35 -5
  7. package/dist/runtime/components/Accordion.vue +17 -19
  8. package/dist/runtime/components/Alert.vue +6 -9
  9. package/dist/runtime/components/App.vue +14 -19
  10. package/dist/runtime/components/Avatar.vue +5 -8
  11. package/dist/runtime/components/AvatarGroup.vue +2 -5
  12. package/dist/runtime/components/Badge.vue +3 -6
  13. package/dist/runtime/components/Breadcrumb.vue +22 -23
  14. package/dist/runtime/components/Button.vue +2 -3
  15. package/dist/runtime/components/ButtonGroup.vue +2 -5
  16. package/dist/runtime/components/Calendar.vue +185 -0
  17. package/dist/runtime/components/Card.vue +2 -5
  18. package/dist/runtime/components/Carousel.vue +11 -16
  19. package/dist/runtime/components/Checkbox.vue +13 -11
  20. package/dist/runtime/components/Chip.vue +6 -9
  21. package/dist/runtime/components/Collapsible.vue +2 -5
  22. package/dist/runtime/components/Drawer.vue +88 -45
  23. package/dist/runtime/components/DropdownMenu.vue +143 -0
  24. package/dist/runtime/components/DropdownMenuContent.vue +188 -0
  25. package/dist/runtime/components/Form.vue +311 -0
  26. package/dist/runtime/components/FormItem.vue +129 -0
  27. package/dist/runtime/components/Input.vue +34 -23
  28. package/dist/runtime/components/InputNumber.vue +23 -18
  29. package/dist/runtime/components/Kbd.vue +2 -6
  30. package/dist/runtime/components/Link.vue +25 -7
  31. package/dist/runtime/components/Modal.vue +31 -22
  32. package/dist/runtime/components/OverlayProvider.vue +29 -0
  33. package/dist/runtime/components/Pagination.vue +34 -27
  34. package/dist/runtime/components/PinInput.vue +23 -17
  35. package/dist/runtime/components/Popover.vue +5 -8
  36. package/dist/runtime/components/Progress.vue +2 -5
  37. package/dist/runtime/components/RadioGroup.vue +52 -49
  38. package/dist/runtime/components/ScrollArea.vue +2 -6
  39. package/dist/runtime/components/Select.vue +96 -89
  40. package/dist/runtime/components/Separator.vue +2 -6
  41. package/dist/runtime/components/Skeleton.vue +2 -5
  42. package/dist/runtime/components/Slider.vue +13 -11
  43. package/dist/runtime/components/Switch.vue +18 -11
  44. package/dist/runtime/components/Table.vue +23 -13
  45. package/dist/runtime/components/Tabs.vue +14 -16
  46. package/dist/runtime/components/Textarea.vue +20 -17
  47. package/dist/runtime/components/Toast.vue +12 -13
  48. package/dist/runtime/components/{Toaster.vue → ToastProvider.vue} +18 -16
  49. package/dist/runtime/components/Tooltip.vue +5 -8
  50. package/dist/runtime/composables/useFormItem.d.ts +27 -0
  51. package/dist/runtime/composables/useFormItem.js +64 -0
  52. package/dist/runtime/composables/useKbd.d.ts +1 -1
  53. package/dist/runtime/composables/useOverlay.d.ts +29 -0
  54. package/dist/runtime/composables/useOverlay.js +69 -0
  55. package/dist/runtime/composables/useTheme.d.ts +6 -2
  56. package/dist/runtime/composables/useTheme.js +10 -3
  57. package/dist/runtime/composables/useToast.d.ts +4 -20
  58. package/dist/runtime/composables/useToast.js +6 -5
  59. package/dist/runtime/index.d.ts +6 -2
  60. package/dist/runtime/index.js +6 -2
  61. package/dist/runtime/locale/en.js +6 -0
  62. package/dist/runtime/locale/zh-tw.js +6 -0
  63. package/dist/runtime/theme/accordion.js +3 -3
  64. package/dist/runtime/theme/alert.js +3 -3
  65. package/dist/runtime/theme/app.d.ts +1 -0
  66. package/dist/runtime/theme/app.js +2 -1
  67. package/dist/runtime/theme/avatar.js +2 -2
  68. package/dist/runtime/theme/badge.d.ts +21 -45
  69. package/dist/runtime/theme/breadcrumb.d.ts +3 -3
  70. package/dist/runtime/theme/breadcrumb.js +5 -5
  71. package/dist/runtime/theme/button.d.ts +111 -57
  72. package/dist/runtime/theme/button.js +13 -13
  73. package/dist/runtime/theme/calendar.d.ts +56 -0
  74. package/dist/runtime/theme/calendar.js +69 -0
  75. package/dist/runtime/theme/card.js +6 -6
  76. package/dist/runtime/theme/carousel.js +1 -1
  77. package/dist/runtime/theme/checkbox.js +5 -5
  78. package/dist/runtime/theme/chip.d.ts +11 -44
  79. package/dist/runtime/theme/chip.js +3 -3
  80. package/dist/runtime/theme/drawer.d.ts +85 -47
  81. package/dist/runtime/theme/drawer.js +46 -19
  82. package/dist/runtime/theme/dropdown-menu.d.ts +71 -0
  83. package/dist/runtime/theme/dropdown-menu.js +83 -0
  84. package/dist/runtime/theme/form-item.d.ts +76 -0
  85. package/dist/runtime/theme/form-item.js +34 -0
  86. package/dist/runtime/theme/form.d.ts +8 -0
  87. package/dist/runtime/theme/form.js +7 -0
  88. package/dist/runtime/theme/index.d.ts +5 -1
  89. package/dist/runtime/theme/index.js +5 -1
  90. package/dist/runtime/theme/input-number.d.ts +41 -61
  91. package/dist/runtime/theme/input-number.js +1 -1
  92. package/dist/runtime/theme/input.d.ts +99 -71
  93. package/dist/runtime/theme/input.js +15 -15
  94. package/dist/runtime/theme/kbd.d.ts +2 -2
  95. package/dist/runtime/theme/kbd.js +1 -1
  96. package/dist/runtime/theme/link.d.ts +1 -1
  97. package/dist/runtime/theme/link.js +3 -3
  98. package/dist/runtime/theme/modal.d.ts +5 -33
  99. package/dist/runtime/theme/modal.js +4 -4
  100. package/dist/runtime/theme/pagination.d.ts +27 -3
  101. package/dist/runtime/theme/pagination.js +6 -2
  102. package/dist/runtime/theme/pinInput.d.ts +42 -42
  103. package/dist/runtime/theme/pinInput.js +13 -13
  104. package/dist/runtime/theme/progress.d.ts +117 -53
  105. package/dist/runtime/theme/progress.js +4 -4
  106. package/dist/runtime/theme/radio-group.d.ts +2 -2
  107. package/dist/runtime/theme/radio-group.js +7 -7
  108. package/dist/runtime/theme/select.d.ts +100 -84
  109. package/dist/runtime/theme/select.js +21 -20
  110. package/dist/runtime/theme/separator.d.ts +13 -28
  111. package/dist/runtime/theme/separator.js +1 -1
  112. package/dist/runtime/theme/skeleton.d.ts +1 -1
  113. package/dist/runtime/theme/skeleton.js +1 -1
  114. package/dist/runtime/theme/slider.js +1 -1
  115. package/dist/runtime/theme/switch.js +5 -5
  116. package/dist/runtime/theme/table.d.ts +3 -0
  117. package/dist/runtime/theme/table.js +8 -7
  118. package/dist/runtime/theme/tabs.d.ts +51 -68
  119. package/dist/runtime/theme/tabs.js +10 -10
  120. package/dist/runtime/theme/textarea.d.ts +37 -43
  121. package/dist/runtime/theme/textarea.js +13 -13
  122. package/dist/runtime/theme/{toaster.d.ts → toast-provider.d.ts} +26 -41
  123. package/dist/runtime/theme/toast.js +5 -5
  124. package/dist/runtime/theme/tooltip.js +1 -1
  125. package/dist/runtime/types/components.d.ts +6 -1
  126. package/dist/runtime/types/form.d.ts +45 -0
  127. package/dist/runtime/types/form.js +0 -0
  128. package/dist/runtime/types/index.d.ts +5 -2
  129. package/dist/runtime/types/index.js +1 -0
  130. package/dist/runtime/types/locale.d.ts +6 -0
  131. package/dist/runtime/types/utils.d.ts +35 -12
  132. package/dist/runtime/utils/extend-theme.js +15 -4
  133. package/dist/runtime/utils/form.d.ts +5 -0
  134. package/dist/runtime/utils/form.js +24 -0
  135. package/dist/runtime/utils/index.d.ts +2 -0
  136. package/dist/runtime/utils/index.js +4 -0
  137. package/dist/runtime/utils/link.d.ts +4 -26
  138. package/dist/runtime/utils/link.js +10 -3
  139. package/dist/shared/ui.3e7fad19.mjs +5 -0
  140. package/dist/shared/ui.3e7fad19.mjs.map +1 -0
  141. package/dist/unocss.mjs +21 -16
  142. package/dist/unocss.mjs.map +1 -1
  143. package/dist/unplugin.mjs +1 -1
  144. package/dist/vite.mjs +1 -1
  145. package/package.json +20 -18
  146. package/dist/runtime/components/ModalProvider.vue +0 -11
  147. package/dist/runtime/composables/useModal.d.ts +0 -10
  148. package/dist/runtime/composables/useModal.js +0 -47
  149. package/dist/shared/ui.ba24b380.mjs +0 -4
  150. package/dist/shared/ui.ba24b380.mjs.map +0 -1
  151. /package/dist/runtime/theme/{toaster.js → toast-provider.js} +0 -0
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
- import type { HoverCardRootProps, PopoverArrowProps, PopoverContentProps, PopoverRootEmits, PopoverRootProps } from 'reka-ui'
2
+ import type { HoverCardRootProps, PopoverArrowProps, PopoverContentEmits, PopoverContentProps, PopoverRootEmits, PopoverRootProps } from 'reka-ui'
3
3
  import type { popover } from '../theme'
4
- import type { ComponentAttrs } from '../types'
4
+ import type { ComponentAttrs, EmitsToProps } from '../types'
5
5
 
6
6
  export interface PopoverEmits extends PopoverRootEmits {}
7
7
 
@@ -17,7 +17,7 @@ export interface PopoverProps extends ComponentAttrs<typeof popover>, PopoverRoo
17
17
  */
18
18
  mode?: 'click' | 'hover'
19
19
  /** @default { side: 'bottom', sideOffset: 8, collisionPadding: 8 } */
20
- content?: Omit<PopoverContentProps, 'as' | 'asChild' | 'forceMount'>
20
+ content?: Omit<PopoverContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<PopoverContentEmits>>
21
21
  arrow?: boolean | Omit<PopoverArrowProps, 'as' | 'asChild'>
22
22
  /** @default true */
23
23
  portal?: boolean
@@ -69,11 +69,8 @@ const arrowProps = toRef(() => props.arrow as PopoverArrowProps)
69
69
 
70
70
  const Component = computed(() => props.mode === 'hover' ? HoverCard : Popover)
71
71
 
72
- const { theme, createStyler } = useTheme()
73
- const style = computed(() => {
74
- const styler = createStyler(theme.value.popover)
75
- return styler(props)
76
- })
72
+ const { generateStyle } = useTheme()
73
+ const style = computed(() => generateStyle('popover', props))
77
74
  </script>
78
75
 
79
76
  <template>
@@ -83,11 +83,8 @@ const percent = computed(() => {
83
83
  })
84
84
 
85
85
  const { dir } = useLocale()
86
- const { theme, createStyler } = useTheme()
87
- const style = computed(() => {
88
- const styler = createStyler(theme.value.progress)
89
- return styler(props)
90
- })
86
+ const { generateStyle } = useTheme()
87
+ const style = computed(() => generateStyle('progress', props))
91
88
 
92
89
  const indicatorStyle = computed(() => {
93
90
  if (percent.value === undefined)
@@ -1,44 +1,35 @@
1
1
  <script lang="ts">
2
2
  import type { VariantProps } from '@byyuurin/ui-kit'
3
- import type { AcceptableValue, PrimitiveProps, RadioGroupRootProps } from 'reka-ui'
3
+ import type { PrimitiveProps, RadioGroupRootProps } from 'reka-ui'
4
4
  import type { radioGroup } from '../theme'
5
- import type { ComponentAttrs } from '../types'
5
+ import type { AcceptableValue, ComponentAttrs } from '../types'
6
6
 
7
7
  export interface RadioGroupEmits {
8
- (event: 'update:modelValue', payload: string): void
9
- (event: 'change', payload: Event): void
8
+ 'update:modelValue': [payload: string]
9
+ 'change': [payload: Event]
10
10
  }
11
11
 
12
- type SlotProps<T> = (props: { item: NormalizeItem<T>, modelValue?: AcceptableValue }) => any
12
+ export type RadioGroupValue = AcceptableValue
13
13
 
14
- export interface RadioGroupSlots<T> {
15
- legend?: (props?: {}) => any
14
+ export type RadioGroupItem = {
15
+ label?: string
16
+ description?: string
17
+ disabled?: boolean
18
+ value?: RadioGroupValue
19
+ [key: string]: any
20
+ } | RadioGroupValue
21
+
22
+ type SlotProps<T extends RadioGroupItem> = (props: { item: T & { id: string }, modelValue?: RadioGroupValue }) => any
23
+
24
+ export interface RadioGroupSlots<T extends RadioGroupItem = RadioGroupItem> {
25
+ legend?: any
16
26
  label?: SlotProps<T>
17
27
  description?: SlotProps<T>
18
28
  }
19
29
 
20
- type NormalizeItem<T> = { id: string } & (
21
- T extends RadioOption
22
- ? T
23
- : {
24
- id: string
25
- label: string
26
- value: any
27
- description: string
28
- disabled: false
29
- }
30
- )
31
-
32
30
  type RadioGroupVariants = VariantProps<typeof radioGroup>
33
31
 
34
- export interface RadioOption {
35
- label?: string
36
- description?: string
37
- disabled?: boolean
38
- value?: string
39
- }
40
-
41
- export interface RadioGroupProps<T> extends ComponentAttrs<typeof radioGroup>, Pick<RadioGroupRootProps, 'defaultValue' | 'disabled' | 'loop' | 'modelValue' | 'name' | 'required'> {
32
+ export interface RadioGroupProps<T extends RadioGroupItem = RadioGroupItem> extends ComponentAttrs<typeof radioGroup>, Pick<RadioGroupRootProps, 'defaultValue' | 'disabled' | 'loop' | 'modelValue' | 'name' | 'required'> {
42
33
  /**
43
34
  * The element or component this component should render as.
44
35
  * @default "div"
@@ -70,10 +61,11 @@ export interface RadioGroupProps<T> extends ComponentAttrs<typeof radioGroup>, P
70
61
  }
71
62
  </script>
72
63
 
73
- <script lang="ts" setup generic="T extends RadioOption | AcceptableValue">
64
+ <script lang="ts" setup generic="T extends RadioGroupItem">
74
65
  import { reactivePick } from '@vueuse/core'
75
66
  import { Label, RadioGroupIndicator, RadioGroupItem, RadioGroupRoot, useForwardPropsEmits } from 'reka-ui'
76
67
  import { computed, useId } from 'vue'
68
+ import { useFormItem } from '../composables/useFormItem'
77
69
  import { useTheme } from '../composables/useTheme'
78
70
  import { get } from '../utils'
79
71
 
@@ -88,22 +80,33 @@ const emit = defineEmits<RadioGroupEmits>()
88
80
  const slots = defineSlots<RadioGroupSlots<T>>()
89
81
 
90
82
  const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'loop', 'required'), emit)
91
- const id = useId()
92
83
 
93
- const { theme, createStyler } = useTheme()
94
- const style = computed(() => {
95
- const styler = createStyler(theme.value.radioGroup)
96
- return styler(props)
97
- })
84
+ const { id: _id, name, size, disabled, ariaAttrs, emitFormChange, emitFormInput } = useFormItem<RadioGroupProps<T>>(props)
85
+ const id = _id.value ?? useId()
86
+
87
+ const { generateStyle } = useTheme()
88
+ const style = computed(() => generateStyle('radioGroup', {
89
+ ...props,
90
+ size: size.value,
91
+ disabled: disabled.value,
92
+ }))
93
+
94
+ function normalizeItem(item: any) {
95
+ if (item === null) {
96
+ return {
97
+ id: `${id}:null`,
98
+ label: undefined,
99
+ value: undefined,
100
+ }
101
+ }
98
102
 
99
- function normalizeItem(item: any): NormalizeItem<T> {
100
- if (['string', 'number', 'boolean'].includes(typeof item)) {
103
+ if (typeof item === 'string' || typeof item === 'number') {
101
104
  return {
102
105
  id: `${id}:${item}`,
106
+ label: String(item),
103
107
  value: item,
104
- label: item,
105
- description: '',
106
- } as any
108
+ disabled: disabled.value,
109
+ }
107
110
  }
108
111
 
109
112
  const value = get(item, props.valueKey)
@@ -112,10 +115,11 @@ function normalizeItem(item: any): NormalizeItem<T> {
112
115
 
113
116
  return {
114
117
  ...item,
115
- value,
118
+ id: `${id}:${value}`,
116
119
  label,
120
+ value,
117
121
  description,
118
- id: `${id}:${value}`,
122
+ disabled: disabled.value || item.disabled,
119
123
  }
120
124
  }
121
125
 
@@ -130,26 +134,25 @@ function onUpdate(value: any) {
130
134
  // @ts-expect-error - 'target' does not exist in type 'EventInit'
131
135
  const event = new Event('change', { target: { value } })
132
136
  emit('change', event)
137
+ emitFormChange()
138
+ emitFormInput()
133
139
  }
134
140
  </script>
135
141
 
136
142
  <template>
137
143
  <RadioGroupRoot
138
- :id="id"
139
144
  v-slot="{ modelValue }"
140
- v-bind="rootProps"
141
- :name="props.name"
142
- :disabled="props.disabled"
145
+ v-bind="{ ...rootProps, id, name, disabled }"
143
146
  :class="style.root({ class: [props.class, props.ui?.root] })"
144
147
  @update:model-value="onUpdate"
145
148
  >
146
- <fieldset :class="style.fieldset({ class: props.ui?.fieldset })">
149
+ <fieldset v-bind="ariaAttrs" :class="style.fieldset({ class: props.ui?.fieldset })">
147
150
  <legend v-if="props.legend || slots.legend" :class="style.legend({ class: props.ui?.legend })">
148
151
  <slot name="legend">
149
152
  {{ props.legend }}
150
153
  </slot>
151
154
  </legend>
152
- <div v-for="item in normalizedItems" :key="item.value" :class="style.item({ class: props.ui?.item })">
155
+ <div v-for="item in normalizedItems" :key="item.value" :class="style.item({ class: props.ui?.item, disabled: item.disabled })">
153
156
  <div :class="style.container({ class: props.ui?.container })">
154
157
  <RadioGroupItem
155
158
  :id="item.id"
@@ -163,10 +166,10 @@ function onUpdate(value: any) {
163
166
 
164
167
  <div :class="style.wrapper({ class: props.ui?.wrapper })">
165
168
  <Label :for="item.id" :class="style.label({ class: props.ui?.label })">
166
- <slot name="label" :item="item" :model-value="modelValue">{{ item.label }}</slot>
169
+ <slot name="label" :item="item" :model-value="(modelValue as RadioGroupValue)">{{ item.label }}</slot>
167
170
  </Label>
168
171
  <p v-if="item.description || slots.description" :class="style.description({ class: props.ui?.description })">
169
- <slot name="description" :item="item" :model-value="modelValue">
172
+ <slot name="description" :item="item" :model-value="(modelValue as RadioGroupValue)">
170
173
  {{ item.description }}
171
174
  </slot>
172
175
  </p>
@@ -17,12 +17,8 @@ const props = withDefaults(defineProps<ScrollAreaProps>(), {})
17
17
  const rootRef = ref<InstanceType<typeof ScrollAreaRoot>>()
18
18
  const rootProps = reactivePick(props, 'type', 'dir', 'scrollHideDelay')
19
19
 
20
- const { theme, createStyler } = useTheme()
21
-
22
- const style = computed(() => {
23
- const styler = createStyler(theme.value.scrollArea)
24
- return styler(props)
25
- })
20
+ const { generateStyle } = useTheme()
21
+ const style = computed(() => generateStyle('scrollArea', props))
26
22
 
27
23
  defineExpose({
28
24
  scrollTop,
@@ -1,33 +1,11 @@
1
1
  <script lang="ts">
2
2
  import type { VariantProps } from '@byyuurin/ui-kit'
3
- import type { AcceptableValue, SelectArrowProps, SelectContentProps, SelectRootProps } from 'reka-ui'
3
+ import type { SelectArrowProps, SelectContentEmits, SelectContentProps, SelectRootEmits, SelectRootProps } from 'reka-ui'
4
4
  import type { UseComponentIconsProps } from '../composables/useComponentIcons'
5
5
  import type { select } from '../theme'
6
- import type { ComponentAttrs, MaybeArray, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectOptionKey } from '../types'
6
+ import type { AcceptableValue, ArrayOrNested, ComponentAttrs, EmitsToProps, GetItemKeys, GetItemValue, GetModelValue, GetModelValueEmits, MaybeArray, NestedItem } from '../types'
7
7
 
8
- export interface SelectEmits<T, V, M extends boolean> {
9
- (event: 'update:open', value: boolean): void
10
- (event: 'update:modelValue', payload: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>): void
11
- (event: 'change', payload: Event): void
12
- (event: 'blur', payload: FocusEvent): void
13
- (event: 'focus', payload: FocusEvent): void
14
- }
15
-
16
- type SlotProps<T> = (props: { item: T, index: number }) => any
17
-
18
- export interface SelectSlots<T, M extends boolean> {
19
- 'leading'?: (props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean, ui: ComponentAttrs<typeof select>['ui'] }) => any
20
- 'default'?: (props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean }) => any
21
- 'trailing'?: (props: { modelValue?: M extends true ? AcceptableValue[] : AcceptableValue, open: boolean, ui: ComponentAttrs<typeof select>['ui'] }) => any
22
- 'item'?: SlotProps<T>
23
- 'item-leading'?: SlotProps<T>
24
- 'item-label'?: SlotProps<T>
25
- 'item-trailing'?: SlotProps<T>
26
- }
27
-
28
- type SelectVariants = VariantProps<typeof select>
29
-
30
- export interface SelectOption {
8
+ interface SelectItemBase {
31
9
  label?: string
32
10
  icon?: string
33
11
  /**
@@ -35,18 +13,43 @@ export interface SelectOption {
35
13
  * @default "option"
36
14
  */
37
15
  type?: 'label' | 'separator' | 'option'
38
- value?: string
16
+ value?: AcceptableValue
39
17
  disabled?: boolean
18
+ [key: string]: any
19
+ }
20
+
21
+ export type SelectItem = SelectItemBase | AcceptableValue | boolean
22
+
23
+ export type SelectEmits<T extends ArrayOrNested<SelectItem>, VK extends GetItemKeys<T> | undefined, M extends boolean> = Omit<SelectRootEmits, 'update:modelValue'> & {
24
+ change: [payload: Event]
25
+ blur: [payload: FocusEvent]
26
+ focus: [payload: FocusEvent]
27
+ } & GetModelValueEmits<T, VK, M>
28
+
29
+ type SlotProps<T extends SelectItem> = (props: { item: T, index: number }) => any
30
+
31
+ export interface SelectSlots<
32
+ T extends ArrayOrNested<SelectItem> = ArrayOrNested<SelectItem>,
33
+ VK extends GetItemKeys<T> | undefined = undefined,
34
+ M extends boolean = false,
35
+ I extends NestedItem<T> = NestedItem<T>,
36
+ > {
37
+ 'leading'?: (props: { modelValue?: GetModelValue<T, VK, M>, open: boolean, ui: ComponentAttrs<typeof select>['ui'] }) => any
38
+ 'default'?: (props: { modelValue?: GetModelValue<T, VK, M>, open: boolean }) => any
39
+ 'trailing'?: (props: { modelValue?: GetModelValue<T, VK, M>, open: boolean, ui: ComponentAttrs<typeof select>['ui'] }) => any
40
+ 'item'?: SlotProps<I>
41
+ 'item-leading'?: SlotProps<I>
42
+ 'item-label'?: SlotProps<I>
43
+ 'item-trailing'?: SlotProps<I>
40
44
  }
41
45
 
42
- export type SelectOptionValue = SelectOption | AcceptableValue | boolean
46
+ type SelectVariants = VariantProps<typeof select>
43
47
 
44
48
  export interface SelectProps<
45
- T extends MaybeArrayOfArrayItem<I>,
46
- I extends MaybeArrayOfArray<SelectOptionValue> = MaybeArrayOfArray<SelectOptionValue>,
47
- V extends SelectOptionKey<T> | undefined = undefined,
49
+ T extends ArrayOrNested<SelectItem> = ArrayOrNested<SelectItem>,
50
+ VK extends GetItemKeys<T> = 'value',
48
51
  M extends boolean = false,
49
- > extends Omit<SelectRootProps<T>, 'dir' | 'multiple' | 'modelValue' | 'defaultValue' | 'by'>, ComponentAttrs<typeof select>, UseComponentIconsProps {
52
+ > extends ComponentAttrs<typeof select>, UseComponentIconsProps, Omit<SelectRootProps<T>, 'dir' | 'multiple' | 'modelValue' | 'defaultValue' | 'by'> {
50
53
  id?: string
51
54
  /** The placeholder text when the select is empty. */
52
55
  placeholder?: string
@@ -66,7 +69,7 @@ export interface SelectProps<
66
69
  * The content of the menu.
67
70
  * @default { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
68
71
  */
69
- content?: Omit<SelectContentProps, 'as' | 'asChild' | 'forceMount'>
72
+ content?: Omit<SelectContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<SelectContentEmits>>
70
73
  /**
71
74
  * Display an arrow alongside the menu.
72
75
  * @default false
@@ -81,17 +84,17 @@ export interface SelectProps<
81
84
  * When `options` is an array of objects, select the field to use as the value.
82
85
  * @default "value"
83
86
  */
84
- valueKey?: V
87
+ valueKey?: VK
85
88
  /**
86
89
  * When `options` is an array of objects, select the field to use as the label.
87
90
  * @default "label"
88
91
  */
89
- labelKey?: V
90
- options?: I
92
+ labelKey?: VK
93
+ options?: T
91
94
  /** The value of the Select when initially rendered. Use when you do not need to control the state of the Select. */
92
- defaultValue?: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>
95
+ defaultValue?: GetModelValue<T, VK, M>
93
96
  /** The controlled value of the Select. Can be bind as `v-model`. */
94
- modelValue?: SelectModelValue<T, V, M, T extends { value: infer U } ? U : never>
97
+ modelValue?: GetModelValue<T, VK, M>
95
98
  /** Whether multiple options can be selected or not. */
96
99
  multiple?: M & boolean
97
100
  /** Highlight the ring color like a focus state. */
@@ -100,85 +103,89 @@ export interface SelectProps<
100
103
  }
101
104
  </script>
102
105
 
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">
106
+ <script lang="ts" setup generic="T extends ArrayOrNested<SelectItem>, VK extends GetItemKeys<T> = 'value', M extends boolean = false">
104
107
  import { reactivePick } from '@vueuse/core'
105
108
  import { defu } from 'defu'
106
109
  import { SelectArrow, SelectContent, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectPortal, SelectRoot, SelectSeparator, SelectTrigger, SelectViewport, useForwardPropsEmits } from 'reka-ui'
107
110
  import { computed, toRef } from 'vue'
108
111
  import { useButtonGroup } from '../composables/useButtonGroup'
109
112
  import { useComponentIcons } from '../composables/useComponentIcons'
113
+ import { useFormItem } from '../composables/useFormItem'
110
114
  import { useTheme } from '../composables/useTheme'
111
- import { compare, get } from '../utils'
115
+ import { compare, get, isArrayOfArray } from '../utils'
116
+
117
+ defineOptions({
118
+ inheritAttrs: false,
119
+ })
112
120
 
113
- const props = withDefaults(defineProps<SelectProps<T, I, V, M>>(), {
114
- variant: 'outline',
121
+ const props = withDefaults(defineProps<SelectProps<T, VK, M>>(), {
115
122
  valueKey: 'value' as never,
116
123
  labelKey: 'label' as never,
117
124
  portal: true,
118
125
  })
119
126
 
120
- const emit = defineEmits<SelectEmits<T, V, M>>()
121
- const slots = defineSlots<SelectSlots<T, M>>()
127
+ const emit = defineEmits<SelectEmits<T, VK, M>>()
128
+ const slots = defineSlots<SelectSlots<T, VK, M>>()
122
129
 
123
130
  const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required', 'multiple'), emit)
124
131
  const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as SelectContentProps)
125
132
  const arrowProps = toRef(() => props.arrow as SelectArrowProps)
126
133
 
127
- const { theme, createStyler } = useTheme()
134
+ const { id, name, size: formItemSize, highlight, disabled, ariaAttrs, emitFormChange, emitFormInput, emitFormBlur, emitFormFocus } = useFormItem<SelectProps<T, VK, M>>(props)
135
+ const { size: buttonGroupSize, orientation } = useButtonGroup(props)
128
136
 
129
- const { size, orientation } = useButtonGroup(props)
137
+ const { theme, generateStyle } = useTheme()
130
138
  const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, {
131
139
  trailingIcon: theme.value.app.icons.chevronDown,
132
140
  })))
133
141
 
134
- const groups = computed(() => props.options?.length ? (Array.isArray(props.options[0]) ? props.options : [props.options]) as SelectOption[][] : [])
135
- const items = computed(() => groups.value.flat() as T[])
142
+ const style = computed(() => generateStyle('select', {
143
+ ...props,
144
+ groupOrientation: orientation.value,
145
+ size: buttonGroupSize.value || formItemSize.value,
146
+ highlight: highlight.value,
147
+ leading: isLeading.value,
148
+ trailing: isTrailing.value,
149
+ }))
136
150
 
137
- const style = computed(() => {
138
- const styler = createStyler(theme.value.select)
139
- return styler({
140
- ...props,
141
- size: size.value,
142
- groupOrientation: orientation.value,
143
- leading: isLeading.value,
144
- trailing: isTrailing.value,
145
- })
146
- })
147
-
148
- function typedItem(item: SelectOption) {
149
- return item as unknown as T
150
- }
151
+ const groups = computed<SelectItem[][]>(
152
+ () => props.options?.length
153
+ ? isArrayOfArray(props.options) ? props.options : [props.options]
154
+ : [],
155
+ )
151
156
 
152
- function typedValue<V extends MaybeArray<AcceptableValue>>(value?: V) {
153
- return value as unknown as M extends true ? AcceptableValue[] : AcceptableValue
154
- }
155
-
156
- function typedModelValue(value: any) {
157
- return value as MaybeArray<AcceptableValue> | undefined
158
- }
157
+ const items = computed(() => groups.value.flat() as T[])
159
158
 
160
- function displayValue(value?: MaybeArray<AcceptableValue>): string | undefined {
159
+ function displayValue(value?: MaybeArray<GetItemValue<T, VK>>): string {
161
160
  if (props.multiple && Array.isArray(value))
162
161
  return value.map((v) => displayValue(v)).filter(Boolean).join(', ')
163
162
 
164
163
  const item = items.value.find((item) => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
165
- return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
164
+ return (item != null) && (typeof item === 'object' ? get(item, props.labelKey as string) : String(item))
165
+ }
166
+
167
+ function isSelectItem(item: SelectItem): item is SelectItemBase {
168
+ return typeof item === 'object' && item !== null
166
169
  }
167
170
 
168
171
  function onUpdate(value: any) {
169
172
  // @ts-expect-error - 'target' does not exist in type 'EventInit'
170
173
  const event = new Event('change', { target: { value } })
171
174
  emit('change', event)
175
+ emitFormChange()
176
+ emitFormInput()
172
177
  }
173
178
 
174
179
  function onUpdateOpen(value: boolean) {
175
180
  if (value) {
176
181
  const event = new FocusEvent('focus')
177
182
  emit('focus', event)
183
+ emitFormFocus()
178
184
  }
179
185
  else {
180
186
  const event = new FocusEvent('blur')
181
187
  emit('blur', event)
188
+ emitFormBlur()
182
189
  }
183
190
  }
184
191
  </script>
@@ -186,24 +193,24 @@ function onUpdateOpen(value: boolean) {
186
193
  <template>
187
194
  <SelectRoot
188
195
  v-slot="{ modelValue: innerValue, open }"
189
- :name="props.name"
190
196
  v-bind="rootProps"
197
+ :name="name"
191
198
  :autocomplete="props.autocomplete"
192
- :disabled="props.disabled"
193
- :default-value="typedModelValue(props.defaultValue)"
194
- :model-value="typedModelValue(props.modelValue)"
199
+ :disabled="disabled"
200
+ :default-value="(props.defaultValue as MaybeArray<AcceptableValue>)"
201
+ :model-value="(props.modelValue as MaybeArray<AcceptableValue>)"
195
202
  @update:model-value="onUpdate"
196
203
  @update:open="onUpdateOpen"
197
204
  >
198
- <SelectTrigger :id="props.id" :class="style.base({ class: [props.class, props.ui?.base] })">
205
+ <SelectTrigger v-bind="{ ...$attrs, ...ariaAttrs, id }" :class="style.base({ class: [props.class, props.ui?.base] })">
199
206
  <span v-if="isLeading || slots.leading" :class="style.leading({ class: props.ui?.leading })">
200
- <slot name="leading" :model-value="typedValue(innerValue)" :open="open" :ui="props.ui">
207
+ <slot name="leading" :model-value="(innerValue as GetModelValue<T, VK, M>)" :open="open" :ui="props.ui">
201
208
  <span v-if="isLeading && leadingIconName" :class="style.leadingIcon({ class: [leadingIconName, props.ui?.leadingIcon] })"></span>
202
209
  </slot>
203
210
  </span>
204
211
 
205
- <slot :model-value="typedValue(innerValue)" :open="open">
206
- <template v-for="displayedModelValue in [displayValue(innerValue)]" :key="displayedModelValue">
212
+ <slot :model-value="(innerValue as GetModelValue<T, VK, M>)" :open="open">
213
+ <template v-for="displayedModelValue in [displayValue((innerValue as GetModelValue<T, VK, M>))]" :key="displayedModelValue">
207
214
  <span v-if="displayedModelValue" :class="style.value({ class: props.ui?.value })">
208
215
  {{ displayedModelValue }}
209
216
  </span>
@@ -214,7 +221,7 @@ function onUpdateOpen(value: boolean) {
214
221
  </slot>
215
222
 
216
223
  <span v-if="isTrailing || !!slots.trailing" :class="style.trailing({ class: props.ui?.trailing })">
217
- <slot name="trailing" :model-value="typedValue(innerValue)" :open="open" :ui="props.ui">
224
+ <slot name="trailing" :model-value="(innerValue as GetModelValue<T, VK, M>)" :open="open" :ui="props.ui">
218
225
  <span v-if="trailingIconName" :class="style.trailingIcon({ class: [trailingIconName, props.ui?.trailingIcon] })"></span>
219
226
  </slot>
220
227
  </span>
@@ -225,30 +232,30 @@ function onUpdateOpen(value: boolean) {
225
232
  <SelectViewport :class="style.viewport({ class: props.ui?.viewport })">
226
233
  <SelectGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="style.group({ class: props.ui?.group })">
227
234
  <template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
228
- <SelectLabel v-if="item.type === 'label'" :class="style.label({ class: props.ui?.label })">
235
+ <SelectLabel v-if="isSelectItem(item) && item.type === 'label'" :class="style.label({ class: props.ui?.label })">
229
236
  {{ get(item, props.labelKey as string) }}
230
237
  </SelectLabel>
231
- <SelectSeparator v-else-if="item.type === 'separator'" :class="style.separator({ class: props.ui?.separator })" />
238
+ <SelectSeparator v-else-if="isSelectItem(item) && item.type === 'separator'" :class="style.separator({ class: props.ui?.separator })" />
232
239
 
233
240
  <SelectItem
234
241
  v-else
235
242
  :class="style.item({ class: props.ui?.item })"
236
- :disabled="item.disabled"
237
- :value="typeof item === 'object' ? get(item, props.valueKey as string) : item"
243
+ :disabled="isSelectItem(item) && item.disabled"
244
+ :value="isSelectItem(item) ? get(item, props.valueKey as string) : item"
238
245
  >
239
- <slot name="item" :item="typedItem(item)" :index="index">
240
- <slot name="item-leading" :item="typedItem(item)" :index="index">
241
- <span v-if="item.icon" :class="style.itemLeadingIcon({ class: [item.icon, props.ui?.itemLeadingIcon] })"></span>
246
+ <slot name="item" :item="(item as NestedItem<T>)" :index="index">
247
+ <slot name="item-leading" :item="(item as NestedItem<T>)" :index="index">
248
+ <span v-if="isSelectItem(item) && item.icon" :class="style.itemLeadingIcon({ class: [item.icon, props.ui?.itemLeadingIcon] })"></span>
242
249
  </slot>
243
250
 
244
251
  <SelectItemText :class="style.itemLabel({ class: props.ui?.itemLabel })">
245
- <slot name="item-label" :item="typedItem(item)" :index="index">
246
- {{ typeof item === 'object' ? get(item, props.labelKey as string) : item }}
252
+ <slot name="item-label" :item="(item as NestedItem<T>)" :index="index">
253
+ {{ isSelectItem(item) ? get(item, props.labelKey as string) : item }}
247
254
  </slot>
248
255
  </SelectItemText>
249
256
 
250
257
  <span :class="style.itemTrailing({ class: props.ui?.itemTrailing })">
251
- <slot name="item-trailing" :item="typedItem(item)" :index="index"></slot>
258
+ <slot name="item-trailing" :item="(item as NestedItem<T>)" :index="index"></slot>
252
259
 
253
260
  <SelectItemIndicator as-child>
254
261
  <span :class="style.itemTrailingIcon({ class: [props.selectedIcon || theme.app.icons.check, props.ui?.itemTrailingIcon] })"></span>
@@ -37,12 +37,8 @@ const slots = defineSlots<SeparatorSlots>()
37
37
 
38
38
  const rootProps = useForwardProps(reactivePick(props, 'as', 'decorative', 'orientation'))
39
39
 
40
- const { theme, createStyler } = useTheme()
41
-
42
- const style = computed(() => {
43
- const styler = createStyler(theme.value.separator)
44
- return styler(props)
45
- })
40
+ const { generateStyle } = useTheme()
41
+ const style = computed(() => generateStyle('separator', props))
46
42
  </script>
47
43
 
48
44
  <template>
@@ -19,11 +19,8 @@ import { useTheme } from '../composables/useTheme'
19
19
 
20
20
  const props = withDefaults(defineProps<SkeletonProps>(), {})
21
21
 
22
- const { theme, createStyler } = useTheme()
23
- const styler = computed(() => {
24
- const styler = createStyler(theme.value.skeleton)
25
- return styler(props)
26
- })
22
+ const { generateStyle } = useTheme()
23
+ const styler = computed(() => generateStyle('skeleton', props))
27
24
  </script>
28
25
 
29
26
  <template>
@@ -2,11 +2,11 @@
2
2
  import type { VariantProps } from '@byyuurin/ui-kit'
3
3
  import type { SliderRootProps } from 'reka-ui'
4
4
  import type { slider } from '../theme'
5
- import type { ComponentAttrs } from '../types'
5
+ import type { ComponentAttrs, MaybeArray } from '../types'
6
6
 
7
7
  export interface SliderEmits {
8
- (event: 'update:modelValue', payload: number | number[]): void
9
- (event: 'change', payload: Event): void
8
+ 'update:modelValue': [payload: MaybeArray<number>]
9
+ 'change': [payload: Event]
10
10
  }
11
11
 
12
12
  type SliderVariants = VariantProps<typeof slider>
@@ -28,6 +28,7 @@ export interface SliderProps extends ComponentAttrs<typeof slider>, Pick<SliderR
28
28
  import { reactivePick } from '@vueuse/core'
29
29
  import { SliderRange, SliderRoot, SliderThumb, SliderTrack, useForwardPropsEmits } from 'reka-ui'
30
30
  import { computed } from 'vue'
31
+ import { useFormItem } from '../composables/useFormItem'
31
32
  import { useTheme } from '../composables/useTheme'
32
33
 
33
34
  const props = withDefaults(defineProps<SliderProps>(), {
@@ -63,25 +64,26 @@ const sliderValue = computed({
63
64
 
64
65
  const thumbsCount = computed(() => sliderValue.value?.length ?? 1)
65
66
 
66
- const { theme, createStyler } = useTheme()
67
- const style = computed(() => {
68
- const styler = createStyler(theme.value.slider)
69
- return styler(props)
70
- })
67
+ const { id, size, name, disabled, ariaAttrs, emitFormChange, emitFormInput } = useFormItem<SliderProps>(props)
68
+ const { generateStyle } = useTheme()
69
+ const style = computed(() => generateStyle('slider', {
70
+ ...props,
71
+ size: size.value,
72
+ }))
71
73
 
72
74
  function onChange(value: any) {
73
75
  // @ts-expect-error - 'target' does not exist in type 'EventInit'
74
76
  const event = new Event('change', { target: { value } })
75
77
  emit('change', event)
78
+ emitFormChange()
79
+ emitFormInput()
76
80
  }
77
81
  </script>
78
82
 
79
83
  <template>
80
84
  <SliderRoot
81
- v-bind="rootProps"
85
+ v-bind="{ ...rootProps, ...ariaAttrs, id, name, disabled }"
82
86
  v-model="sliderValue"
83
- :name="props.name"
84
- :disabled="props.disabled"
85
87
  :class="style.root({ class: [props.class, props.ui?.root] })"
86
88
  :default-value="defaultSliderValue"
87
89
  :data-steps="thumbsCount"