@byyuurin/ui 0.0.0

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 (101) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +32 -0
  3. package/dist/components/Accordion.vue +104 -0
  4. package/dist/components/App.vue +57 -0
  5. package/dist/components/Button.vue +94 -0
  6. package/dist/components/Card.vue +76 -0
  7. package/dist/components/Checkbox.vue +104 -0
  8. package/dist/components/Drawer.vue +133 -0
  9. package/dist/components/Input.vue +169 -0
  10. package/dist/components/Link.vue +117 -0
  11. package/dist/components/Modal.vue +145 -0
  12. package/dist/components/ModalProvider.vue +10 -0
  13. package/dist/components/Popover.vue +97 -0
  14. package/dist/components/RadioGroup.vue +180 -0
  15. package/dist/components/Select.vue +258 -0
  16. package/dist/components/Switch.vue +99 -0
  17. package/dist/components/Tabs.vue +117 -0
  18. package/dist/components/Toast.vue +126 -0
  19. package/dist/components/Toaster.vue +143 -0
  20. package/dist/components/Tooltip.vue +71 -0
  21. package/dist/components/index.d.ts +18 -0
  22. package/dist/components/index.mjs +18 -0
  23. package/dist/composables/index.d.ts +4 -0
  24. package/dist/composables/index.mjs +4 -0
  25. package/dist/composables/useComponentIcons.d.ts +26 -0
  26. package/dist/composables/useComponentIcons.mjs +24 -0
  27. package/dist/composables/useModal.d.ts +15 -0
  28. package/dist/composables/useModal.mjs +51 -0
  29. package/dist/composables/useTheme.d.ts +8 -0
  30. package/dist/composables/useTheme.mjs +18 -0
  31. package/dist/composables/useToast.d.ts +24 -0
  32. package/dist/composables/useToast.mjs +48 -0
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.mjs +3 -0
  35. package/dist/internal/constants.d.ts +3 -0
  36. package/dist/internal/constants.mjs +21 -0
  37. package/dist/internal/extend-theme.d.ts +9 -0
  38. package/dist/internal/extend-theme.mjs +16 -0
  39. package/dist/internal/extend-theme.test.d.ts +1 -0
  40. package/dist/internal/extend-theme.test.mjs +45 -0
  41. package/dist/internal/index.d.ts +4 -0
  42. package/dist/internal/index.mjs +4 -0
  43. package/dist/internal/link.d.ts +15 -0
  44. package/dist/internal/link.mjs +4 -0
  45. package/dist/internal/styler.d.ts +5 -0
  46. package/dist/internal/styler.mjs +236 -0
  47. package/dist/internal/styler.test.d.ts +1 -0
  48. package/dist/internal/styler.test.mjs +10 -0
  49. package/dist/nuxt.d.ts +10 -0
  50. package/dist/nuxt.mjs +28 -0
  51. package/dist/resolver.d.ts +10 -0
  52. package/dist/resolver.mjs +18 -0
  53. package/dist/theme/accordion.d.ts +39 -0
  54. package/dist/theme/accordion.mjs +25 -0
  55. package/dist/theme/app.d.ts +10 -0
  56. package/dist/theme/app.mjs +9 -0
  57. package/dist/theme/button.d.ts +184 -0
  58. package/dist/theme/button.mjs +140 -0
  59. package/dist/theme/card.d.ts +43 -0
  60. package/dist/theme/card.mjs +11 -0
  61. package/dist/theme/checkbox.d.ts +97 -0
  62. package/dist/theme/checkbox.mjs +53 -0
  63. package/dist/theme/drawer.d.ts +72 -0
  64. package/dist/theme/drawer.mjs +72 -0
  65. package/dist/theme/index.d.ts +17 -0
  66. package/dist/theme/index.mjs +17 -0
  67. package/dist/theme/input.d.ts +159 -0
  68. package/dist/theme/input.mjs +133 -0
  69. package/dist/theme/link.d.ts +31 -0
  70. package/dist/theme/link.mjs +23 -0
  71. package/dist/theme/modal.d.ts +50 -0
  72. package/dist/theme/modal.mjs +54 -0
  73. package/dist/theme/popover.d.ts +27 -0
  74. package/dist/theme/popover.mjs +10 -0
  75. package/dist/theme/radioGroup.d.ts +131 -0
  76. package/dist/theme/radioGroup.mjs +67 -0
  77. package/dist/theme/select.d.ts +177 -0
  78. package/dist/theme/select.mjs +154 -0
  79. package/dist/theme/switch.d.ts +131 -0
  80. package/dist/theme/switch.mjs +78 -0
  81. package/dist/theme/tabs.d.ts +101 -0
  82. package/dist/theme/tabs.mjs +117 -0
  83. package/dist/theme/toast.d.ts +51 -0
  84. package/dist/theme/toast.mjs +27 -0
  85. package/dist/theme/toaster.d.ts +73 -0
  86. package/dist/theme/toaster.mjs +89 -0
  87. package/dist/theme/tooltip.d.ts +31 -0
  88. package/dist/theme/tooltip.mjs +8 -0
  89. package/dist/types/components.d.ts +18 -0
  90. package/dist/types/components.mjs +0 -0
  91. package/dist/types/index.d.ts +7 -0
  92. package/dist/types/index.mjs +2 -0
  93. package/dist/types/utils.d.ts +29 -0
  94. package/dist/types/utils.mjs +0 -0
  95. package/dist/unocss-preset.d.ts +37 -0
  96. package/dist/unocss-preset.mjs +164 -0
  97. package/dist/utils/index.d.ts +18 -0
  98. package/dist/utils/index.mjs +70 -0
  99. package/dist/utils/unocss.d.ts +3 -0
  100. package/dist/utils/unocss.mjs +50 -0
  101. package/package.json +103 -0
