[Design System] chore: add scroll example of Navigation Menu (#26974)

* init

* Update navigation-menu.mdx

* Update navigation-menu.tsx
This commit is contained in:
Jonathan Summers-Muir authored and Chris Gwilliams committed 2024-06-04 17:55:07 +03:00
1 parent cf86672529
commit 19a898b7c5
8 files changed
+317 -76

No files matched your search

+11
View File
@@ -764,6 +764,17 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"navigation-menu-responsive": {
name: "navigation-menu-responsive",
type: "components:example",
registryDependencies: ["navigation-menu"],
component: React.lazy(() => import("@/registry/default/example/navigation-menu-responsive")),
source: "",
files: ["registry/default/example/navigation-menu-responsive.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"popover-demo": {
name: "popover-demo",
type: "components:example",
@@ -127,7 +127,7 @@ export function ComponentPreview({
<div className={cn('mt-4 mb-12', wideClasses)}>
<div
className={cn(
'relative rounded-tl-md rounded-tr-md border-t border-l border-r bg-studio overflow-hidden'
'relative rounded-tl-md rounded-tr-md border-t border-l border-r bg-studio'
)}
>
{showGrid && (
@@ -170,9 +170,7 @@ export function ComponentPreview({
return (
<div className={cn('mt-4 mb-12', wideClasses)}>
<div
className={cn(
'relative rounded-tl-md rounded-tr-md border-t border-l border-r bg-studio overflow-hidden'
)}
className={cn('relative rounded-tl-md rounded-tr-md border-t border-l border-r bg-studio')}
>
{showGrid && (
<div className="pointer-events-none absolute h-full w-full bg-[linear-gradient(to_right,hsla(var(--foreground-default)/0.02)_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]"></div>
@@ -97,3 +97,34 @@ import { navigationMenuTriggerStyle } from '@/components/ui/navigation-menu'
```
See also the [Radix UI documentation](https://www.radix-ui.com/docs/primitives/components/navigation-menu#with-client-side-routing) for handling client side routing.
### With horizontal scroll
We can wrap a the `<NavigationMenuList />` in a `<ScrollArea />` component to make the menu scroll.
However, then we must also make sure the `<NavigationMenuContent />` is positioned correctly and rendered outside the scroll area.
We set `renderViewport={false}` on the `<NavigationMenu />` component to prevent the viewport from being rendered.
And then add our own Viewport with `<NavigationMenuViewport />`.
Then all our content panels will render themselves outside the scroll area and inside the viewport.
You can also add props to `<NavigationMenuViewport />` to style it as you like, including the `containerProps` prop to add classes to the container div element inside the viewport.
```tsx {1, 2, 11-13}
<NavigationMenu renderViewport={false}>
<ScrollArea>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Item One</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuLink>Link</NavigationMenuLink>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
<ScrollBar />
</ScrollArea>
<NavigationMenuViewport containerProps={{ className: 'w-full' }} />
</NavigationMenu>
```
<ComponentPreview name="navigation-menu-responsive" />
@@ -3,8 +3,7 @@
import * as React from 'react'
import Link from 'next/link'
import { cn } from 'ui'
// import { Icons } from '@/components/icons'
import { buttonVariants, cn } from 'ui'
import {
NavigationMenu,
NavigationMenuContent,
@@ -54,60 +53,66 @@ const components: { title: string; href: string; description: string }[] = [
export default function NavigationMenuDemo() {
return (
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid gap-3 p-6 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
<li className="row-span-3">
<NavigationMenuLink asChild>
<a
className="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b from-muted/50 to-muted p-6 no-underline outline-none focus:shadow-md"
href="/"
>
{/* <Icons.logo className="h-6 w-6" /> */}
<div className="mb-2 mt-4 text-lg font-medium">shadcn/ui</div>
<p className="text-sm leading-tight text-muted-foreground">
Beautifully designed components that you can copy and paste into your apps.
Accessible. Customizable. Open Source.
</p>
</a>
</NavigationMenuLink>
</li>
<ListItem href="/docs" title="Introduction">
Re-usable components built using Radix UI and Tailwind CSS.
</ListItem>
<ListItem href="/docs/installation" title="Installation">
How to install dependencies and structure your app.
</ListItem>
<ListItem href="/docs/primitives/typography" title="Typography">
Styles for headings, paragraphs, lists...etc
</ListItem>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{components.map((component) => (
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
<div>
<NavigationMenu className="w-fit">
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger className={cn(buttonVariants({ type: 'text', size: 'small' }))}>
Getting started
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid gap-3 p-6 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
<li className="row-span-3">
<NavigationMenuLink asChild>
<a
className="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b border from-background-surface-100/20 to-background-surface-100 p-6 no-underline outline-none focus:shadow-md"
href="/"
>
{/* <Icons.logo className="h-6 w-6" /> */}
<div className="mb-2 mt-4 text-lg font-medium">shadcn/ui</div>
<p className="text-sm leading-tight text-muted-foreground">
Beautifully designed components that you can copy and paste into your apps.
Accessible. Customizable. Open Source.
</p>
</a>
</NavigationMenuLink>
</li>
<ListItem href="/docs" title="Introduction">
Re-usable components built using Radix UI and Tailwind CSS.
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="/docs" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
Documentation
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
<ListItem href="/docs/installation" title="Installation">
How to install dependencies and structure your app.
</ListItem>
<ListItem href="/docs/primitives/typography" title="Typography">
Styles for headings, paragraphs, lists...etc
</ListItem>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger className={cn(buttonVariants({ type: 'text', size: 'small' }))}>
Components
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{components.map((component) => (
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="/docs" legacyBehavior passHref>
<NavigationMenuLink className={buttonVariants({ type: 'text', size: 'small' })}>
Documentation
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
)
}
@@ -119,13 +124,13 @@ const ListItem = React.forwardRef<React.ElementRef<'a'>, React.ComponentPropsWit
<a
ref={ref}
className={cn(
'block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
'block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-surface-100/50 hover:text-foreground focus:bg-surface-100/50 focus:text-foreground',
className
)}
{...props}
>
<div className="text-sm font-medium leading-none">{title}</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">{children}</p>
<div className="text-sm text-foreground leading-none">{title}</div>
<p className="line-clamp-2 text-sm leading-snug text-foreground-lighter">{children}</p>
</a>
</NavigationMenuLink>
</li>
@@ -0,0 +1,182 @@
'use client'
import * as React from 'react'
import Link from 'next/link'
import { NavigationMenuViewport, ScrollArea, ScrollBar, buttonVariants, cn } from 'ui'
// import { Icons } from '@/components/icons'
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from 'ui'
const components: { title: string; href: string; description: string }[] = [
{
title: 'Alert Dialog',
href: '/docs/primitives/alert-dialog',
description:
'A modal dialog that interrupts the user with important content and expects a response.',
},
{
title: 'Hover Card',
href: '/docs/primitives/hover-card',
description: 'For sighted users to preview content available behind a link.',
},
{
title: 'Progress',
href: '/docs/primitives/progress',
description:
'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.',
},
{
title: 'Scroll-area',
href: '/docs/primitives/scroll-area',
description: 'Visually or semantically separates content.',
},
{
title: 'Tabs',
href: '/docs/primitives/tabs',
description:
'A set of layered sections of content—known as tab panels—that are displayed one at a time.',
},
{
title: 'Tooltip',
href: '/docs/primitives/tooltip',
description:
'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
},
]
export default function NavigationMenuDemo() {
return (
<NavigationMenu renderViewport={false} className="max-w-[500px] border rounded-md">
<ScrollArea>
<NavigationMenuList className="w-full p-3">
<NavigationMenuItem>
<NavigationMenuContent>
<ul className="grid gap-3 p-6 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
<li className="row-span-3">
<NavigationMenuLink asChild>
<a
className="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b from-muted/50 to-muted p-6 no-underline outline-none focus:shadow-md"
href="/"
>
{/* <Icons.logo className="h-6 w-6" /> */}
shadcn/ui
<p className="text-sm leading-tight text-foreground-muted">
Beautifully designed components that you can copy and paste into your apps.
Accessible. Customizable. Open Source.
</p>
</a>
</NavigationMenuLink>
</li>
<ListItem href="/docs" title="Introduction">
Re-usable components built using Radix UI and Tailwind CSS.
</ListItem>
<ListItem href="/docs/installation" title="Installation">
How to install dependencies and structure your app.
</ListItem>
<ListItem href="/docs/primitives/typography" title="Typography">
Styles for headings, paragraphs, lists...etc
</ListItem>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger className={cn(buttonVariants({ type: 'text', size: 'small' }))}>
Components
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{components.map((component) => (
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger className={cn(buttonVariants({ type: 'text', size: 'small' }))}>
Components
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{components.map((component) => (
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger className={cn(buttonVariants({ type: 'text', size: 'small' }))}>
Components
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{components.map((component) => (
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger className={cn(buttonVariants({ type: 'text', size: 'small' }))}>
Components
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{components.map((component) => (
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="/docs" legacyBehavior passHref>
<NavigationMenuLink className={buttonVariants({ type: 'text', size: 'small' })}>
Documentation
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
</NavigationMenuList>
<ScrollBar orientation="horizontal" />
</ScrollArea>
<NavigationMenuViewport containerProps={{ className: 'w-full' }} />
</NavigationMenu>
)
}
const ListItem = React.forwardRef<React.ElementRef<'a'>, React.ComponentPropsWithoutRef<'a'>>(
({ className, title, children, ...props }, ref) => {
return (
<li>
<NavigationMenuLink asChild>
<a
ref={ref}
className={cn(
'block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-surface-100 hover:text-accent-foreground focus:bg-surface-100 focus:text-accent-foreground',
className
)}
{...props}
>
<div className="text-sm font-medium leading-none">{title}</div>
<p className="line-clamp-2 text-sm leading-snug text-foreground-muted">{children}</p>
</a>
</NavigationMenuLink>
</li>
)
}
)
ListItem.displayName = 'ListItem'
+6
View File
@@ -491,6 +491,12 @@ export const examples: Registry = [
registryDependencies: ['navigation-menu'],
files: ['example/navigation-menu-demo.tsx'],
},
{
name: 'navigation-menu-responsive',
type: 'components:example',
registryDependencies: ['navigation-menu'],
files: ['example/navigation-menu-responsive.tsx'],
},
// {
// name: 'pagination-demo',
// type: 'components:example',
+8 -3
View File
@@ -5,7 +5,7 @@ import { useRouter } from 'next/router'
import { useTheme } from 'next-themes'
import { useWindowSize } from 'react-use'
import { Button, cn } from 'ui'
import { Button, buttonVariants, cn } from 'ui'
import {
NavigationMenu,
NavigationMenuContent,
@@ -136,11 +136,16 @@ const Nav = (props: Props) => {
{menu.primaryNav.map((menuItem) =>
menuItem.hasDropdown ? (
<NavigationMenuItem className="text-sm font-medium" key={menuItem.title}>
<NavigationMenuTrigger className="bg-transparent text-foreground hover:text-brand-link data-[state=open]:!text-brand-link data-[radix-collection-item]:focus-visible:ring-2 data-[radix-collection-item]:focus-visible:ring-foreground-lighter data-[radix-collection-item]:focus-visible:text-foreground p-2 h-auto">
<NavigationMenuTrigger
className={cn(
buttonVariants({ type: 'text', size: 'small' }),
'!bg-transparent hover:text-brand-link data-[state=open]:!text-brand-link data-[radix-collection-item]:focus-visible:ring-2 data-[radix-collection-item]:focus-visible:ring-foreground-lighter data-[radix-collection-item]:focus-visible:text-foreground px-2 h-auto'
)}
>
{menuItem.title}
</NavigationMenuTrigger>
<NavigationMenuContent
className={cn('rounded-xl', menuItem.dropdownContainerClassName)}
// className={cn('rounded-xl', menuItem.dropdownContainerClassName)}
>
{menuItem.dropdown}
</NavigationMenuContent>
@@ -4,23 +4,25 @@ import { ChevronDown } from 'lucide-react'
import * as React from 'react'
import { cn } from '../../../lib/utils/cn'
import { buttonVariants } from './../../Button/Button'
interface NavigationMenuProps
extends React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root> {
viewportClassName?: string | undefined
renderViewport?: boolean | undefined
}
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
NavigationMenuProps
>(({ className, viewportClassName, children, ...props }, ref) => (
>(({ className, viewportClassName, renderViewport = true, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn('relative z-10 flex flex-1 items-center justify-center', className)}
{...props}
>
{children}
<NavigationMenuViewport className={viewportClassName} />
{renderViewport && <NavigationMenuViewport className={viewportClassName} />}
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
@@ -47,11 +49,7 @@ const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), 'group', className)}
{...props}
>
<NavigationMenuPrimitive.Trigger ref={ref} className={cn('group', className)} {...props}>
{children}{' '}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
@@ -80,12 +78,17 @@ const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn('absolute left-0 top-full flex justify-center')}>
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport> & {
containerProps?: React.HTMLAttributes<HTMLDivElement>
}
>(({ className, containerProps, ...props }, ref) => (
<div
{...containerProps}
className={cn('absolute left-0 top-full flex justify-center', containerProps?.className)}
>
<NavigationMenuPrimitive.Viewport
className={cn(
'origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-overlay text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=open]:fade-in data-[state=open]:slide-in-from-right-10 data-[state=open]:duration-100 data-[state=open]:ease-out data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=closed]:duration-75 md:w-[var(--radix-navigation-menu-viewport-width)]',
'origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=open]:fade-in data-[state=open]:slide-in-from-right-10 data-[state=open]:duration-100 data-[state=open]:ease-out data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=closed]:duration-75 md:w-[var(--radix-navigation-menu-viewport-width)]',
className
)}
ref={ref}