Skip to content

Commit cffaaaa

Browse files
committed
fix(NavigationMenu): proxy modelValue / defaultValue in vertical orientation
Resolves #5392
1 parent c16ee33 commit cffaaaa

File tree

1 file changed

+72
-13
lines changed

1 file changed

+72
-13
lines changed

src/runtime/components/NavigationMenu.vue

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!-- eslint-disable vue/block-tag-newline -->
22
<script lang="ts">
3-
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, NavigationMenuContentEmits, AccordionRootProps } from 'reka-ui'
3+
import type { NavigationMenuRootProps, NavigationMenuContentProps, NavigationMenuContentEmits, AccordionRootProps } from 'reka-ui'
44
import type { AppConfig } from '@nuxt/schema'
55
import theme from '#build/ui/navigation-menu'
66
import type { AvatarProps, BadgeProps, IconProps, LinkProps, PopoverProps, TooltipProps } from '../types'
@@ -63,12 +63,49 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
6363
[key: string]: any
6464
}
6565
66-
export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>> extends Pick<NavigationMenuRootProps, 'modelValue' | 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'>, Pick<AccordionRootProps, 'disabled' | 'type' | 'collapsible'> {
66+
type SingleOrMultipleType = 'single' | 'multiple'
67+
type Orientation = NavigationMenuRootProps['orientation']
68+
69+
type NavigationMenuModelValue<
70+
K extends SingleOrMultipleType = SingleOrMultipleType,
71+
O extends Orientation = Orientation
72+
> = O extends 'horizontal' ? string : K extends 'single' ? string : K extends 'multiple' ? string[] : string | string[]
73+
74+
export interface NavigationMenuProps<
75+
T extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>,
76+
K extends SingleOrMultipleType = SingleOrMultipleType,
77+
O extends Orientation = Orientation
78+
> extends Pick<NavigationMenuRootProps, 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'>, Pick<AccordionRootProps, 'disabled' | 'collapsible'> {
6779
/**
6880
* The element or component this component should render as.
6981
* @defaultValue 'div'
7082
*/
7183
as?: any
84+
/**
85+
* Determines whether a "single" or "multiple" items can be selected at a time.
86+
*
87+
* Only works when `orientation` is `vertical`.
88+
* @defaultValue 'multiple'
89+
*/
90+
type?: K
91+
/**
92+
* The controlled value of the active item(s).
93+
* - In horizontal orientation: always `string`
94+
* - In vertical orientation with `type="single"`: `string`
95+
* - In vertical orientation with `type="multiple"`: `string[]`
96+
*
97+
* Use this when you need to control the state of the items. Can be binded with `v-model`
98+
*/
99+
modelValue?: NavigationMenuModelValue<K, O>
100+
/**
101+
* The default active value of the item(s).
102+
* - In horizontal orientation: always `string`
103+
* - In vertical orientation with `type="single"`: `string`
104+
* - In vertical orientation with `type="multiple"`: `string[]`
105+
*
106+
* Use when you do not need to control the state of the item(s).
107+
*/
108+
defaultValue?: NavigationMenuModelValue<K, O>
72109
/**
73110
* The icon displayed to open the menu.
74111
* @defaultValue appConfig.ui.icons.chevronDown
@@ -95,7 +132,7 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
95132
* The orientation of the menu.
96133
* @defaultValue 'horizontal'
97134
*/
98-
orientation?: NavigationMenuRootProps['orientation']
135+
orientation?: O
99136
/**
100137
* Collapse the navigation menu to only show icons.
101138
* Only works when `orientation` is `vertical`.
@@ -142,7 +179,18 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
142179
ui?: NavigationMenu['slots']
143180
}
144181
145-
export interface NavigationMenuEmits extends NavigationMenuRootEmits {}
182+
export type NavigationMenuEmits<
183+
K extends SingleOrMultipleType = SingleOrMultipleType,
184+
O extends Orientation = Orientation
185+
> = {
186+
/**
187+
* Event handler called when the value changes.
188+
* - In horizontal orientation: emits `string`
189+
* - In vertical orientation with `type="single"`: emits `string | undefined`
190+
* - In vertical orientation with `type="multiple"`: emits `string[] | undefined`
191+
*/
192+
'update:modelValue': [value: NavigationMenuModelValue<K, O> | undefined]
193+
}
146194
147195
type SlotProps<T extends NavigationMenuItem> = (props: { item: T, index: number, active?: boolean, ui: NavigationMenu['ui'] }) => any
148196
@@ -163,7 +211,7 @@ export type NavigationMenuSlots<
163211
164212
</script>
165213

