@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.
- package/LICENSE.md +21 -0
- package/README.md +32 -0
- package/dist/components/Accordion.vue +104 -0
- package/dist/components/App.vue +57 -0
- package/dist/components/Button.vue +94 -0
- package/dist/components/Card.vue +76 -0
- package/dist/components/Checkbox.vue +104 -0
- package/dist/components/Drawer.vue +133 -0
- package/dist/components/Input.vue +169 -0
- package/dist/components/Link.vue +117 -0
- package/dist/components/Modal.vue +145 -0
- package/dist/components/ModalProvider.vue +10 -0
- package/dist/components/Popover.vue +97 -0
- package/dist/components/RadioGroup.vue +180 -0
- package/dist/components/Select.vue +258 -0
- package/dist/components/Switch.vue +99 -0
- package/dist/components/Tabs.vue +117 -0
- package/dist/components/Toast.vue +126 -0
- package/dist/components/Toaster.vue +143 -0
- package/dist/components/Tooltip.vue +71 -0
- package/dist/components/index.d.ts +18 -0
- package/dist/components/index.mjs +18 -0
- package/dist/composables/index.d.ts +4 -0
- package/dist/composables/index.mjs +4 -0
- package/dist/composables/useComponentIcons.d.ts +26 -0
- package/dist/composables/useComponentIcons.mjs +24 -0
- package/dist/composables/useModal.d.ts +15 -0
- package/dist/composables/useModal.mjs +51 -0
- package/dist/composables/useTheme.d.ts +8 -0
- package/dist/composables/useTheme.mjs +18 -0
- package/dist/composables/useToast.d.ts +24 -0
- package/dist/composables/useToast.mjs +48 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +3 -0
- package/dist/internal/constants.d.ts +3 -0
- package/dist/internal/constants.mjs +21 -0
- package/dist/internal/extend-theme.d.ts +9 -0
- package/dist/internal/extend-theme.mjs +16 -0
- package/dist/internal/extend-theme.test.d.ts +1 -0
- package/dist/internal/extend-theme.test.mjs +45 -0
- package/dist/internal/index.d.ts +4 -0
- package/dist/internal/index.mjs +4 -0
- package/dist/internal/link.d.ts +15 -0
- package/dist/internal/link.mjs +4 -0
- package/dist/internal/styler.d.ts +5 -0
- package/dist/internal/styler.mjs +236 -0
- package/dist/internal/styler.test.d.ts +1 -0
- package/dist/internal/styler.test.mjs +10 -0
- package/dist/nuxt.d.ts +10 -0
- package/dist/nuxt.mjs +28 -0
- package/dist/resolver.d.ts +10 -0
- package/dist/resolver.mjs +18 -0
- package/dist/theme/accordion.d.ts +39 -0
- package/dist/theme/accordion.mjs +25 -0
- package/dist/theme/app.d.ts +10 -0
- package/dist/theme/app.mjs +9 -0
- package/dist/theme/button.d.ts +184 -0
- package/dist/theme/button.mjs +140 -0
- package/dist/theme/card.d.ts +43 -0
- package/dist/theme/card.mjs +11 -0
- package/dist/theme/checkbox.d.ts +97 -0
- package/dist/theme/checkbox.mjs +53 -0
- package/dist/theme/drawer.d.ts +72 -0
- package/dist/theme/drawer.mjs +72 -0
- package/dist/theme/index.d.ts +17 -0
- package/dist/theme/index.mjs +17 -0
- package/dist/theme/input.d.ts +159 -0
- package/dist/theme/input.mjs +133 -0
- package/dist/theme/link.d.ts +31 -0
- package/dist/theme/link.mjs +23 -0
- package/dist/theme/modal.d.ts +50 -0
- package/dist/theme/modal.mjs +54 -0
- package/dist/theme/popover.d.ts +27 -0
- package/dist/theme/popover.mjs +10 -0
- package/dist/theme/radioGroup.d.ts +131 -0
- package/dist/theme/radioGroup.mjs +67 -0
- package/dist/theme/select.d.ts +177 -0
- package/dist/theme/select.mjs +154 -0
- package/dist/theme/switch.d.ts +131 -0
- package/dist/theme/switch.mjs +78 -0
- package/dist/theme/tabs.d.ts +101 -0
- package/dist/theme/tabs.mjs +117 -0
- package/dist/theme/toast.d.ts +51 -0
- package/dist/theme/toast.mjs +27 -0
- package/dist/theme/toaster.d.ts +73 -0
- package/dist/theme/toaster.mjs +89 -0
- package/dist/theme/tooltip.d.ts +31 -0
- package/dist/theme/tooltip.mjs +8 -0
- package/dist/types/components.d.ts +18 -0
- package/dist/types/components.mjs +0 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.mjs +2 -0
- package/dist/types/utils.d.ts +29 -0
- package/dist/types/utils.mjs +0 -0
- package/dist/unocss-preset.d.ts +37 -0
- package/dist/unocss-preset.mjs +164 -0
- package/dist/utils/index.d.ts +18 -0
- package/dist/utils/index.mjs +70 -0
- package/dist/utils/unocss.d.ts +3 -0
- package/dist/utils/unocss.mjs +50 -0
- 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 }}
|
|
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>
|