mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
@@ -3,7 +3,7 @@
|
||||
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, NavigationMenuContentEmits, AccordionRootProps } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import theme from '#build/ui/navigation-menu'
|
||||
import type { AvatarProps, BadgeProps, LinkProps, TooltipProps } from '../types'
|
||||
import type { AvatarProps, BadgeProps, LinkProps, PopoverProps, TooltipProps } from '../types'
|
||||
import type { ArrayOrNested, DynamicSlots, MergeTypes, NestedItem, EmitsToProps, ComponentConfig } from '../types/utils'
|
||||
|
||||
type NavigationMenu = ComponentConfig<typeof theme, AppConfig, 'navigationMenu'>
|
||||
@@ -27,11 +27,15 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
|
||||
*/
|
||||
badge?: string | number | BadgeProps
|
||||
/**
|
||||
* Display a tooltip on the item.
|
||||
* Only works when `type` is `link`.
|
||||
* `{ content: { side: 'right' } }`{lang="ts-type"}
|
||||
* Display a tooltip on the item when the menu is collapsed with the label of the item.
|
||||
* This has priority over the global `tooltip` prop.
|
||||
*/
|
||||
tooltip?: TooltipProps
|
||||
tooltip?: boolean | TooltipProps
|
||||
/**
|
||||
* Display a popover on the item when the menu is collapsed with the children list.
|
||||
* This has priority over the global `popover` prop.
|
||||
*/
|
||||
popover?: boolean | PopoverProps
|
||||
/**
|
||||
* @IconifyIcon
|
||||
*/
|
||||
@@ -53,7 +57,7 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
|
||||
open?: boolean
|
||||
onSelect?(e: Event): void
|
||||
class?: any
|
||||
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'>
|
||||
ui?: Pick<NavigationMenu['slots'], 'item' | 'linkLeadingAvatarSize' | 'linkLeadingAvatar' | 'linkLeadingIcon' | 'linkLabel' | 'linkLabelExternalIcon' | 'linkTrailing' | 'linkTrailingBadgeSize' | 'linkTrailingBadge' | 'linkTrailingIcon' | 'label' | 'link' | 'content' | 'childList' | 'childLabel' | 'childItem' | 'childLink' | 'childLinkIcon' | 'childLinkWrapper' | 'childLinkLabel' | 'childLinkLabelExternalIcon' | 'childLinkDescription'>
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
@@ -96,6 +100,18 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
|
||||
* @defaultValue false
|
||||
*/
|
||||
collapsed?: boolean
|
||||
/**
|
||||
* Display a tooltip on the items when the menu is collapsed with the label of the item.
|
||||
* `{ delayDuration: 0, content: { side: 'right' } }`{lang="ts-type"}
|
||||
* @defaultValue false
|
||||
*/
|
||||
tooltip?: boolean | TooltipProps
|
||||
/**
|
||||
* Display a popover on the items when the menu is collapsed with the children list.
|
||||
* `{ mode: 'hover', content: { side: 'right', align: 'start', alignOffset: 2 } }`{lang="ts-type"}
|
||||
* @defaultValue false
|
||||
*/
|
||||
popover?: boolean | PopoverProps
|
||||
/** Display a line next to the active item. */
|
||||
highlight?: boolean
|
||||
/**
|
||||
@@ -146,6 +162,7 @@ export type NavigationMenuSlots<
|
||||
<script setup lang="ts" generic="T extends ArrayOrNested<NavigationMenuItem>">
|
||||
import { computed, toRef } from 'vue'
|
||||
import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, AccordionRoot, AccordionItem, AccordionTrigger, AccordionContent, useForwardPropsEmits } from 'reka-ui'
|
||||
import { defu } from 'defu'
|
||||
import { reactivePick, createReusableTemplate } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { get, isArrayOfArray } from '../utils'
|
||||
@@ -156,6 +173,7 @@ import ULink from './Link.vue'
|
||||
import UAvatar from './Avatar.vue'
|
||||
import UIcon from './Icon.vue'
|
||||
import UBadge from './Badge.vue'
|
||||
import UPopover from './Popover.vue'
|
||||
import UTooltip from './Tooltip.vue'
|
||||
|
||||
const props = withDefaults(defineProps<NavigationMenuProps<T>>(), {
|
||||
@@ -187,6 +205,8 @@ const rootProps = useForwardPropsEmits(computed(() => ({
|
||||
})), emits)
|
||||
const accordionProps = useForwardPropsEmits(reactivePick(props, 'collapsible', 'disabled', 'type', 'unmountOnHide'), emits)
|
||||
const contentProps = toRef(() => props.content)
|
||||
const tooltipProps = toRef(() => defu(typeof props.tooltip === 'boolean' ? {} : props.tooltip, { delayDuration: 0, content: { side: 'right' } }) as TooltipProps)
|
||||
const popoverProps = toRef(() => defu(typeof props.popover === 'boolean' ? {} : props.popover, { mode: 'hover', content: { side: 'right', align: 'start', alignOffset: 2 } }) as PopoverProps)
|
||||
|
||||
const [DefineLinkTemplate, ReuseLinkTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number, active?: boolean }>()
|
||||
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number, level?: number }>({
|
||||
@@ -253,7 +273,7 @@ function getAccordionDefaultValue(list: NavigationMenuItem[]) {
|
||||
<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 })" />
|
||||
</span>
|
||||
|
||||
<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>
|
||||
<component :is="orientation === 'vertical' && item.children?.length && !collapsed ? 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>
|
||||
<slot :name="((item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
|
||||
<UBadge
|
||||
v-if="item.badge"
|
||||
@@ -273,22 +293,52 @@ function getAccordionDefaultValue(list: NavigationMenuItem[]) {
|
||||
|
||||
<DefineItemTemplate v-slot="{ item, index, level = 0 }">
|
||||
<component
|
||||
:is="(orientation === 'vertical' && item.children?.length) ? AccordionItem : NavigationMenuItem"
|
||||
:is="(orientation === 'vertical' && !collapsed) ? AccordionItem : NavigationMenuItem"
|
||||
as="li"
|
||||
:value="item.value || (level > 0 ? `item-${level}-${index}` : `item-${index}`)"
|
||||
>
|
||||
<div v-if="orientation === 'vertical' && item.type === 'label'" :class="ui.label({ class: [props.ui?.label, item.ui?.label, item.class] })">
|
||||
<div v-if="orientation === 'vertical' && item.type === 'label' && !collapsed" :class="ui.label({ class: [props.ui?.label, item.ui?.label, item.class] })">
|
||||
<ReuseLinkTemplate :item="item" :index="index" />
|
||||
</div>
|
||||
<ULink v-else-if="item.type !== 'label'" v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item as Omit<NavigationMenuItem, 'type'>)" custom>
|
||||
<component
|
||||
: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)"
|
||||
:is="(orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) ? NavigationMenuTrigger : ((orientation === 'vertical' && item.children?.length && !collapsed && !(slotProps as any).href) ? AccordionTrigger : NavigationMenuLink)"
|
||||
as-child
|
||||
:active="active"
|
||||
:disabled="item.disabled"
|
||||
@select="item.onSelect"
|
||||
>
|
||||
<UTooltip v-if="!!item.tooltip && orientation === 'vertical' && collapsed" :content="{ side: 'right' }" v-bind="item.tooltip">
|
||||
<UPopover v-if="orientation === 'vertical' && collapsed && item.children?.length && (!!props.popover || !!item.popover)" v-bind="{ ...popoverProps, ...(typeof item.popover === 'boolean' ? {} : item.popover || {}) }" :ui="{ content: ui.content({ class: [props.ui?.content, item.ui?.content] }) }">
|
||||
<ULinkBase v-bind="slotProps" :class="ui.link({ class: [props.ui?.link, item.ui?.link, item.class], active, disabled: !!item.disabled, level: level > 0 })">
|
||||
<ReuseLinkTemplate :item="item" :active="active" :index="index" />
|
||||
</ULinkBase>
|
||||
|
||||
<template #content>
|
||||
<slot :name="((item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
|
||||
<ul :class="ui.childList({ class: [props.ui?.childList, item.ui?.childList] })">
|
||||
<li :class="ui.childLabel({ class: [props.ui?.childLabel, item.ui?.childLabel] })">
|
||||
{{ get(item, props.labelKey as string) }}
|
||||
</li>
|
||||
<li v-for="(childItem, childIndex) in item.children" :key="childIndex" :class="ui.childItem({ class: [props.ui?.childItem, item.ui?.childItem] })">
|
||||
<ULink v-slot="{ active: childActive, ...childSlotProps }" v-bind="pickLinkProps(childItem)" custom>
|
||||
<NavigationMenuLink as-child :active="childActive" @select="childItem.onSelect">
|
||||
<ULinkBase v-bind="childSlotProps" :class="ui.childLink({ class: [props.ui?.childLink, item.ui?.childLink, childItem.class], active: childActive })">
|
||||
<UIcon v-if="childItem.icon" :name="childItem.icon" :class="ui.childLinkIcon({ class: [props.ui?.childLinkIcon, item.ui?.childLinkIcon], active: childActive })" />
|
||||
|
||||
<span :class="ui.childLinkLabel({ class: [props.ui?.childLinkLabel, item.ui?.childLinkLabel], active: childActive })">
|
||||
{{ get(childItem, props.labelKey as string) }}
|
||||
|
||||
<UIcon v-if="childItem.target === '_blank' && externalIcon !== false" :name="typeof externalIcon === 'string' ? externalIcon : appConfig.ui.icons.external" :class="ui.childLinkLabelExternalIcon({ class: [props.ui?.childLinkLabelExternalIcon, item.ui?.childLinkLabelExternalIcon], active: childActive })" />
|
||||
</span>
|
||||
</ULinkBase>
|
||||
</NavigationMenuLink>
|
||||
</ULink>
|
||||
</li>
|
||||
</ul>
|
||||
</slot>
|
||||
</template>
|
||||
</UPopover>
|
||||
<UTooltip v-else-if="orientation === 'vertical' && collapsed && (!!props.tooltip || !!item.tooltip)" :text="get(item, props.labelKey as string)" v-bind="{ ...tooltipProps, ...(typeof item.tooltip === 'boolean' ? {} : item.tooltip || {}) }">
|
||||
<ULinkBase v-bind="slotProps" :class="ui.link({ class: [props.ui?.link, item.ui?.link, item.class], active, disabled: !!item.disabled, level: level > 0 })">
|
||||
<ReuseLinkTemplate :item="item" :active="active" :index="index" />
|
||||
</ULinkBase>
|
||||
@@ -346,11 +396,11 @@ function getAccordionDefaultValue(list: NavigationMenuItem[]) {
|
||||
|
||||
<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
|
||||
<component
|
||||
v-bind="orientation === 'vertical' ? {
|
||||
v-bind="orientation === 'vertical' && !collapsed ? {
|
||||
...accordionProps,
|
||||
defaultValue: getAccordionDefaultValue(list)
|
||||
} : {}"
|
||||
:is="orientation === 'vertical' ? AccordionRoot : NavigationMenuList"
|
||||
:is="orientation === 'vertical' && !collapsed ? AccordionRoot : NavigationMenuList"
|
||||
as="ul"
|
||||
:class="ui.list({ class: props.ui?.list })"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user