{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "menubar-dense",
  "title": "Dense menubar",
  "description": "Styled menubar: the Dense set. 5 decorative menubar variations (CompactMenubar2, NarrowMenubar, TightMenubar, SlimMenubar, MicroMenubar) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/menubar-dense.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Dense menubar family: 5 decorative, SELF-SUFFICIENT menubars. NO radix or\n   portal. The visual language is the same in every variant; the whole difference is\n   the SCALE: Compact tightens slightly, Narrow trims the horizontal padding, Tight\n   lowers the line height, Slim thins the bar, and Micro drops to toolbar density.\n   The size prop scales each variant within its own density. Classic menubar\n   behavior: while one menu is open, hovering a sibling trigger switches to that\n   menu. It closes on an outside click, on Escape (focus returns to the trigger) and\n   on an item selection. Left/Right arrows move between the top-level menus and\n   Up/Down moves inside the open one. No transform under reduced motion. Color comes\n   ONLY from tokens, via alpha color-mix. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\ntype MenuItem = { label: React.ReactNode; onSelect?: () => void }\ntype Menu = { label: React.ReactNode; items: MenuItem[] }\n\ninterface MenubarProps {\n  className?: string\n  size?: StyledSize\n  menus?: Menu[]\n}\n\nconst defaultMenus: Menu[] = [\n  {\n    label: \"File\",\n    items: [\n      { label: \"New File\" },\n      { label: \"Open...\" },\n      { label: \"Save\" },\n      { label: \"Save As...\" },\n    ],\n  },\n  {\n    label: \"Edit\",\n    items: [\n      { label: \"Undo\" },\n      { label: \"Redo\" },\n      { label: \"Cut\" },\n      { label: \"Copy\" },\n      { label: \"Paste\" },\n    ],\n  },\n  {\n    label: \"View\",\n    items: [{ label: \"Zoom In\" }, { label: \"Zoom Out\" }, { label: \"Full Screen\" }],\n  },\n]\n\ntype Scale = {\n  bar: Record<StyledSize, string>\n  trigger: Record<StyledSize, string>\n  panel: Record<StyledSize, string>\n  item: Record<StyledSize, string>\n  panelPad: string\n}\n\nconst softHover =\n  \"hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)]\"\n\nconst triggerBase =\n  \"inline-flex select-none items-center gap-1.5 whitespace-nowrap rounded-md font-medium outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 [&>svg]:size-4 [&>svg]:shrink-0 [&>i]:text-base [&>i]:leading-none\"\n\nconst itemBase =\n  \"flex w-full cursor-default select-none items-center gap-2 rounded-md text-left text-popover-foreground outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 [&>svg]:size-4 [&>svg]:shrink-0 [&>i]:text-base [&>i]:leading-none\"\n\nconst panelBase =\n  \"absolute left-0 top-full z-50 origin-top rounded-lg border border-border bg-popover text-popover-foreground shadow-lg outline-none\"\n\nconst barBase = \"inline-flex items-center border border-border bg-card shadow-xs\"\n\n/* The shared menubar core. The panel is always absolutely positioned inside the\n   relative wrapper of its own trigger, so many examples on a single page never\n   collide. */\nfunction MenubarCore({\n  props,\n  scale,\n  barClassName,\n}: {\n  props: MenubarProps\n  scale: Scale\n  barClassName?: string\n}) {\n  const { className, size = \"md\", menus = defaultMenus } = props\n  const reduce = useReducedMotion()\n  const uid = React.useId()\n  const [openIndex, setOpenIndex] = React.useState<number | null>(null)\n  const [autoFocusItem, setAutoFocusItem] = React.useState(false)\n  const rootRef = React.useRef<HTMLDivElement>(null)\n  const triggerRefs = React.useRef<Array<HTMLButtonElement | null>>([])\n  const itemRefs = React.useRef<Array<HTMLButtonElement | null>>([])\n\n  React.useEffect(() => {\n    if (openIndex === null) return\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key !== \"Escape\") return\n      triggerRefs.current[openIndex]?.focus()\n      setOpenIndex(null)\n    }\n    const onPointer = (e: PointerEvent) => {\n      const node = rootRef.current\n      if (node && e.target instanceof Node && !node.contains(e.target)) {\n        setOpenIndex(null)\n      }\n    }\n    window.addEventListener(\"keydown\", onKey)\n    window.addEventListener(\"pointerdown\", onPointer)\n    return () => {\n      window.removeEventListener(\"keydown\", onKey)\n      window.removeEventListener(\"pointerdown\", onPointer)\n    }\n  }, [openIndex])\n\n  React.useEffect(() => {\n    if (openIndex === null || !autoFocusItem) return\n    const id = window.requestAnimationFrame(() => itemRefs.current[0]?.focus())\n    setAutoFocusItem(false)\n    return () => window.cancelAnimationFrame(id)\n  }, [openIndex, autoFocusItem])\n\n  const moveTrigger = (from: number, dir: 1 | -1) => {\n    const next = (from + dir + menus.length) % menus.length\n    triggerRefs.current[next]?.focus()\n    if (openIndex !== null) setOpenIndex(next)\n  }\n\n  const onTriggerKeyDown = (e: React.KeyboardEvent, i: number) => {\n    if (e.key === \"ArrowRight\") {\n      e.preventDefault()\n      moveTrigger(i, 1)\n    } else if (e.key === \"ArrowLeft\") {\n      e.preventDefault()\n      moveTrigger(i, -1)\n    } else if (e.key === \"ArrowDown\") {\n      e.preventDefault()\n      setOpenIndex(i)\n      setAutoFocusItem(true)\n    }\n  }\n\n  const onItemKeyDown = (e: React.KeyboardEvent, j: number, count: number, i: number) => {\n    if (e.key === \"ArrowDown\") {\n      e.preventDefault()\n      itemRefs.current[(j + 1) % count]?.focus()\n    } else if (e.key === \"ArrowUp\") {\n      e.preventDefault()\n      itemRefs.current[(j - 1 + count) % count]?.focus()\n    } else if (e.key === \"ArrowRight\" || e.key === \"ArrowLeft\") {\n      e.preventDefault()\n      moveTrigger(i, e.key === \"ArrowRight\" ? 1 : -1)\n      setAutoFocusItem(true)\n    }\n  }\n\n  const enter = reduce\n    ? { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } }\n    : {\n        initial: { opacity: 0, scale: 0.97, y: -4 },\n        animate: { opacity: 1, scale: 1, y: 0 },\n        exit: { opacity: 0, scale: 0.97, y: -4 },\n      }\n\n  return (\n    <div\n      ref={rootRef}\n      data-slot=\"styled-menubar\"\n      role=\"menubar\"\n      className={cn(barBase, scale.bar[size], barClassName, className)}\n    >\n      {menus.map((menu, i) => {\n        const isOpen = openIndex === i\n        const triggerId = `${uid}-trigger-${i}`\n        const menuId = `${uid}-menu-${i}`\n        return (\n          <div key={i} className=\"relative\">\n            <button\n              type=\"button\"\n              id={triggerId}\n              ref={(node) => {\n                triggerRefs.current[i] = node\n              }}\n              role=\"menuitem\"\n              aria-haspopup=\"menu\"\n              aria-expanded={isOpen}\n              aria-controls={isOpen ? menuId : undefined}\n              onClick={() => setOpenIndex(isOpen ? null : i)}\n              onPointerEnter={() => {\n                if (openIndex !== null) setOpenIndex(i)\n              }}\n              onKeyDown={(e) => onTriggerKeyDown(e, i)}\n              className={cn(\n                triggerBase,\n                scale.trigger[size],\n                isOpen\n                  ? \"bg-accent text-accent-foreground\"\n                  : cn(\"text-muted-foreground hover:text-foreground\", softHover)\n              )}\n            >\n              {menu.label}\n            </button>\n\n            <AnimatePresence>\n              {isOpen ? (\n                <motion.div\n                  id={menuId}\n                  role=\"menu\"\n                  aria-labelledby={triggerId}\n                  className={cn(panelBase, scale.panel[size], scale.panelPad)}\n                  initial={enter.initial}\n                  animate={enter.animate}\n                  exit={enter.exit}\n                  transition={\n                    reduce\n                      ? { duration: 0.12 }\n                      : { type: \"spring\" as const, stiffness: 360, damping: 26 }\n                  }\n                >\n                  {menu.items.map((item, j) => (\n                    <button\n                      key={j}\n                      type=\"button\"\n                      role=\"menuitem\"\n                      ref={(node) => {\n                        if (openIndex === i) itemRefs.current[j] = node\n                      }}\n                      onClick={() => {\n                        item.onSelect?.()\n                        triggerRefs.current[i]?.focus()\n                        setOpenIndex(null)\n                      }}\n                      onKeyDown={(e) => onItemKeyDown(e, j, menu.items.length, i)}\n                      className={cn(itemBase, scale.item[size], softHover)}\n                    >\n                      {item.label}\n                    </button>\n                  ))}\n                </motion.div>\n              ) : null}\n            </AnimatePresence>\n          </div>\n        )\n      })}\n    </div>\n  )\n}\n\n/* Compact: temel olcegin bir tik sikilastirilmis hali. */\nconst compactScale: Scale = {\n  bar: {\n    sm: \"h-7 gap-0.5 rounded-md px-0.5 text-xs\",\n    md: \"h-8 gap-0.5 rounded-md px-1 text-xs\",\n    lg: \"h-9 gap-1 rounded-lg px-1 text-sm\",\n    xl: \"h-10 gap-1 rounded-lg px-1.5 text-sm\",\n  },\n  trigger: {\n    sm: \"h-5 px-1.5\",\n    md: \"h-6 px-2\",\n    lg: \"h-7 px-2.5\",\n    xl: \"h-8 px-3\",\n  },\n  panel: {\n    sm: \"mt-1 min-w-36 text-xs\",\n    md: \"mt-1 min-w-40 text-xs\",\n    lg: \"mt-1 min-w-44 text-sm\",\n    xl: \"mt-1.5 min-w-48 text-sm\",\n  },\n  item: {\n    sm: \"px-1.5 py-1\",\n    md: \"px-2 py-1\",\n    lg: \"px-2.5 py-1.5\",\n    xl: \"px-3 py-1.5\",\n  },\n  panelPad: \"p-1\",\n}\n\nexport function CompactMenubar2({ className, size = \"md\", menus }: MenubarProps) {\n  return <MenubarCore props={{ className, size, menus }} scale={compactScale} />\n}\n\n/* Narrow: the horizontal padding is trimmed, the row height is kept. */\nconst narrowScale: Scale = {\n  bar: {\n    sm: \"h-8 gap-0 rounded-md px-0.5 text-xs\",\n    md: \"h-9 gap-0 rounded-md px-0.5 text-sm\",\n    lg: \"h-10 gap-0.5 rounded-lg px-1 text-sm\",\n    xl: \"h-11 gap-0.5 rounded-lg px-1 text-base\",\n  },\n  trigger: {\n    sm: \"h-6 px-1\",\n    md: \"h-7 px-1.5\",\n    lg: \"h-8 px-2\",\n    xl: \"h-9 px-2\",\n  },\n  panel: {\n    sm: \"mt-1 min-w-32 text-xs\",\n    md: \"mt-1 min-w-36 text-sm\",\n    lg: \"mt-1 min-w-40 text-sm\",\n    xl: \"mt-1.5 min-w-44 text-base\",\n  },\n  item: {\n    sm: \"px-1.5 py-1.5\",\n    md: \"px-1.5 py-1.5\",\n    lg: \"px-2 py-2\",\n    xl: \"px-2 py-2\",\n  },\n  panelPad: \"p-0.5\",\n}\n\nexport function NarrowMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return <MenubarCore props={{ className, size, menus }} scale={narrowScale} />\n}\n\n/* Tight: dikey ritim sikisir, menu satirlari birbirine yaklasir. */\nconst tightScale: Scale = {\n  bar: {\n    sm: \"h-6 gap-0.5 rounded-md px-0.5 text-[0.6875rem]\",\n    md: \"h-7 gap-0.5 rounded-md px-0.5 text-xs\",\n    lg: \"h-8 gap-0.5 rounded-md px-1 text-xs\",\n    xl: \"h-9 gap-1 rounded-lg px-1 text-sm\",\n  },\n  trigger: {\n    sm: \"h-5 px-1.5\",\n    md: \"h-5 px-2\",\n    lg: \"h-6 px-2\",\n    xl: \"h-7 px-2.5\",\n  },\n  panel: {\n    sm: \"mt-0.5 min-w-32 text-[0.6875rem]\",\n    md: \"mt-1 min-w-36 text-xs\",\n    lg: \"mt-1 min-w-40 text-xs\",\n    xl: \"mt-1 min-w-44 text-sm\",\n  },\n  item: {\n    sm: \"px-1.5 py-0.5\",\n    md: \"px-2 py-0.5\",\n    lg: \"px-2 py-1\",\n    xl: \"px-2.5 py-1\",\n  },\n  panelPad: \"p-0.5\",\n}\n\nexport function TightMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return <MenubarCore props={{ className, size, menus }} scale={tightScale} />\n}\n\n/* Slim: the bar is borderless and thin, carrying a hairline underneath. */\nconst slimScale: Scale = {\n  bar: {\n    sm: \"h-6 gap-1 px-0 text-[0.6875rem]\",\n    md: \"h-7 gap-1 px-0 text-xs\",\n    lg: \"h-8 gap-1.5 px-0 text-xs\",\n    xl: \"h-9 gap-1.5 px-0 text-sm\",\n  },\n  trigger: {\n    sm: \"h-5 px-1.5\",\n    md: \"h-6 px-2\",\n    lg: \"h-6 px-2\",\n    xl: \"h-7 px-2.5\",\n  },\n  panel: {\n    sm: \"mt-1 min-w-32 text-[0.6875rem]\",\n    md: \"mt-1 min-w-36 text-xs\",\n    lg: \"mt-1 min-w-40 text-xs\",\n    xl: \"mt-1.5 min-w-44 text-sm\",\n  },\n  item: {\n    sm: \"px-1.5 py-1\",\n    md: \"px-2 py-1\",\n    lg: \"px-2 py-1\",\n    xl: \"px-2.5 py-1.5\",\n  },\n  panelPad: \"p-0.5\",\n}\n\nexport function SlimMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return (\n    <MenubarCore\n      props={{ className, size, menus }}\n      scale={slimScale}\n      barClassName=\"rounded-none border-0 border-b border-border bg-transparent shadow-none\"\n    />\n  )\n}\n\n/* Micro: en yogun kademe, arac cubugu olcegi. */\nconst microScale: Scale = {\n  bar: {\n    sm: \"h-5 gap-0 rounded px-0 text-[0.625rem]\",\n    md: \"h-6 gap-0 rounded px-0.5 text-[0.6875rem]\",\n    lg: \"h-7 gap-0 rounded-md px-0.5 text-xs\",\n    xl: \"h-8 gap-0.5 rounded-md px-0.5 text-xs\",\n  },\n  trigger: {\n    sm: \"h-4 gap-1 rounded-sm px-1\",\n    md: \"h-5 gap-1 rounded-sm px-1\",\n    lg: \"h-6 gap-1 rounded px-1.5\",\n    xl: \"h-7 gap-1 rounded px-1.5\",\n  },\n  panel: {\n    sm: \"mt-0.5 min-w-28 rounded-md text-[0.625rem]\",\n    md: \"mt-0.5 min-w-32 rounded-md text-[0.6875rem]\",\n    lg: \"mt-1 min-w-36 rounded-md text-xs\",\n    xl: \"mt-1 min-w-40 text-xs\",\n  },\n  item: {\n    sm: \"gap-1 rounded-sm px-1 py-0.5\",\n    md: \"gap-1 rounded-sm px-1.5 py-0.5\",\n    lg: \"gap-1.5 rounded px-1.5 py-0.5\",\n    xl: \"gap-1.5 rounded px-2 py-1\",\n  },\n  panelPad: \"p-0.5\",\n}\n\nexport function MicroMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return (\n    <MenubarCore\n      props={{ className, size, menus }}\n      scale={microScale}\n      barClassName=\"[&_button]:font-normal\"\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/menubar-dense.tsx"
    }
  ],
  "categories": [
    "styled",
    "menubar"
  ],
  "type": "registry:component"
}