{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "expandable-menu-navbar",
  "type": "registry:ui",
  "title": "Expandable Menu Navbar",
  "description": "A monochrome floating navbar that springs open into a stacked menu of grouped, metadata-rich nav items with a status footer and keyboard shortcut.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/ruixenui/expandable-menu-navbar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\n\ninterface NavItem {\n  label: string;\n  meta?: string;\n  href?: string;\n  external?: boolean;\n}\n\ninterface NavSection {\n  title?: string;\n  items: NavItem[];\n}\n\ninterface ExpandableMenuNavbarProps {\n  brand?: React.ReactNode;\n  brandLabel?: string;\n  sections?: NavSection[];\n  status?: string;\n  version?: string;\n  shortcut?: string;\n  height?: number | string;\n  className?: string;\n}\n\nconst DEFAULT_SECTIONS: NavSection[] = [\n  {\n    items: [\n      { label: \"Index\", meta: \"01\" },\n      { label: \"Work\", meta: \"24\" },\n      { label: \"Notes\", meta: \"12\" },\n      { label: \"Lab\", meta: \"08\" },\n    ],\n  },\n  {\n    title: \"Resources\",\n    items: [\n      { label: \"Changelog\", meta: \"v2.4\" },\n      { label: \"Library\", meta: \"126\" },\n      { label: \"Tools\", meta: \"07\" },\n      { label: \"Press kit\", meta: \"↗\", external: true },\n    ],\n  },\n  {\n    title: \"Connect\",\n    items: [\n      { label: \"Contact\" },\n      { label: \"Newsletter\", meta: \"monthly\" },\n      { label: \"Twitter\", meta: \"↗\", external: true },\n      { label: \"GitHub\", meta: \"↗\", external: true },\n    ],\n  },\n];\n\nfunction StackedMark() {\n  return (\n    <svg\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 20 20\"\n      aria-hidden\n      className=\"text-foreground\"\n    >\n      <rect x=\"2\" y=\"2\" width=\"11\" height=\"11\" rx=\"1.5\" fill=\"currentColor\" />\n      <rect\n        x=\"7\"\n        y=\"7\"\n        width=\"11\"\n        height=\"11\"\n        rx=\"1.5\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"1.4\"\n      />\n    </svg>\n  );\n}\n\nconst SPRING = { type: \"spring\", stiffness: 280, damping: 32 } as const;\nconst ITEM_EASE = [0.22, 0.61, 0.36, 1] as const;\nconst COLLAPSED_HEIGHT = 56;\nconst WRAPPER_PADDING = 32;\n\nexport default function ExpandableMenuNavbar({\n  brand,\n  brandLabel = \"Index\",\n  sections = DEFAULT_SECTIONS,\n  status = \"Available\",\n  version = \"v2.4.1\",\n  shortcut = \"M\",\n  height = 560,\n  className,\n}: ExpandableMenuNavbarProps) {\n  const [open, setOpen] = React.useState(false);\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const scrollRef = React.useRef<HTMLDivElement>(null);\n  const [measuredOpenHeight, setMeasuredOpenHeight] = React.useState(0);\n\n  React.useEffect(() => {\n    const el = containerRef.current;\n    if (!el) return;\n    const measure = () => {\n      setMeasuredOpenHeight(Math.max(0, el.clientHeight - WRAPPER_PADDING));\n    };\n    measure();\n    const ro = new ResizeObserver(measure);\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, []);\n\n  React.useEffect(() => {\n    if (!open) return;\n    const handler = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") setOpen(false);\n    };\n    window.addEventListener(\"keydown\", handler);\n    return () => window.removeEventListener(\"keydown\", handler);\n  }, [open]);\n\n  React.useEffect(() => {\n    if (open && scrollRef.current) {\n      scrollRef.current.scrollTop = 0;\n    }\n  }, [open]);\n\n  const fallbackOpenHeight =\n    (typeof height === \"number\" ? height : 560) - WRAPPER_PADDING;\n  const openHeight =\n    measuredOpenHeight > 0 ? measuredOpenHeight : fallbackOpenHeight;\n\n  const flatIndex = React.useMemo(() => {\n    const offsets: number[] = [];\n    let acc = 0;\n    for (const s of sections) {\n      offsets.push(acc);\n      acc += s.items.length;\n    }\n    return offsets;\n  }, [sections]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={[\n        \"relative w-full overflow-hidden rounded-2xl border border-border bg-background\",\n        className,\n      ]\n        .filter(Boolean)\n        .join(\" \")}\n      style={{ height }}\n    >\n      <AnimatePresence>\n        {open && (\n          <motion.div\n            key=\"scrim\"\n            aria-hidden\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{ duration: 0.22 }}\n            className=\"absolute inset-0 z-[1] bg-foreground/[0.04] backdrop-blur-[2px]\"\n            onClick={() => setOpen(false)}\n          />\n        )}\n      </AnimatePresence>\n\n      <div className=\"pointer-events-none absolute inset-0 z-10 flex items-start justify-center p-4\">\n        <motion.div\n          initial={false}\n          animate={{ height: open ? openHeight : COLLAPSED_HEIGHT }}\n          transition={SPRING}\n          className={[\n            \"pointer-events-auto flex w-full max-w-md flex-col overflow-hidden\",\n            \"rounded-2xl border border-border bg-card/85 backdrop-blur-md\",\n            \"shadow-[0_1px_2px_rgba(0,0,0,0.04),0_8px_28px_-12px_rgba(0,0,0,0.18)]\",\n            \"dark:shadow-[0_1px_2px_rgba(0,0,0,0.4),0_8px_28px_-12px_rgba(0,0,0,0.6)]\",\n          ].join(\" \")}\n        >\n          <button\n            type=\"button\"\n            onClick={() => setOpen((v) => !v)}\n            aria-expanded={open}\n            aria-controls=\"expandable-menu-content\"\n            className=\"flex h-14 shrink-0 items-center justify-between gap-3 px-4 text-foreground outline-none focus-visible:ring-1 focus-visible:ring-foreground/20\"\n          >\n            <span className=\"flex items-center gap-2.5\">\n              {brand ?? <StackedMark />}\n              <span className=\"text-sm font-medium tracking-tight\">\n                {brandLabel}\n              </span>\n            </span>\n            <span className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n              <kbd className=\"hidden h-5 select-none items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-[10px] text-muted-foreground sm:inline-flex\">\n                {shortcut}\n              </kbd>\n              <span className=\"font-medium uppercase tracking-[0.08em] text-foreground\">\n                {open ? \"Close\" : \"Menu\"}\n              </span>\n              <motion.svg\n                width=\"14\"\n                height=\"14\"\n                viewBox=\"0 0 14 14\"\n                animate={{ rotate: open ? 180 : 0 }}\n                transition={{ type: \"spring\", stiffness: 320, damping: 28 }}\n                aria-hidden\n              >\n                <path\n                  d=\"M3.5 5.25 7 8.75l3.5-3.5\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  fill=\"none\"\n                />\n              </motion.svg>\n            </span>\n          </button>\n\n          <AnimatePresence initial={false}>\n            {open && (\n              <motion.div\n                key=\"panel\"\n                id=\"expandable-menu-content\"\n                ref={scrollRef}\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 1 }}\n                exit={{ opacity: 0 }}\n                transition={{ duration: 0.18 }}\n                style={{ touchAction: \"pan-y\" }}\n                className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain border-t border-border [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n              >\n                <div className=\"flex flex-col gap-4 p-3\">\n                  {sections.map((section, sIdx) => (\n                    <div key={sIdx} className=\"flex flex-col gap-0.5\">\n                      {section.title && (\n                        <motion.div\n                          initial={{ opacity: 0, y: 4 }}\n                          animate={{ opacity: 1, y: 0 }}\n                          transition={{\n                            duration: 0.3,\n                            delay: 0.06 + sIdx * 0.04,\n                            ease: ITEM_EASE,\n                          }}\n                          className=\"px-3 py-1.5 font-mono text-[10px] uppercase tracking-[0.16em] text-muted-foreground\"\n                        >\n                          {section.title}\n                        </motion.div>\n                      )}\n\n                      {section.items.map((item, iIdx) => {\n                        const idx = flatIndex[sIdx] + iIdx;\n                        return (\n                          <motion.a\n                            key={item.label}\n                            href={item.href ?? \"#\"}\n                            target={item.external ? \"_blank\" : undefined}\n                            rel={\n                              item.external ? \"noopener noreferrer\" : undefined\n                            }\n                            initial={{ opacity: 0, y: 6 }}\n                            animate={{ opacity: 1, y: 0 }}\n                            transition={{\n                              duration: 0.32,\n                              delay: 0.1 + idx * 0.022,\n                              ease: ITEM_EASE,\n                            }}\n                            className=\"group flex items-center justify-between rounded-lg px-3 py-2.5 text-sm text-foreground outline-none transition-colors hover:bg-accent focus-visible:bg-accent\"\n                          >\n                            <span className=\"flex items-center gap-2 font-medium tracking-tight\">\n                              {item.label}\n                            </span>\n                            <span className=\"flex items-center gap-2 font-mono text-xs text-muted-foreground\">\n                              {item.meta && <span>{item.meta}</span>}\n                              <svg\n                                width=\"12\"\n                                height=\"12\"\n                                viewBox=\"0 0 12 12\"\n                                aria-hidden\n                                className=\"-translate-x-1 opacity-0 transition-all duration-150 group-hover:translate-x-0 group-hover:opacity-100\"\n                              >\n                                <path\n                                  d=\"M4 2.5 7.5 6 4 9.5\"\n                                  stroke=\"currentColor\"\n                                  strokeWidth=\"1.5\"\n                                  strokeLinecap=\"round\"\n                                  strokeLinejoin=\"round\"\n                                  fill=\"none\"\n                                />\n                              </svg>\n                            </span>\n                          </motion.a>\n                        );\n                      })}\n                    </div>\n                  ))}\n\n                  <motion.div\n                    initial={{ opacity: 0 }}\n                    animate={{ opacity: 1 }}\n                    transition={{ delay: 0.36, duration: 0.3 }}\n                    className=\"mt-1 flex items-center justify-between border-t border-border px-3 pt-3 font-mono text-[11px] text-muted-foreground\"\n                  >\n                    <span className=\"flex items-center gap-1.5\">\n                      <span className=\"relative flex h-1.5 w-1.5\">\n                        <span className=\"absolute inset-0 animate-ping rounded-full bg-foreground/40\" />\n                        <span className=\"relative inline-flex h-1.5 w-1.5 rounded-full bg-foreground/70\" />\n                      </span>\n                      {status}\n                    </span>\n                    <span>{version}</span>\n                  </motion.div>\n                </div>\n              </motion.div>\n            )}\n          </AnimatePresence>\n        </motion.div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ruixen/expandable-menu-navbar.tsx"
    }
  ]
}