{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mega-menu",
  "type": "registry:ui",
  "title": "Mega menu",
  "description": "Keeps highlight, panel, and focus movement in one continuous navigation path.",
  "dependencies": [
    "motion"
  ],
  "categories": [
    "navigation"
  ],
  "docs": "https://motion-lexicon.pages.dev/en/components/mega-menu/",
  "meta": {
    "engines": [
      "motion"
    ],
    "runtimeCost": "light",
    "signature": "Mega menu driven by a shared highlight",
    "sceneFamily": "product-mono",
    "motionRole": "ui",
    "primaryState": "Expanded navigation panel with active highlight",
    "assetProvenance": "self-contained"
  },
  "files": [
    {
      "path": "src/registry/components/mega-menu.tsx",
      "type": "registry:ui",
      "target": "components/motion-lexicon/mega-menu.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useRef, useState, type ReactNode } from \"react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\n\nconst MOVE = { type: \"spring\", stiffness: 420, damping: 32, mass: 0.6 } as const;\nconst INSTANT = { duration: 0 } as const;\nconst linkRefKey = (sectionId: string, linkId: string) => `${sectionId}\\u0000${linkId}`;\n\nexport type MegaMenuLink = { id: string; label: string; description?: string; onSelect: () => void };\nexport type MegaMenuSection = { id: string; label: string; links: readonly MegaMenuLink[]; preview?: ReactNode };\n\nexport type MegaMenuProps = {\n  sections: readonly MegaMenuSection[];\n  label: string;\n  className?: string;\n};\n\nexport function MegaMenu({ sections, label, className = \"\" }: MegaMenuProps) {\n  const [active, setActive] = useState<string | null>(null);\n  const [focusedLinkId, setFocusedLinkId] = useState<string | null>(null);\n  const [focusCommit, setFocusCommit] = useState(0);\n  const root = useRef<HTMLDivElement>(null);\n  const buttons = useRef(new Map<string, HTMLButtonElement>());\n  const linkButtons = useRef(new Map<string, HTMLButtonElement>());\n  const closeTimer = useRef<number | null>(null);\n  const lastActiveIndex = useRef(0);\n  const lastFocusedLinkIndex = useRef(0);\n  const focusedOwner = useRef<string | null>(null);\n  const focusInPanel = useRef(false);\n  const pendingPanelFocus = useRef<{ sectionId: string; linkId: string } | null>(null);\n  const uid = useId();\n  const reduced = useReducedMotion() === true;\n  const index = sections.findIndex((item) => item.id === active);\n  const current = index >= 0 ? sections[index] : null;\n  const matchedFocusedLinkIndex = current && focusedLinkId !== null\n    ? current.links.findIndex((link) => link.id === focusedLinkId)\n    : -1;\n  const focusedLinkIndex = matchedFocusedLinkIndex >= 0\n    ? matchedFocusedLinkIndex\n    : current && current.links.length > 0\n      ? Math.min(lastFocusedLinkIndex.current, current.links.length - 1)\n      : -1;\n  const effectiveFocusedLinkId = current?.links[focusedLinkIndex]?.id ?? null;\n  const currentId = current?.id ?? null;\n  const currentLinkCount = current?.links.length ?? 0;\n  const currentLinkOrder = current?.links.map((link) => link.id).join(\"\\u0000\") ?? \"\";\n  const panelCurrent = current && current.links.length > 0 ? current : null;\n\n  const cancelClose = () => {\n    if (closeTimer.current === null) return;\n    window.clearTimeout(closeTimer.current);\n    closeTimer.current = null;\n  };\n  const close = () => {\n    cancelClose();\n    pendingPanelFocus.current = null;\n    setActive(null);\n  };\n  const scheduleClose = () => {\n    cancelClose();\n    closeTimer.current = window.setTimeout(() => {\n      closeTimer.current = null;\n      pendingPanelFocus.current = null;\n      setActive(null);\n    }, 120);\n  };\n  const openSection = (id: string, moveFocusToPanel = false) => {\n    const nextIndex = sections.findIndex((section) => section.id === id);\n    if (nextIndex < 0) return;\n    const section = sections[nextIndex];\n    if (section.links.length === 0) return;\n    cancelClose();\n    lastActiveIndex.current = nextIndex;\n    lastFocusedLinkIndex.current = 0;\n    pendingPanelFocus.current = moveFocusToPanel\n      ? { sectionId: section.id, linkId: section.links[0].id }\n      : null;\n    if (moveFocusToPanel) setFocusCommit((value) => value + 1);\n    setFocusedLinkId(section.links[0].id);\n    setActive(id);\n  };\n  const choose = (at: number, direction: -1 | 1) => {\n    const count = sections.length;\n    if (count < 2) return;\n    for (let distance = 1; distance < count; distance += 1) {\n      const index = (at + direction * distance + count) % count;\n      const section = sections[index];\n      if (!section || section.links.length === 0) continue;\n      openSection(section.id);\n      buttons.current.get(section.id)?.focus({ preventScroll: true });\n      return;\n    }\n  };\n\n  const focusMenuItem = (next: number) => {\n    const count = current?.links.length ?? 0;\n    if (count === 0) return;\n    const normalized = (next + count) % count;\n    const link = current?.links[normalized];\n    if (!link) return;\n    lastFocusedLinkIndex.current = normalized;\n    setFocusedLinkId(link.id);\n    if (current) {\n      linkButtons.current.get(linkRefKey(current.id, link.id))?.focus({ preventScroll: true });\n    }\n  };\n\n  useEffect(() => () => {\n    if (closeTimer.current !== null) window.clearTimeout(closeTimer.current);\n  }, []);\n\n  useEffect(() => {\n    if (active === null) return;\n    const dismissFromOutside = (event: globalThis.PointerEvent) => {\n      if (!(event.target instanceof Node) || root.current?.contains(event.target)) return;\n      cancelClose();\n      pendingPanelFocus.current = null;\n      setActive(null);\n    };\n    document.addEventListener(\"pointerdown\", dismissFromOutside, true);\n    return () => document.removeEventListener(\"pointerdown\", dismissFromOutside, true);\n  }, [active]);\n\n  useEffect(() => {\n    if (active !== null && index >= 0) lastActiveIndex.current = index;\n  }, [active, index]);\n\n  useEffect(() => {\n    if (active === null || index >= 0) return;\n    if (closeTimer.current !== null) {\n      window.clearTimeout(closeTimer.current);\n      closeTimer.current = null;\n    }\n    const shouldRestoreFocus = focusedOwner.current === active;\n    const fallbackIndex = Math.min(lastActiveIndex.current, sections.length - 1);\n    pendingPanelFocus.current = null;\n    setFocusedLinkId(null);\n    setActive(null);\n    if (shouldRestoreFocus && fallbackIndex >= 0) {\n      const fallback = sections[fallbackIndex];\n      if (fallback) buttons.current.get(fallback.id)?.focus({ preventScroll: true });\n    }\n  }, [active, index, sections, sections.length]);\n\n  useEffect(() => {\n    if (currentId === null) return;\n    if (currentLinkCount === 0) {\n      const shouldRestoreFocus = focusedOwner.current === currentId;\n      pendingPanelFocus.current = null;\n      focusInPanel.current = false;\n      setFocusedLinkId(null);\n      setActive(null);\n      if (shouldRestoreFocus) {\n        buttons.current.get(currentId)?.focus({ preventScroll: true });\n      }\n      return;\n    }\n    if (effectiveFocusedLinkId === null || focusedLinkIndex < 0) return;\n    lastFocusedLinkIndex.current = focusedLinkIndex;\n    if (focusedLinkId !== effectiveFocusedLinkId) {\n      setFocusedLinkId(effectiveFocusedLinkId);\n    }\n    if (focusInPanel.current) {\n      linkButtons.current.get(linkRefKey(currentId, effectiveFocusedLinkId))?.focus({ preventScroll: true });\n    }\n  }, [currentId, currentLinkCount, currentLinkOrder, effectiveFocusedLinkId, focusedLinkId, focusedLinkIndex]);\n\n  useEffect(() => {\n    const pending = pendingPanelFocus.current;\n    if (!pending || pending.sectionId !== currentId) return;\n    const target = linkButtons.current.get(linkRefKey(pending.sectionId, pending.linkId));\n    if (!target) return;\n    pendingPanelFocus.current = null;\n    target.focus({ preventScroll: true });\n  }, [currentId, currentLinkOrder, focusCommit]);\n\n  const keyDown = (event: React.KeyboardEvent, at: number) => {\n    const section = sections[at];\n    if (!section || section.links.length === 0) return;\n    if (event.key === \"ArrowRight\") { event.preventDefault(); choose(at, 1); }\n    else if (event.key === \"ArrowLeft\") { event.preventDefault(); choose(at, -1); }\n    else if (event.key === \"ArrowDown\") {\n      event.preventDefault();\n      openSection(section.id, true);\n    } else if (event.key === \"Escape\") {\n      event.preventDefault();\n      close();\n      buttons.current.get(section.id)?.focus({ preventScroll: true });\n    }\n  };\n\n  const menuKeyDown = (event: React.KeyboardEvent, at: number) => {\n    if (event.key === \"ArrowDown\") { event.preventDefault(); focusMenuItem(at + 1); }\n    else if (event.key === \"ArrowUp\") { event.preventDefault(); focusMenuItem(at - 1); }\n    else if (event.key === \"Home\") { event.preventDefault(); focusMenuItem(0); }\n    else if (event.key === \"End\") { event.preventDefault(); focusMenuItem((current?.links.length ?? 1) - 1); }\n    else if (event.key === \"Escape\") {\n      event.preventDefault();\n      const sectionId = current?.id;\n      close();\n      if (sectionId) buttons.current.get(sectionId)?.focus({ preventScroll: true });\n    }\n  };\n\n  return (\n    <div\n      ref={root}\n      className={`relative w-full max-w-[620px] ${className}`}\n      onPointerEnter={(event) => { if (event.pointerType !== \"touch\") cancelClose(); }}\n      onPointerLeave={(event) => { if (event.pointerType !== \"touch\") scheduleClose(); }}\n      onBlur={(event) => {\n        if (event.currentTarget.contains(event.relatedTarget)) return;\n        focusedOwner.current = null;\n        focusInPanel.current = false;\n        close();\n      }}\n    >\n      <nav aria-label={label} className=\"flex min-h-12 items-center gap-1 rounded-lg border border-neutral-200 bg-white p-1 shadow-[0_4px_8px_-7px_rgba(28,25,23,.64)] dark:border-white/15 dark:bg-[#202020]\">\n        {sections.map((section, at) => {\n          const isOpen = section.id === panelCurrent?.id;\n          const unavailable = section.links.length === 0;\n          return (\n            <button\n              key={section.id}\n              ref={(node) => {\n                if (node) buttons.current.set(section.id, node);\n                else buttons.current.delete(section.id);\n              }}\n              type=\"button\"\n              aria-haspopup=\"menu\"\n              aria-expanded={isOpen}\n              aria-controls={isOpen ? `${uid}-panel` : undefined}\n              aria-disabled={unavailable}\n              onPointerDown={(event) => { if (unavailable) event.preventDefault(); }}\n              onPointerEnter={(event) => {\n                if (!unavailable && event.pointerType !== \"touch\") openSection(section.id);\n              }}\n              onClick={() => {\n                if (unavailable) return;\n                if (isOpen) close();\n                else openSection(section.id);\n              }}\n              onFocus={() => {\n                if (unavailable) return;\n                focusedOwner.current = section.id;\n                focusInPanel.current = false;\n              }}\n              onKeyDown={(event) => keyDown(event, at)}\n              className={`relative min-h-11 min-w-0 flex-1 rounded-[9px] px-3 text-[12px] font-medium outline-none transition-colors duration-150 focus-visible:shadow-[0_0_0_2px_rgba(69,104,255,.22)] aria-disabled:cursor-not-allowed aria-disabled:opacity-50 ${isOpen ? \"text-neutral-900 dark:text-white\" : \"text-neutral-600 hover:text-neutral-800 dark:text-neutral-300 dark:hover:text-neutral-100\"}`}\n            >\n              {isOpen ? <motion.span layoutId={`${uid}-active`} transition={reduced ? INSTANT : MOVE} className=\"absolute inset-0 rounded-[9px] bg-neutral-100 shadow-[inset_0_0_0_1px_rgba(41,41,41,.05)] dark:bg-white/[.08]\" /> : null}\n              <span className=\"relative block truncate\">{section.label}</span>\n            </button>\n          );\n        })}\n      </nav>\n\n      <AnimatePresence>\n        {panelCurrent ? (\n          <motion.div\n            id={`${uid}-panel`}\n            key={panelCurrent.id}\n            initial={reduced ? { opacity: 1 } : { opacity: 0, transform: \"translate3d(0,-6px,0) scale(.985)\" }}\n            animate={{ opacity: 1, transform: \"translate3d(0,0,0) scale(1)\" }}\n            exit={{ opacity: 0, transform: reduced ? \"none\" : \"translate3d(0,-4px,0) scale(.99)\" }}\n            transition={reduced ? INSTANT : { duration: 0.18, ease: [0.23, 1, 0.32, 1] }}\n            onPointerEnter={(event) => { if (event.pointerType !== \"touch\") cancelClose(); }}\n            className=\"absolute inset-x-0 top-[calc(100%+7px)] z-30 grid min-h-[190px] grid-cols-[minmax(0,1fr)_minmax(120px,.72fr)] overflow-hidden rounded-[10px] border border-neutral-200 bg-white shadow-[0_4px_8px_-6px_rgba(28,25,23,.68)] dark:border-white/15 dark:bg-[#1F1F1C]\"\n          >\n            <div role=\"menu\" aria-label={panelCurrent.label} className=\"grid content-start gap-1 p-2.5\">\n              {panelCurrent.links.map((link, at) => (\n                <button\n                  key={link.id}\n                  ref={(node) => {\n                    const key = linkRefKey(panelCurrent.id, link.id);\n                    if (node) linkButtons.current.set(key, node);\n                    else linkButtons.current.delete(key);\n                  }}\n                  data-menu-link\n                  type=\"button\"\n                  role=\"menuitem\"\n                  tabIndex={link.id === effectiveFocusedLinkId ? 0 : -1}\n                  onClick={() => { link.onSelect(); close(); }}\n                  onFocus={() => {\n                    focusedOwner.current = panelCurrent.id;\n                    focusInPanel.current = true;\n                    lastFocusedLinkIndex.current = at;\n                    setFocusedLinkId(link.id);\n                  }}\n                  onKeyDown={(event) => menuKeyDown(event, at)}\n                  className=\"group min-h-11 rounded-[10px] px-3 py-2 text-left outline-none transition-colors duration-150 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus-visible:shadow-[inset_0_0_0_2px_rgba(69,104,255,.25)] dark:hover:bg-white/[.07] dark:focus-visible:bg-white/[.07]\"\n                >\n                  <strong className=\"block text-[12px] font-medium text-neutral-800 dark:text-neutral-100\">{link.label}</strong>\n                  {link.description ? <span className=\"mt-0.5 block text-[10px] text-neutral-600 dark:text-neutral-300\">{link.description}</span> : null}\n                </button>\n              ))}\n            </div>\n            <div className=\"grid place-items-center bg-[#DDD7CD] p-3 dark:bg-[#292825]\">{panelCurrent.preview}</div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  );\n}\n"
    }
  ]
}
