@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.
Files changed (71) hide show
  1. package/.nuxt/ui/button.ts +36 -36
  2. package/.nuxt/ui/container.ts +1 -1
  3. package/.nuxt/ui/navigation-menu.ts +2 -2
  4. package/.nuxt/ui/switch.ts +1 -1
  5. package/.nuxt/ui/tabs.ts +1 -1
  6. package/dist/module.cjs +1 -1
  7. package/dist/module.d.cts +2 -1
  8. package/dist/module.d.mts +2 -1
  9. package/dist/module.d.ts +2 -1
  10. package/dist/module.json +1 -1
  11. package/dist/module.mjs +1 -1
  12. package/dist/runtime/components/Accordion.vue +9 -8
  13. package/dist/runtime/components/Alert.vue +1 -1
  14. package/dist/runtime/components/App.vue +1 -1
  15. package/dist/runtime/components/Avatar.vue +7 -5
  16. package/dist/runtime/components/Breadcrumb.vue +11 -10
  17. package/dist/runtime/components/Button.vue +1 -1
  18. package/dist/runtime/components/Calendar.vue +40 -11
  19. package/dist/runtime/components/Carousel.vue +7 -3
  20. package/dist/runtime/components/CommandPalette.vue +12 -11
  21. package/dist/runtime/components/ContextMenu.vue +24 -12
  22. package/dist/runtime/components/ContextMenuContent.vue +22 -15
  23. package/dist/runtime/components/Drawer.vue +1 -5
  24. package/dist/runtime/components/DropdownMenu.vue +23 -12
  25. package/dist/runtime/components/DropdownMenuContent.vue +22 -15
  26. package/dist/runtime/components/Form.vue +1 -0
  27. package/dist/runtime/components/FormField.vue +1 -0
  28. package/dist/runtime/components/InputMenu.vue +92 -54
  29. package/dist/runtime/components/Link.vue +3 -0
  30. package/dist/runtime/components/LinkBase.vue +1 -0
  31. package/dist/runtime/components/Modal.vue +1 -1
  32. package/dist/runtime/components/NavigationMenu.vue +49 -25
  33. package/dist/runtime/components/RadioGroup.vue +23 -12
  34. package/dist/runtime/components/Select.vue +69 -42
  35. package/dist/runtime/components/SelectMenu.vue +83 -48
  36. package/dist/runtime/components/Slideover.vue +1 -1
  37. package/dist/runtime/components/Stepper.vue +11 -10
  38. package/dist/runtime/components/Table.vue +7 -2
  39. package/dist/runtime/components/Tabs.vue +6 -5
  40. package/dist/runtime/components/Toast.vue +1 -1
  41. package/dist/runtime/components/Tree.vue +46 -32
  42. package/dist/runtime/composables/defineShortcuts.js +0 -1
  43. package/dist/runtime/composables/useFormField.js +1 -1
  44. package/dist/runtime/composables/useOverlay.d.ts +3 -2
  45. package/dist/runtime/locale/hy.d.ts +3 -0
  46. package/dist/runtime/locale/hy.js +54 -0
  47. package/dist/runtime/locale/index.d.ts +1 -0
  48. package/dist/runtime/locale/index.js +1 -0
  49. package/dist/runtime/types/form.d.ts +1 -0
  50. package/dist/runtime/types/index.d.ts +1 -0
  51. package/dist/runtime/types/index.js +1 -0
  52. package/dist/runtime/types/utils.d.ts +28 -8
  53. package/dist/runtime/utils/index.d.ts +1 -0
  54. package/dist/runtime/utils/index.js +3 -0
  55. package/dist/runtime/utils/link.d.ts +2 -25
  56. package/dist/runtime/utils/link.js +31 -1
  57. package/dist/runtime/vue/components/Link.vue +3 -0
  58. package/dist/runtime/vue/stubs.d.ts +7 -2
  59. package/dist/runtime/vue/stubs.js +11 -2
  60. package/dist/shared/{ui.C6uTSFTI.cjs → ui.BL-XB3u1.cjs} +16 -16
  61. package/dist/shared/{ui.BCaWD5Ek.mjs → ui.BkNUP964.mjs} +16 -16
  62. package/dist/types.d.mts +7 -1
  63. package/dist/types.d.ts +7 -1
  64. package/dist/unplugin.cjs +1 -1
  65. package/dist/unplugin.d.cts +2 -1
  66. package/dist/unplugin.d.mts +2 -1
  67. package/dist/unplugin.d.ts +2 -1
  68. package/dist/unplugin.mjs +1 -1
  69. package/dist/vite.cjs +1 -1
  70. package/dist/vite.mjs +1 -1
  71. package/package.json +39 -22
