Skip to content

Commit

Permalink
feat(NavigationMenu): add contentOrientation prop
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamincanac committed Jan 24, 2025
1 parent 8f7f579 commit ac86ee0
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 19 deletions.
79 changes: 79 additions & 0 deletions docs/content/3.components/navigation-menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,85 @@ props:
The arrow is animated to follow the active item.
::

### Content Orientation

Use the `content-orientation` prop to change the orientation of the content.

::warning
This prop only works when `orientation` is `horizontal`.
::

::component-code
---
collapse: true
ignore:
- items
- arrow
- class
external:
- items
props:
arrow: true
contentOrientation: 'vertical'
items:
- label: Guide
icon: i-lucide-book-open
to: /getting-started
children:
- label: Introduction
description: Fully styled and customizable components for Nuxt.
icon: i-lucide-house
- label: Installation
description: Learn how to install and configure Nuxt UI in your application.
icon: i-lucide-cloud-download
- label: 'Icons'
icon: 'i-lucide-smile'
description: 'You have nothing to do, @nuxt/icon will handle it automatically.'
- label: Composables
icon: i-lucide-database
to: /composables
children:
- label: defineShortcuts
icon: i-lucide-file-text
description: Define shortcuts for your application.
to: /composables/define-shortcuts
- label: useModal
icon: i-lucide-file-text
description: Display a modal within your application.
to: /composables/use-modal
- label: useSlideover
icon: i-lucide-file-text
description: Display a slideover within your application.
to: /composables/use-slideover
- label: useToast
icon: i-lucide-file-text
description: Display a toast within your application.
to: /composables/use-toast
- label: Components
icon: i-lucide-box
to: /components
active: true
children:
- label: Link
icon: i-lucide-file-text
description: Use NuxtLink with superpowers.
to: /components/link
- label: Modal
icon: i-lucide-file-text
description: Display a modal within your application.
to: /components/modal
- label: NavigationMenu
icon: i-lucide-file-text
description: Display a list of links.
to: /components/navigation-menu
- label: Pagination
icon: i-lucide-file-text
description: Display a list of pages.
to: /components/pagination
class: 'w-full justify-center'
---
::

### Unmount