@@ -0,0 +1,180 @@
1
+ <script lang="ts">
2
+ import type { VariantProps } from '@byyuurin/ui-kit'
3
+ import type { AcceptableValue, PrimitiveProps, RadioGroupRootProps } from 'reka-ui'
4
+ import type { radioGroup } from '../theme'
5
+ import type { ComponentAttrs } from '../types'
6
+
7
+ type RadioGroupVariants = VariantProps<typeof radioGroup>
8
+
9
+ export interface RadioOption {
10
+ label?: string
11
+ description?: string
12
+ disabled?: boolean
13
+ value?: string
14
+ }
15
+
16
+ export interface RadioGroupProps<T> extends ComponentAttrs<typeof radioGroup>, Pick<RadioGroupRootProps, 'defaultValue' | 'disabled' | 'loop' | 'modelValue' | 'name' | 'required'> {
17
+ as?: PrimitiveProps['as']
18
+ legend?: string
19
+ /**
20
+ * When `options` is an array of objects, select the field to use as the value.
21
+ * @default 'value'
22
+ */
23
+ valueKey?: string
24
+ /**
25
+ * When `options` is an array of objects, select the field to use as the label.
26
+ * @default 'label'
27
+ */
28
+ labelKey?: string
29
+ /**
30
+ * When `options` is an array of objects, select the field to use as the description.
31
+ * @default 'description'
32
+ */
33
+ descriptionKey?: string
34
+ options?: T[]
35
+ size?: RadioGroupVariants['size']
36
+ /**
37
+ * The orientation the radio buttons are laid out.
38
+ * @default 'vertical'
39
+ */
40
+ orientation?: RadioGroupRootProps['orientation']
41
+ /** @default true */
42
+ round?: boolean
43
+ /** @default true */
44
+ dot?: boolean
45
+ }
46
+
47
+ export interface RadioGroupEmits {
48
+ (event: 'update:modelValue', payload: string): void
49
+ (event: 'change', payload: Event): void
50
+ }
51
+
52
+ type SlotProps<T> = (props: { item: NormalizeItem<T>, modelValue?: AcceptableValue }) => any
53
+
54
+ export interface RadioGroupSlots<T> {
55
+ legend?: (props?: {}) => any
56
+ label?: SlotProps<T>
57
+ description?: SlotProps<T>
58
+ }
59
+
60
+ type NormalizeItem<T> = { id: string } & (
61
+ T extends RadioOption
62
+ ? T
63
+ : {
64
+ id: string
65
+ label: string
66
+ value: any
67
+ description: string
68
+ disabled: false
69
+ }
70
+ )
71
+ </script>
72
+
73
+ <script lang="ts" setup generic="T extends RadioOption | AcceptableValue">
74
+ import { reactivePick } from '@vueuse/core'
75
+ import { Label, RadioGroupIndicator, RadioGroupItem, RadioGroupRoot, useForwardPropsEmits } from 'reka-ui'
76
+ import { computed, useId } from 'vue'
77
+ import { useTheme } from '../composables'
78
+ import { get } from '../utils'
79
+
80
+ const props = withDefaults(defineProps<RadioGroupProps<T>>(), {
81
+ size: 'md',
82
+ valueKey: 'value',
83
+ labelKey: 'label',
84
+ descriptionKey: 'description',
85
+ orientation: 'vertical',
86
+ dot: true,
87
+ round: true,
88
+ })
89
+
90
+ const emit = defineEmits<RadioGroupEmits>()
91
+ const slots = defineSlots<RadioGroupSlots<T>>()
92
+
93
+ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'loop', 'required'), emit)
94
+ const id = useId()
95
+
96
+ const { theme, createStyler } = useTheme()
97
+ const style = computed(() => {
98
+ const styler = createStyler(theme.value.radioGroup)
99
+ return styler(props)
100
+ })
101
+
102
+ function normalizeItem(item: any): NormalizeItem<T> {
103
+ if (['string', 'number', 'boolean'].includes(typeof item)) {
104
+ return {
105
+ id: `${id}:${item}`,
106
+ value: item,
107
+ label: item,
108
+ description: '',
109
+ } as any
110
+ }
111
+
112
+ const value = get(item, props.valueKey as string)
113
+ const label = get(item, props.labelKey as string)
114
+ const description = get(item, props.descriptionKey as string)
115
+
116
+ return {
117
+ ...item,
118
+ value,
119
+ label,
120
+ description,
121
+ id: `${id}:${value}`,
122
+ }
123
+ }
124
+
125
+ const normalizedItems = computed(() => {
126
+ if (!props.options)
127
+ return []
128
+
129
+ return props.options.map(normalizeItem)
130
+ })
131
+
132
+ function onUpdate(value: any) {
133
+ // @ts-expect-error - 'target' does not exist in type 'EventInit'
134
+ const event = new Event('change', { target: { value } })
135
+ emit('change', event)
136
+ }
137
+ </script>
138
+
139
+ <template>
140
+ <RadioGroupRoot
141
+ :id="id"
142
+ v-slot="{ modelValue }"
143
+ v-bind="rootProps"
144
+ :name="props.name"
145
+ :disabled="props.disabled"
146
+ :class="style.root({ class: [props.class, props.ui?.root] })"
147
+ @update:model-value="onUpdate"
148
+ >
149
+ <fieldset :class="style.fieldset({ class: props.ui?.fieldset })">
150
+ <legend v-if="props.legend || slots.legend" :class="style.legend({ class: props.ui?.legend })">
151
+ <slot name="legend">
152
+ {{ props.legend }}
153
+ </slot>
154
+ </legend>
155
+ <div v-for="item in normalizedItems" :key="item.value" :class="style.item({ class: props.ui?.item })">
156
+ <div :class="style.container({ class: props.ui?.container })">
157
+ <RadioGroupItem
158
+ :id="item.id"
159
+ :value="item.value"
160
+ :disabled="item.disabled"
161
+ :class="style.base({ class: props.ui?.base })"
162
+ >
163
+ <RadioGroupIndicator :class="style.indicator({ class: props.ui?.indicator })" />
164
+ </RadioGroupItem>
165
+ </div>
166
+
167
+ <div :class="style.wrapper({ class: props.ui?.wrapper })">
168
+ <Label :for="item.id" :class="style.label({ class: props.ui?.label })">
169
+ <slot name="label" :item="item" :model-value="modelValue">{{ item.label }}</slot>
170
+ </Label>
171
+ <p v-if="item.description || slots.description" :class="style.description({ class: props.ui?.description })">
172
+ <slot name="description" :item="item" :model-value="modelValue">
173
+ {{ item.description }}
174
+ </slot>
175
+ </p>
176
+ </div>
177
+ </div>
178
+ </fieldset>
179
+ </RadioGroupRoot>
180
+ </template>
@@ -0,0 +1,258 @@
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>
@@ -0,0 +1,99 @@
1
+ <script lang="ts">
2
+ import type { VariantProps } from '@byyuurin/ui-kit'
3
+ import type { PrimitiveProps, SwitchRootProps } from 'reka-ui'
4
+ import type { switch as _switch } from '../theme'
5
+ import type { ComponentAttrs } from '../types'
6
+
7
+ type SwitchVariants = VariantProps<typeof _switch>
8
+
9
+ export interface SwitchProps extends ComponentAttrs<typeof _switch>, Pick<SwitchRootProps, 'disabled' | 'id' | 'name' | 'required' | 'value' | 'defaultValue'> {
10
+ as?: PrimitiveProps['as']
11
+ size?: SwitchVariants['size']
12
+ /** When `true`, the loading icon will be displayed. */
13
+ loading?: boolean
14
+ /**
15
+ * The icon when the `loading` prop is `true`.
16
+ * @default app.icons.loading
17
+ */
18
+ loadingIcon?: string
19
+ /** Display an icon when the switch is checked. */
20
+ checkedIcon?: string
21
+ /** Display an icon when the switch is unchecked. */
22
+ uncheckedIcon?: string
23
+ label?: string
24
+ description?: string
25
+ round?: boolean
26
+ }
27
+
28
+ export interface SwitchEmits {
29
+ (event: 'change', payload: Event): void
30
+ }
31
+
32
+ export interface SwitchSlots {
33
+ label?: (props: { label?: string }) => any
34
+ description?: (props: { description?: string }) => any
35
+ }
36
+ </script>
37
+
38
+ <script lang="ts" setup>
39
+ import { reactivePick } from '@vueuse/core'
40
+ import { Label, Primitive, SwitchRoot, SwitchThumb, useForwardProps } from 'reka-ui'
41
+ import { computed, useId } from 'vue'
42
+ import { useTheme } from '../composables'
43
+
44
+ const props = withDefaults(defineProps<SwitchProps>(), {
45
+ size: 'md',
46
+ })
47
+ const emit = defineEmits<SwitchEmits>()
48
+ const slots = defineSlots<SwitchSlots>()
49
+ const modelValue = defineModel<boolean>({ default: undefined })
50
+
51
+ const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
52
+ const id = useId()
53
+
54
+ const { theme, createStyler } = useTheme()
55
+ const style = computed(() => {
56
+ const styler = createStyler(theme.value.switch)
57
+ return styler({ ...props, checked: false, unchecked: false })
58
+ })
59
+
60
+ function onUpdate(value: any) {
61
+ // @ts-expect-error - 'target' does not exist in type 'EventInit'
62
+ const event = new Event('change', { target: { value } })
63
+ emit('change', event)
64
+ }
65
+ </script>
66
+
67
+ <template>
68
+ <Primitive :as="props.as" :class="style.root({ class: [props.class, props.ui?.root] })">
69
+ <div :class="style.container({ class: props.ui?.container })">
70
+ <SwitchRoot
71
+ :id="id"
72
+ v-bind="rootProps"
73
+ v-model="modelValue"
74
+ :name="props.name"
75
+ :disabled="props.disabled || props.loading"
76
+ :class="style.base({ class: props.ui?.base })"
77
+ @update:model-value="onUpdate"
78
+ >
79
+ <SwitchThumb :class="style.thumb({ class: props.ui?.thumb })">
80
+ <i v-if="props.loading" :class="style.icon({ class: [theme.app.icons.loading, props.ui?.icon], checked: true, unchecked: true })"></i>
81
+ <template v-else>
82
+ <i v-if="props.checkedIcon" :class="style.icon({ class: [props.checkedIcon, props.ui?.icon], checked: true })"></i>
83
+ <i v-if="props.uncheckedIcon" :class="style.icon({ class: [props.uncheckedIcon, props.ui?.icon], unchecked: true })"></i>
84
+ </template>
85
+ </SwitchThumb>
86
+ </SwitchRoot>
87
+ </div>
88
+ <div v-if="props.label || slots.label || props.description || slots.description" :class="style.wrapper({ class: props.ui?.wrapper })">
89
+ <Label v-if="props.label || slots.label" :for="id" :class="style.label({ class: props.ui?.label })">
90
+ <slot name="label" :label="props.label">{{ props.label }}</slot>
91
+ </Label>
92
+ <p v-if="props.description || slots.description" :class="style.description({ class: props.ui?.description })">
93
+ <slot name="description" :description="props.description">
94
+ {{ props.description }}
95
+ </slot>
96
+ </p>
97
+ </div>
98
+ </Primitive>
99
+ </template>
@@ -0,0 +1,117 @@
1
+ <script lang="ts">
2
+ import type { VariantProps } from '@byyuurin/ui-kit'
3
+ import type { PrimitiveProps, TabsRootEmits, TabsRootProps } from 'reka-ui'
4
+ import type { tabs } from '../theme'
5
+ import type { ComponentAttrs, DynamicSlots } from '../types'
6
+
7
+ export interface TabsItem {
8
+ label?: string
9
+ icon?: string
10
+ slot?: string
11
+ content?: string
12
+ /** A unique value for the tab item. Defaults to the index. */
13
+ value?: string | number
14
+ disabled?: boolean
15
+ }
16
+
17
+ type TabsVariants = VariantProps<typeof tabs>
18
+
19
+ export interface TabsProps<T> extends ComponentAttrs<typeof tabs>, Pick<TabsRootProps<string | number>, 'defaultValue' | 'modelValue' | 'activationMode' | 'unmountOnHide'> {
20
+ as?: PrimitiveProps['as']
21
+ items?: T[]
22
+ variant?: TabsVariants['variant']
23
+ orientation?: TabsVariants['orientation']
24
+ size?: TabsVariants['size']
25
+ /** @default true */
26
+ full?: boolean
27
+ /**
28
+ * The content of the tabs, can be disabled to prevent rendering the content.
29
+ * @default true
30
+ */
31
+ content?: boolean
32
+ /**
33
+ * The key used to get the label from the item.
34
+ * @default 'label'
35
+ */
36
+ labelKey?: string
37
+ }
38
+
39
+ export interface TabsEmits extends TabsRootEmits<string | number> {}
40
+
41
+ type SlotProps<T> = (props: { item: T, index: number }) => any
42
+
43
+ export type TabsSlots<T extends { slot?: string }> = {
44
+ leading?: SlotProps<T>
45
+ default?: SlotProps<T>
46
+ trailing?: SlotProps<T>
47
+ content?: SlotProps<T>
48
+ } & DynamicSlots<T, SlotProps<T>>
49
+ </script>
50
+
51
+ <script lang="ts" setup generic="T extends TabsItem">
52
+ import { reactivePick } from '@vueuse/core'
53
+ import { TabsContent, TabsIndicator, TabsList, TabsRoot, TabsTrigger, useForwardPropsEmits } from 'reka-ui'
54
+ import { computed } from 'vue'
55
+ import { useTheme } from '../composables'
56
+ import { get } from '../utils'
57
+
58
+ const props = withDefaults(defineProps<TabsProps<T>>(), {
59
+ defaultValue: '0',
60
+ variant: 'solid',
61
+ orientation: 'horizontal',
62
+ size: 'md',
63
+ full: true,
64
+ content: true,
65
+ labelKey: 'label',
66
+ })
67
+
68
+ const emits = defineEmits<TabsEmits>()
69
+ const slots = defineSlots<TabsSlots<T>>()
70
+
71
+ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'activationMode', 'unmountOnHide'), emits)
72
+
73
+ const { theme, createStyler } = useTheme()
74
+ const style = computed(() => {
75
+ const styler = createStyler(theme.value.tabs)
76
+ return styler(props)
77
+ })
78
+ </script>
79
+
80
+ <template>
81
+ <TabsRoot v-bind="rootProps" :class="style.root({ class: [props.class, props.ui?.root] })">
82
+ <TabsList :class="style.list({ class: props.ui?.list })">
83
+ <TabsIndicator :class="style.indicator({ class: props.ui?.indicator })" />
84
+
85
+ <TabsTrigger
86
+ v-for="(item, index) of items"
87
+ :key="index"
88
+ :value="item.value || String(index)"
89
+ :disabled="item.disabled"
90
+ :class="style.trigger({ class: props.ui?.trigger })"
91
+ >
92
+ <slot name="leading" :item="item" :index="index">
93
+ <i v-if="item.icon" :class="style.leadingIcon({ class: [item.icon, props.ui?.leadingIcon] })"></i>
94
+ </slot>
95
+
96
+ <span v-if="get(item, props.labelKey) || slots.default" :class="style.label({ class: props.ui?.label })">
97
+ <slot :item="item" :index="index">{{ get(item, props.labelKey) }}</slot>
98
+ </span>
99
+
100
+ <slot name="trailing" :item="item" :index="index"></slot>
101
+ </TabsTrigger>
102
+ </TabsList>
103
+
104
+ <template v-if="props.content">
105
+ <TabsContent
106
+ v-for="(item, index) of items"
107
+ :key="index"
108
+ :value="item.value || String(index)"
109
+ :class="style.content({ class: props.ui?.content })"
110
+ >
111
+ <slot :name="item.slot || 'content'" :item="item" :index="index">
112
+ {{ item.content }}
113
+ </slot>
114
+ </TabsContent>
115
+ </template>
116
+ </TabsRoot>
117
+ </template>