@@ -1,8 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type { VariantProps } from 'tailwind-variants'
3
- import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'reka-ui'
3
+ import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootProps, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'reka-ui'
4
4
  import type { DateValue } from '@internationalized/date'
5
5
  import type { AppConfig } from '@nuxt/schema'
6
+ import type { ButtonProps } from '../types'
6
7
  import _appConfig from '#build/app.config'
7
8
  import theme from '#build/ui/calendar'
8
9
  import { tv } from '../utils/tv'
@@ -14,13 +15,21 @@ const calendar = tv({ extend: tv(theme), ...(appConfigCalendar.ui?.calendar || {
14
15
 
15
16
  type CalendarVariants = VariantProps<typeof calendar>
16
17
 
17
- type CalendarModelValue<R extends boolean = false, M extends boolean = false> = R extends true
18
+ type CalendarDefaultValue<R extends boolean = false, M extends boolean = false> = R extends true
18
19
  ? DateRange
19
20
  : M extends true
20
21
  ? DateValue[]
21
22
  : DateValue
23
+ type CalendarModelValue<R extends boolean = false, M extends boolean = false> = R extends true
24
+ ? (DateRange | null)
25
+ : M extends true
26
+ ? (DateValue[] | undefined)
27
+ : (DateValue | undefined)
22
28
 
23
- export interface CalendarProps<R extends boolean, M extends boolean> extends Omit<CalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'locale' | 'calendarLabel' | 'multiple'> {
29
+ type _CalendarRootProps = Omit<CalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'locale' | 'calendarLabel' | 'multiple'>
30
+ type _RangeCalendarRootProps = Omit<RangeCalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'locale' | 'calendarLabel' | 'multiple'>
31
+
32
+ export interface CalendarProps<R extends boolean = false, M extends boolean = false> extends _RangeCalendarRootProps, _CalendarRootProps {
24
33
  /**
25
34
  * The element or component this component should render as.
26
35
  * @defaultValue 'div'
@@ -32,24 +41,44 @@ export interface CalendarProps<R extends boolean, M extends boolean> extends Omi
32
41
  * @IconifyIcon
33
42
  */
34
43
  nextYearIcon?: string
44
+ /**
45
+ * Configure the next year button.
46
+ * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
47
+ */
48
+ nextYear?: ButtonProps
35
49
  /**
36
50
  * The icon to use for the next month control.
37
51
  * @defaultValue appConfig.ui.icons.chevronRight
38
52
  * @IconifyIcon
39
53
  */
40
54
  nextMonthIcon?: string
55
+ /**
56
+ * Configure the next month button.
57
+ * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
58
+ */
59
+ nextMonth?: ButtonProps
41
60
  /**
42
61
  * The icon to use for the previous year control.
43
62
  * @defaultValue appConfig.ui.icons.chevronDoubleLeft
44
63
  * @IconifyIcon
45
64
  */
46
65
  prevYearIcon?: string
66
+ /**
67
+ * Configure the prev year button.
68
+ * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
69
+ */
70
+ prevYear?: ButtonProps
47
71
  /**
48
72
  * The icon to use for the previous month control.
49
73
  * @defaultValue appConfig.ui.icons.chevronLeft
50
74
  * @IconifyIcon
51
75
  */
52
76
  prevMonthIcon?: string
77
+ /**
78
+ * Configure the prev month button.
79
+ * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
80
+ */
81
+ prevMonth?: ButtonProps
53
82
  /**
54
83
  * @defaultValue 'primary'
55
84
  */
@@ -66,7 +95,7 @@ export interface CalendarProps<R extends boolean, M extends boolean> extends Omi
66
95
  monthControls?: boolean
67
96
  /** Show year controls */
68
97
  yearControls?: boolean
69
- defaultValue?: CalendarModelValue<R, M>
98
+ defaultValue?: CalendarDefaultValue<R, M>
70
99
  modelValue?: CalendarModelValue<R, M>
71
100
  class?: any
72
101
  ui?: PartialString<typeof calendar.slots>
@@ -83,7 +112,7 @@ export interface CalendarSlots {
83
112
  }
84
113
  </script>
85
114
 
86
- <script setup lang="ts" generic="R extends boolean = false, M extends boolean = false">
115
+ <script setup lang="ts" generic="R extends boolean, M extends boolean">
87
116
  import { computed } from 'vue'
88
117
  import { useForwardPropsEmits } from 'reka-ui'
89
118
  import { Calendar as SingleCalendar, RangeCalendar } from 'reka-ui/namespaced'
@@ -130,18 +159,18 @@ const Calendar = computed(() => props.range ? RangeCalendar : SingleCalendar)
130
159
  <Calendar.Root
131
160
  v-slot="{ weekDays, grid }"
132
161
  v-bind="rootProps"
133
- :model-value="(modelValue as CalendarModelValue<true & false>)"
134
- :default-value="(defaultValue as CalendarModelValue<true & false>)"
162
+ :model-value="modelValue"
163
+ :default-value="defaultValue"
135
164
  :locale="locale"
136
165
  :dir="dir"
137
166
  :class="ui.root({ class: [props.class, props.ui?.root] })"
138
167
  >
139
168
  <Calendar.Header :class="ui.header({ class: props.ui?.header })">
140
169
  <Calendar.Prev v-if="props.yearControls" :prev-page="(date: DateValue) => paginateYear(date, -1)" :aria-label="t('calendar.prevYear')" as-child>
141
- <UButton :icon="prevYearIcon" :size="props.size" color="neutral" variant="ghost" />
170
+ <UButton :icon="prevYearIcon" :size="props.size" color="neutral" variant="ghost" v-bind="props.prevYear" />
142
171
  </Calendar.Prev>
143
172
  <Calendar.Prev v-if="props.monthControls" :aria-label="t('calendar.prevMonth')" as-child>
144
- <UButton :icon="prevMonthIcon" :size="props.size" color="neutral" variant="ghost" />
173
+ <UButton :icon="prevMonthIcon" :size="props.size" color="neutral" variant="ghost" v-bind="props.prevMonth" />
145
174
  </Calendar.Prev>
146
175
  <Calendar.Heading v-slot="{ headingValue }" :class="ui.heading({ class: props.ui?.heading })">
147
176
  <slot name="heading" :value="headingValue">
@@ -149,10 +178,10 @@ const Calendar = computed(() => props.range ? RangeCalendar : SingleCalendar)
149
178
  </slot>
150
179
  </Calendar.Heading>
151
180
  <Calendar.Next v-if="props.monthControls" :aria-label="t('calendar.nextMonth')" as-child>
152
- <UButton :icon="nextMonthIcon" :size="props.size" color="neutral" variant="ghost" />
181
+ <UButton :icon="nextMonthIcon" :size="props.size" color="neutral" variant="ghost" v-bind="props.nextMonth" />
153
182
  </Calendar.Next>
154
183
  <Calendar.Next v-if="props.yearControls" :next-page="(date: DateValue) => paginateYear(date, 1)" :aria-label="t('calendar.nextYear')" as-child>
155
- <UButton :icon="nextYearIcon" :size="props.size" color="neutral" variant="ghost" />
184
+ <UButton :icon="nextYearIcon" :size="props.size" color="neutral" variant="ghost" v-bind="props.nextYear" />
156
185
  </Calendar.Next>
157
186
  </Calendar.Header>
158
187
  <div :class="ui.body({ class: props.ui?.body })">
@@ -1,3 +1,4 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
1
2
  <script lang="ts">
2
3
  import type { VariantProps } from 'tailwind-variants'
3
4
  import type { AppConfig } from '@nuxt/schema'
@@ -21,7 +22,9 @@ const carousel = tv({ extend: tv(theme), ...(appConfigCarousel.ui?.carousel || {
21
22
 
22
23
  type CarouselVariants = VariantProps<typeof carousel>
23
24
 
24
- export interface CarouselProps<T> extends Omit<EmblaOptionsType, 'axis' | 'container' | 'slides' | 'direction'> {
25
+ export type CarouselItem = AcceptableValue
26
+
27
+ export interface CarouselProps<T extends CarouselItem = CarouselItem> extends Omit<EmblaOptionsType, 'axis' | 'container' | 'slides' | 'direction'> {
25
28
  /**
26
29
  * The element or component this component should render as.
27
30
  * @defaultValue 'div'
@@ -99,12 +102,13 @@ export interface CarouselProps<T> extends Omit<EmblaOptionsType, 'axis' | 'conta
99
102
  ui?: PartialString<typeof carousel.slots>
100
103
  }
101
104
 
102
- export type CarouselSlots<T> = {
105
+ export type CarouselSlots<T extends CarouselItem = CarouselItem> = {
103
106
  default(props: { item: T, index: number }): any
104
107
  }
108
+
105
109
  </script>
106
110
 
107
- <script setup lang="ts" generic="T extends AcceptableValue">
111
+ <script setup lang="ts" generic="T extends CarouselItem">
108
112
  import { computed, ref, watch, onMounted } from 'vue'
109
113
  import useEmblaCarousel from 'embla-carousel-vue'
110
114
  import { Primitive, useForwardProps } from 'reka-ui'
@@ -9,7 +9,7 @@ import theme from '#build/ui/command-palette'
9
9
  import type { UseComponentIconsProps } from '../composables/useComponentIcons'
10
10
  import { tv } from '../utils/tv'
11
11
  import type { AvatarProps, ButtonProps, ChipProps, KbdProps, InputProps, LinkProps } from '../types'
12
- import type { DynamicSlots, PartialString } from '../types/utils'
12
+ import type { PartialString } from '../types/utils'
13
13
 
14
14
  const appConfigCommandPalette = _appConfig as AppConfig & { ui: { commandPalette: Partial<typeof theme> } }
15
15
 
@@ -31,6 +31,7 @@ export interface CommandPaletteItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
31
31
  disabled?: boolean
32
32
  slot?: string
33
33
  onSelect?(e?: Event): void
34
+ [key: string]: any
34
35
  }
35
36
 
36
37
  export interface CommandPaletteGroup<T> {
@@ -73,7 +74,7 @@ export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multi
73
74
  selectedIcon?: string
74
75
  /**
75
76
  * The placeholder text for the input.
76
- * @defaultValue 'Type a command or search...'
77
+ * @defaultValue t('commandPalette.placeholder')
77
78
  */
78
79
  placeholder?: InputProps['placeholder']
79
80
  /**
@@ -125,12 +126,12 @@ type SlotProps<T> = (props: { item: T, index: number }) => any
125
126
 
126
127
  export type CommandPaletteSlots<G extends { slot?: string }, T extends { slot?: string }> = {
127
128
  'empty'(props: { searchTerm?: string }): any
128
- 'close'(props: { ui: any }): any
129
+ 'close'(props: { ui: ReturnType<typeof commandPalette> }): any
129
130
  'item': SlotProps<T>
130
131
  'item-leading': SlotProps<T>
131
132
  'item-label': SlotProps<T>
132
133
  'item-trailing': SlotProps<T>
133
- } & DynamicSlots<G, SlotProps<T>> & DynamicSlots<T, SlotProps<T>>
134
+ } & Record<string, SlotProps<G>> & Record<string, SlotProps<T>>
134
135
 
135
136
  </script>
136
137
 
@@ -282,14 +283,14 @@ const groups = computed(() => {
282
283
 
283
284
  <ListboxContent :class="ui.content({ class: props.ui?.content })">
284
285
  <div v-if="groups?.length" :class="ui.viewport({ class: props.ui?.viewport })">
285
- <ListboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
286
+ <ListboxGroup v-for="group in groups" :key="`group-${group.id}`" :class="ui.group({ class: props.ui?.group })">
286
287
  <ListboxGroupLabel v-if="get(group, props.labelKey as string)" :class="ui.label({ class: props.ui?.label })">
287
288
  {{ get(group, props.labelKey as string) }}
288
289
  </ListboxGroupLabel>
289
290
 
290
291
  <ListboxItem
291
292
  v-for="(item, index) in group.items"
292
- :key="`group-${groupIndex}-${index}`"
293
+ :key="`group-${group.id}-${index}`"
293
294
  :value="omit(item, ['matches' as any, 'group' as any, 'onSelect', 'labelHtml', 'suffixHtml'])"
294
295
  :disabled="item.disabled"
295
296
  as-child
@@ -297,8 +298,8 @@ const groups = computed(() => {
297
298
  >
298
299
  <ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item)" custom>
299
300
  <ULinkBase v-bind="slotProps" :class="ui.item({ class: props.ui?.item, active: active || item.active })">
300
- <slot :name="item.slot || group.slot || 'item'" :item="item" :index="index">
301
- <slot :name="item.slot ? `${item.slot}-leading` : group.slot ? `${group.slot}-leading` : `item-leading`" :item="item" :index="index">
301
+ <slot :name="((item.slot || group.slot || 'item') as keyof CommandPaletteSlots<G, T>)" :item="(item as any)" :index="index">
302
+ <slot :name="((item.slot ? `${item.slot}-leading` : group.slot ? `${group.slot}-leading` : `item-leading`) as keyof CommandPaletteSlots<G, T>)" :item="(item as any)" :index="index">
302
303
  <UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, loading: true })" />
303
304
  <UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, active: active || item.active })" />
304
305
  <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, active: active || item.active })" />
@@ -312,8 +313,8 @@ const groups = computed(() => {
312
313
  />
313
314
  </slot>
314
315
 
315
- <span v-if="item.labelHtml || get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel({ class: props.ui?.itemLabel, active: active || item.active })">
316
- <slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index">
316
+ <span v-if="item.labelHtml || get(item, props.labelKey as string) || !!slots[(item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`) as keyof CommandPaletteSlots<G, T>]" :class="ui.itemLabel({ class: props.ui?.itemLabel, active: active || item.active })">
317
+ <slot :name="((item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`) as keyof CommandPaletteSlots<G, T>)" :item="(item as any)" :index="index">
317
318
  <span v-if="item.prefix" :class="ui.itemLabelPrefix({ class: props.ui?.itemLabelPrefix })">{{ item.prefix }}</span>
318
319
 
319
320
  <span :class="ui.itemLabelBase({ class: props.ui?.itemLabelBase, active: active || item.active })" v-html="item.labelHtml || get(item, props.labelKey as string)" />
@@ -323,7 +324,7 @@ const groups = computed(() => {
323
324
  </span>
324
325
 
325
326
  <span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
326
- <slot :name="item.slot ? `${item.slot}-trailing` : group.slot ? `${group.slot}-trailing` : `item-trailing`" :item="item" :index="index">
327
+ <slot :name="((item.slot ? `${item.slot}-trailing` : group.slot ? `${group.slot}-trailing` : `item-trailing`) as keyof CommandPaletteSlots<G, T>)" :item="(item as any)" :index="index">
327
328
  <span v-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: props.ui?.itemTrailingKbds })">
328
329
  <UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="((props.ui?.itemTrailingKbdsSize || ui.itemTrailingKbdsSize()) as KbdProps['size'])" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
329
330
  </span>
@@ -7,7 +7,14 @@ import _appConfig from '#build/app.config'
7
7
  import theme from '#build/ui/context-menu'
8
8
  import { tv } from '../utils/tv'
9
9
  import type { AvatarProps, KbdProps, LinkProps } from '../types'
10
- import type { DynamicSlots, PartialString, EmitsToProps } from '../types/utils'
10
+ import type {
11
+ ArrayOrNested,
12
+ DynamicSlots,
13
+ MergeTypes,
14
+ NestedItem,
15
+ PartialString,
16
+ EmitsToProps
17
+ } from '../types/utils'
11
18
 
12
19
  const appConfigContextMenu = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } }
13
20
 
@@ -36,17 +43,18 @@ export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custo
36
43
  checked?: boolean
37
44
  open?: boolean
38
45
  defaultOpen?: boolean
39
- children?: ContextMenuItem[] | ContextMenuItem[][]
46
+ children?: ArrayOrNested<ContextMenuItem>
40
47
  onSelect?(e: Event): void
41
48
  onUpdateChecked?(checked: boolean): void
49
+ [key: string]: any
42
50
  }
43
51
 
44
- export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'> {
52
+ export interface ContextMenuProps<T extends ArrayOrNested<ContextMenuItem> = ArrayOrNested<ContextMenuItem>> extends Omit<ContextMenuRootProps, 'dir'> {
45
53
  /**
46
54
  * @defaultValue 'md'
47
55
  */
48
56
  size?: ContextMenuVariants['size']
49
- items?: T[] | T[][]
57
+ items?: T
50
58
  /**
51
59
  * The icon displayed when an item is checked.
52
60
  * @defaultValue appConfig.ui.icons.check
@@ -77,7 +85,7 @@ export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'> {
77
85
  * The key used to get the label from the item.
78
86
  * @defaultValue 'label'
79
87
  */
80
- labelKey?: string
88
+ labelKey?: keyof NestedItem<T>
81
89
  disabled?: boolean
82
90
  class?: any
83
91
  ui?: PartialString<typeof contextMenu.slots>
@@ -85,19 +93,22 @@ export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'> {
85
93
 
86
94
  export interface ContextMenuEmits extends ContextMenuRootEmits {}
87
95
 
88
- type SlotProps<T> = (props: { item: T, active?: boolean, index: number }) => any
96
+ type SlotProps<T extends ContextMenuItem> = (props: { item: T, active?: boolean, index: number }) => any
89
97
 
90
- export type ContextMenuSlots<T extends { slot?: string }> = {
98
+ export type ContextMenuSlots<
99
+ A extends ArrayOrNested<ContextMenuItem> = ArrayOrNested<ContextMenuItem>,
100
+ T extends NestedItem<A> = NestedItem<A>
101
+ > = {
91
102
  'default'(props?: {}): any
92
103
  'item': SlotProps<T>
93
104
  'item-leading': SlotProps<T>
94
105
  'item-label': SlotProps<T>
95
106
  'item-trailing': SlotProps<T>
96
- } & DynamicSlots<T, SlotProps<T>>
107
+ } & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing', { active?: boolean, index: number }>
97
108
 
98
109
  </script>
99
110
 
100
- <script setup lang="ts" generic="T extends ContextMenuItem">
111
+ <script setup lang="ts" generic="T extends ArrayOrNested<ContextMenuItem>">
101
112
  import { computed, toRef } from 'vue'
102
113
  import { ContextMenuRoot, ContextMenuTrigger, useForwardPropsEmits } from 'reka-ui'
103
114
  import { reactivePick } from '@vueuse/core'
@@ -114,8 +125,9 @@ const emits = defineEmits<ContextMenuEmits>()
114
125
  const slots = defineSlots<ContextMenuSlots<T>>()
115
126
 
116
127
  const rootProps = useForwardPropsEmits(reactivePick(props, 'modal'), emits)
128
+
117
129
  const contentProps = toRef(() => props.content)
118
- const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]>
130
+ const proxySlots = omit(slots, ['default'])
119
131
 
120
132
  const ui = computed(() => contextMenu({
121
133
  size: props.size
@@ -135,13 +147,13 @@ const ui = computed(() => contextMenu({
135
147
  v-bind="contentProps"
136
148
  :items="items"
137
149
  :portal="portal"
138
- :label-key="labelKey"
150
+ :label-key="(labelKey as keyof NestedItem<T>)"
139
151
  :checked-icon="checkedIcon"
140
152
  :loading-icon="loadingIcon"
141
153
  :external-icon="externalIcon"
142
154
  >
143
155
  <template v-for="(_, name) in proxySlots" #[name]="slotData">
144
- <slot :name="name" v-bind="slotData" />
156
+ <slot :name="(name as keyof ContextMenuSlots<T>)" v-bind="slotData" />
145
157
  </template>
146
158
  </UContextMenuContent>
147
159
  </ContextMenuRoot>
@@ -2,15 +2,16 @@
2
2
  import type { ContextMenuContentProps as RekaContextMenuContentProps, ContextMenuContentEmits as RekaContextMenuContentEmits } from 'reka-ui'
3
3
  import theme from '#build/ui/context-menu'
4
4
  import { tv } from '../utils/tv'
5
- import type { KbdProps, AvatarProps, ContextMenuItem, ContextMenuSlots } from '../types'
5
+ import type { AvatarProps, ContextMenuItem, ContextMenuSlots, KbdProps } from '../types'
6
+ import type { ArrayOrNested, NestedItem } from '../types/utils'
6
7
 
7
8
  const _contextMenu = tv(theme)()
8
9
 
9
- interface ContextMenuContentProps<T> extends Omit<RekaContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
10
- items?: T[] | T[][]
10
+ interface ContextMenuContentProps<T extends ArrayOrNested<ContextMenuItem>> extends Omit<RekaContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
11
+ items?: T
11
12
  portal?: boolean
12
13
  sub?: boolean
13
- labelKey: string
14
+ labelKey: keyof NestedItem<T>
14
15
  /**
15
16
  * @IconifyIcon
16
17
  */
@@ -31,13 +32,13 @@ interface ContextMenuContentProps<T> extends Omit<RekaContextMenuContentProps, '
31
32
  interface ContextMenuContentEmits extends RekaContextMenuContentEmits {}
32
33
  </script>
33
34
 
34
- <script setup lang="ts" generic="T extends ContextMenuItem">
35
+ <script setup lang="ts" generic="T extends ArrayOrNested<ContextMenuItem>">
35
36
  import { computed } from 'vue'
36
37
  import { ContextMenu } from 'reka-ui/namespaced'
37
38
  import { useForwardPropsEmits } from 'reka-ui'
38
39
  import { reactiveOmit, createReusableTemplate } from '@vueuse/core'
39
40
  import { useAppConfig } from '#imports'
40
- import { omit, get } from '../utils'
41
+ import { omit, get, isArrayOfArray } from '../utils'
41
42
  import { pickLinkProps } from '../utils/link'
42
43
  import ULinkBase from './LinkBase.vue'
43
44
  import ULink from './Link.vue'
@@ -53,24 +54,30 @@ const slots = defineSlots<ContextMenuSlots<T>>()
53
54
 
54
55
  const appConfig = useAppConfig()
55
56
  const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits)
56
- const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]>
57
+ const proxySlots = omit(slots, ['default'])
57
58
 
58
59
  const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: ContextMenuItem, active?: boolean, index: number }>()
59
60
 
60
- const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as T[][] : [])
61
+ const groups = computed<ContextMenuItem[][]>(() =>
62
+ props.items?.length
63
+ ? isArrayOfArray(props.items)
64
+ ? props.items
65
+ : [props.items]
66
+ : []
67
+ )
61
68
  </script>
62
69
 
63
70
  <template>
64
71
  <DefineItemTemplate v-slot="{ item, active, index }">
65
- <slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
66
- <slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="(item as T)" :active="active" :index="index">
72
+ <slot :name="((item.slot || 'item') as keyof ContextMenuSlots<T>)" :item="item" :index="index">
73
+ <slot :name="((item.slot ? `${item.slot}-leading`: 'item-leading') as keyof ContextMenuSlots<T>)" :item="item" :active="active" :index="index">
67
74
  <UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, color: item?.color, loading: true })" />
68
75
  <UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, color: item?.color, active })" />
69
76
  <UAvatar v-else-if="item.avatar" :size="((props.uiOverride?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: uiOverride?.itemLeadingAvatar, active })" />
70
77
  </slot>
71
78
 
72
- <span v-if="get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel({ class: uiOverride?.itemLabel, active })">
73
- <slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="(item as T)" :active="active" :index="index">
79
+ <span v-if="get(item, props.labelKey as string) || !!slots[(item.slot ? `${item.slot}-label`: 'item-label') as keyof ContextMenuSlots<T>]" :class="ui.itemLabel({ class: uiOverride?.itemLabel, active })">
80
+ <slot :name="((item.slot ? `${item.slot}-label`: 'item-label') as keyof ContextMenuSlots<T>)" :item="item" :active="active" :index="index">
74
81
  {{ get(item, props.labelKey as string) }}
75
82
  </slot>
76
83
 
@@ -78,7 +85,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
78
85
  </span>
79
86
 
80
87
  <span :class="ui.itemTrailing({ class: uiOverride?.itemTrailing })">
81
- <slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="(item as T)" :active="active" :index="index">
88
+ <slot :name="((item.slot ? `${item.slot}-trailing`: 'item-trailing') as keyof ContextMenuSlots<T>)" :item="item" :active="active" :index="index">
82
89
  <UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.itemTrailingIcon({ class: uiOverride?.itemTrailingIcon, color: item?.color, active })" />
83
90
  <span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: uiOverride?.itemTrailingKbds })">
84
91
  <UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="((props.uiOverride?.itemTrailingKbdsSize || ui.itemTrailingKbdsSize()) as KbdProps['size'])" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
@@ -117,7 +124,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
117
124
  :ui="ui"
118
125
  :ui-override="uiOverride"
119
126
  :portal="portal"
120
- :items="item.children"
127
+ :items="(item.children as T)"
121
128
  :align-offset="-4"
122
129
  :label-key="labelKey"
123
130
  :checked-icon="checkedIcon"
@@ -126,7 +133,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
126
133
  v-bind="item.content"
127
134
  >
128
135
  <template v-for="(_, name) in proxySlots" #[name]="slotData: any">
129
- <slot :name="name" v-bind="slotData" />
136
+ <slot :name="(name as keyof ContextMenuSlots<T>)" v-bind="slotData" />
130
137
  </template>
131
138
  </UContextMenuContent>
132
139
  </ContextMenu.Sub>
@@ -41,10 +41,6 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
41
41
  * @defaultValue true
42
42
  */
43
43
  portal?: boolean
44
- /**
45
- * Index of a `snapPoint` from which the overlay fade should be applied. Defaults to the last snap point.
46
- */
47
- fadeFromIndex?: any
48
44
  class?: any
49
45
  ui?: Partial<typeof drawer.slots>
50
46
  }
@@ -79,7 +75,7 @@ const props = withDefaults(defineProps<DrawerProps>(), {
79
75
  const emits = defineEmits<DrawerEmits>()
80
76
  const slots = defineSlots<DrawerSlots>()
81
77
 
82
- const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'shouldScaleBackground', 'setBackgroundColorOnScale', 'scrollLockTimeout', 'fixed', 'dismissible', 'modal', 'open', 'defaultOpen', 'nested', 'direction', 'noBodyStyles', 'handleOnly', 'preventScrollRestoration', 'snapPoints', 'fadeFromIndex'), emits)
78
+ const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'shouldScaleBackground', 'setBackgroundColorOnScale', 'scrollLockTimeout', 'fixed', 'dismissible', 'modal', 'open', 'defaultOpen', 'nested', 'direction', 'noBodyStyles', 'handleOnly', 'preventScrollRestoration', 'snapPoints'), emits)
83
79
  const contentProps = toRef(() => props.content)
84
80
  const contentEvents = {
85
81
  closeAutoFocus: (e: Event) => e.preventDefault()
@@ -7,7 +7,14 @@ import _appConfig from '#build/app.config'
7
7
  import theme from '#build/ui/dropdown-menu'
8
8
  import { tv } from '../utils/tv'
9
9
  import type { AvatarProps, KbdProps, LinkProps } from '../types'
10
- import type { DynamicSlots, PartialString, EmitsToProps } from '../types/utils'
10
+ import type {
11
+ ArrayOrNested,
12
+ DynamicSlots,
13
+ MergeTypes,
14
+ NestedItem,
15
+ PartialString,
16
+ EmitsToProps
17
+ } from '../types/utils'
11
18
 
12
19
  const appConfigDropdownMenu = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
13
20
 
@@ -36,17 +43,18 @@ export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cust
36
43
  checked?: boolean
37
44
  open?: boolean
38
45
  defaultOpen?: boolean
39
- children?: DropdownMenuItem[] | DropdownMenuItem[][]
46
+ children?: ArrayOrNested<DropdownMenuItem>
40
47
  onSelect?(e: Event): void
41
48
  onUpdateChecked?(checked: boolean): void
49
+ [key: string]: any
42
50
  }
43
51
 
44
- export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'> {
52
+ export interface DropdownMenuProps<T extends ArrayOrNested<DropdownMenuItem> = ArrayOrNested<DropdownMenuItem>> extends Omit<DropdownMenuRootProps, 'dir'> {
45
53
  /**
46
54
  * @defaultValue 'md'
47
55
  */
48
56
  size?: DropdownMenuVariants['size']
49
- items?: T[] | T[][]
57
+ items?: T
50
58
  /**
51
59
  * The icon displayed when an item is checked.
52
60
  * @defaultValue appConfig.ui.icons.check
@@ -85,7 +93,7 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
85
93
  * The key used to get the label from the item.
86
94
  * @defaultValue 'label'
87
95
  */
88
- labelKey?: string
96
+ labelKey?: keyof NestedItem<T>
89
97
  disabled?: boolean
90
98
  class?: any
91
99
  ui?: PartialString<typeof dropdownMenu.slots>
@@ -93,19 +101,22 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
93
101
 
94
102
  export interface DropdownMenuEmits extends DropdownMenuRootEmits {}
95
103
 
96
- type SlotProps<T> = (props: { item: T, active?: boolean, index: number }) => any
104
+ type SlotProps<T extends DropdownMenuItem> = (props: { item: T, active?: boolean, index: number }) => any
97
105
 
98
- export type DropdownMenuSlots<T extends { slot?: string }> = {
106
+ export type DropdownMenuSlots<
107
+ A extends ArrayOrNested<DropdownMenuItem> = ArrayOrNested<DropdownMenuItem>,
108
+ T extends NestedItem<A> = NestedItem<A>
109
+ > = {
99
110
  'default'(props: { open: boolean }): any
100
111
  'item': SlotProps<T>
101
112
  'item-leading': SlotProps<T>
102
113
  'item-label': SlotProps<T>
103
114
  'item-trailing': SlotProps<T>
104
- } & DynamicSlots<T, SlotProps<T>>
115
+ } & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing', { active?: boolean, index: number }>
105
116
 
106
117
  </script>
107
118
 
108
- <script setup lang="ts" generic="T extends DropdownMenuItem">
119
+ <script setup lang="ts" generic="T extends ArrayOrNested<DropdownMenuItem>">
109
120
  import { computed, toRef } from 'vue'
110
121
  import { defu } from 'defu'
111
122
  import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuArrow, useForwardPropsEmits } from 'reka-ui'
@@ -125,7 +136,7 @@ const slots = defineSlots<DropdownMenuSlots<T>>()
125
136
  const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultOpen', 'open', 'modal'), emits)
126
137
  const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8 }) as DropdownMenuContentProps)
127
138
  const arrowProps = toRef(() => props.arrow as DropdownMenuArrowProps)
128
- const proxySlots = omit(slots, ['default']) as Record<string, DropdownMenuSlots<T>[string]>
139
+ const proxySlots = omit(slots, ['default'])
129
140
 
130
141
  const ui = computed(() => dropdownMenu({
131
142
  size: props.size
@@ -145,13 +156,13 @@ const ui = computed(() => dropdownMenu({
145
156
  v-bind="contentProps"
146
157
  :items="items"
147
158
  :portal="portal"
148
- :label-key="labelKey"
159
+ :label-key="(labelKey as keyof NestedItem<T>)"
149
160
  :checked-icon="checkedIcon"
150
161
  :loading-icon="loadingIcon"
151
162
  :external-icon="externalIcon"
152
163
  >
153
164
  <template v-for="(_, name) in proxySlots" #[name]="slotData">
154
- <slot :name="name" v-bind="slotData" />
165
+ <slot :name="(name as keyof DropdownMenuSlots<T>)" v-bind="slotData" />
155
166
  </template>
156
167
 
157
168
  <DropdownMenuArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />