@nuxt/ui 3.0.1 → 3.0.2
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/.nuxt/ui/button.ts +36 -36
- package/.nuxt/ui/container.ts +1 -1
- package/.nuxt/ui/navigation-menu.ts +2 -2
- package/.nuxt/ui/switch.ts +1 -1
- package/.nuxt/ui/tabs.ts +1 -1
- package/dist/module.cjs +1 -1
- package/dist/module.d.cts +2 -1
- package/dist/module.d.mts +2 -1
- package/dist/module.d.ts +2 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/Accordion.vue +9 -8
- package/dist/runtime/components/Alert.vue +1 -1
- package/dist/runtime/components/App.vue +1 -1
- package/dist/runtime/components/Avatar.vue +7 -5
- package/dist/runtime/components/Breadcrumb.vue +11 -10
- package/dist/runtime/components/Button.vue +1 -1
- package/dist/runtime/components/Calendar.vue +40 -11
- package/dist/runtime/components/Carousel.vue +7 -3
- package/dist/runtime/components/CommandPalette.vue +12 -11
- package/dist/runtime/components/ContextMenu.vue +24 -12
- package/dist/runtime/components/ContextMenuContent.vue +22 -15
- package/dist/runtime/components/Drawer.vue +1 -5
- package/dist/runtime/components/DropdownMenu.vue +23 -12
- package/dist/runtime/components/DropdownMenuContent.vue +22 -15
- package/dist/runtime/components/Form.vue +1 -0
- package/dist/runtime/components/FormField.vue +1 -0
- package/dist/runtime/components/InputMenu.vue +92 -54
- package/dist/runtime/components/Link.vue +3 -0
- package/dist/runtime/components/LinkBase.vue +1 -0
- package/dist/runtime/components/Modal.vue +1 -1
- package/dist/runtime/components/NavigationMenu.vue +49 -25
- package/dist/runtime/components/RadioGroup.vue +23 -12
- package/dist/runtime/components/Select.vue +69 -42
- package/dist/runtime/components/SelectMenu.vue +83 -48
- package/dist/runtime/components/Slideover.vue +1 -1
- package/dist/runtime/components/Stepper.vue +11 -10
- package/dist/runtime/components/Table.vue +7 -2
- package/dist/runtime/components/Tabs.vue +6 -5
- package/dist/runtime/components/Toast.vue +1 -1
- package/dist/runtime/components/Tree.vue +46 -32
- package/dist/runtime/composables/defineShortcuts.js +0 -1
- package/dist/runtime/composables/useFormField.js +1 -1
- package/dist/runtime/composables/useOverlay.d.ts +3 -2
- package/dist/runtime/locale/hy.d.ts +3 -0
- package/dist/runtime/locale/hy.js +54 -0
- package/dist/runtime/locale/index.d.ts +1 -0
- package/dist/runtime/locale/index.js +1 -0
- package/dist/runtime/types/form.d.ts +1 -0
- package/dist/runtime/types/index.d.ts +1 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/types/utils.d.ts +28 -8
- package/dist/runtime/utils/index.d.ts +1 -0
- package/dist/runtime/utils/index.js +3 -0
- package/dist/runtime/utils/link.d.ts +2 -25
- package/dist/runtime/utils/link.js +31 -1
- package/dist/runtime/vue/components/Link.vue +3 -0
- package/dist/runtime/vue/stubs.d.ts +7 -2
- package/dist/runtime/vue/stubs.js +11 -2
- package/dist/shared/{ui.C6uTSFTI.cjs → ui.BL-XB3u1.cjs} +16 -16
- package/dist/shared/{ui.BCaWD5Ek.mjs → ui.BkNUP964.mjs} +16 -16
- package/dist/types.d.mts +7 -1
- package/dist/types.d.ts +7 -1
- package/dist/unplugin.cjs +1 -1
- package/dist/unplugin.d.cts +2 -1
- package/dist/unplugin.d.mts +2 -1
- package/dist/unplugin.d.ts +2 -1
- package/dist/unplugin.mjs +1 -1
- package/dist/vite.cjs +1 -1
- package/dist/vite.mjs +1 -1
- package/package.json +39 -22
|
@@ -7,15 +7,23 @@ import _appConfig from '#build/app.config'
|
|
|
7
7
|
import theme from '#build/ui/navigation-menu'
|
|
8
8
|
import { tv } from '../utils/tv'
|
|
9
9
|
import type { AvatarProps, BadgeProps, LinkProps } from '../types'
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
ArrayOrNested,
|
|
12
|
+
DynamicSlots,
|
|
13
|
+
MergeTypes,
|
|
14
|
+
NestedItem,
|
|
15
|
+
PartialString,
|
|
16
|
+
EmitsToProps
|
|
17
|
+
} from '../types/utils'
|
|
11
18
|
|
|
12
19
|
const appConfigNavigationMenu = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } }
|
|
13
20
|
|
|
14
21
|
const navigationMenu = tv({ extend: tv(theme), ...(appConfigNavigationMenu.ui?.navigationMenu || {}) })
|
|
15
22
|
|
|
16
|
-
export interface NavigationMenuChildItem extends Omit<NavigationMenuItem, '
|
|
23
|
+
export interface NavigationMenuChildItem extends Omit<NavigationMenuItem, 'type'> {
|
|
17
24
|
/** Description is only used when `orientation` is `horizontal`. */
|
|
18
25
|
description?: string
|
|
26
|
+
[key: string]: any
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'>, Pick<CollapsibleRootProps, 'defaultOpen' | 'open'> {
|
|
@@ -44,11 +52,12 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
|
|
|
44
52
|
value?: string
|
|
45
53
|
children?: NavigationMenuChildItem[]
|
|
46
54
|
onSelect?(e: Event): void
|
|
55
|
+
[key: string]: any
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
type NavigationMenuVariants = VariantProps<typeof navigationMenu>
|
|
50
59
|
|
|
51
|
-
export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'modelValue' | 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'> {
|
|
60
|
+
export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>> extends Pick<NavigationMenuRootProps, 'modelValue' | 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'> {
|
|
52
61
|
/**
|
|
53
62
|
* The element or component this component should render as.
|
|
54
63
|
* @defaultValue 'div'
|
|
@@ -110,31 +119,34 @@ export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'm
|
|
|
110
119
|
* The key used to get the label from the item.
|
|
111
120
|
* @defaultValue 'label'
|
|
112
121
|
*/
|
|
113
|
-
labelKey?:
|
|
122
|
+
labelKey?: keyof NestedItem<T>
|
|
114
123
|
class?: any
|
|
115
124
|
ui?: PartialString<typeof navigationMenu.slots>
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
export interface NavigationMenuEmits extends NavigationMenuRootEmits {}
|
|
119
128
|
|
|
120
|
-
type SlotProps<T> = (props: { item: T, index: number, active?: boolean }) => any
|
|
129
|
+
type SlotProps<T extends NavigationMenuItem> = (props: { item: T, index: number, active?: boolean }) => any
|
|
121
130
|
|
|
122
|
-
export type NavigationMenuSlots<
|
|
131
|
+
export type NavigationMenuSlots<
|
|
132
|
+
A extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>,
|
|
133
|
+
T extends NestedItem<A> = NestedItem<A>
|
|
134
|
+
> = {
|
|
123
135
|
'item': SlotProps<T>
|
|
124
136
|
'item-leading': SlotProps<T>
|
|
125
137
|
'item-label': SlotProps<T>
|
|
126
138
|
'item-trailing': SlotProps<T>
|
|
127
139
|
'item-content': SlotProps<T>
|
|
128
|
-
} & DynamicSlots<T,
|
|
140
|
+
} & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing' | 'content', { index: number, active?: boolean }>
|
|
129
141
|
|
|
130
142
|
</script>
|
|
131
143
|
|
|
132
|
-
<script setup lang="ts" generic="T extends
|
|
144
|
+
<script setup lang="ts" generic="T extends ArrayOrNested<NavigationMenuItem>">
|
|
133
145
|
import { computed, toRef } from 'vue'
|
|
134
146
|
import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, useForwardPropsEmits } from 'reka-ui'
|
|
135
147
|
import { createReusableTemplate } from '@vueuse/core'
|
|
136
148
|
import { useAppConfig } from '#imports'
|
|
137
|
-
import { get } from '../utils'
|
|
149
|
+
import { get, isArrayOfArray } from '../utils'
|
|
138
150
|
import { pickLinkProps } from '../utils/link'
|
|
139
151
|
import ULinkBase from './LinkBase.vue'
|
|
140
152
|
import ULink from './Link.vue'
|
|
@@ -143,7 +155,7 @@ import UIcon from './Icon.vue'
|
|
|
143
155
|
import UBadge from './Badge.vue'
|
|
144
156
|
import UCollapsible from './Collapsible.vue'
|
|
145
157
|
|
|
146
|
-
const props = withDefaults(defineProps<NavigationMenuProps<
|
|
158
|
+
const props = withDefaults(defineProps<NavigationMenuProps<T>>(), {
|
|
147
159
|
orientation: 'horizontal',
|
|
148
160
|
contentOrientation: 'horizontal',
|
|
149
161
|
externalIcon: true,
|
|
@@ -170,8 +182,14 @@ const rootProps = useForwardPropsEmits(computed(() => ({
|
|
|
170
182
|
const contentProps = toRef(() => props.content)
|
|
171
183
|
|
|
172
184
|
const appConfig = useAppConfig()
|
|
173
|
-
const [DefineLinkTemplate, ReuseLinkTemplate] = createReusableTemplate<
|
|
174
|
-
|
|
185
|
+
const [DefineLinkTemplate, ReuseLinkTemplate] = createReusableTemplate<
|
|
186
|
+
{ item: NavigationMenuItem, index: number, active?: boolean },
|
|
187
|
+
NavigationMenuSlots<T>
|
|
188
|
+
>()
|
|
189
|
+
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<
|
|
190
|
+
{ item: NavigationMenuItem, index: number, level?: number },
|
|
191
|
+
NavigationMenuSlots<T>
|
|
192
|
+
>({
|
|
175
193
|
props: {
|
|
176
194
|
item: Object,
|
|
177
195
|
index: Number,
|
|
@@ -189,30 +207,36 @@ const ui = computed(() => navigationMenu({
|
|
|
189
207
|
highlightColor: props.highlightColor || props.color
|
|
190
208
|
}))
|
|
191
209
|
|
|
192
|
-
const lists = computed
|
|
210
|
+
const lists = computed<NavigationMenuItem[][]>(() =>
|
|
211
|
+
props.items?.length
|
|
212
|
+
? isArrayOfArray(props.items)
|
|
213
|
+
? props.items
|
|
214
|
+
: [props.items]
|
|
215
|
+
: []
|
|
216
|
+
)
|
|
193
217
|
</script>
|
|
194
218
|
|
|
195
219
|
<template>
|
|
196
220
|
<DefineLinkTemplate v-slot="{ item, active, index }">
|
|
197
|
-
<slot :name="item.slot || 'item'" :item="
|
|
198
|
-
<slot :name="item.slot ? `${item.slot}-leading` : 'item-leading'" :item="
|
|
221
|
+
<slot :name="((item.slot || 'item') as keyof NavigationMenuSlots<T>)" :item="item" :index="index">
|
|
222
|
+
<slot :name="((item.slot ? `${item.slot}-leading` : 'item-leading') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
|
|
199
223
|
<UAvatar v-if="item.avatar" :size="((props.ui?.linkLeadingAvatarSize || ui.linkLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ class: props.ui?.linkLeadingAvatar, active, disabled: !!item.disabled })" />
|
|
200
224
|
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ class: props.ui?.linkLeadingIcon, active, disabled: !!item.disabled })" />
|
|
201
225
|
</slot>
|
|
202
226
|
|
|
203
227
|
<span
|
|
204
|
-
v-if="(!collapsed || orientation !== 'vertical') && (get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label` : 'item-label'])"
|
|
228
|
+
v-if="(!collapsed || orientation !== 'vertical') && (get(item, props.labelKey as string) || !!slots[(item.slot ? `${item.slot}-label` : 'item-label') as keyof NavigationMenuSlots<T>])"
|
|
205
229
|
:class="ui.linkLabel({ class: props.ui?.linkLabel })"
|
|
206
230
|
>
|
|
207
|
-
<slot :name="item.slot ? `${item.slot}-label` : 'item-label'" :item="
|
|
231
|
+
<slot :name="((item.slot ? `${item.slot}-label` : 'item-label') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
|
|
208
232
|
{{ get(item, props.labelKey as string) }}
|
|
209
233
|
</slot>
|
|
210
234
|
|
|
211
235
|
<UIcon v-if="item.target === '_blank' && externalIcon !== false" :name="typeof externalIcon === 'string' ? externalIcon : appConfig.ui.icons.external" :class="ui.linkLabelExternalIcon({ class: props.ui?.linkLabelExternalIcon, active })" />
|
|
212
236
|
</span>
|
|
213
237
|
|
|
214
|
-
<span v-if="(!collapsed || orientation !== 'vertical') && (item.badge || (orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])) || (orientation === 'vertical' && item.children?.length) || item.trailingIcon || !!slots[item.slot ? `${item.slot}-trailing` : 'item-trailing'])" :class="ui.linkTrailing({ class: props.ui?.linkTrailing })">
|
|
215
|
-
<slot :name="item.slot ? `${item.slot}-trailing` : 'item-trailing'" :item="
|
|
238
|
+
<span v-if="(!collapsed || orientation !== 'vertical') && (item.badge || (orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) || (orientation === 'vertical' && item.children?.length) || item.trailingIcon || !!slots[(item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>])" :class="ui.linkTrailing({ class: props.ui?.linkTrailing })">
|
|
239
|
+
<slot :name="((item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
|
|
216
240
|
<UBadge
|
|
217
241
|
v-if="item.badge"
|
|
218
242
|
color="neutral"
|
|
@@ -222,7 +246,7 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
|
|
|
222
246
|
:class="ui.linkTrailingBadge({ class: props.ui?.linkTrailingBadge })"
|
|
223
247
|
/>
|
|
224
248
|
|
|
225
|
-
<UIcon v-if="(orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])) || (orientation === 'vertical' && item.children?.length)" :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" :class="ui.linkTrailingIcon({ class: props.ui?.linkTrailingIcon, active })" />
|
|
249
|
+
<UIcon v-if="(orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) || (orientation === 'vertical' && item.children?.length)" :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" :class="ui.linkTrailingIcon({ class: props.ui?.linkTrailingIcon, active })" />
|
|
226
250
|
<UIcon v-else-if="item.trailingIcon" :name="item.trailingIcon" :class="ui.linkTrailingIcon({ class: props.ui?.linkTrailingIcon, active })" />
|
|
227
251
|
</slot>
|
|
228
252
|
</span>
|
|
@@ -239,23 +263,23 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
|
|
|
239
263
|
:open="item.open"
|
|
240
264
|
>
|
|
241
265
|
<div v-if="orientation === 'vertical' && item.type === 'label'" :class="ui.label({ class: props.ui?.label })">
|
|
242
|
-
<ReuseLinkTemplate :item="
|
|
266
|
+
<ReuseLinkTemplate :item="item" :index="index" />
|
|
243
267
|
</div>
|
|
244
268
|
<ULink v-else-if="item.type !== 'label'" v-slot="{ active, ...slotProps }" v-bind="(orientation === 'vertical' && item.children?.length && !collapsed) ? {} : pickLinkProps(item as Omit<NavigationMenuItem, 'type'>)" custom>
|
|
245
269
|
<component
|
|
246
|
-
:is="(orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])) ? NavigationMenuTrigger : NavigationMenuLink"
|
|
270
|
+
:is="(orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) ? NavigationMenuTrigger : NavigationMenuLink"
|
|
247
271
|
as-child
|
|
248
272
|
:active="active || item.active"
|
|
249
273
|
:disabled="item.disabled"
|
|
250
274
|
@select="item.onSelect"
|
|
251
275
|
>
|
|
252
276
|
<ULinkBase v-bind="slotProps" :class="ui.link({ class: [props.ui?.link, item.class], active: active || item.active, disabled: !!item.disabled, level: orientation === 'horizontal' || level > 0 })">
|
|
253
|
-
<ReuseLinkTemplate :item="
|
|
277
|
+
<ReuseLinkTemplate :item="item" :active="active || item.active" :index="index" />
|
|
254
278
|
</ULinkBase>
|
|
255
279
|
</component>
|
|
256
280
|
|
|
257
|
-
<NavigationMenuContent v-if="orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])" v-bind="contentProps" :class="ui.content({ class: props.ui?.content })">
|
|
258
|
-
<slot :name="item.slot ? `${item.slot}-content` : 'item-content'" :item="
|
|
281
|
+
<NavigationMenuContent v-if="orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])" v-bind="contentProps" :class="ui.content({ class: props.ui?.content })">
|
|
282
|
+
<slot :name="((item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
|
|
259
283
|
<ul :class="ui.childList({ class: props.ui?.childList })">
|
|
260
284
|
<li v-for="(childItem, childIndex) in item.children" :key="childIndex" :class="ui.childItem({ class: props.ui?.childItem })">
|
|
261
285
|
<ULink v-slot="{ active: childActive, ...childSlotProps }" v-bind="pickLinkProps(childItem)" custom>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { VariantProps } from 'tailwind-variants'
|
|
3
|
-
import type { RadioGroupRootProps, RadioGroupRootEmits
|
|
3
|
+
import type { RadioGroupRootProps, RadioGroupRootEmits } from 'reka-ui'
|
|
4
4
|
import type { AppConfig } from '@nuxt/schema'
|
|
5
5
|
import _appConfig from '#build/app.config'
|
|
6
6
|
import theme from '#build/ui/radio-group'
|
|
7
7
|
import { tv } from '../utils/tv'
|
|
8
|
+
import type { AcceptableValue } from '../types/utils'
|
|
8
9
|
|
|
9
10
|
const appConfigRadioGroup = _appConfig as AppConfig & { ui: { radioGroup: Partial<typeof theme> } }
|
|
10
11
|
|
|
@@ -12,14 +13,16 @@ const radioGroup = tv({ extend: tv(theme), ...(appConfigRadioGroup.ui?.radioGrou
|
|
|
12
13
|
|
|
13
14
|
type RadioGroupVariants = VariantProps<typeof radioGroup>
|
|
14
15
|
|
|
15
|
-
export
|
|
16
|
+
export type RadioGroupValue = AcceptableValue
|
|
17
|
+
export type RadioGroupItem = {
|
|
16
18
|
label?: string
|
|
17
19
|
description?: string
|
|
18
20
|
disabled?: boolean
|
|
19
21
|
value?: string
|
|
20
|
-
|
|
22
|
+
[key: string]: any
|
|
23
|
+
} | RadioGroupValue
|
|
21
24
|
|
|
22
|
-
export interface RadioGroupProps<T> extends Pick<RadioGroupRootProps, 'defaultValue' | 'disabled' | 'loop' | 'modelValue' | 'name' | 'required'> {
|
|
25
|
+
export interface RadioGroupProps<T extends RadioGroupItem = RadioGroupItem> extends Pick<RadioGroupRootProps, 'defaultValue' | 'disabled' | 'loop' | 'modelValue' | 'name' | 'required'> {
|
|
23
26
|
/**
|
|
24
27
|
* The element or component this component should render as.
|
|
25
28
|
* @defaultValue 'div'
|
|
@@ -63,16 +66,16 @@ export type RadioGroupEmits = RadioGroupRootEmits & {
|
|
|
63
66
|
change: [payload: Event]
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
type SlotProps<T> = (props: { item: T, modelValue?:
|
|
69
|
+
type SlotProps<T extends RadioGroupItem> = (props: { item: T & { id: string }, modelValue?: RadioGroupValue }) => any
|
|
67
70
|
|
|
68
|
-
export interface RadioGroupSlots<T> {
|
|
71
|
+
export interface RadioGroupSlots<T extends RadioGroupItem = RadioGroupItem> {
|
|
69
72
|
legend(props?: {}): any
|
|
70
73
|
label: SlotProps<T>
|
|
71
74
|
description: SlotProps<T>
|
|
72
75
|
}
|
|
73
76
|
</script>
|
|
74
77
|
|
|
75
|
-
<script setup lang="ts" generic="T extends RadioGroupItem
|
|
78
|
+
<script setup lang="ts" generic="T extends RadioGroupItem">
|
|
76
79
|
import { computed, useId } from 'vue'
|
|
77
80
|
import { RadioGroupRoot, RadioGroupItem, RadioGroupIndicator, Label, useForwardPropsEmits } from 'reka-ui'
|
|
78
81
|
import { reactivePick } from '@vueuse/core'
|
|
@@ -102,11 +105,19 @@ const ui = computed(() => radioGroup({
|
|
|
102
105
|
}))
|
|
103
106
|
|
|
104
107
|
function normalizeItem(item: any) {
|
|
105
|
-
if (
|
|
108
|
+
if (item === null) {
|
|
109
|
+
return {
|
|
110
|
+
id: `${id}:null`,
|
|
111
|
+
value: undefined,
|
|
112
|
+
label: undefined
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (typeof item === 'string' || typeof item === 'number') {
|
|
106
117
|
return {
|
|
107
118
|
id: `${id}:${item}`,
|
|
108
|
-
value: item,
|
|
109
|
-
label: item
|
|
119
|
+
value: String(item),
|
|
120
|
+
label: String(item)
|
|
110
121
|
}
|
|
111
122
|
}
|
|
112
123
|
|
|
@@ -170,10 +181,10 @@ function onUpdate(value: any) {
|
|
|
170
181
|
|
|
171
182
|
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
|
|
172
183
|
<Label :class="ui.label({ class: props.ui?.label })" :for="item.id">
|
|
173
|
-
<slot name="label" :item="item" :model-value="modelValue">{{ item.label }}</slot>
|
|
184
|
+
<slot name="label" :item="item" :model-value="(modelValue as RadioGroupValue)">{{ item.label }}</slot>
|
|
174
185
|
</Label>
|
|
175
186
|
<p v-if="item.description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
|
|
176
|
-
<slot name="description" :item="item" :model-value="modelValue">
|
|
187
|
+
<slot name="description" :item="item" :model-value="(modelValue as RadioGroupValue)">
|
|
177
188
|
{{ item.description }}
|
|
178
189
|
</slot>
|
|
179
190
|
</p>
|
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { VariantProps } from 'tailwind-variants'
|
|
3
|
-
import type { SelectRootProps, SelectRootEmits, SelectContentProps, SelectContentEmits, SelectArrowProps
|
|
3
|
+
import type { SelectRootProps, SelectRootEmits, SelectContentProps, SelectContentEmits, SelectArrowProps } from 'reka-ui'
|
|
4
4
|
import type { AppConfig } from '@nuxt/schema'
|
|
5
5
|
import _appConfig from '#build/app.config'
|
|
6
6
|
import theme from '#build/ui/select'
|
|
7
7
|
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
|
8
8
|
import { tv } from '../utils/tv'
|
|
9
9
|
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
AcceptableValue,
|
|
12
|
+
ArrayOrNested,
|
|
13
|
+
GetItemKeys,
|
|
14
|
+
GetItemValue,
|
|
15
|
+
GetModelValue,
|
|
16
|
+
GetModelValueEmits,
|
|
17
|
+
NestedItem,
|
|
18
|
+
PartialString,
|
|
19
|
+
EmitsToProps
|
|
20
|
+
} from '../types/utils'
|
|
11
21
|
|
|
12
22
|
const appConfigSelect = _appConfig as AppConfig & { ui: { select: Partial<typeof theme> } }
|
|
13
23
|
|
|
14
24
|
const select = tv({ extend: tv(theme), ...(appConfigSelect.ui?.select || {}) })
|
|
15
25
|
|
|
16
|
-
|
|
26
|
+
interface SelectItemBase {
|
|
17
27
|
label?: string
|
|
18
28
|
/**
|
|
19
29
|
* @IconifyIcon
|
|
@@ -26,13 +36,15 @@ export interface SelectItem {
|
|
|
26
36
|
* @defaultValue 'item'
|
|
27
37
|
*/
|
|
28
38
|
type?: 'label' | 'separator' | 'item'
|
|
29
|
-
value?: string
|
|
39
|
+
value?: string | number
|
|
30
40
|
disabled?: boolean
|
|
41
|
+
[key: string]: any
|
|
31
42
|
}
|
|
43
|
+
export type SelectItem = SelectItemBase | AcceptableValue | boolean
|
|
32
44
|
|
|
33
45
|
type SelectVariants = VariantProps<typeof select>
|
|
34
46
|
|
|
35
|
-
export interface SelectProps<T extends
|
|
47
|
+
export interface SelectProps<T extends ArrayOrNested<SelectItem> = ArrayOrNested<SelectItem>, VK extends GetItemKeys<T> = 'value', M extends boolean = false> extends Omit<SelectRootProps<T>, 'dir' | 'multiple' | 'modelValue' | 'defaultValue' | 'by'>, UseComponentIconsProps {
|
|
36
48
|
id?: string
|
|
37
49
|
/** The placeholder text when the select is empty. */
|
|
38
50
|
placeholder?: string
|
|
@@ -79,17 +91,17 @@ export interface SelectProps<T extends MaybeArrayOfArrayItem<I>, I extends Maybe
|
|
|
79
91
|
* When `items` is an array of objects, select the field to use as the value.
|
|
80
92
|
* @defaultValue 'value'
|
|
81
93
|
*/
|
|
82
|
-
valueKey?:
|
|
94
|
+
valueKey?: VK
|
|
83
95
|
/**
|
|
84
96
|
* When `items` is an array of objects, select the field to use as the label.
|
|
85
97
|
* @defaultValue 'label'
|
|
86
98
|
*/
|
|
87
|
-
labelKey?:
|
|
88
|
-
items?:
|
|
99
|
+
labelKey?: keyof NestedItem<T>
|
|
100
|
+
items?: T
|
|
89
101
|
/** The value of the Select when initially rendered. Use when you do not need to control the state of the Select. */
|
|
90
|
-
defaultValue?:
|
|
102
|
+
defaultValue?: GetModelValue<T, VK, M>
|
|
91
103
|
/** The controlled value of the Select. Can be bind as `v-model`. */
|
|
92
|
-
modelValue?:
|
|
104
|
+
modelValue?: GetModelValue<T, VK, M>
|
|
93
105
|
/** Whether multiple options can be selected or not. */
|
|
94
106
|
multiple?: M & boolean
|
|
95
107
|
/** Highlight the ring color like a focus state. */
|
|
@@ -98,18 +110,23 @@ export interface SelectProps<T extends MaybeArrayOfArrayItem<I>, I extends Maybe
|
|
|
98
110
|
ui?: PartialString<typeof select.slots>
|
|
99
111
|
}
|
|
100
112
|
|
|
101
|
-
export type SelectEmits<
|
|
113
|
+
export type SelectEmits<A extends ArrayOrNested<SelectItem>, VK extends GetItemKeys<A> | undefined, M extends boolean> = Omit<SelectRootEmits, 'update:modelValue'> & {
|
|
102
114
|
change: [payload: Event]
|
|
103
115
|
blur: [payload: FocusEvent]
|
|
104
116
|
focus: [payload: FocusEvent]
|
|
105
|
-
} &
|
|
117
|
+
} & GetModelValueEmits<A, VK, M>
|
|
106
118
|
|
|
107
|
-
type SlotProps<T> = (props: { item: T, index: number }) => any
|
|
119
|
+
type SlotProps<T extends SelectItem> = (props: { item: T, index: number }) => any
|
|
108
120
|
|
|
109
|
-
export interface SelectSlots<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
export interface SelectSlots<
|
|
122
|
+
A extends ArrayOrNested<SelectItem> = ArrayOrNested<SelectItem>,
|
|
123
|
+
VK extends GetItemKeys<A> | undefined = undefined,
|
|
124
|
+
M extends boolean = false,
|
|
125
|
+
T extends NestedItem<A> = NestedItem<A>
|
|
126
|
+
> {
|
|
127
|
+
'leading'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof select> }): any
|
|
128
|
+
'default'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean }): any
|
|
129
|
+
'trailing'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof select> }): any
|
|
113
130
|
'item': SlotProps<T>
|
|
114
131
|
'item-leading': SlotProps<T>
|
|
115
132
|
'item-label': SlotProps<T>
|
|
@@ -117,7 +134,7 @@ export interface SelectSlots<T, M extends boolean> {
|
|
|
117
134
|
}
|
|
118
135
|
</script>
|
|
119
136
|
|
|
120
|
-
<script setup lang="ts" generic="T extends
|
|
137
|
+
<script setup lang="ts" generic="T extends ArrayOrNested<SelectItem>, VK extends GetItemKeys<T> = 'value', M extends boolean = false">
|
|
121
138
|
import { computed, toRef } from 'vue'
|
|
122
139
|
import { SelectRoot, SelectArrow, SelectTrigger, SelectPortal, SelectContent, SelectViewport, SelectLabel, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectSeparator, useForwardPropsEmits } from 'reka-ui'
|
|
123
140
|
import { defu } from 'defu'
|
|
@@ -126,20 +143,20 @@ import { useAppConfig } from '#imports'
|
|
|
126
143
|
import { useButtonGroup } from '../composables/useButtonGroup'
|
|
127
144
|
import { useComponentIcons } from '../composables/useComponentIcons'
|
|
128
145
|
import { useFormField } from '../composables/useFormField'
|
|
129
|
-
import { get,
|
|
146
|
+
import { compare, get, isArrayOfArray } from '../utils'
|
|
130
147
|
import UIcon from './Icon.vue'
|
|
131
148
|
import UAvatar from './Avatar.vue'
|
|
132
149
|
import UChip from './Chip.vue'
|
|
133
150
|
|
|
134
151
|
defineOptions({ inheritAttrs: false })
|
|
135
152
|
|
|
136
|
-
const props = withDefaults(defineProps<SelectProps<T,
|
|
153
|
+
const props = withDefaults(defineProps<SelectProps<T, VK, M>>(), {
|
|
137
154
|
valueKey: 'value' as never,
|
|
138
155
|
labelKey: 'label' as never,
|
|
139
156
|
portal: true
|
|
140
157
|
})
|
|
141
|
-
const emits = defineEmits<SelectEmits<T,
|
|
142
|
-
const slots = defineSlots<SelectSlots<T, M>>()
|
|
158
|
+
const emits = defineEmits<SelectEmits<T, VK, M>>()
|
|
159
|
+
const slots = defineSlots<SelectSlots<T, VK, M>>()
|
|
143
160
|
|
|
144
161
|
const appConfig = useAppConfig()
|
|
145
162
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'disabled', 'autocomplete', 'required', 'multiple'), emits)
|
|
@@ -163,11 +180,17 @@ const ui = computed(() => select({
|
|
|
163
180
|
buttonGroup: orientation.value
|
|
164
181
|
}))
|
|
165
182
|
|
|
166
|
-
const groups = computed
|
|
183
|
+
const groups = computed<SelectItem[][]>(() =>
|
|
184
|
+
props.items?.length
|
|
185
|
+
? isArrayOfArray(props.items)
|
|
186
|
+
? props.items
|
|
187
|
+
: [props.items]
|
|
188
|
+
: []
|
|
189
|
+
)
|
|
167
190
|
// eslint-disable-next-line vue/no-dupe-keys
|
|
168
191
|
const items = computed(() => groups.value.flatMap(group => group) as T[])
|
|
169
192
|
|
|
170
|
-
function displayValue(value?:
|
|
193
|
+
function displayValue(value?: GetItemValue<T, VK> | GetItemValue<T, VK>[]): string {
|
|
171
194
|
if (props.multiple && Array.isArray(value)) {
|
|
172
195
|
return value.map(v => displayValue(v)).filter(Boolean).join(', ')
|
|
173
196
|
}
|
|
@@ -195,6 +218,10 @@ function onUpdateOpen(value: boolean) {
|
|
|
195
218
|
emitFormFocus()
|
|
196
219
|
}
|
|
197
220
|
}
|
|
221
|
+
|
|
222
|
+
function isSelectItem(item: SelectItem): item is SelectItemBase {
|
|
223
|
+
return typeof item === 'object' && item !== null
|
|
224
|
+
}
|
|
198
225
|
</script>
|
|
199
226
|
|
|
200
227
|
<!-- eslint-disable vue/no-template-shadow -->
|
|
@@ -205,21 +232,21 @@ function onUpdateOpen(value: boolean) {
|
|
|
205
232
|
v-bind="rootProps"
|
|
206
233
|
:autocomplete="autocomplete"
|
|
207
234
|
:disabled="disabled"
|
|
208
|
-
:default-value="(defaultValue as (AcceptableValue | AcceptableValue[]
|
|
209
|
-
:model-value="(modelValue as (AcceptableValue | AcceptableValue[]
|
|
235
|
+
:default-value="(defaultValue as (AcceptableValue | AcceptableValue[]))"
|
|
236
|
+
:model-value="(modelValue as (AcceptableValue | AcceptableValue[]))"
|
|
210
237
|
@update:model-value="onUpdate"
|
|
211
238
|
@update:open="onUpdateOpen"
|
|
212
239
|
>
|
|
213
240
|
<SelectTrigger :id="id" :class="ui.base({ class: [props.class, props.ui?.base] })" v-bind="{ ...$attrs, ...ariaAttrs }">
|
|
214
241
|
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
|
215
|
-
<slot name="leading" :model-value="(modelValue as
|
|
242
|
+
<slot name="leading" :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open" :ui="ui">
|
|
216
243
|
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
|
217
244
|
<UAvatar v-else-if="!!avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
|
218
245
|
</slot>
|
|
219
246
|
</span>
|
|
220
247
|
|
|
221
|
-
<slot :model-value="(modelValue as
|
|
222
|
-
<template v-for="displayedModelValue in [displayValue(modelValue)]" :key="displayedModelValue">
|
|
248
|
+
<slot :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open">
|
|
249
|
+
<template v-for="displayedModelValue in [displayValue(modelValue as GetModelValue<T, VK, M>)]" :key="displayedModelValue">
|
|
223
250
|
<span v-if="displayedModelValue" :class="ui.value({ class: props.ui?.value })">
|
|
224
251
|
{{ displayedModelValue }}
|
|
225
252
|
</span>
|
|
@@ -230,7 +257,7 @@ function onUpdateOpen(value: boolean) {
|
|
|
230
257
|
</slot>
|
|
231
258
|
|
|
232
259
|
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
|
|
233
|
-
<slot name="trailing" :model-value="(modelValue as
|
|
260
|
+
<slot name="trailing" :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open" :ui="ui">
|
|
234
261
|
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
|
|
235
262
|
</slot>
|
|
236
263
|
</span>
|
|
@@ -241,24 +268,24 @@ function onUpdateOpen(value: boolean) {
|
|
|
241
268
|
<SelectViewport :class="ui.viewport({ class: props.ui?.viewport })">
|
|
242
269
|
<SelectGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
|
|
243
270
|
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
|
|
244
|
-
<SelectLabel v-if="item
|
|
271
|
+
<SelectLabel v-if="isSelectItem(item) && item.type === 'label'" :class="ui.label({ class: props.ui?.label })">
|
|
245
272
|
{{ get(item, props.labelKey as string) }}
|
|
246
273
|
</SelectLabel>
|
|
247
274
|
|
|
248
|
-
<SelectSeparator v-else-if="item
|
|
275
|
+
<SelectSeparator v-else-if="isSelectItem(item) && item.type === 'separator'" :class="ui.separator({ class: props.ui?.separator })" />
|
|
249
276
|
|
|
250
277
|
<SelectItem
|
|
251
278
|
v-else
|
|
252
279
|
:class="ui.item({ class: props.ui?.item })"
|
|
253
|
-
:disabled="item.disabled"
|
|
254
|
-
:value="
|
|
280
|
+
:disabled="isSelectItem(item) && item.disabled"
|
|
281
|
+
:value="isSelectItem(item) ? get(item, props.valueKey as string) : item"
|
|
255
282
|
>
|
|
256
|
-
<slot name="item" :item="(item as T)" :index="index">
|
|
257
|
-
<slot name="item-leading" :item="(item as T)" :index="index">
|
|
258
|
-
<UIcon v-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon })" />
|
|
259
|
-
<UAvatar v-else-if="item.avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
|
283
|
+
<slot name="item" :item="(item as NestedItem<T>)" :index="index">
|
|
284
|
+
<slot name="item-leading" :item="(item as NestedItem<T>)" :index="index">
|
|
285
|
+
<UIcon v-if="isSelectItem(item) && item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon })" />
|
|
286
|
+
<UAvatar v-else-if="isSelectItem(item) && item.avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
|
260
287
|
<UChip
|
|
261
|
-
v-else-if="item.chip"
|
|
288
|
+
v-else-if="isSelectItem(item) && item.chip"
|
|
262
289
|
:size="((props.ui?.itemLeadingChipSize || ui.itemLeadingChipSize()) as ChipProps['size'])"
|
|
263
290
|
inset
|
|
264
291
|
standalone
|
|
@@ -268,13 +295,13 @@ function onUpdateOpen(value: boolean) {
|
|
|
268
295
|
</slot>
|
|
269
296
|
|
|
270
297
|
<SelectItemText :class="ui.itemLabel({ class: props.ui?.itemLabel })">
|
|
271
|
-
<slot name="item-label" :item="(item as T)" :index="index">
|
|
272
|
-
{{
|
|
298
|
+
<slot name="item-label" :item="(item as NestedItem<T>)" :index="index">
|
|
299
|
+
{{ isSelectItem(item) ? get(item, props.labelKey as string) : item }}
|
|
273
300
|
</slot>
|
|
274
301
|
</SelectItemText>
|
|
275
302
|
|
|
276
303
|
<span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
|
|
277
|
-
<slot name="item-trailing" :item="(item as T)" :index="index" />
|
|
304
|
+
<slot name="item-trailing" :item="(item as NestedItem<T>)" :index="index" />
|
|
278
305
|
|
|
279
306
|
<SelectItemIndicator as-child>
|
|
280
307
|
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: props.ui?.itemTrailingIcon })" />
|