{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "menubar-tone",
  "title": "Tone menubar",
  "description": "Styled menubar: the Tone set. 5 decorative menubar variations (InfoMenubar, SuccessMenubar, WarningMenubar, DangerMenubar, MutedMenubar) 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",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/menubar-tone.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AlertTriangle, CheckCircle2, Info, Minus, OctagonAlert } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Tone menubar family: 5 decorative, SELF-SUFFICIENT menubars. NO radix or portal.\n   The skeleton is the same in every variant; the whole difference is the SEMANTIC\n   TONE family: info, success, warning, danger and the neutral muted. Each variant\n   uses its own token family (<tone>, <tone>-foreground, <tone>-soft,\n   <tone>-soft-foreground): a soft tone surface for the bar, a filled tone for the\n   open menu, a tone border for the panel and tone-soft for item hover. Classic\n   menubar behavior: while one menu is open, hovering a sibling trigger switches to\n   that menu. It closes on an outside click, on Escape (focus returns to the\n   trigger) and on an item selection. Left/Right arrows move between the top-level\n   menus and Up/Down moves inside the open one. No transform under reduced motion.\n   Color comes ONLY from tokens, via alpha color-mix. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nexport type MenubarTone = \"info\" | \"success\" | \"warning\" | \"danger\" | \"muted\"\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\nconst barSize: Record<StyledSize, string> = {\n  sm: \"h-8 gap-0.5 px-1 text-xs\",\n  md: \"h-9 gap-1 px-1 text-sm\",\n  lg: \"h-10 gap-1 px-1.5 text-sm\",\n  xl: \"h-11 gap-1.5 px-2 text-base\",\n}\n\nconst triggerSize: Record<StyledSize, string> = {\n  sm: \"h-6 px-2\",\n  md: \"h-7 px-2.5\",\n  lg: \"h-8 px-3\",\n  xl: \"h-9 px-3.5\",\n}\n\nconst panelSize: Record<StyledSize, string> = {\n  sm: \"min-w-40 text-xs\",\n  md: \"min-w-44 text-sm\",\n  lg: \"min-w-48 text-sm\",\n  xl: \"min-w-52 text-base\",\n}\n\nconst itemSize: Record<StyledSize, string> = {\n  sm: \"px-2 py-1.5\",\n  md: \"px-2.5 py-1.5\",\n  lg: \"px-3 py-2\",\n  xl: \"px-3.5 py-2\",\n}\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 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 mt-1.5 origin-top rounded-lg border p-1 shadow-lg outline-none\"\n\nconst barBase = \"inline-flex items-center rounded-lg border shadow-xs\"\n\ntype ToneStyle = {\n  bar: string\n  triggerActive: string\n  triggerIdle: string\n  panel: string\n  item: string\n  lead: string\n  Icon: React.ComponentType<{ className?: string }>\n}\n\n/* Every tone is fed from its own token family. The classes are written out\n   statically so the Tailwind scanner can see them. */\nconst tones: Record<MenubarTone, ToneStyle> = {\n  info: {\n    bar: \"border-info/30 bg-info-soft\",\n    triggerActive: \"bg-info text-info-foreground shadow-sm\",\n    triggerIdle: \"text-info-soft-foreground hover:bg-[color-mix(in_oklab,var(--color-info)_16%,transparent)]\",\n    panel: \"border-info/30 bg-popover text-popover-foreground\",\n    item: \"text-popover-foreground hover:bg-info-soft hover:text-info-soft-foreground\",\n    lead: \"text-info\",\n    Icon: Info,\n  },\n  success: {\n    bar: \"border-success/30 bg-success-soft\",\n    triggerActive: \"bg-success text-success-foreground shadow-sm\",\n    triggerIdle:\n      \"text-success-soft-foreground hover:bg-[color-mix(in_oklab,var(--color-success)_16%,transparent)]\",\n    panel: \"border-success/30 bg-popover text-popover-foreground\",\n    item: \"text-popover-foreground hover:bg-success-soft hover:text-success-soft-foreground\",\n    lead: \"text-success\",\n    Icon: CheckCircle2,\n  },\n  warning: {\n    bar: \"border-warning/40 bg-warning-soft\",\n    triggerActive: \"bg-warning text-warning-foreground shadow-sm\",\n    triggerIdle:\n      \"text-warning-soft-foreground hover:bg-[color-mix(in_oklab,var(--color-warning)_20%,transparent)]\",\n    panel: \"border-warning/40 bg-popover text-popover-foreground\",\n    item: \"text-popover-foreground hover:bg-warning-soft hover:text-warning-soft-foreground\",\n    lead: \"text-warning-soft-foreground\",\n    Icon: AlertTriangle,\n  },\n  danger: {\n    bar: \"border-danger/30 bg-danger-soft\",\n    triggerActive: \"bg-danger text-danger-foreground shadow-sm\",\n    triggerIdle:\n      \"text-danger-soft-foreground hover:bg-[color-mix(in_oklab,var(--color-danger)_16%,transparent)]\",\n    panel: \"border-danger/30 bg-popover text-popover-foreground\",\n    item: \"text-popover-foreground hover:bg-danger-soft hover:text-danger-soft-foreground\",\n    lead: \"text-danger\",\n    Icon: OctagonAlert,\n  },\n  muted: {\n    bar: \"border-border bg-muted\",\n    triggerActive: \"bg-foreground text-background shadow-sm\",\n    triggerIdle:\n      \"text-muted-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)] hover:text-foreground\",\n    panel: \"border-border bg-popover text-popover-foreground\",\n    item: \"text-popover-foreground hover:bg-muted hover:text-foreground\",\n    lead: \"text-muted-foreground\",\n    Icon: Minus,\n  },\n}\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({ props, tone }: { props: MenubarProps; tone: MenubarTone }) {\n  const { className, size = \"md\", menus = defaultMenus } = props\n  const t = tones[tone]\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  const Icon = t.Icon\n\n  return (\n    <div\n      ref={rootRef}\n      data-slot=\"styled-menubar\"\n      data-tone={tone}\n      role=\"menubar\"\n      className={cn(barBase, barSize[size], t.bar, 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                triggerSize[size],\n                isOpen ? t.triggerActive : t.triggerIdle\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                  data-tone={tone}\n                  className={cn(panelBase, panelSize[size], t.panel)}\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, itemSize[size], t.item)}\n                    >\n                      <Icon className={cn(\"size-3.5 shrink-0\", t.lead)} aria-hidden />\n                      {item.label}\n                    </button>\n                  ))}\n                </motion.div>\n              ) : null}\n            </AnimatePresence>\n          </div>\n        )\n      })}\n    </div>\n  )\n}\n\n/* Info: bilgi tonu, notr-mavi token ailesi. */\nexport function InfoMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return <MenubarCore props={{ className, size, menus }} tone=\"info\" />\n}\n\n/* Success: onay tonu, yesil token ailesi. */\nexport function SuccessMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return <MenubarCore props={{ className, size, menus }} tone=\"success\" />\n}\n\n/* Warning: dikkat tonu, sari token ailesi. */\nexport function WarningMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return <MenubarCore props={{ className, size, menus }} tone=\"warning\" />\n}\n\n/* Danger: yikici tonu, kirmizi token ailesi. */\nexport function DangerMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return <MenubarCore props={{ className, size, menus }} tone=\"danger\" />\n}\n\n/* Muted: notr ton, muted token ailesi. */\nexport function MutedMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return <MenubarCore props={{ className, size, menus }} tone=\"muted\" />\n}\n",
      "type": "registry:component",
      "target": "components/ui/menubar-tone.tsx"
    }
  ],
  "categories": [
    "styled",
    "menubar"
  ],
  "type": "registry:component"
}