{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "menubar-shortcut",
  "title": "Shortcut menubar",
  "description": "Styled menubar: the Shortcut set. 5 decorative menubar variations (KbdMenubar, InlineMenubar, TrailingMenubar, GroupedMenubar, CompactMenubar) 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-shortcut.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/* Shortcut menubar family: 5 decorative, SELF-SUFFICIENT menubars. NO radix or\n   portal. The bar is the same in every variant; the whole difference is the\n   presentation of the KEYBOARD SHORTCUT inside the menu: kbd chips, an inline note\n   right beside the label, right-aligned mono text, a list grouped with separators,\n   and a compressed single line. Classic menubar behavior: while one menu is open,\n   hovering a sibling trigger switches to that menu. It closes on an outside click,\n   on Escape (focus returns to the trigger) and on an item selection. Left/Right\n   arrows move between the top-level menus and Up/Down moves inside the open one. No\n   transform under reduced motion. Color comes ONLY from tokens, via alpha\n   color-mix. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\ntype MenuItem = {\n  label: React.ReactNode\n  shortcut?: string\n  /** In the Grouped variant a separator is drawn after this item; the others ignore\n    it. */\n  separatorAfter?: boolean\n  onSelect?: () => void\n}\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\", shortcut: \"Ctrl N\" },\n      { label: \"Open...\", shortcut: \"Ctrl O\", separatorAfter: true },\n      { label: \"Save\", shortcut: \"Ctrl S\" },\n      { label: \"Save As...\", shortcut: \"Ctrl Shift S\" },\n    ],\n  },\n  {\n    label: \"Edit\",\n    items: [\n      { label: \"Undo\", shortcut: \"Ctrl Z\" },\n      { label: \"Redo\", shortcut: \"Ctrl Y\", separatorAfter: true },\n      { label: \"Cut\", shortcut: \"Ctrl X\" },\n      { label: \"Copy\", shortcut: \"Ctrl C\" },\n      { label: \"Paste\", shortcut: \"Ctrl V\" },\n    ],\n  },\n  {\n    label: \"View\",\n    items: [\n      { label: \"Zoom In\", shortcut: \"Ctrl +\" },\n      { label: \"Zoom Out\", shortcut: \"Ctrl -\", separatorAfter: true },\n      { label: \"Full Screen\", shortcut: \"F11\" },\n    ],\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-48 text-xs\",\n  md: \"min-w-56 text-sm\",\n  lg: \"min-w-60 text-sm\",\n  xl: \"min-w-64 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 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 mt-1.5 origin-top rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-lg outline-none\"\n\n/* Kisayol dizesini tek tek tuslara ayirir: \"Ctrl Shift S\" -> [\"Ctrl\",\"Shift\",\"S\"]. */\nfunction keys(shortcut: string) {\n  return shortcut.split(\" \").filter(Boolean)\n}\n\ntype ShortcutRender = (shortcut: string | undefined, size: StyledSize) => React.ReactNode\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  renderShortcut,\n  showSeparators = false,\n  itemClassName,\n  panelClassName,\n}: {\n  props: MenubarProps\n  renderShortcut: ShortcutRender\n  showSeparators?: boolean\n  itemClassName?: string\n  panelClassName?: 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(\n        \"inline-flex items-center rounded-lg border border-border bg-card shadow-xs\",\n        barSize[size],\n        className\n      )}\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\n                  ? \"bg-accent text-accent-foreground\"\n                  : cn(\"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, panelSize[size], panelClassName)}\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                    <React.Fragment key={j}>\n                      <button\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], softHover, itemClassName)}\n                      >\n                        {item.label}\n                        {renderShortcut(item.shortcut, size)}\n                      </button>\n                      {showSeparators && item.separatorAfter ? (\n                        <div\n                          aria-hidden\n                          className=\"my-1 h-px bg-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)]\"\n                        />\n                      ) : null}\n                    </React.Fragment>\n                  ))}\n                </motion.div>\n              ) : null}\n            </AnimatePresence>\n          </div>\n        )\n      })}\n    </div>\n  )\n}\n\nconst kbdSize: Record<StyledSize, string> = {\n  sm: \"h-4 min-w-4 px-1 text-[0.625rem]\",\n  md: \"h-5 min-w-5 px-1 text-[0.6875rem]\",\n  lg: \"h-5 min-w-5 px-1.5 text-xs\",\n  xl: \"h-6 min-w-6 px-1.5 text-xs\",\n}\n\n/* Kbd: every key is right-aligned as a separate token kbd chip. */\nexport function KbdMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return (\n    <MenubarCore\n      props={{ className, size, menus }}\n      renderShortcut={(shortcut, s) =>\n        shortcut ? (\n          <span className=\"ml-auto inline-flex items-center gap-1 pl-4\">\n            {keys(shortcut).map((k) => (\n              <kbd\n                key={k}\n                className={cn(\n                  \"inline-flex items-center justify-center rounded border border-border bg-muted font-medium text-muted-foreground\",\n                  kbdSize[s]\n                )}\n              >\n                {k}\n              </kbd>\n            ))}\n          </span>\n        ) : null\n      }\n    />\n  )\n}\n\n/* Inline: kisayol etiketin hemen yaninda, sessiz bir not olarak akar. */\nexport function InlineMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return (\n    <MenubarCore\n      props={{ className, size, menus }}\n      renderShortcut={(shortcut) =>\n        shortcut ? (\n          <span className=\"text-[0.75em] font-normal text-muted-foreground\">\n            ({keys(shortcut).join(\"+\")})\n          </span>\n        ) : null\n      }\n    />\n  )\n}\n\n/* Trailing: kisayol saga yaslanmis, aralikli mono metin. */\nexport function TrailingMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return (\n    <MenubarCore\n      props={{ className, size, menus }}\n      renderShortcut={(shortcut) =>\n        shortcut ? (\n          <span className=\"ml-auto pl-6 font-mono text-[0.75em] tracking-widest text-muted-foreground\">\n            {keys(shortcut).join(\"+\")}\n          </span>\n        ) : null\n      }\n    />\n  )\n}\n\n/* Grouped: iliskili itemler ayraclarla bloklara ayrilir, kisayollar saga yaslanir. */\nexport function GroupedMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return (\n    <MenubarCore\n      props={{ className, size, menus }}\n      showSeparators\n      renderShortcut={(shortcut, s) =>\n        shortcut ? (\n          <kbd\n            className={cn(\n              \"ml-auto inline-flex items-center justify-center rounded border border-border bg-muted font-medium tracking-wide text-muted-foreground\",\n              kbdSize[s]\n            )}\n          >\n            {keys(shortcut).join(\" \")}\n          </kbd>\n        ) : null\n      }\n    />\n  )\n}\n\n/* Compact: dar satirlar, kisayol soluk ve tek parca halinde sagda. */\nexport function CompactMenubar({ className, size = \"md\", menus }: MenubarProps) {\n  return (\n    <MenubarCore\n      props={{ className, size, menus }}\n      itemClassName=\"py-1\"\n      panelClassName=\"p-0.5\"\n      renderShortcut={(shortcut) =>\n        shortcut ? (\n          <span className=\"ml-auto pl-4 text-[0.7em] text-[color-mix(in_oklab,var(--color-muted-foreground)_80%,transparent)]\">\n            {keys(shortcut).join(\"\")}\n          </span>\n        ) : null\n      }\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/menubar-shortcut.tsx"
    }
  ],
  "categories": [
    "styled",
    "menubar"
  ],
  "type": "registry:component"
}