From eaefc9068dee12d562da9607aa2cc12302132166 Mon Sep 17 00:00:00 2001 From: DemaPy Date: Sun, 29 Dec 2024 22:52:34 +0100 Subject: [PATCH 1/2] feature(navigation-menu) --- apps/www/__registry__/index.tsx | 30 ++++ apps/www/components/component-preview.tsx | 4 +- .../docs/components/navigation-menu.mdx | 29 ++++ apps/www/hooks/use-content-position.ts | 74 +++++++++ .../default/navigation-menu-options-demo.json | 15 ++ .../r/styles/default/navigation-menu.json | 2 +- .../navigation-menu-options-demo.json | 15 ++ .../r/styles/new-york/navigation-menu.json | 2 +- .../examples/navigation-menu-options-demo.tsx | 152 ++++++++++++++++++ .../registry/default/ui/navigation-menu.tsx | 74 +++++++-- .../examples/navigation-menu-options-demo.tsx | 152 ++++++++++++++++++ .../registry/new-york/ui/navigation-menu.tsx | 75 +++++++-- apps/www/registry/registry-examples.ts | 11 ++ 13 files changed, 598 insertions(+), 37 deletions(-) create mode 100644 apps/www/hooks/use-content-position.ts create mode 100644 apps/www/public/r/styles/default/navigation-menu-options-demo.json create mode 100644 apps/www/public/r/styles/new-york/navigation-menu-options-demo.json create mode 100644 apps/www/registry/default/examples/navigation-menu-options-demo.tsx create mode 100644 apps/www/registry/new-york/examples/navigation-menu-options-demo.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 5b61ac36371..e26128064bc 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -4209,6 +4209,21 @@ export const Index: Record = { source: "", meta: undefined, }, + "navigation-menu-options-demo": { + name: "navigation-menu-options-demo", + description: "", + type: "registry:example", + registryDependencies: ["navigation-menu"], + files: [{ + path: "registry/new-york/examples/navigation-menu-options-demo.tsx", + type: "registry:example", + target: "" + }], + categories: undefined, + component: React.lazy(() => import("@/registry/new-york/examples/navigation-menu-options-demo.tsx")), + source: "", + meta: undefined, + }, "pagination-demo": { name: "pagination-demo", description: "", @@ -9464,6 +9479,21 @@ export const Index: Record = { source: "", meta: undefined, }, + "navigation-menu-options-demo": { + name: "navigation-menu-options-demo", + description: "", + type: "registry:example", + registryDependencies: ["navigation-menu"], + files: [{ + path: "registry/default/examples/navigation-menu-options-demo.tsx", + type: "registry:example", + target: "" + }], + categories: undefined, + component: React.lazy(() => import("@/registry/default/examples/navigation-menu-options-demo.tsx")), + source: "", + meta: undefined, + }, "pagination-demo": { name: "pagination-demo", description: "", diff --git a/apps/www/components/component-preview.tsx b/apps/www/components/component-preview.tsx index b0e8105e726..6541cb83d60 100644 --- a/apps/www/components/component-preview.tsx +++ b/apps/www/components/component-preview.tsx @@ -27,6 +27,7 @@ interface ComponentPreviewProps extends React.HTMLAttributes { description?: string hideCode?: boolean type?: "block" | "component" | "example" + componentProps?: Record } export function ComponentPreview({ @@ -39,6 +40,7 @@ export function ComponentPreview({ align = "center", description, hideCode = false, + componentProps, ...props }: ComponentPreviewProps) { const [config] = useConfig() @@ -62,7 +64,7 @@ export function ComponentPreview({ ) } - return + return }, [name, config.style]) const codeString = React.useMemo(() => { diff --git a/apps/www/content/docs/components/navigation-menu.mdx b/apps/www/content/docs/components/navigation-menu.mdx index 1c588100ad6..460e2f6a0c4 100644 --- a/apps/www/content/docs/components/navigation-menu.mdx +++ b/apps/www/content/docs/components/navigation-menu.mdx @@ -95,4 +95,33 @@ import { navigationMenuTriggerStyle } from "@/components/ui/navigation-menu" ``` +### Content options + +`align`: `"start"` | `"center"` | `"end"` +assign align type for content position +`contentWidth`: `number[]` +assign width for every NavigationMenuContent + + +`contentOptions={{ align: "start", contentWidth: }}` + + + +`contentOptions={{ align: "center" }}` + + + +`contentOptions={{ align: "end" }}` + + + 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. diff --git a/apps/www/hooks/use-content-position.ts b/apps/www/hooks/use-content-position.ts new file mode 100644 index 00000000000..7b2547b0891 --- /dev/null +++ b/apps/www/hooks/use-content-position.ts @@ -0,0 +1,74 @@ +import React from "react"; + +const aligns = { + start: "start", + center: "center", + end: "end", +} as const; + +export interface ContentOptions { + align: keyof typeof aligns; + contentWidth: number[]; +} + +const alignFns: Record< + keyof typeof aligns, + (computedWidth: number, contentWidth: number, buttonWidth: number) => number +> = { + start: (computedWidth, contentWidth, _) => { + return computedWidth - contentWidth * 0; + }, + center: (computedWidth, contentWidth, buttonWidth) => { + return computedWidth - contentWidth / 2 + buttonWidth / 2; + }, + end: (computedWidth, contentWidth, buttonWidth) => { + return computedWidth - contentWidth + buttonWidth; + }, +}; + +const useContentPosition = (options?: ContentOptions) => { + const ref = React.useRef(null); + const [position, setPosition] = React.useState<{ + x: number; + }>({ x: 0 }); + + React.useLayoutEffect(() => { + const container = ref.current; + if (!container || !options) return; + + const handleMouseEnter = (ev: MouseEvent) => { + const { clientX, clientY } = ev; + const element = document.elementFromPoint(clientX, clientY); + if (!(element instanceof HTMLButtonElement)) { + return; + } + container.childNodes.forEach((_, idx) => { + const _elem = container.childNodes[idx].firstChild + if (_elem === element) { + const containerCoords = container.getBoundingClientRect(); + const elemCoords = element.getBoundingClientRect(); + const computedWidth = elemCoords.left - containerCoords.left; + const X = alignFns[options.align]( + computedWidth, + options.contentWidth[idx], + elemCoords.width + ); + setPosition({ + x: X, + }); + } + }); + }; + + container.addEventListener("mousemove", handleMouseEnter); + return () => { + if (container) { + container.removeEventListener("mousemove", handleMouseEnter); + } + }; + }, []); + + return [position, ref] as const; +}; + +export default useContentPosition; diff --git a/apps/www/public/r/styles/default/navigation-menu-options-demo.json b/apps/www/public/r/styles/default/navigation-menu-options-demo.json new file mode 100644 index 00000000000..eebfad24c4a --- /dev/null +++ b/apps/www/public/r/styles/default/navigation-menu-options-demo.json @@ -0,0 +1,15 @@ +{ + "name": "navigation-menu-options-demo", + "type": "registry:example", + "registryDependencies": [ + "navigation-menu" + ], + "files": [ + { + "path": "examples/navigation-menu-options-demo.tsx", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport Link from \"next/link\"\n\nimport { cn } from \"@/lib/utils\"\nimport { type ContentOptions } from \"@/hooks/use-content-position\"\nimport { Icons } from \"@/components/icons\"\nimport {\n NavigationMenu,\n NavigationMenuContent,\n NavigationMenuItem,\n NavigationMenuLink,\n NavigationMenuList,\n NavigationMenuTrigger,\n navigationMenuTriggerStyle,\n} from \"@/registry/default/ui/navigation-menu\"\n\nconst components: { title: string; href: string; description: string }[] = [\n {\n title: \"Alert Dialog\",\n href: \"/docs/primitives/alert-dialog\",\n description:\n \"A modal dialog that interrupts the user with important content and expects a response.\",\n },\n {\n title: \"Hover Card\",\n href: \"/docs/primitives/hover-card\",\n description:\n \"For sighted users to preview content available behind a link.\",\n },\n {\n title: \"Progress\",\n href: \"/docs/primitives/progress\",\n description:\n \"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.\",\n },\n {\n title: \"Scroll-area\",\n href: \"/docs/primitives/scroll-area\",\n description: \"Visually or semantically separates content.\",\n },\n {\n title: \"Tabs\",\n href: \"/docs/primitives/tabs\",\n description:\n \"A set of layered sections of content—known as tab panels—that are displayed one at a time.\",\n },\n {\n title: \"Tooltip\",\n href: \"/docs/primitives/tooltip\",\n description:\n \"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.\",\n },\n]\n\nexport default function NavigationMenuOptionsDemo({\n align,\n}: {\n align: ContentOptions[\"align\"]\n}) {\n return (\n \n \n \n Getting started\n \n
    \n
  • \n \n \n \n
    \n shadcn/ui\n
    \n

    \n Beautifully designed components built with Radix UI and\n Tailwind CSS.\n

    \n \n
    \n
  • \n \n Re-usable components built using Radix UI and Tailwind CSS.\n \n \n How to install dependencies and structure your app.\n \n \n Styles for headings, paragraphs, lists...etc\n \n
\n
\n
\n \n Components\n \n
    \n {components.map((component) => (\n \n {component.description}\n \n ))}\n
\n
\n
\n \n \n \n Documentation\n \n \n \n
\n \n )\n}\n\nconst ListItem = React.forwardRef<\n React.ElementRef<\"a\">,\n React.ComponentPropsWithoutRef<\"a\">\n>(({ className, title, children, ...props }, ref) => {\n return (\n
  • \n \n \n
    {title}
    \n

    \n {children}\n

    \n \n
    \n
  • \n )\n})\nListItem.displayName = \"ListItem\"\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/navigation-menu.json b/apps/www/public/r/styles/default/navigation-menu.json index dda2951c12c..7ea551de9e0 100644 --- a/apps/www/public/r/styles/default/navigation-menu.json +++ b/apps/www/public/r/styles/default/navigation-menu.json @@ -7,7 +7,7 @@ "files": [ { "path": "ui/navigation-menu.tsx", - "content": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva } from \"class-variance-authority\"\nimport { ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst NavigationMenu = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => (\n \n {children}\n \n \n))\nNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName\n\nconst NavigationMenuList = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n))\nNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName\n\nconst NavigationMenuItem = NavigationMenuPrimitive.Item\n\nconst navigationMenuTriggerStyle = cva(\n \"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50\"\n)\n\nconst NavigationMenuTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => (\n \n {children}{\" \"}\n \n \n))\nNavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName\n\nconst NavigationMenuContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n))\nNavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName\n\nconst NavigationMenuLink = NavigationMenuPrimitive.Link\n\nconst NavigationMenuViewport = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n
    \n \n
    \n))\nNavigationMenuViewport.displayName =\n NavigationMenuPrimitive.Viewport.displayName\n\nconst NavigationMenuIndicator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n
    \n \n))\nNavigationMenuIndicator.displayName =\n NavigationMenuPrimitive.Indicator.displayName\n\nexport {\n navigationMenuTriggerStyle,\n NavigationMenu,\n NavigationMenuList,\n NavigationMenuItem,\n NavigationMenuContent,\n NavigationMenuTrigger,\n NavigationMenuLink,\n NavigationMenuIndicator,\n NavigationMenuViewport,\n}\n", + "content": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva } from \"class-variance-authority\"\nimport { ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport useContentPosition, {\n type ContentOptions,\n} from \"@/hooks/use-content-position\"\n\nconst NavigationMenu = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n contentOptions?: ContentOptions\n }\n>(({ className, contentOptions, children, ...props }, ref) => {\n const [position, _ref] = useContentPosition(contentOptions)\n return (\n \n \n {children}\n \n \n \n )\n})\nNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName\n\nconst NavigationMenuList = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n))\nNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName\n\nconst NavigationMenuItem = NavigationMenuPrimitive.Item\n\nconst navigationMenuTriggerStyle = cva(\n \"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50\"\n)\n\nconst NavigationMenuTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => (\n \n {children}{\" \"}\n \n \n))\nNavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName\n\nconst NavigationMenuContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n))\nNavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName\n\nconst NavigationMenuLink = NavigationMenuPrimitive.Link\n\nconst NavigationMenuViewport = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n x: number\n }\n>(({ className, x, ...props }, ref) => (\n \n \n
    \n))\nNavigationMenuViewport.displayName =\n NavigationMenuPrimitive.Viewport.displayName\n\nconst NavigationMenuIndicator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n
    \n \n))\nNavigationMenuIndicator.displayName =\n NavigationMenuPrimitive.Indicator.displayName\n\nconst NavigationMenuChildren = React.forwardRef<\n HTMLLIElement,\n {\n contentOptions?: ContentOptions\n } & React.PropsWithChildren\n>(({ contentOptions, children }, ref) => (\n <>\n {contentOptions ? (\n <>\n {React.Children.map(children, (child) => {\n if (React.isValidElement<{ ref?: React.Ref }>(child)) {\n return React.cloneElement(child, {\n ref,\n })\n }\n })}\n \n ) : (\n children\n )}\n \n))\n\nexport {\n navigationMenuTriggerStyle,\n NavigationMenu,\n NavigationMenuList,\n NavigationMenuItem,\n NavigationMenuContent,\n NavigationMenuTrigger,\n NavigationMenuLink,\n NavigationMenuIndicator,\n NavigationMenuViewport,\n}\n", "type": "registry:ui", "target": "" } diff --git a/apps/www/public/r/styles/new-york/navigation-menu-options-demo.json b/apps/www/public/r/styles/new-york/navigation-menu-options-demo.json new file mode 100644 index 00000000000..074ee2caae3 --- /dev/null +++ b/apps/www/public/r/styles/new-york/navigation-menu-options-demo.json @@ -0,0 +1,15 @@ +{ + "name": "navigation-menu-options-demo", + "type": "registry:example", + "registryDependencies": [ + "navigation-menu" + ], + "files": [ + { + "path": "examples/navigation-menu-options-demo.tsx", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport Link from \"next/link\"\n\nimport { cn } from \"@/lib/utils\"\nimport { type ContentOptions } from \"@/hooks/use-content-position\"\nimport { Icons } from \"@/components/icons\"\nimport {\n NavigationMenu,\n NavigationMenuContent,\n NavigationMenuItem,\n NavigationMenuLink,\n NavigationMenuList,\n NavigationMenuTrigger,\n navigationMenuTriggerStyle,\n} from \"@/registry/new-york/ui/navigation-menu\"\n\nconst components: { title: string; href: string; description: string }[] = [\n {\n title: \"Alert Dialog\",\n href: \"/docs/primitives/alert-dialog\",\n description:\n \"A modal dialog that interrupts the user with important content and expects a response.\",\n },\n {\n title: \"Hover Card\",\n href: \"/docs/primitives/hover-card\",\n description:\n \"For sighted users to preview content available behind a link.\",\n },\n {\n title: \"Progress\",\n href: \"/docs/primitives/progress\",\n description:\n \"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.\",\n },\n {\n title: \"Scroll-area\",\n href: \"/docs/primitives/scroll-area\",\n description: \"Visually or semantically separates content.\",\n },\n {\n title: \"Tabs\",\n href: \"/docs/primitives/tabs\",\n description:\n \"A set of layered sections of content—known as tab panels—that are displayed one at a time.\",\n },\n {\n title: \"Tooltip\",\n href: \"/docs/primitives/tooltip\",\n description:\n \"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.\",\n },\n]\n\nexport default function NavigationMenuOptionsDemo({\n align,\n}: {\n align: ContentOptions[\"align\"]\n}) {\n return (\n \n \n \n Getting started\n \n
      \n
    • \n \n \n \n
      \n shadcn/ui\n
      \n

      \n Beautifully designed components built with Radix UI and\n Tailwind CSS.\n

      \n \n
      \n
    • \n \n Re-usable components built using Radix UI and Tailwind CSS.\n \n \n How to install dependencies and structure your app.\n \n \n Styles for headings, paragraphs, lists...etc\n \n
    \n
    \n
    \n \n Components\n \n
      \n {components.map((component) => (\n \n {component.description}\n \n ))}\n
    \n
    \n
    \n \n \n \n Documentation\n \n \n \n
    \n \n )\n}\n\nconst ListItem = React.forwardRef<\n React.ElementRef<\"a\">,\n React.ComponentPropsWithoutRef<\"a\">\n>(({ className, title, children, ...props }, ref) => {\n return (\n
  • \n \n \n
    {title}
    \n

    \n {children}\n

    \n \n
    \n
  • \n )\n})\nListItem.displayName = \"ListItem\"\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/navigation-menu.json b/apps/www/public/r/styles/new-york/navigation-menu.json index c84330a9fd2..3f9080153f2 100644 --- a/apps/www/public/r/styles/new-york/navigation-menu.json +++ b/apps/www/public/r/styles/new-york/navigation-menu.json @@ -7,7 +7,7 @@ "files": [ { "path": "ui/navigation-menu.tsx", - "content": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva } from \"class-variance-authority\"\nimport { ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst NavigationMenu = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => (\n \n {children}\n \n \n))\nNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName\n\nconst NavigationMenuList = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n))\nNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName\n\nconst NavigationMenuItem = NavigationMenuPrimitive.Item\n\nconst navigationMenuTriggerStyle = cva(\n \"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50\"\n)\n\nconst NavigationMenuTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => (\n \n {children}{\" \"}\n \n \n))\nNavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName\n\nconst NavigationMenuContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n))\nNavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName\n\nconst NavigationMenuLink = NavigationMenuPrimitive.Link\n\nconst NavigationMenuViewport = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n
    \n \n
    \n))\nNavigationMenuViewport.displayName =\n NavigationMenuPrimitive.Viewport.displayName\n\nconst NavigationMenuIndicator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n
    \n \n))\nNavigationMenuIndicator.displayName =\n NavigationMenuPrimitive.Indicator.displayName\n\nexport {\n navigationMenuTriggerStyle,\n NavigationMenu,\n NavigationMenuList,\n NavigationMenuItem,\n NavigationMenuContent,\n NavigationMenuTrigger,\n NavigationMenuLink,\n NavigationMenuIndicator,\n NavigationMenuViewport,\n}\n", + "content": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva } from \"class-variance-authority\"\nimport { ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport useContentPosition, {\n type ContentOptions,\n} from \"@/hooks/use-content-position\"\n\nconst NavigationMenu = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n contentOptions?: ContentOptions\n }\n>(({ className, contentOptions, children, ...props }, ref) => {\n const [position, _ref] = useContentPosition(contentOptions)\n\n return (\n \n \n {children}\n \n \n \n )\n})\nNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName\n\nconst NavigationMenuList = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n))\nNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName\n\nconst NavigationMenuItem = NavigationMenuPrimitive.Item\n\nconst navigationMenuTriggerStyle = cva(\n \"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50\"\n)\n\nconst NavigationMenuTrigger = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => (\n \n {children}{\" \"}\n \n \n))\nNavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName\n\nconst NavigationMenuContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n))\nNavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName\n\nconst NavigationMenuLink = NavigationMenuPrimitive.Link\n\nconst NavigationMenuViewport = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef & {\n x: number\n }\n>(({ className, x, ...props }, ref) => (\n \n \n
    \n))\nNavigationMenuViewport.displayName =\n NavigationMenuPrimitive.Viewport.displayName\n\nconst NavigationMenuIndicator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n
    \n \n))\nNavigationMenuIndicator.displayName =\n NavigationMenuPrimitive.Indicator.displayName\n\nconst NavigationMenuChildren = React.forwardRef<\n HTMLLIElement,\n {\n contentOptions?: ContentOptions\n } & React.PropsWithChildren\n>(({ contentOptions, children }, ref) => (\n <>\n {contentOptions ? (\n <>\n {React.Children.map(children, (child) => {\n if (React.isValidElement<{ ref?: React.Ref }>(child)) {\n return React.cloneElement(child, {\n ref,\n })\n }\n })}\n \n ) : (\n children\n )}\n \n))\n\nexport {\n navigationMenuTriggerStyle,\n NavigationMenu,\n NavigationMenuList,\n NavigationMenuItem,\n NavigationMenuContent,\n NavigationMenuTrigger,\n NavigationMenuLink,\n NavigationMenuIndicator,\n NavigationMenuViewport,\n}\n", "type": "registry:ui", "target": "" } diff --git a/apps/www/registry/default/examples/navigation-menu-options-demo.tsx b/apps/www/registry/default/examples/navigation-menu-options-demo.tsx new file mode 100644 index 00000000000..597a4799c8a --- /dev/null +++ b/apps/www/registry/default/examples/navigation-menu-options-demo.tsx @@ -0,0 +1,152 @@ +"use client" + +import * as React from "react" +import Link from "next/link" + +import { cn } from "@/lib/utils" +import { type ContentOptions } from "@/hooks/use-content-position" +import { Icons } from "@/components/icons" +import { + NavigationMenu, + NavigationMenuContent, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + NavigationMenuTrigger, + navigationMenuTriggerStyle, +} from "@/registry/default/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 default function NavigationMenuOptionsDemo({ + align, +}: { + align: ContentOptions["align"] +}) { + return ( + + + + Getting started + + + + + + Components + +
      + {components.map((component) => ( + + {component.description} + + ))} +
    +
    +
    + + + + Documentation + + + +
    +
    + ) +} + +const ListItem = React.forwardRef< + React.ElementRef<"a">, + React.ComponentPropsWithoutRef<"a"> +>(({ className, title, children, ...props }, ref) => { + return ( +
  • + + +
    {title}
    +

    + {children} +

    +
    +
    +
  • + ) +}) +ListItem.displayName = "ListItem" diff --git a/apps/www/registry/default/ui/navigation-menu.tsx b/apps/www/registry/default/ui/navigation-menu.tsx index 1419f56695b..853341c1acb 100644 --- a/apps/www/registry/default/ui/navigation-menu.tsx +++ b/apps/www/registry/default/ui/navigation-menu.tsx @@ -4,23 +4,33 @@ import { cva } from "class-variance-authority" import { ChevronDown } from "lucide-react" import { cn } from "@/lib/utils" +import useContentPosition, { + type ContentOptions, +} from "@/hooks/use-content-position" const NavigationMenu = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - {children} - - -)) + React.ComponentPropsWithoutRef & { + contentOptions?: ContentOptions + } +>(({ className, contentOptions, children, ...props }, ref) => { + const [position, _ref] = useContentPosition(contentOptions) + return ( + + + {children} + + + + ) +}) NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName const NavigationMenuList = React.forwardRef< @@ -81,9 +91,16 @@ const NavigationMenuLink = NavigationMenuPrimitive.Link const NavigationMenuViewport = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
    + React.ComponentPropsWithoutRef & { + x: number + } +>(({ className, x, ...props }, ref) => ( +
    (({ contentOptions, children }, ref) => ( + <> + {contentOptions ? ( + <> + {React.Children.map(children, (child) => { + if (React.isValidElement<{ ref?: React.Ref }>(child)) { + return React.cloneElement(child, { + ref, + }) + } + })} + + ) : ( + children + )} + +)) + export { navigationMenuTriggerStyle, NavigationMenu, diff --git a/apps/www/registry/new-york/examples/navigation-menu-options-demo.tsx b/apps/www/registry/new-york/examples/navigation-menu-options-demo.tsx new file mode 100644 index 00000000000..96f84705bc8 --- /dev/null +++ b/apps/www/registry/new-york/examples/navigation-menu-options-demo.tsx @@ -0,0 +1,152 @@ +"use client" + +import * as React from "react" +import Link from "next/link" + +import { cn } from "@/lib/utils" +import { type ContentOptions } from "@/hooks/use-content-position" +import { Icons } from "@/components/icons" +import { + NavigationMenu, + NavigationMenuContent, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + NavigationMenuTrigger, + navigationMenuTriggerStyle, +} from "@/registry/new-york/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 default function NavigationMenuOptionsDemo({ + align, +}: { + align: ContentOptions["align"] +}) { + return ( + + + + Getting started + + + + + + Components + +
      + {components.map((component) => ( + + {component.description} + + ))} +
    +
    +
    + + + + Documentation + + + +
    +
    + ) +} + +const ListItem = React.forwardRef< + React.ElementRef<"a">, + React.ComponentPropsWithoutRef<"a"> +>(({ className, title, children, ...props }, ref) => { + return ( +
  • + + +
    {title}
    +

    + {children} +

    +
    +
    +
  • + ) +}) +ListItem.displayName = "ListItem" diff --git a/apps/www/registry/new-york/ui/navigation-menu.tsx b/apps/www/registry/new-york/ui/navigation-menu.tsx index a5d4d27d346..370d0dce12a 100644 --- a/apps/www/registry/new-york/ui/navigation-menu.tsx +++ b/apps/www/registry/new-york/ui/navigation-menu.tsx @@ -4,23 +4,34 @@ import { cva } from "class-variance-authority" import { ChevronDown } from "lucide-react" import { cn } from "@/lib/utils" +import useContentPosition, { + type ContentOptions, +} from "@/hooks/use-content-position" const NavigationMenu = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - {children} - - -)) + React.ComponentPropsWithoutRef & { + contentOptions?: ContentOptions + } +>(({ className, contentOptions, children, ...props }, ref) => { + const [position, _ref] = useContentPosition(contentOptions) + + return ( + + + {children} + + + + ) +}) NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName const NavigationMenuList = React.forwardRef< @@ -81,9 +92,16 @@ const NavigationMenuLink = NavigationMenuPrimitive.Link const NavigationMenuViewport = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
    + React.ComponentPropsWithoutRef & { + x: number + } +>(({ className, x, ...props }, ref) => ( +
    (({ contentOptions, children }, ref) => ( + <> + {contentOptions ? ( + <> + {React.Children.map(children, (child) => { + if (React.isValidElement<{ ref?: React.Ref }>(child)) { + return React.cloneElement(child, { + ref, + }) + } + })} + + ) : ( + children + )} + +)) + export { navigationMenuTriggerStyle, NavigationMenu, diff --git a/apps/www/registry/registry-examples.ts b/apps/www/registry/registry-examples.ts index fde9da6d44e..7c4725ede94 100644 --- a/apps/www/registry/registry-examples.ts +++ b/apps/www/registry/registry-examples.ts @@ -863,6 +863,17 @@ export const examples: Registry = [ }, ], }, + { + name: "navigation-menu-options-demo", + type: "registry:example", + registryDependencies: ["navigation-menu"], + files: [ + { + path: "examples/navigation-menu-options-demo.tsx", + type: "registry:example", + }, + ], + }, { name: "pagination-demo", type: "registry:example", From bb849ee746cc9c8302c83fab89c2d1929176001f Mon Sep 17 00:00:00 2001 From: DemaPy Date: Sun, 29 Dec 2024 23:02:34 +0100 Subject: [PATCH 2/2] fix navigation-menu.mdx --- apps/www/content/docs/components/navigation-menu.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/content/docs/components/navigation-menu.mdx b/apps/www/content/docs/components/navigation-menu.mdx index 460e2f6a0c4..3696c928402 100644 --- a/apps/www/content/docs/components/navigation-menu.mdx +++ b/apps/www/content/docs/components/navigation-menu.mdx @@ -103,7 +103,7 @@ assign align type for content position assign width for every NavigationMenuContent -`contentOptions={{ align: "start", contentWidth: }}` +`contentOptions={{ align: "start" }}`