@byyuurin/ui 0.0.9 → 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 (91) hide show
  1. package/README.md +0 -3
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +1 -1
  4. package/dist/runtime/app/injections.d.ts +9299 -3
  5. package/dist/runtime/app/injections.js +35 -0
  6. package/dist/runtime/components/Accordion.vue +16 -20
  7. package/dist/runtime/components/Alert.vue +1 -1
  8. package/dist/runtime/components/Badge.vue +1 -1
  9. package/dist/runtime/components/Breadcrumb.vue +17 -21
  10. package/dist/runtime/components/Calendar.vue +15 -6
  11. package/dist/runtime/components/Carousel.vue +5 -3
  12. package/dist/runtime/components/Checkbox.vue +12 -7
  13. package/dist/runtime/components/Drawer.vue +12 -12
  14. package/dist/runtime/components/DropdownMenu.vue +143 -0
  15. package/dist/runtime/components/DropdownMenuContent.vue +188 -0
  16. package/dist/runtime/components/Form.vue +311 -0
  17. package/dist/runtime/components/FormItem.vue +129 -0
  18. package/dist/runtime/components/Input.vue +27 -13
  19. package/dist/runtime/components/InputNumber.vue +22 -14
  20. package/dist/runtime/components/Link.vue +17 -2
  21. package/dist/runtime/components/Modal.vue +11 -11
  22. package/dist/runtime/components/PinInput.vue +22 -13
  23. package/dist/runtime/components/Popover.vue +3 -3
  24. package/dist/runtime/components/RadioGroup.vue +50 -46
  25. package/dist/runtime/components/Select.vue +90 -80
  26. package/dist/runtime/components/Slider.vue +12 -7
  27. package/dist/runtime/components/Switch.vue +12 -6
  28. package/dist/runtime/components/Table.vue +21 -8
  29. package/dist/runtime/components/Tabs.vue +12 -11
  30. package/dist/runtime/components/Textarea.vue +19 -13
  31. package/dist/runtime/components/Toast.vue +6 -3
  32. package/dist/runtime/components/Tooltip.vue +3 -3
  33. package/dist/runtime/composables/useFormItem.d.ts +27 -0
  34. package/dist/runtime/composables/useFormItem.js +64 -0
  35. package/dist/runtime/composables/useTheme.js +2 -1
  36. package/dist/runtime/index.d.ts +3 -0
  37. package/dist/runtime/index.js +3 -0
  38. package/dist/runtime/theme/app.d.ts +1 -0
  39. package/dist/runtime/theme/app.js +2 -1
  40. package/dist/runtime/theme/badge.d.ts +21 -45
  41. package/dist/runtime/theme/breadcrumb.d.ts +3 -3
  42. package/dist/runtime/theme/button.d.ts +111 -57
  43. package/dist/runtime/theme/calendar.d.ts +2 -2
  44. package/dist/runtime/theme/chip.d.ts +11 -44
  45. package/dist/runtime/theme/drawer.d.ts +68 -33
  46. package/dist/runtime/theme/dropdown-menu.d.ts +71 -0
  47. package/dist/runtime/theme/dropdown-menu.js +83 -0
  48. package/dist/runtime/theme/form-item.d.ts +76 -0
  49. package/dist/runtime/theme/form-item.js +34 -0
  50. package/dist/runtime/theme/form.d.ts +8 -0
  51. package/dist/runtime/theme/form.js +7 -0
  52. package/dist/runtime/theme/index.d.ts +3 -0
  53. package/dist/runtime/theme/index.js +3 -0
  54. package/dist/runtime/theme/input-number.d.ts +41 -61
  55. package/dist/runtime/theme/input.d.ts +99 -71
  56. package/dist/runtime/theme/input.js +2 -2
  57. package/dist/runtime/theme/modal.d.ts +5 -33
  58. package/dist/runtime/theme/pinInput.d.ts +42 -42
  59. package/dist/runtime/theme/pinInput.js +1 -1
  60. package/dist/runtime/theme/progress.d.ts +117 -53
  61. package/dist/runtime/theme/select.d.ts +100 -84
  62. package/dist/runtime/theme/select.js +2 -1
  63. package/dist/runtime/theme/separator.d.ts +13 -28
  64. package/dist/runtime/theme/table.d.ts +3 -0
  65. package/dist/runtime/theme/table.js +2 -1
  66. package/dist/runtime/theme/tabs.d.ts +51 -68
  67. package/dist/runtime/theme/textarea.d.ts +37 -43
  68. package/dist/runtime/theme/textarea.js +1 -1
  69. package/dist/runtime/theme/toast-provider.d.ts +26 -41
  70. package/dist/runtime/types/components.d.ts +3 -0
  71. package/dist/runtime/types/form.d.ts +45 -0
  72. package/dist/runtime/types/form.js +0 -0
  73. package/dist/runtime/types/index.d.ts +5 -2
  74. package/dist/runtime/types/index.js +1 -0
  75. package/dist/runtime/types/utils.d.ts +32 -11
  76. package/dist/runtime/utils/extend-theme.js +15 -4
  77. package/dist/runtime/utils/form.d.ts +5 -0
  78. package/dist/runtime/utils/form.js +24 -0
  79. package/dist/runtime/utils/index.d.ts +2 -0
  80. package/dist/runtime/utils/index.js +4 -0
  81. package/dist/runtime/utils/link.d.ts +4 -26
  82. package/dist/runtime/utils/link.js +10 -3
  83. package/dist/shared/ui.3e7fad19.mjs +5 -0
  84. package/dist/shared/ui.3e7fad19.mjs.map +1 -0
  85. package/dist/unocss.mjs +2 -2
  86. package/dist/unocss.mjs.map +1 -1
  87. package/dist/unplugin.mjs +1 -1
  88. package/dist/vite.mjs +1 -1
  89. package/package.json +16 -14
  90. package/dist/shared/ui.1a1f119c.mjs +0 -5
  91. package/dist/shared/ui.1a1f119c.mjs.map +0 -1
