{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "navigation-menu",
  "title": "Navigation menu",
  "description": "Small words open a panel of large names, the scale contrast of a poster; or a contents page with one name set as the headline; or the names opened into the line itself.",
  "dependencies": [
    "@radix-ui/react-navigation-menu"
  ],
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/navigation-menu.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\n\nimport { useComposedRefs } from \"@/lib/0nlytype/refs\"\nimport { cn } from \"@/lib/utils\"\n\ntype Variant = \"names\" | \"lead\" | \"inline\"\nconst VariantContext = React.createContext<Variant>(\"names\")\n\ntype NavigationMenuProps = React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {\n  /**\n   * names: a panel of large names hung under the menu.\n   * lead: a contents page; the names small in a column, and the one you point at or focus set as the headline beside them, with its line.\n   * inline: one line of thought; pressing a word opens its names into the line after it, and the words after make room.\n   */\n  variant?: Variant\n}\n\n/**\n * Small words that open a panel of large names. Radix keeps the pointer delay,\n * focus and arrow keys; each panel spans the width of the menu, hung under it.\n * There is no viewport: every panel sits in its own item.\n */\nfunction NavigationMenu({ variant = \"names\", className, dir, ref, ...props }: NavigationMenuProps) {\n  // Radix writes dir=\"ltr\" on the nav unless told otherwise. Without a dir, the menu takes the direction\n  // of the page around it, read as it mounts (as the menus and the tabs do), so its words and keys mirror.\n  const [around, setAround] = React.useState<\"rtl\">()\n  const composedRef = useComposedRefs(React.useCallback((node: HTMLElement | null) => {\n    const up = node?.parentElement\n    if (!dir && up && getComputedStyle(up).direction === \"rtl\") setAround(\"rtl\")\n  }, [dir]), ref)\n  return (\n    <VariantContext.Provider value={variant}>\n      <NavigationMenuPrimitive.Root\n        ref={composedRef}\n        dir={dir ?? around}\n        data-slot=\"navigation-menu\"\n        data-variant={variant}\n        className={cn(\"ot-navmenu\", className)}\n        {...props}\n      />\n    </VariantContext.Provider>\n  )\n}\n\nfunction NavigationMenuList({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {\n  return <NavigationMenuPrimitive.List data-slot=\"navigation-menu-list\" className={className} {...props} />\n}\n\nfunction NavigationMenuItem(props: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {\n  return <NavigationMenuPrimitive.Item data-slot=\"navigation-menu-item\" {...props} />\n}\n\n/** Inline, the names open into the line, so a passing pointer must not open them: only a press does. */\nconst held = (e: React.PointerEvent) => e.preventDefault()\n\n/** A small word with a ↓ that turns over while its panel is open (inline: a → that inks). */\nfunction NavigationMenuTrigger({ className, onPointerMove, onPointerLeave, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {\n  const inline = React.useContext(VariantContext) === \"inline\"\n  return (\n    <NavigationMenuPrimitive.Trigger\n      data-slot=\"navigation-menu-trigger\"\n      className={className}\n      onPointerMove={(e) => (onPointerMove?.(e), inline && held(e))}\n      onPointerLeave={(e) => (onPointerLeave?.(e), inline && held(e))}\n      {...props}\n    />\n  )\n}\n\n/** The panel. Its children are NavigationMenuLinks, each a large name that may hold a <small> line. */\nfunction NavigationMenuContent({ className, children, onPointerOver, onFocus, onPointerLeave, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {\n  const variant = React.useContext(VariantContext)\n  // lead: which name stands as the headline. The first, until another is pointed at or focused.\n  const [lead, setLead] = React.useState(0)\n  const links = React.Children.toArray(children).filter(React.isValidElement<{ children?: React.ReactNode }>)\n  const pick = (from: EventTarget) => {\n    const a = from instanceof Element ? from.closest(\"[data-slot=navigation-menu-link]\") : null\n    const all = a?.parentElement ? [...a.parentElement.children] : []\n    if (a && all.includes(a)) setLead(all.indexOf(a))\n  }\n  return (\n    <NavigationMenuPrimitive.Content\n      data-slot=\"navigation-menu-content\"\n      className={cn(\"ot-navmenu-panel\", className)}\n      onPointerOver={(e) => (onPointerOver?.(e), variant === \"lead\" && pick(e.target))}\n      onFocus={(e) => (onFocus?.(e), variant === \"lead\" && pick(e.target))}\n      onPointerLeave={(e) => (onPointerLeave?.(e), variant === \"inline\" && held(e))}\n      {...props}\n    >\n      {variant === \"lead\" ? (\n        <>\n          <div data-slot=\"navigation-menu-names\" className=\"ot-navmenu-names\">\n            {links.map((link, i) => React.cloneElement(link as React.ReactElement<{ \"data-lead\"?: boolean }>, { \"data-lead\": i === lead || undefined }))}\n          </div>\n          {links[lead] ? (\n            <div key={lead} data-slot=\"navigation-menu-lead\" aria-hidden=\"true\" className=\"ot-navmenu-lead\">\n              {links[lead].props.children}\n            </div>\n          ) : null}\n        </>\n      ) : (\n        children\n      )}\n    </NavigationMenuPrimitive.Content>\n  )\n}\n\nfunction NavigationMenuLink({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {\n  return <NavigationMenuPrimitive.Link data-slot=\"navigation-menu-link\" className={className} {...props} />\n}\n\nexport { type NavigationMenuProps, NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/navigation-menu.css",
      "content": "@layer components {\n  /* ── ot-navmenu ── small words open a panel of large names.\n     Radix marks each trigger and panel with data-state. The panel hangs under the\n     whole list: Radix wraps the list in a positioned box, so the items stay static. */\n  .ot-navmenu { position: relative; }\n  .ot-navmenu [data-slot=\"navigation-menu-list\"] { list-style: none; margin: 0; padding: 0 0 var(--ot-space-3); display: flex; flex-wrap: wrap; column-gap: var(--ot-space-6); }\n  .ot-navmenu [data-slot=\"navigation-menu-item\"] { position: static; }\n  .ot-navmenu :is([data-slot=\"navigation-menu-trigger\"], [data-slot=\"navigation-menu-item\"] > [data-slot=\"navigation-menu-link\"]) {\n    appearance: none; border: 0; background: none; margin: 0; padding: 0; font: inherit; color: var(--ot-graphite); text-decoration: none; cursor: pointer;\n    transition: color var(--ot-allegro);\n  }\n  .ot-navmenu :is([data-slot=\"navigation-menu-trigger\"], [data-slot=\"navigation-menu-item\"] > [data-slot=\"navigation-menu-link\"]):is(:hover, [data-state=\"open\"], [data-force~=\"hover\"]) { color: var(--ot-ink); }\n  .ot-navmenu [data-slot=\"navigation-menu-trigger\"]::after {\n    content: \"↓\" / \"\"; display: inline-block; margin-inline-start: 0.3em; font-size: 0.72em; color: var(--ot-pencil);\n    transition: rotate var(--ot-moderato) var(--ot-spiccato);\n  }\n  .ot-navmenu [data-slot=\"navigation-menu-trigger\"][data-state=\"open\"]::after { rotate: 180deg; }\n  .ot-navmenu-panel {\n    position: absolute; z-index: 20; inset-inline: 0; inset-block-start: 100%; padding: var(--ot-space-6) 0 var(--ot-space-7);\n    background: var(--ot-paper);\n    display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: var(--ot-space-4) var(--ot-gutter); align-content: start;\n    animation: ot-navmenu-in var(--ot-moderato) var(--ot-exhale);\n  }\n  .ot-navmenu-panel[data-state=\"closed\"] { animation: ot-navmenu-out var(--ot-allegro) var(--ot-breath) forwards; }\n  .ot-navmenu-panel::before { content: \"\"; position: absolute; inset: 0 0 auto; height: var(--ot-hairline); background: var(--ot-ink); transform-origin: 0 50%; }\n  :is([dir=\"rtl\"], [dir=\"rtl\"] *) .ot-navmenu-panel::before { transform-origin: 100% 50%; }\n  .ot-navmenu-panel[data-state=\"open\"]::before { animation: ot-draw-x var(--ot-andante) var(--ot-exhale) backwards; }\n  .ot-navmenu-panel[data-state=\"open\"] > * { animation: ot-arrive var(--ot-andante) var(--ot-exhale) backwards; animation-delay: calc(sibling-index() * var(--ot-arpeggio)); }\n  .ot-navmenu-panel > a {\n    display: grid; font-size: var(--ot-mf); line-height: var(--ot-mf-lh); letter-spacing: var(--ot-mf-tr); font-weight: 200; color: var(--ot-ink); text-decoration: none;\n    transition: font-weight var(--ot-moderato) var(--ot-exhale);\n  }\n  .ot-navmenu-panel > a:is(:hover, :focus-visible, [data-force~=\"hover\"]) { font-weight: 500; }\n  .ot-navmenu-panel small { font-size: var(--ot-pp); line-height: var(--ot-pp-lh); letter-spacing: var(--ot-pp-tr); font-weight: 400; color: var(--ot-pencil); }\n  /* lead: a contents page. The names stand small in a narrow column; the one pointed at or focused (the first, at\n     first) is set beside them as the headline, with its line under it, and steps up each time it changes. */\n  .ot-navmenu[data-variant=\"lead\"] .ot-navmenu-panel { grid-template-columns: minmax(9rem, 15rem) minmax(0, 1fr); }\n  .ot-navmenu-names { display: grid; justify-items: start; align-content: start; gap: var(--ot-space-2); padding-block-start: 0.35em; }\n  .ot-navmenu-names > a {\n    display: grid; font-size: var(--ot-mp); line-height: var(--ot-mp-lh); letter-spacing: var(--ot-mp-tr); color: var(--ot-graphite); text-decoration: none;\n    transition: color var(--ot-allegro);\n  }\n  .ot-navmenu-names > a:is([data-lead], :hover, :focus-visible, [data-force~=\"hover\"]) { color: var(--ot-ink); }\n  .ot-navmenu-names small { display: none; }\n  .ot-navmenu-lead {\n    display: grid; align-content: start; font-size: var(--ot-ff); line-height: var(--ot-ff-lh); letter-spacing: var(--ot-ff-tr); font-weight: 200; color: var(--ot-ink);\n    animation: ot-arrive var(--ot-moderato) var(--ot-exhale);\n  }\n  .ot-navmenu-lead small { max-inline-size: 36ch; margin-block-start: var(--ot-space-3); font-size: var(--ot-p); line-height: var(--ot-p-lh); letter-spacing: 0; color: var(--ot-graphite); }\n  /* A phone has no pointer to lead with: the names keep their lines, and the headline stands down. */\n  @media (max-width: 40rem) {\n    .ot-navmenu[data-variant=\"lead\"] .ot-navmenu-panel { grid-template-columns: minmax(0, 1fr); }\n    .ot-navmenu-names small { display: block; }\n    .ot-navmenu-lead { display: none; }\n  }\n\n  /* inline: one line of thought, as \"Healthy habits → for creatives\". Pressing a word opens its names into the line\n     after its arrow, larger, on the same baseline; the words after it make room, and the others step back. */\n  .ot-navmenu[data-variant=\"inline\"] [data-slot=\"navigation-menu-list\"] { align-items: baseline; column-gap: var(--ot-space-5); row-gap: var(--ot-space-2); padding-block-end: 0; }\n  .ot-navmenu[data-variant=\"inline\"] [data-slot=\"navigation-menu-item\"] { display: inline-flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--ot-space-4); }\n  .ot-navmenu[data-variant=\"inline\"] [data-slot=\"navigation-menu-trigger\"]::after { content: \"→\" / \"\"; rotate: none; transition: color var(--ot-allegro), translate var(--ot-moderato) var(--ot-exhale); }\n  .ot-navmenu[data-variant=\"inline\"] [data-slot=\"navigation-menu-trigger\"][data-state=\"open\"]::after { color: var(--ot-ink); translate: 0.15em 0; }\n  :is([dir=\"rtl\"], [dir=\"rtl\"] *) .ot-navmenu[data-variant=\"inline\"] [data-slot=\"navigation-menu-trigger\"]::after { scale: -1 1; }\n  :is([dir=\"rtl\"], [dir=\"rtl\"] *) .ot-navmenu[data-variant=\"inline\"] [data-slot=\"navigation-menu-trigger\"][data-state=\"open\"]::after { translate: -0.15em 0; }\n  .ot-navmenu[data-variant=\"inline\"]:has(.ot-navmenu-panel[data-state=\"open\"]) :is([data-slot=\"navigation-menu-trigger\"], [data-slot=\"navigation-menu-item\"] > [data-slot=\"navigation-menu-link\"]):not([data-state=\"open\"]) { color: var(--ot-pencil); }\n  .ot-navmenu[data-variant=\"inline\"] .ot-navmenu-panel {\n    position: static; display: inline-flex; align-items: baseline; column-gap: var(--ot-space-4); padding: 0; background: none;\n    overflow: clip; overflow-clip-margin: 0.3em; white-space: nowrap; animation: ot-navmenu-open var(--ot-andante) var(--ot-exhale);\n  }\n  .ot-navmenu[data-variant=\"inline\"] .ot-navmenu-panel[data-state=\"closed\"] { animation: ot-navmenu-shut var(--ot-moderato) var(--ot-breath) forwards; }\n  .ot-navmenu[data-variant=\"inline\"] .ot-navmenu-panel::before { content: none; }\n  .ot-navmenu[data-variant=\"inline\"] .ot-navmenu-panel[data-state=\"open\"] > * { animation: none; }\n  .ot-navmenu[data-variant=\"inline\"] .ot-navmenu-panel > a { display: inline; }\n  .ot-navmenu[data-variant=\"inline\"] .ot-navmenu-panel small { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n  @media (max-width: 40rem) {\n    .ot-navmenu[data-variant=\"inline\"] .ot-navmenu-panel { flex-wrap: wrap; white-space: normal; row-gap: 0; }\n    .ot-navmenu[data-variant=\"inline\"] .ot-navmenu-panel > a { font-size: var(--ot-mp); line-height: var(--ot-mp-lh); font-weight: 300; }\n  }\n  @keyframes ot-navmenu-open { from { inline-size: 0; opacity: 0; } }\n  @keyframes ot-navmenu-shut { to { inline-size: 0; opacity: 0; } }\n\n  @media (forced-colors: active) {\n    .ot-navmenu-panel::before { forced-color-adjust: none; background: CanvasText; }\n  }\n  @keyframes ot-navmenu-in { from { opacity: 0; } }\n  @keyframes ot-navmenu-out { to { opacity: 0; } }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/navigation-menu.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/navigation-menu.css\"": {}
  },
  "meta": {
    "movement": "VIII",
    "underneath": "radix",
    "contract": "ot-navmenu"
  },
  "type": "registry:ui"
}