166-
<script setup lang="ts" generic="T extends ArrayOrNested<NavigationMenuItem>">
214+
<script setup lang="ts" generic="T extends ArrayOrNested<NavigationMenuItem>, K extends SingleOrMultipleType = SingleOrMultipleType, O extends Orientation = Orientation">
167215
import { computed, toRef } from 'vue'
168216
import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, AccordionRoot, AccordionItem, AccordionTrigger, AccordionContent, useForwardPropsEmits } from 'reka-ui'
169217
import { defu } from 'defu'
@@ -182,25 +230,23 @@ import UTooltip from './Tooltip.vue'
182230
183231
defineOptions({ inheritAttrs: false })
184232
185-
const props = withDefaults(defineProps<NavigationMenuProps<T>>(), {
186-
orientation: 'horizontal',
233+
const props = withDefaults(defineProps<NavigationMenuProps<T, K, O>>(), {
234+
orientation: 'horizontal' as never,
187235
contentOrientation: 'horizontal',
188236
externalIcon: true,
189237
delayDuration: 0,
190-
type: 'multiple',
238+
type: 'multiple' as never,
191239
collapsible: true,
192240
unmountOnHide: true,
193241
labelKey: 'label'
194242
})
195-
const emits = defineEmits<NavigationMenuEmits>()
243+
const emits = defineEmits<NavigationMenuEmits<K, O>>()
196244
const slots = defineSlots<NavigationMenuSlots<T>>()
197245
198246
const appConfig = useAppConfig() as NavigationMenu['AppConfig']
199247
200248
const rootProps = useForwardPropsEmits(computed(() => ({
201249
as: props.as,
202-
modelValue: props.modelValue,
203-
defaultValue: props.defaultValue,
204250
delayDuration: props.delayDuration,
205251
skipDelayDuration: props.skipDelayDuration,
206252
orientation: props.orientation,
@@ -415,14 +461,27 @@ function getAccordionDefaultValue(list: NavigationMenuItem[], level = 0) {
415461
</component>
416462
</DefineItemTemplate>
417463

418-
<NavigationMenuRoot v-bind="{ ...rootProps, ...$attrs }" :data-collapsed="collapsed" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })">
464+
<NavigationMenuRoot
465+
v-bind="{
466+
...rootProps,
467+
...(orientation === 'horizontal' ? {
468+
modelValue: modelValue as string,
469+
defaultValue: defaultValue as string
470+
} : {}),
471+
...$attrs
472+
}"
473+
:data-collapsed="collapsed"
474+
data-slot="root"
475+
:class="ui.root({ class: [props.ui?.root, props.class] })"
476+
>
419477
<slot name="list-leading" />
420478

421479
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
422480
<component
423481
v-bind="orientation === 'vertical' && !collapsed ? {
424482
...accordionProps,
425-
defaultValue: getAccordionDefaultValue(list)
483+
modelValue,
484+
defaultValue: defaultValue ?? getAccordionDefaultValue(list)
426485
} : {}"
427486
:is="orientation === 'vertical' && !collapsed ? AccordionRoot : NavigationMenuList"
428487
as="ul"

0 commit comments

Comments
 (0)