Use the `unmount-on-hide` prop to control the content unmounting behavior. Defaults to `true`.
Expand Down
6 changes: 5 additions & 1 deletion playground/app/pages/components/navigation-menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import theme from '#build/ui/navigation-menu'
const colors = Object.keys(theme.variants.color)
const variants = Object.keys(theme.variants.variant)
const orientations = Object.keys(theme.variants.orientation)
const contentOrientations = Object.keys(theme.variants.contentOrientation)
const color = ref(theme.defaultVariants.color)
const highlightColor = ref()
const variant = ref(theme.defaultVariants.variant)
const orientation = ref('vertical' as const)
const orientation = ref('horizontal' as const)
const contentOrientation = ref('horizontal' as const)
const highlight = ref(true)
const collapsed = ref(false)
Expand Down Expand Up @@ -93,6 +95,7 @@ const items = [
<USelect v-model="color" :items="colors" placeholder="Color" />
<USelect v-model="variant" :items="variants" placeholder="Variant" />
<USelect v-model="orientation" :items="orientations" placeholder="Orientation" />
<USelect v-model="contentOrientation" :items="contentOrientations" placeholder="Content orientation" />
<USwitch v-model="collapsed" label="Collapsed" />
<USwitch v-model="highlight" label="Highlight" />
<USelect v-model="highlightColor" :items="colors" placeholder="Highlight color" />
Expand All @@ -105,6 +108,7 @@ const items = [
:color="color"
:variant="variant"
:orientation="orientation"
:viewport-orientation="contentOrientation"
:highlight="highlight"
:highlight-color="highlightColor"
:class="highlight && 'data-[orientation=horizontal]:border-b border-[var(--ui-border)]'"
Expand Down
8 changes: 8 additions & 0 deletions src/runtime/components/NavigationMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'm
highlightColor?: NavigationMenuVariants['highlightColor']
/** The content of the menu. */
content?: Omit<NavigationMenuContentProps, 'as' | 'asChild' | 'forceMount'>
/**
* The orientation of the content.
* Only works when `orientation` is `horizontal`.
* @defaultValue 'horizontal'
*/
contentOrientation?: NavigationMenuVariants['contentOrientation']
/**
* Display an arrow alongside the menu.
* @defaultValue false
Expand Down Expand Up @@ -153,6 +159,7 @@ import UCollapsible from './Collapsible.vue'
const props = withDefaults(defineProps<NavigationMenuProps<I>>(), {
orientation: 'horizontal',
contentOrientation: 'horizontal',
delayDuration: 0,
unmountOnHide: true,
labelKey: 'label'
Expand Down Expand Up @@ -180,6 +187,7 @@ const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: N
const ui = computed(() => navigationMenu({
orientation: props.orientation,
contentOrientation: props.contentOrientation,
collapsed: props.collapsed,
color: props.color,
variant: props.variant,
Expand Down
32 changes: 28 additions & 4 deletions src/theme/navigation-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export default (options: Required<ModuleOptions>) => ({
childLinkLabelExternalIcon: 'inline-block size-3 align-top text-[var(--ui-text-dimmed)]',
childLinkDescription: 'text-sm text-[var(--ui-text-muted)]',
separator: 'px-2 h-px bg-[var(--ui-border)]',
viewportWrapper: 'absolute top-full left-0 flex w-full justify-center',
viewport: 'relative overflow-hidden bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] h-[var(--reka-navigation-menu-viewport-height)] w-full transition-[width,height] origin-[top_center] data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
content: 'absolute top-0 left-0 w-full data-[motion=from-start]:animate-[enter-from-left_200ms_ease] data-[motion=from-end]:animate-[enter-from-right_200ms_ease] data-[motion=to-start]:animate-[exit-to-left_200ms_ease] data-[motion=to-end]:animate-[exit-to-right_200ms_ease]',
viewportWrapper: 'absolute top-full left-0 flex w-full',
viewport: 'relative overflow-hidden bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] h-[var(--reka-navigation-menu-viewport-height)] w-full transition-[width,height,left] duration-200 origin-[top_center] data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
content: 'absolute top-0 left-0 w-full',
indicator: 'absolute data-[state=visible]:animate-[fade-in_100ms_ease-out] data-[state=hidden]:animate-[fade-out_100ms_ease-in] data-[state=hidden]:opacity-0 bottom-0 z-[1] w-[var(--reka-navigation-menu-indicator-size)] translate-x-[var(--reka-navigation-menu-indicator-position)] flex h-2.5 items-end justify-center overflow-hidden transition-[translate,width] duration-200',
arrow: 'relative top-[50%] size-2.5 rotate-45 border border-[var(--ui-border)] bg-[var(--ui-bg)] z-[1] rounded-[calc(var(--ui-radius)/2)]'
},
Expand Down Expand Up @@ -56,13 +56,24 @@ export default (options: Required<ModuleOptions>) => ({
list: 'flex items-center',
item: 'py-2',
link: 'px-2.5 py-1.5 before:inset-x-px before:inset-y-0',
childList: 'grid grid-cols-2 gap-2 p-2'
childList: 'grid p-2'
},
vertical: {
root: 'flex-col',
link: 'flex-row px-2.5 py-1.5 before:inset-y-px before:inset-x-0'
}
},
contentOrientation: {
horizontal: {
viewport: '',
viewportWrapper: 'justify-center',
content: 'data-[motion=from-start]:animate-[enter-from-left_200ms_ease] data-[motion=from-end]:animate-[enter-from-right_200ms_ease] data-[motion=to-start]:animate-[exit-to-left_200ms_ease] data-[motion=to-end]:animate-[exit-to-right_200ms_ease]'
},
vertical: {
viewport: 'sm:w-[var(--reka-navigation-menu-viewport-width)] left-[var(--reka-navigation-menu-viewport-left)]',
content: ''
}
},
active: {
true: {
childLink: 'bg-[var(--ui-bg-elevated)] text-[var(--ui-text-highlighted)]',
Expand Down Expand Up @@ -91,6 +102,19 @@ export default (options: Required<ModuleOptions>) => ({
}
},
compoundVariants: [{
orientation: 'horizontal',
contentOrientation: 'horizontal',
class: {
childList: 'grid-cols-2 gap-2'
}
}, {
orientation: 'horizontal',
contentOrientation: 'vertical',
class: {
childList: 'gap-1',
content: 'w-60'
}
}, {
orientation: 'horizontal',
highlight: true,
class: {
Expand Down
6 changes: 4 additions & 2 deletions test/components/NavigationMenu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,11 @@ describe('NavigationMenu', () => {
it.each([
// Props
['with items', { props }],
['with modelValue', { props: { ...props, modelValue: '0' } }],
['with labelKey', { props: { ...props, labelKey: 'icon' } }],
['with arrow', { props: { ...props, arrow: true } }],
['with orientation vertical', { props: { ...props, orientation: 'vertical' as const } }],
['with arrow', { props: { ...props, arrow: true, modelValue: '0' } }],
['with orientation vertical', { props: { ...props, orientation: 'vertical' as const, modelValue: '0' } }],
['with content orientation vertical', { props: { ...props, contentOrientation: 'vertical' as const, modelValue: '0' } }],
...variants.map((variant: string) => [`with primary variant ${variant}`, { props: { ...props, variant } }]),
...variants.map((variant: string) => [`with neutral variant ${variant}`, { props: { ...props, variant, color: 'neutral' } }]),
...variants.map((variant: string) => [`with primary variant ${variant} highlight`, { props: { ...props, variant, highlight: true } }]),
Expand Down
Loading

0 comments on commit ac86ee0

Please sign in to comment.