{"version":3,"file":"Menubar.cjs","names":[],"sources":["../../../src/components/Menubar/Menubar.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — a menubar has two keyboard planes — arrow keys\n * move between menus, arrows inside one move between items — and the body owns both\n * because a key press is routed by which plane is open.\n */\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Menubar.module.css\";\n\nexport type MenubarItem =\n    | {\n          /** Visible label for the action. */\n          label: ReactNode;\n          /** Invoked when the action is selected. */\n          onSelect?: () => void;\n          /** Disable the action. */\n          disabled?: boolean;\n          /** Optional shortcut hint rendered right-aligned (e.g. `\"⌘S\"`). */\n          shortcut?: string;\n      }\n    | { separator: true };\n\nexport interface MenubarMenu {\n    /** Top-level menu label (e.g. `\"File\"`). */\n    label: ReactNode;\n    /** Items shown in the menu's dropdown. */\n    items: MenubarItem[];\n}\n\nexport interface MenubarProps extends HTMLAttributes<HTMLDivElement> {\n    /** Top-level menus rendered left-to-right. */\n    menus: MenubarMenu[];\n}\n\nfunction isSeparator(item: MenubarItem): item is { separator: true } {\n    return \"separator\" in item;\n}\n\n/**\n * Application menubar (File / Edit-style).\n *\n * - `role=\"menubar\"`; each menu is a button that opens a dropdown.\n * - Arrow Left/Right moves between menus (wrapping); opening one closes others.\n * - Closes on outside click, Escape, or selecting an item.\n * - Items may carry a `shortcut` (right-aligned) or be a `{ separator: true }`.\n *\n * @example\n * <Menubar\n *     menus={[\n *         {\n *             label: \"File\",\n *             items: [\n *                 { label: \"New\", shortcut: \"⌘N\", onSelect: () => create() },\n *                 { separator: true },\n *                 { label: \"Quit\", onSelect: () => quit() },\n *             ],\n *         },\n *     ]}\n * />\n */\nexport function Menubar({ menus, className, ...props }: MenubarProps) {\n    const [openIndex, setOpenIndex] = useState<number>(-1);\n    const baseId = useId();\n    const rootRef = useRef<HTMLDivElement>(null);\n    const triggerRefs = useRef<Array<HTMLButtonElement | null>>([]);\n\n    const close = useCallback((): void => {\n        setOpenIndex(-1);\n    }, []);\n\n    useEffect(() => {\n        if (openIndex === -1) return;\n        const onKey = (event: KeyboardEvent): void => {\n            if (event.key === \"Escape\") {\n                close();\n                triggerRefs.current[openIndex]?.focus();\n                return;\n            }\n            if (event.key === \"ArrowRight\") {\n                event.preventDefault();\n                const next = (openIndex + 1) % menus.length;\n                setOpenIndex(next);\n                triggerRefs.current[next]?.focus();\n            }\n            if (event.key === \"ArrowLeft\") {\n                event.preventDefault();\n                const prev = (openIndex - 1 + menus.length) % menus.length;\n                setOpenIndex(prev);\n                triggerRefs.current[prev]?.focus();\n            }\n        };\n        const onDown = (event: MouseEvent): void => {\n            if (rootRef.current && !rootRef.current.contains(event.target as Node)) close();\n        };\n        window.addEventListener(\"keydown\", onKey);\n        window.addEventListener(\"mousedown\", onDown);\n        return () => {\n            window.removeEventListener(\"keydown\", onKey);\n            window.removeEventListener(\"mousedown\", onDown);\n        };\n    }, [openIndex, menus.length, close]);\n\n    return (\n        <div ref={rootRef} role=\"menubar\" className={cn(styles.root, className)} {...props}>\n            {menus.map((menu, index) => {\n                const open = openIndex === index;\n                const panelId = `${baseId}-panel-${index}`;\n                return (\n                    <div key={index} className={styles.menu}>\n                        <button\n                            ref={(el) => {\n                                triggerRefs.current[index] = el;\n                            }}\n                            type=\"button\"\n                            role=\"menuitem\"\n                            aria-haspopup=\"menu\"\n                            aria-expanded={open}\n                            aria-controls={open ? panelId : undefined}\n                            className={cn(styles.trigger, open && styles.triggerOpen)}\n                            onClick={() => setOpenIndex((prev) => (prev === index ? -1 : index))}\n                        >\n                            {menu.label}\n                        </button>\n                        {open && (\n                            <ul id={panelId} role=\"menu\" className={styles.panel}>\n                                {menu.items.map((item, itemIndex) => {\n                                    if (isSeparator(item)) {\n                                        return (\n                                            <li\n                                                key={itemIndex}\n                                                role=\"separator\"\n                                                className={styles.separator}\n                                                aria-hidden\n                                            />\n                                        );\n                                    }\n                                    return (\n                                        <li key={itemIndex} role=\"none\">\n                                            <button\n                                                type=\"button\"\n                                                role=\"menuitem\"\n                                                className={styles.item}\n                                                disabled={item.disabled}\n                                                onClick={() => {\n                                                    item.onSelect?.();\n                                                    close();\n                                                }}\n                                            >\n                                                <span className={styles.itemLabel}>\n                                                    {item.label}\n                                                </span>\n                                                {item.shortcut && (\n                                                    <span className={styles.shortcut}>\n                                                        {item.shortcut}\n                                                    </span>\n                                                )}\n                                            </button>\n                                        </li>\n                                    );\n                                })}\n                            </ul>\n                        )}\n                    </div>\n                );\n            })}\n        </div>\n    );\n}\n"],"mappings":"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"}