fix(NavigationMenu): handle disabled through variants + isolate list + use separator instead of divide

This commit is contained in:
Benjamin Canac
2024-04-15 15:21:25 +02:00
parent cd214f91db
commit f664f69097
3 changed files with 77 additions and 62 deletions

View File

@@ -4,7 +4,7 @@ import type { NavigationMenuRootProps, NavigationMenuRootEmits } from 'radix-vue
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/ui/navigation-menu'
import type { AvatarProps, BadgeProps, IconProps, LinkProps } from '#ui/types'
import type { AvatarProps, BadgeProps, IconProps, LinkProps, SeparatorProps } from '#ui/types'
const appConfig = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } }
@@ -21,6 +21,7 @@ export interface NavigationMenuLink extends LinkProps {
export interface NavigationMenuProps<T> extends Omit<NavigationMenuRootProps, 'asChild' | 'dir'> {
links?: T[] | T[][]
separator?: SeparatorProps
class?: any
ui?: Partial<typeof navigationMenu.slots>
}
@@ -56,38 +57,42 @@ const lists = computed(() => props.links?.length ? (Array.isArray(props.links[0]
<template>
<NavigationMenuRoot v-bind="rootProps" :class="ui.root({ class: props.class })">
<NavigationMenuList v-for="(list, index) in lists" :key="`list-${index}`" :class="ui.list()">
<NavigationMenuItem v-for="(link, linkIndex) in list" :key="`list-${index}-${linkIndex}`" :value="link.value || String(index)" :class="ui.item()">
<ULink v-slot="{ active, ...slotProps }" v-bind="omit(link, ['label', 'icon', 'avatar', 'badge', 'select'])" custom>
<NavigationMenuLink as-child :active="active" @select="link.select">
<ULinkBase v-bind="slotProps" :class="ui.link({ active })">
<slot name="leading" :link="link" :active="active">
<UAvatar v-if="link.avatar" size="2xs" v-bind="link.avatar" :class="ui.linkLeadingAvatar({ active })" />
<UIcon v-else-if="link.icon" :name="link.icon" :class="ui.linkLeadingIcon({ active })" />
</slot>
<span v-if="link.label || $slots.default" :class="ui.linkLabel()">
<slot :link="link" :active="active">
{{ link.label }}
<template v-for="(list, index) in lists" :key="`list-${index}`">
<NavigationMenuList :class="ui.list()">
<NavigationMenuItem v-for="(link, linkIndex) in list" :key="`list-${index}-${linkIndex}`" :value="link.value || String(index)" :class="ui.item()">
<ULink v-slot="{ active, ...slotProps }" v-bind="omit(link, ['label', 'icon', 'avatar', 'badge', 'select'])" custom>
<NavigationMenuLink as-child :active="active" @select="link.select">
<ULinkBase v-bind="slotProps" :class="ui.link({ active, disabled: link.disabled })">
<slot name="leading" :link="link" :active="active">
<UAvatar v-if="link.avatar" size="2xs" v-bind="link.avatar" :class="ui.linkLeadingAvatar({ active })" />
<UIcon v-else-if="link.icon" :name="link.icon" :class="ui.linkLeadingIcon({ active })" />
</slot>
</span>
<span v-if="$slots.trailing || link.badge" :class="ui.linkTrailing()">
<slot name="trailing" :link="link" :active="active">
<UBadge
v-if="link.badge"
color="gray"
variant="solid"
size="xs"
v-bind="(typeof link.badge === 'string' || typeof link.badge === 'number') ? { label: link.badge } : link.badge"
:class="ui.linkTrailingBadge()"
/>
</slot>
</span>
</ULinkBase>
</NavigationMenuLink>
</ULink>
</NavigationMenuItem>
</NavigationMenuList>
<span v-if="link.label || $slots.default" :class="ui.linkLabel()">
<slot :link="link" :active="active">
{{ link.label }}
</slot>
</span>
<span v-if="$slots.trailing || link.badge" :class="ui.linkTrailing()">
<slot name="trailing" :link="link" :active="active">
<UBadge
v-if="link.badge"
color="gray"
variant="solid"
size="xs"
v-bind="(typeof link.badge === 'string' || typeof link.badge === 'number') ? { label: link.badge } : link.badge"
:class="ui.linkTrailingBadge()"
/>
</slot>
</span>
</ULinkBase>
</NavigationMenuLink>
</ULink>
</NavigationMenuItem>
</NavigationMenuList>
<USeparator v-if="orientation === 'vertical' && index < lists.length - 1" v-bind="separator" orientation="horizontal" :class="ui.separator()" />
</template>
</NavigationMenuRoot>
</template>

View File

@@ -1,25 +1,26 @@
export default {
slots: {
root: 'relative',
list: '',
root: 'relative flex gap-1.5',
list: 'isolate',
item: '',
link: 'group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-primary-500 dark:focus-visible:before:ring-primary-400 disabled:cursor-not-allowed disabled:opacity-75',
link: 'group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-primary-500 dark:focus-visible:before:ring-primary-400',
linkLeadingIcon: 'shrink-0 size-5',
linkLeadingAvatar: 'shrink-0',
linkLabel: 'truncate',
linkTrailing: 'ms-auto',
linkTrailingBadge: 'shrink-0 rounded'
linkTrailingBadge: 'shrink-0 rounded',
separator: 'px-2'
},
variants: {
orientation: {
horizontal: {
root: 'w-full flex items-center justify-between',
root: 'w-full items-center justify-between',
list: 'flex items-center min-w-0',
item: 'min-w-0',
link: 'px-2.5 py-3.5 before:inset-x-0 before:inset-y-2 hover:before:bg-gray-50 dark:hover:before:bg-gray-800/50 after:absolute after:bottom-0 after:inset-x-2.5 after:block after:h-[2px] after:mt-2 after:rounded-full'
},
vertical: {
root: 'flex flex-col *:py-1.5 first:*:pt-0 last:*:pb-0 divide-y divide-gray-200 dark:divide-gray-800',
root: 'flex-col',
link: 'px-2.5 py-1.5 before:inset-px'
}
},
@@ -32,6 +33,11 @@ export default {
link: 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white',
linkLeadingIcon: 'text-gray-400 dark:text-gray-500 group-hover:text-gray-700 dark:group-hover:text-gray-200'
}
},
disabled: {
true: {
link: 'cursor-not-allowed opacity-75'
}
}
},
compoundVariants: [{