11<!-- eslint-disable vue/block-tag-newline -->
22<script lang="ts">
3- import type { NavigationMenuRootProps , NavigationMenuRootEmits , NavigationMenuContentProps , NavigationMenuContentEmits , CollapsibleRootProps } from ' reka-ui'
3+ import type { NavigationMenuRootProps , NavigationMenuRootEmits , NavigationMenuContentProps , NavigationMenuContentEmits , AccordionRootProps } from ' reka-ui'
44import type { AppConfig } from ' @nuxt/schema'
55import theme from ' #build/ui/navigation-menu'
66import type { AvatarProps , BadgeProps , LinkProps , TooltipProps } from ' ../types'
@@ -14,7 +14,7 @@ export interface NavigationMenuChildItem extends Omit<NavigationMenuItem, 'type'
1414 [key : string ]: any
1515}
1616
17- export interface NavigationMenuItem extends Omit <LinkProps , ' type' | ' raw' | ' custom' >, Pick < CollapsibleRootProps , ' defaultOpen ' | ' open ' > {
17+ export interface NavigationMenuItem extends Omit <LinkProps , ' type' | ' raw' | ' custom' > {
1818 label? : string
1919 /**
2020 * @IconifyIcon
@@ -49,13 +49,15 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
4949 */
5050 value? : string
5151 children? : NavigationMenuChildItem []
52+ defaultOpen? : boolean
53+ open? : boolean
5254 onSelect? (e : Event ): void
5355 class? : any
5456 ui? : Pick <NavigationMenu [' slots' ], ' item' | ' linkLeadingAvatarSize' | ' linkLeadingAvatar' | ' linkLeadingIcon' | ' linkLabel' | ' linkLabelExternalIcon' | ' linkTrailing' | ' linkTrailingBadgeSize' | ' linkTrailingBadge' | ' linkTrailingIcon' | ' label' | ' link' | ' content' | ' childList' | ' childItem' | ' childLink' | ' childLinkIcon' | ' childLinkWrapper' | ' childLinkLabel' | ' childLinkLabelExternalIcon' | ' childLinkDescription' >
5557 [key : string ]: any
5658}
5759
58- export interface NavigationMenuProps <T extends ArrayOrNested <NavigationMenuItem > = ArrayOrNested <NavigationMenuItem >> 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' >, Pick < AccordionRootProps , ' disabled ' | ' type ' | ' collapsible ' > {
5961 /**
6062 * The element or component this component should render as.
6163 * @defaultValue 'div'
@@ -143,8 +145,8 @@ export type NavigationMenuSlots<
143145
144146<script setup lang="ts" generic =" T extends ArrayOrNested <NavigationMenuItem >" >
145147import { computed , toRef } from ' vue'
146- import { NavigationMenuRoot , NavigationMenuList , NavigationMenuItem , NavigationMenuTrigger , NavigationMenuContent , NavigationMenuLink , NavigationMenuIndicator , NavigationMenuViewport , useForwardPropsEmits } from ' reka-ui'
147- import { createReusableTemplate } from ' @vueuse/core'
148+ import { NavigationMenuRoot , NavigationMenuList , NavigationMenuItem , NavigationMenuTrigger , NavigationMenuContent , NavigationMenuLink , NavigationMenuIndicator , NavigationMenuViewport , AccordionRoot , AccordionItem , AccordionTrigger , AccordionContent , useForwardPropsEmits } from ' reka-ui'
149+ import { reactivePick , createReusableTemplate } from ' @vueuse/core'
148150import { useAppConfig } from ' #imports'
149151import { get , isArrayOfArray } from ' ../utils'
150152import { tv } from ' ../utils/tv'
@@ -154,14 +156,15 @@ import ULink from './Link.vue'
154156import UAvatar from ' ./Avatar.vue'
155157import UIcon from ' ./Icon.vue'
156158import UBadge from ' ./Badge.vue'
157- import UCollapsible from ' ./Collapsible.vue'
158159import UTooltip from ' ./Tooltip.vue'
159160
160161const props = withDefaults (defineProps <NavigationMenuProps <T >>(), {
161162 orientation: ' horizontal' ,
162163 contentOrientation: ' horizontal' ,
163164 externalIcon: true ,
164165 delayDuration: 0 ,
166+ type: ' multiple' ,
167+ collapsible: true ,
165168 unmountOnHide: true ,
166169 labelKey: ' label'
167170})
@@ -182,6 +185,7 @@ const rootProps = useForwardPropsEmits(computed(() => ({
182185 disablePointerLeaveClose: props .disablePointerLeaveClose ,
183186 unmountOnHide: props .unmountOnHide
184187})), emits )
188+ const accordionProps = useForwardPropsEmits (reactivePick (props , ' collapsible' , ' disabled' , ' type' , ' unmountOnHide' ), emits )
185189const contentProps = toRef (() => props .content )
186190
187191const [DefineLinkTemplate, ReuseLinkTemplate] = createReusableTemplate <{ item: NavigationMenuItem , index: number , active? : boolean }>()
@@ -195,7 +199,7 @@ const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: N
195199
196200const ui = computed (() => tv ({ extend: tv (theme ), ... (appConfig .ui ?.navigationMenu || {}) })({
197201 orientation: props .orientation ,
198- contentOrientation: props .contentOrientation ,
202+ contentOrientation: props .orientation === ' vertical ' ? undefined : props . contentOrientation ,
199203 collapsed: props .collapsed ,
200204 color: props .color ,
201205 variant: props .variant ,
@@ -210,6 +214,24 @@ const lists = computed<NavigationMenuItem[][]>(() =>
210214 : [props .items ]
211215 : []
212216)
217+
218+ function getAccordionDefaultValue(list : NavigationMenuItem []) {
219+ function findItemsWithDefaultOpen(items : NavigationMenuItem [], level = 0 ): string [] {
220+ return items .reduce ((acc : string [], item , index ) => {
221+ if (item .defaultOpen || item .open ) {
222+ acc .push (item .value || (level > 0 ? ` item-${level }-${index } ` : ` item-${index } ` ))
223+ }
224+ if (item .children ?.length ) {
225+ acc .push (... findItemsWithDefaultOpen (item .children , level + 1 ))
226+ }
227+ return acc
228+ }, [])
229+ }
230+
231+ const indexes = findItemsWithDefaultOpen (list )
232+
233+ return props .type === ' single' ? indexes [0 ] : indexes
234+ }
213235 </script >
214236
215237<template >
@@ -231,7 +253,7 @@ const lists = computed<NavigationMenuItem[][]>(() =>
231253 <UIcon v-if =" item.target === '_blank' && externalIcon !== false" :name =" typeof externalIcon === 'string' ? externalIcon : appConfig.ui.icons.external" :class =" ui.linkLabelExternalIcon({ class: [props.ui?.linkLabelExternalIcon, item.ui?.linkLabelExternalIcon], active })" />
232254 </span >
233255
234- <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, item.ui?.linkTrailing] })" >
256+ <component :is = " orientation === 'vertical' && item.children?.length ? AccordionTrigger : ' 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>])" as = " span " :class =" ui.linkTrailing({ class: [props.ui?.linkTrailing, item.ui?.linkTrailing] })" @click.stop.prevent >
235257 <slot :name =" ((item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>)" :item =" item" :active =" active" :index =" index" >
236258 <UBadge
237259 v-if =" item.badge"
@@ -245,37 +267,34 @@ const lists = computed<NavigationMenuItem[][]>(() =>
245267 <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, item.ui?.linkTrailingIcon], active })" />
246268 <UIcon v-else-if =" item.trailingIcon" :name =" item.trailingIcon" :class =" ui.linkTrailingIcon({ class: [props.ui?.linkTrailingIcon, item.ui?.linkTrailingIcon], active })" />
247269 </slot >
248- </span >
270+ </component >
249271 </slot >
250272 </DefineLinkTemplate >
251273
252274 <DefineItemTemplate v-slot =" { item, index, level = 0 }" >
253275 <component
254- :is =" (orientation === 'vertical' && item.children?.length && !collapsed ) ? UCollapsible : NavigationMenuItem"
276+ :is =" (orientation === 'vertical' && item.children?.length) ? AccordionItem : NavigationMenuItem"
255277 as =" li"
256- :value =" item.value || `item-${index}`"
257- :default-open =" item.defaultOpen"
258- :unmount-on-hide =" (orientation === 'vertical' && item.children?.length && !collapsed) ? unmountOnHide : undefined"
259- :open =" item.open"
278+ :value =" item.value || (level > 0 ? `item-${level}-${index}` : `item-${index}`)"
260279 >
261280 <div v-if =" orientation === 'vertical' && item.type === 'label'" :class =" ui.label({ class: [props.ui?.label, item.ui?.label, item.class] })" >
262281 <ReuseLinkTemplate :item =" item" :index =" index" />
263282 </div >
264- <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 >
283+ <ULink v-else-if =" item.type !== 'label'" v-slot =" { active, ...slotProps }" v-bind =" pickLinkProps(item as Omit<NavigationMenuItem, 'type'>)" custom >
265284 <component
266- :is =" (orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) ? NavigationMenuTrigger : NavigationMenuLink"
285+ :is =" (orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) ? NavigationMenuTrigger : ((orientation === 'vertical' && item.children?.length && !(slotProps as any).href) ? AccordionTrigger : NavigationMenuLink) "
267286 as-child
268- :active =" active || item.active "
287+ :active =" active"
269288 :disabled =" item.disabled"
270289 @select =" item.onSelect"
271290 >
272291 <UTooltip v-if =" !!item.tooltip" :content =" { side: 'right' }" v-bind =" item.tooltip" >
273- <ULinkBase v-bind =" slotProps" :class =" ui.link({ class: [props.ui?.link, item.ui?.link, item.class], active: active || item.active , disabled: !!item.disabled, level: orientation === 'horizontal' || level > 0 })" >
274- <ReuseLinkTemplate :item =" item" :active =" active || item.active " :index =" index" />
292+ <ULinkBase v-bind =" slotProps" :class =" ui.link({ class: [props.ui?.link, item.ui?.link, item.class], active, disabled: !!item.disabled, level: orientation === 'horizontal' || level > 0 })" >
293+ <ReuseLinkTemplate :item =" item" :active =" active" :index =" index" />
275294 </ULinkBase >
276295 </UTooltip >
277- <ULinkBase v-else v-bind =" slotProps" :class =" ui.link({ class: [props.ui?.link, item.ui?.link, item.class], active: active || item.active , disabled: !!item.disabled, level: orientation === 'horizontal' || level > 0 })" >
278- <ReuseLinkTemplate :item =" item" :active =" active || item.active " :index =" index" />
296+ <ULinkBase v-else v-bind =" slotProps" :class =" ui.link({ class: [props.ui?.link, item.ui?.link, item.class], active, disabled: !!item.disabled, level: orientation === 'horizontal' || level > 0 })" >
297+ <ReuseLinkTemplate :item =" item" :active =" active" :index =" index" />
279298 </ULinkBase >
280299 </component >
281300
@@ -307,7 +326,7 @@ const lists = computed<NavigationMenuItem[][]>(() =>
307326 </NavigationMenuContent >
308327 </ULink >
309328
310- <template v-if =" orientation === ' vertical' && item .children ?.length && ! collapsed " # content >
329+ <AccordionContent v-if =" orientation === 'vertical' && item.children?.length && !collapsed" :class = " ui. content({ class: [props.ui?.content, item.ui?.content] }) " >
311330 <ul :class =" ui.childList({ class: props.ui?.childList })" >
312331 <ReuseItemTemplate
313332 v-for =" (childItem, childIndex) in item.children"
@@ -318,17 +337,25 @@ const lists = computed<NavigationMenuItem[][]>(() =>
318337 :class =" ui.childItem({ class: [props.ui?.childItem, childItem.ui?.childItem] })"
319338 />
320339 </ul >
321- </template >
340+ </AccordionContent >
322341 </component >
323342 </DefineItemTemplate >
324343
325344 <NavigationMenuRoot v-bind =" rootProps" :data-collapsed =" collapsed" :class =" ui.root({ class: [props.ui?.root, props.class] })" >
326345 <slot name =" list-leading" />
327346
328347 <template v-for =" (list , listIndex ) in lists " :key =" ` list-${listIndex } ` " >
329- <NavigationMenuList :class =" ui.list({ class: props.ui?.list })" >
348+ <component
349+ v-bind =" orientation === 'vertical' ? {
350+ ...accordionProps,
351+ defaultValue: getAccordionDefaultValue(list)
352+ } : {}"
353+ :is =" orientation === 'vertical' ? AccordionRoot : NavigationMenuList"
354+ as =" ul"
355+ :class =" ui.list({ class: props.ui?.list })"
356+ >
330357 <ReuseItemTemplate v-for =" (item, index) in list" :key =" `list-${listIndex}-${index}`" :item =" item" :index =" index" :class =" ui.item({ class: [props.ui?.item, item.ui?.item] })" />
331- </NavigationMenuList >
358+ </component >
332359
333360 <div v-if =" orientation === 'vertical' && listIndex < lists.length - 1" :class =" ui.separator({ class: props.ui?.separator })" />
334361 </template >
0 commit comments