mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
feat: add navigation-menu (#31)
* feat: navigation-menu * fix: export at the end * fix: rounded-md * fix: demo design * rename: NavigationMenuRoot -> NavigationMenu * feat: update roadmap * feat: update style for navigation menu * feat: implement navigation menu component * feat: implement navigation menu for www --------- Co-authored-by: shadcn <m@shadcn.com>
This commit is contained in:
committed by
GitHub
parent
8ccac51527
commit
a0b1f89455
@@ -13,7 +13,7 @@ Beautifully designed components built with Radix UI and Tailwind CSS.
|
||||
- [ ] Toggle
|
||||
- [ ] Toggle Group
|
||||
- [ ] Toolbar
|
||||
- [ ] Navigation Menu
|
||||
- [ ] Navigation Menu (Vertical and Submenus)
|
||||
- [ ] Figma?
|
||||
|
||||
## Get Started
|
||||
|
||||
@@ -3,8 +3,8 @@ import { AlertDialogDemo } from "@/components/examples/alert-dialog/demo"
|
||||
import { AspectRatioDemo } from "@/components/examples/aspect-ratio/demo"
|
||||
import { AvatarDemo } from "@/components/examples/avatar/demo"
|
||||
import { ButtonDemo } from "@/components/examples/button/demo"
|
||||
import { ButtonGhost } from "@/components/examples/button/ghost"
|
||||
import { ButtonDestructive } from "@/components/examples/button/destructive"
|
||||
import { ButtonGhost } from "@/components/examples/button/ghost"
|
||||
import { ButtonLink } from "@/components/examples/button/link"
|
||||
import { ButtonLoading } from "@/components/examples/button/loading"
|
||||
import { ButtonOutline } from "@/components/examples/button/outline"
|
||||
@@ -27,6 +27,7 @@ import { InputWithLabel } from "@/components/examples/input/with-label"
|
||||
import { InputWithText } from "@/components/examples/input/with-text"
|
||||
import { LabelDemo } from "@/components/examples/label/demo"
|
||||
import { MenubarDemo } from "@/components/examples/menubar/demo"
|
||||
import { NavigationMenuDemo } from "@/components/examples/navigation-menu/demo"
|
||||
import { PopoverDemo } from "@/components/examples/popover/demo"
|
||||
import { ProgressDemo } from "@/components/examples/progress/demo"
|
||||
import { RadioGroupDemo } from "@/components/examples/radio-group/demo"
|
||||
@@ -87,6 +88,7 @@ export const examples = {
|
||||
InputWithText,
|
||||
LabelDemo,
|
||||
MenubarDemo,
|
||||
NavigationMenuDemo,
|
||||
PopoverDemo,
|
||||
ProgressDemo,
|
||||
RadioGroupDemo,
|
||||
|
||||
145
apps/www/components/examples/navigation-menu/demo.tsx
Normal file
145
apps/www/components/examples/navigation-menu/demo.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Icons } from "@/components/icons"
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
navigationMenuTriggerStyle,
|
||||
} from "@/components/ui/navigation-menu"
|
||||
|
||||
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 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-rose-500 to-indigo-700 p-6 no-underline outline-none focus:shadow-md"
|
||||
href="/"
|
||||
>
|
||||
<Icons.logo className="h-6 w-6 text-white" />
|
||||
<div className="mt-4 mb-2 text-lg font-medium text-white">
|
||||
shadcn/ui
|
||||
</div>
|
||||
<p className="text-sm leading-tight text-white/90">
|
||||
Beautifully designed components built with Radix UI and
|
||||
Tailwind CSS.
|
||||
</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-[600px] gap-3 p-4 md:grid-cols-2">
|
||||
{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={navigationMenuTriggerStyle()}>
|
||||
Documentation
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</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-slate-100 focus:bg-slate-100 dark:hover:bg-slate-700 dark:focus:bg-slate-700",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">{title}</div>
|
||||
<p className="text-sm leading-snug text-slate-500 line-clamp-2 dark:text-slate-400">
|
||||
{children}
|
||||
</p>
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
ListItem.displayName = "ListItem"
|
||||
@@ -2,111 +2,131 @@
|
||||
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
import { useSelectedLayoutSegment } from "next/navigation"
|
||||
import { MainNavItem } from "types/nav"
|
||||
import { allDocs } from "contentlayer/generated"
|
||||
|
||||
import { docsConfig } from "@/config/docs"
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
|
||||
interface MainNavProps {
|
||||
items?: MainNavItem[]
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export function MainNav({ items, children }: MainNavProps) {
|
||||
const segment = useSelectedLayoutSegment()
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
navigationMenuTriggerStyle,
|
||||
} from "@/components/ui/navigation-menu"
|
||||
import { buttonVariants } from "./ui/button"
|
||||
import { Separator } from "./ui/separator"
|
||||
|
||||
export function MainNav() {
|
||||
return (
|
||||
<div className="flex gap-6 md:gap-10">
|
||||
<Link href="/" className="hidden items-center space-x-2 md:flex">
|
||||
<div className="hidden md:flex">
|
||||
<Link href="/" className="mr-6 flex items-center space-x-2">
|
||||
<Icons.logo className="h-6 w-6" />
|
||||
<span className="hidden font-bold sm:inline-block">
|
||||
{siteConfig.name}
|
||||
</span>
|
||||
</Link>
|
||||
{items?.length ? (
|
||||
<nav className="hidden gap-6 md:flex">
|
||||
{items?.map(
|
||||
(item, index) =>
|
||||
item.href && (
|
||||
<Link
|
||||
key={index}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"flex items-center text-lg font-semibold text-slate-600 hover:text-slate-900 dark:text-slate-100 sm:text-sm",
|
||||
item.href.startsWith(`/${segment}`) && "text-slate-900",
|
||||
item.disabled && "cursor-not-allowed opacity-80"
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
) : null}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="-ml-4 text-base hover:bg-transparent focus:ring-0 md:hidden"
|
||||
>
|
||||
<Icons.logo className="mr-2 h-4 w-4" />{" "}
|
||||
<span className="font-bold">Menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
sideOffset={24}
|
||||
className="w-[300px] overflow-scroll"
|
||||
>
|
||||
<DropdownMenuLabel>
|
||||
<Link href="/" className="flex items-center">
|
||||
<Icons.logo className="mr-2 h-4 w-4" /> {siteConfig.name}
|
||||
</Link>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<ScrollArea className="h-[400px]">
|
||||
{items?.map(
|
||||
(item, index) =>
|
||||
item.href && (
|
||||
<DropdownMenuItem key={index} asChild>
|
||||
<Link href={item.href}>{item.title}</Link>
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
)}
|
||||
{docsConfig.sidebarNav.map((item, index) => (
|
||||
<DropdownMenuGroup key={index}>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>{item.title}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{item?.items?.length &&
|
||||
item.items.map((item) => (
|
||||
<DropdownMenuItem key={item.title} asChild>
|
||||
{item.href ? (
|
||||
<Link href={item.href}>{item.title}</Link>
|
||||
) : (
|
||||
item.title
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger className="h-9">
|
||||
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">
|
||||
<Link href="/" passHref legacyBehavior>
|
||||
<NavigationMenuLink
|
||||
className="flex h-full w-full select-none
|
||||
flex-col justify-end space-y-2 rounded-md bg-gradient-to-b from-rose-500 to-indigo-700 p-6 no-underline outline-none focus:shadow-md"
|
||||
>
|
||||
<div className="text-lg font-medium text-white">
|
||||
{siteConfig.name}
|
||||
</div>
|
||||
<p className="text-sm leading-snug text-white/90">
|
||||
{siteConfig.description}
|
||||
</p>
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</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="h-9">
|
||||
Components
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid w-[600px] grid-cols-2 gap-3 p-4">
|
||||
{allDocs
|
||||
.filter((doc) => doc.featured)
|
||||
.map((doc) => (
|
||||
<ListItem key={doc._id} title={doc.title} href={doc.slug}>
|
||||
{doc.description}
|
||||
</ListItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ul>
|
||||
<div className="p-4 pt-0">
|
||||
<Separator className="mb-4" />
|
||||
<Link href="/docs/primitives/accordion" passHref legacyBehavior>
|
||||
<NavigationMenuLink
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"w-full dark:hover:bg-slate-700"
|
||||
)}
|
||||
>
|
||||
Browse components
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</div>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem className="hidden lg:flex">
|
||||
<Link href={siteConfig.links.github} legacyBehavior passHref>
|
||||
<NavigationMenuLink
|
||||
className={cn(navigationMenuTriggerStyle(), "h-9")}
|
||||
>
|
||||
GitHub
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ListItem = React.forwardRef<
|
||||
React.ElementRef<typeof Link>,
|
||||
React.ComponentPropsWithoutRef<typeof Link>
|
||||
>(({ className, title, children, href, ...props }, ref) => {
|
||||
return (
|
||||
<li>
|
||||
<Link href={href} passHref legacyBehavior {...props}>
|
||||
<NavigationMenuLink
|
||||
className={cn(
|
||||
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-slate-100 focus:bg-slate-100 dark:hover:bg-slate-700 dark:focus:bg-slate-700",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">{title}</div>
|
||||
<p className="text-sm leading-snug text-slate-500 line-clamp-2 dark:text-slate-400">
|
||||
{children}
|
||||
</p>
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
ListItem.displayName = "ListItem"
|
||||
|
||||
80
apps/www/components/mobile-nav.tsx
Normal file
80
apps/www/components/mobile-nav.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
|
||||
import { docsConfig } from "@/config/docs"
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
|
||||
export function MobileNav() {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="-ml-4 text-base hover:bg-transparent focus:ring-0 focus:ring-offset-0 md:hidden"
|
||||
>
|
||||
<Icons.logo className="mr-2 h-4 w-4" />{" "}
|
||||
<span className="font-bold">Menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
sideOffset={24}
|
||||
alignOffset={4}
|
||||
className="w-[300px] overflow-scroll"
|
||||
>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/" className="flex items-center">
|
||||
<Icons.logo className="mr-2 h-4 w-4" /> {siteConfig.name}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<ScrollArea className="h-[400px]">
|
||||
{docsConfig.sidebarNav?.map(
|
||||
(item, index) =>
|
||||
item.href && (
|
||||
<DropdownMenuItem key={index} asChild>
|
||||
<Link href={item.href}>{item.title}</Link>
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
)}
|
||||
{docsConfig.sidebarNav.map((item, index) => (
|
||||
<DropdownMenuGroup key={index}>
|
||||
<DropdownMenuSeparator
|
||||
className={cn({
|
||||
hidden: index === 0,
|
||||
})}
|
||||
/>
|
||||
<DropdownMenuLabel>{item.title}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator className="-mx-2" />
|
||||
{item?.items?.length &&
|
||||
item.items.map((item) => (
|
||||
<DropdownMenuItem key={item.title} asChild>
|
||||
{item.href ? (
|
||||
<Link href={item.href}>{item.title}</Link>
|
||||
) : (
|
||||
item.title
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export function DocsSearch({ className, ...props }: DocsSearchProps) {
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search documentation..."
|
||||
className="h-9 sm:w-64 sm:pr-12"
|
||||
className="h-9 sm:pr-12 md:w-40 lg:w-64"
|
||||
disabled
|
||||
/>
|
||||
<kbd className="pointer-events-none absolute top-2 right-1.5 hidden h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400 sm:flex">
|
||||
|
||||
@@ -4,16 +4,17 @@ import { docsConfig } from "@/config/docs"
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { MainNav } from "@/components/main-nav"
|
||||
import { MobileNav } from "@/components/mobile-nav"
|
||||
import { ModeToggle } from "@/components/mode-toggle"
|
||||
import { DocsSearch } from "@/components/search"
|
||||
import { DocsSidebarNav } from "@/components/sidebar-nav"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="sticky top-0 z-40 w-full border-b border-b-slate-200 bg-white dark:border-b-slate-700 dark:bg-slate-900">
|
||||
<div className="container flex h-16 items-center space-x-4 sm:justify-between sm:space-x-0">
|
||||
<MainNav items={docsConfig.mainNav} />
|
||||
<div className="container flex h-16 items-center">
|
||||
<MainNav />
|
||||
<MobileNav />
|
||||
<div className="flex flex-1 items-center justify-end space-x-4">
|
||||
<div className="hidden flex-1 sm:grow-0 md:flex">
|
||||
<DocsSearch />
|
||||
|
||||
128
apps/www/components/ui/navigation-menu.tsx
Normal file
128
apps/www/components/ui/navigation-menu.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import * as React from "react"
|
||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||
import { cva } from "class-variance-authority"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const NavigationMenu = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-10 flex flex-1 items-center justify-center",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<NavigationMenuViewport />
|
||||
</NavigationMenuPrimitive.Root>
|
||||
))
|
||||
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
|
||||
|
||||
const NavigationMenuList = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group flex flex-1 list-none items-center justify-center",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
||||
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:bg-slate-100 disabled:opacity-50 dark:focus:bg-slate-800 disabled:pointer-events-none bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-slate-50 dark:data-[state=open]:bg-slate-800 h-10 py-2 px-4 group"
|
||||
)
|
||||
|
||||
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}
|
||||
>
|
||||
{children}{" "}
|
||||
<ChevronDown
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
))
|
||||
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
|
||||
|
||||
const NavigationMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"absolute top-0 left-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=to-start]:slide-out-to-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=from-end]:slide-in-from-right-52 md:w-auto",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
|
||||
|
||||
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")}>
|
||||
<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 border-slate-200 bg-white shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:zoom-in-90 data-[state=closed]:zoom-out-95 dark:border-slate-700 dark:bg-slate-800 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
NavigationMenuViewport.displayName =
|
||||
NavigationMenuPrimitive.Viewport.displayName
|
||||
|
||||
const NavigationMenuIndicator = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=visible]:fade-in data-[state=hidden]:fade-out",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-slate-200 shadow-md dark:bg-slate-800" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
))
|
||||
NavigationMenuIndicator.displayName =
|
||||
NavigationMenuPrimitive.Indicator.displayName
|
||||
|
||||
export {
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
}
|
||||
@@ -120,6 +120,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: "/docs/primitives/menubar",
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Navigation Menu",
|
||||
href: "/docs/primitives/navigation-menu",
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Popover",
|
||||
href: "/docs/primitives/popover",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Alert Dialog
|
||||
description: A modal dialog that interrupts the user with important content and expects a response.
|
||||
featured: true
|
||||
radix:
|
||||
link: https://www.radix-ui.com/docs/primitives/components/alert-dialog
|
||||
api: https://www.radix-ui.com/docs/primitives/components/alert-dialog#api-reference
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Button
|
||||
description: Displays a button or a component that looks like a button.
|
||||
featured: true
|
||||
---
|
||||
|
||||
<ComponentExample src="/components/examples/button/demo.tsx">
|
||||
@@ -71,6 +72,7 @@ import { Button } from "@/components/ui/button"
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
### Link
|
||||
|
||||
<ComponentExample src="/components/examples/button/link.tsx">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Collapsible
|
||||
description: An interactive component which expands/collapses a panel.
|
||||
featured: true
|
||||
radix:
|
||||
link: https://www.radix-ui.com/docs/primitives/components/collapsible
|
||||
api: https://www.radix-ui.com/docs/primitives/components/collapsible#api-reference
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Dialog
|
||||
description: A modal dialog that interrupts the user with important content and expects a response.
|
||||
featured: true
|
||||
radix:
|
||||
link: https://www.radix-ui.com/docs/primitives/components/dialog
|
||||
api: https://www.radix-ui.com/docs/primitives/components/dialog#api-reference
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Dropdown Menu
|
||||
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
|
||||
featured: true
|
||||
radix:
|
||||
link: https://www.radix-ui.com/docs/primitives/components/dropdown-menu
|
||||
api: https://www.radix-ui.com/docs/primitives/components/dropdown-menu#api-reference
|
||||
|
||||
76
apps/www/content/docs/primitives/navigation-menu.mdx
Normal file
76
apps/www/content/docs/primitives/navigation-menu.mdx
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: Navigation Menu
|
||||
description: A collection of links for navigating websites.
|
||||
radix:
|
||||
link: https://www.radix-ui.com/docs/primitives/components/navigation-menu
|
||||
api: https://www.radix-ui.com/docs/primitives/components/navigation-menu#api-reference
|
||||
---
|
||||
|
||||
<ComponentExample src="/components/examples/navigation-menu/demo.tsx">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentExample>
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install the `@radix-ui/react-navigation-menu` component from radix-ui:
|
||||
|
||||
```bash
|
||||
npm install @radix-ui/react-navigation-menu
|
||||
```
|
||||
|
||||
2. Copy and paste the following code into your project.
|
||||
|
||||
<ComponentSource src="/components/ui/navigation-menu.tsx" />
|
||||
|
||||
<Callout>
|
||||
This is the `<NavigationMenu />` primitive. You can place it in a file at
|
||||
`components/ui/navigation-menu.tsx`.
|
||||
</Callout>
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuViewport,
|
||||
} from "@/components/ui/navigation-menu"
|
||||
```
|
||||
|
||||
```tsx
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>Item One</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<NavigationMenuLink>Link</NavigationMenuLink>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
```
|
||||
|
||||
### Link Component
|
||||
|
||||
When using the Next.js `<Link />` component, you can use `navigationMenuTriggerStyle()` to apply the correct styles to the trigger.
|
||||
|
||||
```tsx
|
||||
import { navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"
|
||||
```
|
||||
|
||||
```tsx {3-5}
|
||||
<NavigationMenuItem>
|
||||
<Link href="/docs" legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
Documentation
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Select
|
||||
description: Displays a list of options for the user to pick from—triggered by a button.
|
||||
featured: true
|
||||
radix:
|
||||
link: https://www.radix-ui.com/docs/primitives/components/select
|
||||
api: https://www.radix-ui.com/docs/primitives/components/select#api-reference
|
||||
|
||||
@@ -60,6 +60,11 @@ export const Doc = defineDocumentType(() => ({
|
||||
type: "nested",
|
||||
of: RadixProperties,
|
||||
},
|
||||
featured: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
computedFields,
|
||||
}))
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@radix-ui/react-hover-card": "^1.0.3",
|
||||
"@radix-ui/react-label": "^2.0.0",
|
||||
"@radix-ui/react-menubar": "^1.0.0",
|
||||
"@radix-ui/react-navigation-menu": "^1.1.1",
|
||||
"@radix-ui/react-popover": "^1.0.2",
|
||||
"@radix-ui/react-progress": "^1.0.1",
|
||||
"@radix-ui/react-radio-group": "^1.1.0",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.7",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^3.7.1",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-next": "13.0.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
|
||||
41
pnpm-lock.yaml
generated
41
pnpm-lock.yaml
generated
@@ -8,6 +8,7 @@ importers:
|
||||
'@commitlint/cli': ^17.3.0
|
||||
'@commitlint/config-conventional': ^17.3.0
|
||||
'@ianvs/prettier-plugin-sort-imports': ^3.7.1
|
||||
'@tailwindcss/line-clamp': ^0.4.2
|
||||
'@types/node': ^18.11.9
|
||||
'@types/react': 18.0.15
|
||||
'@types/react-dom': 18.0.6
|
||||
@@ -29,6 +30,7 @@ importers:
|
||||
dependencies:
|
||||
'@babel/core': 7.20.7
|
||||
'@ianvs/prettier-plugin-sort-imports': 3.7.1_prettier@2.8.1
|
||||
'@tailwindcss/line-clamp': 0.4.2_tailwindcss@3.2.4
|
||||
eslint: 8.31.0
|
||||
eslint-config-next: 13.0.0_wogtpudmlxya2leoxia5qf2rl4
|
||||
eslint-config-prettier: 8.5.0_eslint@8.31.0
|
||||
@@ -67,6 +69,7 @@ importers:
|
||||
'@radix-ui/react-hover-card': ^1.0.3
|
||||
'@radix-ui/react-label': ^2.0.0
|
||||
'@radix-ui/react-menubar': ^1.0.0
|
||||
'@radix-ui/react-navigation-menu': ^1.1.1
|
||||
'@radix-ui/react-popover': ^1.0.2
|
||||
'@radix-ui/react-progress': ^1.0.1
|
||||
'@radix-ui/react-radio-group': ^1.1.0
|
||||
@@ -129,6 +132,7 @@ importers:
|
||||
'@radix-ui/react-hover-card': 1.0.3_ib3m5ricvtkl2cll7qpr2f6lvq
|
||||
'@radix-ui/react-label': 2.0.0_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-menubar': 1.0.0_ib3m5ricvtkl2cll7qpr2f6lvq
|
||||
'@radix-ui/react-navigation-menu': 1.1.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-popover': 1.0.2_ib3m5ricvtkl2cll7qpr2f6lvq
|
||||
'@radix-ui/react-progress': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-radio-group': 1.1.0_biqbaboplfbrettd7655fr4n2y
|
||||
@@ -2014,6 +2018,31 @@ packages:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-navigation-menu/1.1.1_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-Khgf+LwqYfUpbFAHcFPDMj6ZrWxnwCgC96liLYwE48x9YJbXGlutOWzZaSzrgl82xS+PwoPLQunfDe/i4ZITRA==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.6
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-collection': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-compose-refs': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-context': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-direction': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-dismissable-layer': 1.0.2_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-id': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-use-layout-effect': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-use-previous': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-visually-hidden': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-popover/1.0.2_ib3m5ricvtkl2cll7qpr2f6lvq:
|
||||
resolution: {integrity: sha512-4tqZEl9w95R5mlZ/sFdgBnfhCBOEPepLIurBA5kt/qaAhldJ1tNQd0ngr0ET0AHbPotT4mwxMPr7a+MA/wbK0g==}
|
||||
peerDependencies:
|
||||
@@ -2498,6 +2527,14 @@ packages:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@tailwindcss/line-clamp/0.4.2_tailwindcss@3.2.4:
|
||||
resolution: {integrity: sha512-HFzAQuqYCjyy/SX9sLGB1lroPzmcnWv1FHkIpmypte10hptf4oPUfucryMKovZh2u0uiS9U5Ty3GghWfEJGwVw==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1'
|
||||
dependencies:
|
||||
tailwindcss: 3.2.4_ra2vnoek4vhbzktaezawwqbin4
|
||||
dev: false
|
||||
|
||||
/@tsconfig/node10/1.0.9:
|
||||
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
||||
|
||||
@@ -6166,6 +6203,7 @@ packages:
|
||||
lilconfig: 2.0.6
|
||||
postcss: 8.4.20
|
||||
yaml: 1.10.2
|
||||
dev: true
|
||||
|
||||
/postcss-load-config/3.1.4_ra2vnoek4vhbzktaezawwqbin4:
|
||||
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
|
||||
@@ -7068,7 +7106,7 @@ packages:
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders'
|
||||
dependencies:
|
||||
tailwindcss: 3.2.4_postcss@8.4.20
|
||||
tailwindcss: 3.2.4_ra2vnoek4vhbzktaezawwqbin4
|
||||
dev: false
|
||||
|
||||
/tailwindcss/3.2.4_postcss@8.4.20:
|
||||
@@ -7103,6 +7141,7 @@ packages:
|
||||
resolve: 1.22.1
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/tailwindcss/3.2.4_ra2vnoek4vhbzktaezawwqbin4:
|
||||
resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==}
|
||||
|
||||
@@ -32,5 +32,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
plugins: [require("tailwindcss-animate"), require("@tailwindcss/line-clamp")],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user