@@ -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
40
19
  }
41
20
 
42
- export type SelectOptionValue = SelectOption | AcceptableValue | boolean
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>
44
+ }
45
+
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,82 +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'
112
116
 
113
- const props = withDefaults(defineProps<SelectProps<T, I, V, M>>(), {
114
- variant: 'outline',
117
+ defineOptions({
118
+ inheritAttrs: false,
119
+ })
120
+
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, generateStyle } = 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
142
  const style = computed(() => generateStyle('select', {
135
143
  ...props,
136
- size: size.value,
137
144
  groupOrientation: orientation.value,
145
+ size: buttonGroupSize.value || formItemSize.value,
146
+ highlight: highlight.value,
138
147
  leading: isLeading.value,
139
148
  trailing: isTrailing.value,
140
149
  }))
141
150
 
142
- const groups = computed(() => props.options?.length ? (Array.isArray(props.options[0]) ? props.options : [props.options]) as SelectOption[][] : [])
143
- const items = computed(() => groups.value.flat() as T[])
144
-
145
- function typedItem(item: SelectOption) {
146
- return item as unknown as T
147
- }
151
+ const groups = computed<SelectItem[][]>(
152
+ () => props.options?.length
153
+ ? isArrayOfArray(props.options) ? props.options : [props.options]
154
+ : [],
155
+ )
148
156
 
149
- function typedValue<V extends MaybeArray<AcceptableValue>>(value?: V) {
150
- return value as unknown as M extends true ? AcceptableValue[] : AcceptableValue
151
- }
152
-
153
- function typedModelValue(value: any) {
154
- return value as MaybeArray<AcceptableValue> | undefined
155
- }
157
+ const items = computed(() => groups.value.flat() as T[])
156
158
 
157
- function displayValue(value?: MaybeArray<AcceptableValue>): string | undefined {
159
+ function displayValue(value?: MaybeArray<GetItemValue<T, VK>>): string {
158
160
  if (props.multiple && Array.isArray(value))
159
161
  return value.map((v) => displayValue(v)).filter(Boolean).join(', ')
160
162
 
161
163
  const item = items.value.find((item) => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
162
- return (item != null) && (typeof item === 'object' ? get(item, props.labelKey as string) : item.toString())
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
163
169
  }
164
170
 
165
171
  function onUpdate(value: any) {
166
172
  // @ts-expect-error - 'target' does not exist in type 'EventInit'
167
173
  const event = new Event('change', { target: { value } })
168
174
  emit('change', event)
175
+ emitFormChange()
176
+ emitFormInput()
169
177
  }
170
178
 
171
179
  function onUpdateOpen(value: boolean) {
172
180
  if (value) {
173
181
  const event = new FocusEvent('focus')
174
182
  emit('focus', event)
183
+ emitFormFocus()
175
184
  }
176
185
  else {
177
186
  const event = new FocusEvent('blur')
178
187
  emit('blur', event)
188
+ emitFormBlur()
179
189
  }
180
190
  }
181
191
  </script>
@@ -183,24 +193,24 @@ function onUpdateOpen(value: boolean) {
183
193
  <template>
184
194
  <SelectRoot
185
195
  v-slot="{ modelValue: innerValue, open }"
186
- :name="props.name"
187
196
  v-bind="rootProps"
197
+ :name="name"
188
198
  :autocomplete="props.autocomplete"
189
- :disabled="props.disabled"
190
- :default-value="typedModelValue(props.defaultValue)"
191
- :model-value="typedModelValue(props.modelValue)"
199
+ :disabled="disabled"
200
+ :default-value="(props.defaultValue as MaybeArray<AcceptableValue>)"
201
+ :model-value="(props.modelValue as MaybeArray<AcceptableValue>)"
192
202
  @update:model-value="onUpdate"
193
203
  @update:open="onUpdateOpen"
194
204
  >
195
- <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] })">
196
206
  <span v-if="isLeading || slots.leading" :class="style.leading({ class: props.ui?.leading })">
197
- <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">
198
208
  <span v-if="isLeading && leadingIconName" :class="style.leadingIcon({ class: [leadingIconName, props.ui?.leadingIcon] })"></span>
199
209
  </slot>
200
210
  </span>
201
211
 
202
- <slot :model-value="typedValue(innerValue)" :open="open">
203
- <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">
204
214
  <span v-if="displayedModelValue" :class="style.value({ class: props.ui?.value })">
205
215
  {{ displayedModelValue }}
206
216
  </span>
@@ -211,7 +221,7 @@ function onUpdateOpen(value: boolean) {
211
221
  </slot>
212
222
 
213
223
  <span v-if="isTrailing || !!slots.trailing" :class="style.trailing({ class: props.ui?.trailing })">
214
- <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">
215
225
  <span v-if="trailingIconName" :class="style.trailingIcon({ class: [trailingIconName, props.ui?.trailingIcon] })"></span>
216
226
  </slot>
217
227
  </span>
@@ -222,30 +232,30 @@ function onUpdateOpen(value: boolean) {
222
232
  <SelectViewport :class="style.viewport({ class: props.ui?.viewport })">
223
233
  <SelectGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="style.group({ class: props.ui?.group })">
224
234
  <template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
225
- <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 })">
226
236
  {{ get(item, props.labelKey as string) }}
227
237
  </SelectLabel>
228
- <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 })" />
229
239
 
230
240
  <SelectItem
231
241
  v-else
232
242
  :class="style.item({ class: props.ui?.item })"
233
- :disabled="item.disabled"
234
- :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"
235
245
  >
236
- <slot name="item" :item="typedItem(item)" :index="index">
237
- <slot name="item-leading" :item="typedItem(item)" :index="index">
238
- <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>
239
249
  </slot>
240
250
 
241
251
  <SelectItemText :class="style.itemLabel({ class: props.ui?.itemLabel })">
242
- <slot name="item-label" :item="typedItem(item)" :index="index">
243
- {{ 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 }}
244
254
  </slot>
245
255
  </SelectItemText>
246
256
 
247
257
  <span :class="style.itemTrailing({ class: props.ui?.itemTrailing })">
248
- <slot name="item-trailing" :item="typedItem(item)" :index="index"></slot>
258
+ <slot name="item-trailing" :item="(item as NestedItem<T>)" :index="index"></slot>
249
259
 
250
260
  <SelectItemIndicator as-child>
251
261
  <span :class="style.itemTrailingIcon({ class: [props.selectedIcon || theme.app.icons.check, props.ui?.itemTrailingIcon] })"></span>
@@ -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,22 +64,26 @@ const sliderValue = computed({
63
64
 
64
65
  const thumbsCount = computed(() => sliderValue.value?.length ?? 1)
65
66
 
67
+ const { id, size, name, disabled, ariaAttrs, emitFormChange, emitFormInput } = useFormItem<SliderProps>(props)
66
68
  const { generateStyle } = useTheme()
67
- const style = computed(() => generateStyle('slider', props))
69
+ const style = computed(() => generateStyle('slider', {
70
+ ...props,
71
+ size: size.value,
72
+ }))
68
73
 
69
74
  function onChange(value: any) {
70
75
  // @ts-expect-error - 'target' does not exist in type 'EventInit'
71
76
  const event = new Event('change', { target: { value } })
72
77
  emit('change', event)
78
+ emitFormChange()
79
+ emitFormInput()
73
80
  }
74
81
  </script>
75
82
 
76
83
  <template>
77
84
  <SliderRoot
78
- v-bind="rootProps"
85
+ v-bind="{ ...rootProps, ...ariaAttrs, id, name, disabled }"
79
86
  v-model="sliderValue"
80
- :name="props.name"
81
- :disabled="props.disabled"
82
87
  :class="style.root({ class: [props.class, props.ui?.root] })"
83
88
  :default-value="defaultSliderValue"
84
89
  :data-steps="thumbsCount"
@@ -5,7 +5,7 @@ import type { switch as _switch } from '../theme'
5
5
  import type { ComponentAttrs } from '../types'
6
6
 
7
7
  export interface SwitchEmits {
8
- (event: 'change', payload: Event): void
8
+ change: [payload: Event]
9
9
  }
10
10
 
11
11
  export interface SwitchSlots {
@@ -37,6 +37,7 @@ export interface SwitchProps extends ComponentAttrs<typeof _switch>, Pick<Switch
37
37
  import { reactivePick } from '@vueuse/core'
38
38
  import { Label, Primitive, SwitchRoot, SwitchThumb, useForwardProps } from 'reka-ui'
39
39
  import { computed, useId } from 'vue'
40
+ import { useFormItem } from '../composables/useFormItem'
40
41
  import { useTheme } from '../composables/useTheme'
41
42
 
42
43
  const props = withDefaults(defineProps<SwitchProps>(), {})
@@ -45,11 +46,15 @@ const slots = defineSlots<SwitchSlots>()
45
46
  const modelValue = defineModel<boolean>({ default: undefined })
46
47
 
47
48
  const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
48
- const id = useId()
49
+
50
+ const { id: _id, size, name, disabled, ariaAttrs, emitFormChange, emitFormInput } = useFormItem<SwitchProps>(props)
51
+ const id = _id.value ?? useId()
49
52
 
50
53
  const { theme, generateStyle } = useTheme()
51
54
  const style = computed(() => generateStyle('switch', {
52
55
  ...props,
56
+ size: size.value,
57
+ disabled: disabled.value,
53
58
  checked: false,
54
59
  unchecked: false,
55
60
  }))
@@ -57,7 +62,10 @@ const style = computed(() => generateStyle('switch', {
57
62
  function onUpdate(value: any) {
58
63
  // @ts-expect-error - 'target' does not exist in type 'EventInit'
59
64
  const event = new Event('change', { target: { value } })
65
+
60
66
  emit('change', event)
67
+ emitFormChange()
68
+ emitFormInput()
61
69
  }
62
70
  </script>
63
71
 
@@ -65,12 +73,10 @@ function onUpdate(value: any) {
65
73
  <Primitive :as="props.as" :class="style.root({ class: [props.class, props.ui?.root] })">
66
74
  <div :class="style.container({ class: props.ui?.container })">
67
75
  <SwitchRoot
68
- :id="id"
69
- v-bind="rootProps"
76
+ v-bind="{ ...rootProps, ...ariaAttrs, id, name }"
70
77
  v-model="modelValue"
71
- :name="props.name"
72
- :disabled="props.disabled || props.loading"
73
78
  :class="style.base({ class: props.ui?.base })"
79
+ :disabled="disabled || props.loading"
74
80
  @update:model-value="onUpdate"
75
81
  >
76
82
  <SwitchThumb :class="style.thumb({ class: props.ui?.thumb })">
@@ -10,9 +10,10 @@ type DynamicHeaderSlots<T, K = keyof T> = Record<string, (props: HeaderContext<T
10
10
  type DynamicCellSlots<T, K = keyof T> = Record<string, (props: CellContext<T, unknown>) => any> & Record<`${K extends string ? K : never}-cell`, (props: CellContext<T, unknown>) => any>
11
11
 
12
12
  export type TableSlots<T> = {
13
- expanded: (props: { row: Row<T> }) => any
14
- empty: (props?: {}) => any
15
- caption: (props?: {}) => any
13
+ expanded?: (props: { row: Row<T> }) => any
14
+ empty?: any
15
+ loading?: any
16
+ caption?: any
16
17
  } & DynamicHeaderSlots<T> & DynamicCellSlots<T>
17
18
 
18
19
  export type TableData = RowData
@@ -33,11 +34,18 @@ export interface TableProps<T extends TableData> extends ComponentAttrs<typeof t
33
34
  data?: T[]
34
35
  columns?: TableColumn<T>[]
35
36
  caption?: string
37
+ /**
38
+ * The text to display when the table is empty.
39
+ * @default t('table.noData')
40
+ */
41
+ empty?: string
36
42
  /**
37
43
  * Whether the table should have a sticky header.
38
44
  * @default false
39
45
  */
40
46
  sticky?: boolean
47
+ /** Whether the table should be in loading state. */
48
+ loading?: boolean
41
49
  /**
42
50
  * @link [API Docs](https://tanstack.com/table/v8/docs/api/features/global-filtering#table-options)
43
51
  * @link [Guide](https://tanstack.com/table/v8/docs/guide/global-filtering)
@@ -111,8 +119,7 @@ import { useLocale } from '../composables/useLocale'
111
119
  import { useTheme } from '../composables/useTheme'
112
120
 
113
121
  const props = defineProps<TableProps<T>>()
114
-
115
- defineSlots<TableSlots<T>>()
122
+ const slots = defineSlots<TableSlots<T>>()
116
123
 
117
124
  const globalFilterState = defineModel<string>('globalFilter', { default: undefined })
118
125
  const columnFiltersState = defineModel<ColumnFiltersState>('columnFilters', { default: [] })
@@ -137,7 +144,7 @@ const columns = computed<TableColumn<T>[]>(
137
144
  )
138
145
 
139
146
  const tableApi = useVueTable({
140
- ...reactiveOmit(props, 'data', 'columns', 'caption', 'sticky', 'class', 'ui'),
147
+ ...reactiveOmit(props, 'data', 'columns', 'caption', 'sticky', 'loading', 'class', 'ui'),
141
148
  data,
142
149
  columns: columns.value,
143
150
  getCoreRowModel: getCoreRowModel(),
@@ -240,7 +247,7 @@ defineExpose({
240
247
  <template>
241
248
  <Primitive :as="props.as" :class="style.root({ class: [props.class, props.ui?.root] })">
242
249
  <table :class="style.base({ class: props.ui?.base })">
243
- <caption v-if="props.caption" :class="style.caption({ class: props.caption })">
250
+ <caption v-if="props.caption || slots.caption" :class="style.caption({ class: props.caption })">
244
251
  <slot name="caption">
245
252
  {{ props.caption }}
246
253
  </slot>
@@ -284,10 +291,16 @@ defineExpose({
284
291
  </template>
285
292
  </template>
286
293
 
294
+ <tr v-else-if="props.loading && slots.loading">
295
+ <td :colspan="columns.length" :class="style.loading({ class: props.ui?.loading })">
296
+ <slot name="loading"></slot>
297
+ </td>
298
+ </tr>
299
+
287
300
  <tr v-else :class="style.tr({ class: props.ui?.tr })">
288
301
  <td :colspan="columns.length" :class="style.empty({ class: props.ui?.empty })">
289
302
  <slot name="empty">
290
- {{ t('table.noData') }}
303
+ {{ props.empty || t('table.noData') }}
291
304
  </slot>
292
305
  </td>
293
306
  </tr>
@@ -6,15 +6,6 @@ import type { ComponentAttrs, DynamicSlots } from '../types'
6
6
 
7
7
  export interface TabsEmits extends TabsRootEmits<string | number> {}
8
8
 
9
- type SlotProps<T> = (props: { item: T, index: number }) => any
10
-
11
- export type TabsSlots<T extends { slot?: string }> = {
12
- leading?: SlotProps<T>
13
- default?: SlotProps<T>
14
- trailing?: SlotProps<T>
15
- content?: SlotProps<T>
16
- } & DynamicSlots<T, SlotProps<T>>
17
-
18
9
  export interface TabsItem {
19
10
  label?: string
20
11
  icon?: string
@@ -23,11 +14,21 @@ export interface TabsItem {
23
14
  /** A unique value for the tab item. Defaults to the index. */
24
15
  value?: string | number
25
16
  disabled?: boolean
17
+ [key: string]: any
26
18
  }
27
19
 
20
+ type SlotProps<T extends TabsItem> = (props: { item: T, index: number }) => any
21
+
22
+ export type TabsSlots<T extends TabsItem = TabsItem> = {
23
+ leading?: SlotProps<T>
24
+ default?: SlotProps<T>
25
+ trailing?: SlotProps<T>
26
+ content?: SlotProps<T>
27
+ } & DynamicSlots<T, undefined, SlotProps<T>>
28
+
28
29
  type TabsVariants = VariantProps<typeof tabs>
29
30
 
30
- export interface TabsProps<T> extends ComponentAttrs<typeof tabs>, Pick<TabsRootProps<string | number>, 'defaultValue' | 'modelValue' | 'activationMode' | 'unmountOnHide'> {
31
+ export interface TabsProps<T extends TabsItem = TabsItem> extends ComponentAttrs<typeof tabs>, Pick<TabsRootProps<string | number>, 'defaultValue' | 'modelValue' | 'activationMode' | 'unmountOnHide'> {
31
32
  /**
32
33
  * The element or component this component should render as.
33
34
  * @default "div"
@@ -108,7 +109,7 @@ const style = computed(() => generateStyle('tabs', props))
108
109
  :value="item.value || String(index)"
109
110
  :class="style.content({ class: props.ui?.content })"
110
111
  >
111
- <slot :name="item.slot || 'content'" :item="item" :index="index">
112
+ <slot :name="((item.slot || 'content') as keyof TabsSlots<T>)" :item="(item as Extract<T, { slot: string }>)" :index="index">
112
113
  {{ item.content }}
113
114
  </slot>
114
115
  </TabsContent>
@@ -5,9 +5,9 @@ import type { textarea } from '../theme'
5
5
  import type { ComponentAttrs } from '../types'
6
6
 
7
7
  export interface TextareaEmits {
8
- (e: 'update:modelValue', payload: string): void
9
- (e: 'blur', event: FocusEvent): void
10
- (e: 'change', event: Event): void
8
+ 'update:modelValue': [payload: string]
9
+ 'blur': [event: FocusEvent]
10
+ 'change': [event: Event]
11
11
  }
12
12
 
13
13
  export interface TextareaSlots {
@@ -41,7 +41,8 @@ export interface TextareaProps extends ComponentAttrs<typeof textarea> {
41
41
 
42
42
  <script setup lang="ts">
43
43
  import { Primitive } from 'reka-ui'
44
- import { computed, nextTick, onMounted, ref, watch } from 'vue'
44
+ import { computed, nextTick, onMounted, useTemplateRef, watch } from 'vue'
45
+ import { useFormItem } from '../composables/useFormItem'
45
46
  import { useTheme } from '../composables/useTheme'
46
47
 
47
48
  defineOptions({
@@ -59,10 +60,15 @@ const emit = defineEmits<TextareaEmits>()
59
60
  defineSlots<TextareaSlots>()
60
61
  const [modelValue, modelModifiers] = defineModel<string | number>()
61
62
 
62
- const textareaRef = ref<HTMLTextAreaElement | null>(null)
63
+ const textareaRef = useTemplateRef('textareaRef')
63
64
 
65
+ const { id, name, size, highlight, disabled, ariaAttrs, emitFormInput, emitFormChange, emitFormBlur, emitFormFocus } = useFormItem<TextareaProps>(props)
64
66
  const { generateStyle } = useTheme()
65
- const style = computed(() => generateStyle('textarea', props))
67
+ const style = computed(() => generateStyle('textarea', {
68
+ ...props,
69
+ size: size.value,
70
+ highlight: highlight.value,
71
+ }))
66
72
 
67
73
  function autoFocus() {
68
74
  if (props.autofocus)
@@ -74,6 +80,7 @@ function updateInput(value: string) {
74
80
  value = value.trim()
75
81
 
76
82
  modelValue.value = value
83
+ emitFormInput()
77
84
  }
78
85
 
79
86
  function onInput(event: Event) {
@@ -93,10 +100,12 @@ function onChange(event: Event) {
93
100
  (event.target as HTMLInputElement).value = value.trim()
94
101
 
95
102
  emit('change', event)
103
+ emitFormChange()
96
104
  }
97
105
 
98
106
  function onBlur(event: FocusEvent) {
99
107
  emit('blur', event)
108
+ emitFormBlur()
100
109
  }
101
110
 
102
111
  function autoResize() {
@@ -144,23 +153,20 @@ onMounted(() => {
144
153
  <Primitive
145
154
  :as="props.as"
146
155
  :class="style.root({ class: [props.class, props.ui?.root] })"
147
- :aria-disabled="props.disabled ? true : undefined"
156
+ :aria-disabled="disabled ? true : undefined"
148
157
  >
149
158
  <textarea
150
- :id="props.id"
151
159
  ref="textareaRef"
160
+ :class="style.base({ class: props.ui?.base })"
152
161
  :value="modelValue"
153
- :name="props.name"
154
162
  :rows="props.rows"
155
163
  :placeholder="props.placeholder"
156
- :class="style.base({ class: props.ui?.base })"
157
- :disabled="props.disabled"
158
164
  :required="props.required"
159
- v-bind="$attrs"
165
+ v-bind="{ ...$attrs, ...ariaAttrs, id, name, disabled }"
160
166
  @input="onInput"
161
167
  @blur="onBlur"
162
168
  @change="onChange"
163
- @focus="autoResize"
169
+ @focus="emitFormFocus"
164
170
  ></textarea>
165
171
 
166
172
  <slot></slot>