{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "navigation-menu",
  "type": "registry:ui",
  "title": "Navigation Menu",
  "description": "Navigation menu with viewport for Tile UI.",
  "dependencies": [
    "@radix-ui/react-slot",
    "react-dom"
  ],
  "devDependencies": [
    "sass"
  ],
  "registryDependencies": [
    "@tile-ui/core",
    "@tile-ui/styles"
  ],
  "files": [
    {
      "path": "packages/react/src/components/navigation-menu/navigation-menu.tsx",
      "type": "registry:ui",
      "target": "components/ui/navigation-menu/navigation-menu.tsx",
      "content": "import React, { createContext, useCallback, useContext, useLayoutEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Slot } from '@radix-ui/react-slot';\nimport { getNavigationMenuActiveState, getNavigationMenuState, navigationMenuStyleKeys } from '../lib/core';\nimport type { NavigationMenuBaseProps, NavigationMenuItemBaseProps, NavigationMenuLinkBaseProps } from '../lib/core';\nimport styles from './navigation-menu.module.scss';\n\ninterface NavigationMenuContextValue {\n\tactiveValue: string | undefined;\n\tsetActiveValue: (value: string | undefined) => void;\n\tviewportEnabled: boolean;\n\tviewportRef: React.RefObject<HTMLDivElement | null>;\n\tsetIndicatorRect: (rect: { left: number; width: number } | null) => void;\n\tindicatorRect: { left: number; width: number } | null;\n}\n\nconst NavigationMenuContext = createContext<NavigationMenuContextValue | null>(null);\n\nfunction useNavigationMenuContext(): NavigationMenuContextValue {\n\tconst context = useContext(NavigationMenuContext);\n\tif (!context) {\n\t\tthrow new Error('NavigationMenu 子组件必须位于 <NavigationMenu> 内部。');\n\t}\n\treturn context;\n}\n\ninterface NavigationMenuItemContextValue {\n\tvalue: string;\n\tisActive: boolean;\n\ttriggerRef: React.RefObject<HTMLButtonElement | null>;\n}\n\nconst NavigationMenuItemContext = createContext<NavigationMenuItemContextValue | null>(null);\n\nfunction useNavigationMenuItemContext(): NavigationMenuItemContextValue {\n\tconst context = useContext(NavigationMenuItemContext);\n\tif (!context) {\n\t\tthrow new Error('NavigationMenu 触发器/内容必须位于 <NavigationMenuItem> 内部。');\n\t}\n\treturn context;\n}\n\nfunction NavigationMenuChevronIcon() {\n\treturn (\n\t\t<svg\n\t\t\tclassName={styles[navigationMenuStyleKeys.chevron]}\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\twidth=\"16\"\n\t\t\theight=\"16\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\taria-hidden=\"true\">\n\t\t\t<path d=\"m6 9 6 6 6-6\" />\n\t\t</svg>\n\t);\n}\n\nexport interface NavigationMenuProps extends Omit<React.HTMLAttributes<HTMLElement>, 'defaultValue'>, NavigationMenuBaseProps {}\n\nconst NavigationMenu = React.forwardRef<HTMLElement, NavigationMenuProps>(({ className = '', viewport = true, value, defaultValue, onValueChange, children, ...props }, ref) => {\n\tconst [internalValue, setInternalValue] = useState(defaultValue);\n\tconst activeValue = value !== undefined ? value : internalValue;\n\tconst viewportRef = useRef<HTMLDivElement | null>(null);\n\tconst [indicatorRect, setIndicatorRect] = useState<{ left: number; width: number } | null>(null);\n\n\tconst setActiveValue = useCallback(\n\t\t(next: string | undefined) => {\n\t\t\tif (value === undefined) {\n\t\t\t\tsetInternalValue(next);\n\t\t\t}\n\t\t\tonValueChange?.(next);\n\t\t},\n\t\t[value, onValueChange],\n\t);\n\n\treturn (\n\t\t<NavigationMenuContext.Provider value={{ activeValue, setActiveValue, viewportEnabled: viewport, viewportRef, setIndicatorRect, indicatorRect }}>\n\t\t\t<nav ref={ref} data-viewport={viewport} className={`${styles[navigationMenuStyleKeys.root]} ${className}`} {...props}>\n\t\t\t\t{children}\n\t\t\t\t{viewport && <NavigationMenuViewport />}\n\t\t\t</nav>\n\t\t</NavigationMenuContext.Provider>\n\t);\n});\nNavigationMenu.displayName = 'NavigationMenu';\n\nexport interface NavigationMenuListProps extends React.HTMLAttributes<HTMLUListElement> {}\n\nconst NavigationMenuList = React.forwardRef<HTMLUListElement, NavigationMenuListProps>(({ className = '', children, onKeyDown, ...props }, ref) => {\n\tfunction handleKeyDown(event: React.KeyboardEvent<HTMLUListElement>) {\n\t\tonKeyDown?.(event);\n\n\t\tconst isNext = event.key === 'ArrowRight';\n\t\tconst isPrev = event.key === 'ArrowLeft';\n\t\tconst isHome = event.key === 'Home';\n\t\tconst isEnd = event.key === 'End';\n\t\tif (!isNext && !isPrev && !isHome && !isEnd) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst triggers = Array.from(event.currentTarget.querySelectorAll<HTMLButtonElement>('button')).filter((trigger) => !trigger.disabled);\n\t\tif (triggers.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentIndex = triggers.indexOf(document.activeElement as HTMLButtonElement);\n\t\tlet nextIndex: number;\n\t\tif (isHome) {\n\t\t\tnextIndex = 0;\n\t\t} else if (isEnd) {\n\t\t\tnextIndex = triggers.length - 1;\n\t\t} else {\n\t\t\tconst direction = isNext ? 1 : -1;\n\t\t\tnextIndex = (currentIndex + direction + triggers.length) % triggers.length;\n\t\t}\n\n\t\ttriggers[nextIndex]?.focus();\n\t\tevent.preventDefault();\n\t}\n\n\treturn (\n\t\t<ul ref={ref} onKeyDown={handleKeyDown} className={`${styles[navigationMenuStyleKeys.list]} ${className}`} {...props}>\n\t\t\t{children}\n\t\t</ul>\n\t);\n});\nNavigationMenuList.displayName = 'NavigationMenuList';\n\nexport interface NavigationMenuItemProps extends React.HTMLAttributes<HTMLLIElement>, NavigationMenuItemBaseProps {}\n\nconst NavigationMenuItem = React.forwardRef<HTMLLIElement, NavigationMenuItemProps>(({ className = '', value, children, ...props }, ref) => {\n\tconst { activeValue } = useNavigationMenuContext();\n\tconst triggerRef = useRef<HTMLButtonElement | null>(null);\n\tconst isActive = activeValue === value;\n\n\treturn (\n\t\t<NavigationMenuItemContext.Provider value={{ value, isActive, triggerRef }}>\n\t\t\t<li ref={ref} className={`${styles[navigationMenuStyleKeys.item]} ${className}`} {...props}>\n\t\t\t\t{children}\n\t\t\t</li>\n\t\t</NavigationMenuItemContext.Provider>\n\t);\n});\nNavigationMenuItem.displayName = 'NavigationMenuItem';\n\nexport interface NavigationMenuTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}\n\nconst NavigationMenuTrigger = React.forwardRef<HTMLButtonElement, NavigationMenuTriggerProps>(({ className = '', children, onClick, ...props }, ref) => {\n\tconst { activeValue, setActiveValue, setIndicatorRect } = useNavigationMenuContext();\n\tconst { value, isActive, triggerRef } = useNavigationMenuItemContext();\n\n\tfunction setRef(element: HTMLButtonElement | null) {\n\t\ttriggerRef.current = element;\n\t\tif (typeof ref === 'function') {\n\t\t\tref(element);\n\t\t} else if (ref) {\n\t\t\tref.current = element;\n\t\t}\n\t}\n\n\tuseLayoutEffect(() => {\n\t\tif (!isActive || !triggerRef.current) {\n\t\t\treturn;\n\t\t}\n\t\tconst root = triggerRef.current.closest('nav');\n\t\tconst rootRect = root?.getBoundingClientRect();\n\t\tconst triggerRect = triggerRef.current.getBoundingClientRect();\n\t\tif (!rootRect) {\n\t\t\treturn;\n\t\t}\n\t\tsetIndicatorRect({ left: triggerRect.left - rootRect.left, width: triggerRect.width });\n\t}, [isActive, setIndicatorRect, activeValue, triggerRef]);\n\n\tfunction handleClick(event: React.MouseEvent<HTMLButtonElement>) {\n\t\tonClick?.(event);\n\t\tsetActiveValue(isActive ? undefined : value);\n\t}\n\n\tfunction handleMouseEnter() {\n\t\tsetActiveValue(value);\n\t}\n\n\tconst state = getNavigationMenuState(isActive);\n\n\treturn (\n\t\t<button\n\t\t\tref={setRef}\n\t\t\ttype=\"button\"\n\t\t\taria-expanded={isActive}\n\t\t\tdata-state={state}\n\t\t\tclassName={`${styles[navigationMenuStyleKeys.trigger]} ${className}`}\n\t\t\tonClick={handleClick}\n\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\t{...props}>\n\t\t\t{children}\n\t\t\t<NavigationMenuChevronIcon />\n\t\t</button>\n\t);\n});\nNavigationMenuTrigger.displayName = 'NavigationMenuTrigger';\n\nexport interface NavigationMenuContentProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst NavigationMenuContent = React.forwardRef<HTMLDivElement, NavigationMenuContentProps>(({ className = '', children, ...props }, ref) => {\n\tconst { viewportEnabled, viewportRef } = useNavigationMenuContext();\n\tconst { isActive } = useNavigationMenuItemContext();\n\n\tconst state = getNavigationMenuState(isActive);\n\tconst classes = [styles[navigationMenuStyleKeys.content], className].filter(Boolean).join(' ');\n\n\tconst content = (\n\t\t<div ref={ref} data-state={state} data-viewport={viewportEnabled} className={classes} {...props}>\n\t\t\t{children}\n\t\t</div>\n\t);\n\n\tif (!viewportEnabled) {\n\t\treturn isActive ? content : null;\n\t}\n\n\tif (typeof document === 'undefined' || !viewportRef.current) {\n\t\treturn null;\n\t}\n\n\treturn isActive ? createPortal(content, viewportRef.current) : null;\n});\nNavigationMenuContent.displayName = 'NavigationMenuContent';\n\nexport interface NavigationMenuViewportProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst NavigationMenuViewport = React.forwardRef<HTMLDivElement, NavigationMenuViewportProps>(({ className = '', children, ...props }, ref) => {\n\tconst { activeValue, viewportRef } = useNavigationMenuContext();\n\tconst state = getNavigationMenuState(activeValue !== undefined);\n\tconst classes = [styles[navigationMenuStyleKeys.viewport], className].filter(Boolean).join(' ');\n\n\treturn (\n\t\t<div ref={ref} data-state={state} className={classes} {...props}>\n\t\t\t<div ref={viewportRef} className={styles[navigationMenuStyleKeys.viewportInner]}>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t</div>\n\t);\n});\nNavigationMenuViewport.displayName = 'NavigationMenuViewport';\n\nexport interface NavigationMenuIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {\n\tstyle?: React.CSSProperties;\n}\n\nconst NavigationMenuIndicator = React.forwardRef<HTMLDivElement, NavigationMenuIndicatorProps>(({ className = '', style, ...props }, ref) => {\n\tconst { indicatorRect } = useNavigationMenuContext();\n\tconst visible = indicatorRect !== null;\n\tconst state = visible ? 'visible' : 'hidden';\n\tconst classes = [styles[navigationMenuStyleKeys.indicator], className].filter(Boolean).join(' ');\n\n\treturn (\n\t\t<div ref={ref} data-state={state} className={classes} style={style} {...props}>\n\t\t\t<div className={styles[navigationMenuStyleKeys.indicatorArrow]} />\n\t\t</div>\n\t);\n});\nNavigationMenuIndicator.displayName = 'NavigationMenuIndicator';\n\nexport interface NavigationMenuLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement>, NavigationMenuLinkBaseProps {}\n\nconst NavigationMenuLink = React.forwardRef<HTMLAnchorElement, NavigationMenuLinkProps>(({ className = '', active = false, asChild = false, children, ...props }, ref) => {\n\tconst Comp = asChild ? Slot : 'a';\n\tconst state = getNavigationMenuActiveState(active);\n\n\treturn (\n\t\t<Comp ref={ref} data-active={state} className={asChild ? undefined : `${styles[navigationMenuStyleKeys.link]} ${className}`} {...props}>\n\t\t\t{children}\n\t\t</Comp>\n\t);\n});\nNavigationMenuLink.displayName = 'NavigationMenuLink';\n\nexport {\n\tNavigationMenu,\n\tNavigationMenuList,\n\tNavigationMenuItem,\n\tNavigationMenuTrigger,\n\tNavigationMenuContent,\n\tNavigationMenuViewport,\n\tNavigationMenuIndicator,\n\tNavigationMenuLink,\n};\nexport default NavigationMenu;\n"
    },
    {
      "path": "packages/styles/scss/components/navigation-menu.module.scss",
      "type": "registry:file",
      "target": "components/ui/navigation-menu/navigation-menu.module.scss",
      "content": "@use '../../../styles/variables/colors' as *;\n\n// 根容器\n.root {\n\tposition: relative;\n\tdisplay: flex;\n\tmax-width: max-content;\n\tflex: 1;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n// 列表容器\n.list {\n\tdisplay: flex;\n\tflex: 1;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: $spacing-1;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n// 单个菜单项\n.item {\n\tposition: relative;\n}\n\n// 触发器按钮\n.trigger {\n\tdisplay: inline-flex;\n\theight: var(--tile-navigation-height, 2.25rem);\n\twidth: max-content;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: $spacing-1;\n\tborder-radius: var(--tile-navigation-trigger-radius, $radius);\n\tbackground-color: var(--tile-navigation-background, $background);\n\tpadding: var(--tile-navigation-trigger-padding, $spacing-2 $spacing-4);\n\tfont-size: var(--tile-navigation-font-size, $text-sm);\n\tfont-weight: $font-medium;\n\tcolor: var(--tile-navigation-foreground, $foreground);\n\toutline: none;\n\ttransition:\n\t\tbackground-color $transition-fast,\n\t\tcolor $transition-fast;\n\tcursor: pointer;\n\n\t&:hover {\n\t\tbackground-color: var(--tile-navigation-hover-background, $accent);\n\t\tcolor: var(--tile-navigation-hover-foreground, $accent-foreground);\n\t}\n\n\t&[data-state='open'] {\n\t\tbackground-color: var(--tile-navigation-active-background, color-mix(in srgb, $accent 50%, transparent));\n\t\tcolor: var(--tile-navigation-hover-foreground, $accent-foreground);\n\t}\n\n\t&[data-disabled='true'] {\n\t\tpointer-events: none;\n\t\topacity: 0.5;\n\t}\n\n\t&:focus-visible {\n\t\tbox-shadow: var(--tile-navigation-focus-shadow, none);\n\t}\n}\n\n// 触发器箭头\n.chevron {\n\tposition: relative;\n\ttop: 1px;\n\twidth: 0.75rem;\n\theight: 0.75rem;\n\ttransition: transform $transition-slow;\n\n\t&[data-state='open'] {\n\t\ttransform: rotate(180deg);\n\t}\n}\n\n// 内容面板（默认：相对菜单项下方展开的下拉面板）\n.content {\n\tposition: absolute;\n\ttop: 100%;\n\tleft: 0;\n\tz-index: $z-dropdown;\n\tmin-width: 12rem;\n\tmargin-top: $spacing-1_5;\n\tborder-radius: var(--tile-navigation-panel-radius, $radius);\n\tborder: 1px solid var(--tile-navigation-panel-border, $border);\n\tbackground-color: var(--tile-navigation-panel-background, $popover);\n\tcolor: var(--tile-navigation-panel-foreground, $popover-foreground);\n\tpadding: var(--tile-navigation-panel-padding, $spacing-2);\n\tbox-shadow: var(--tile-navigation-panel-shadow, $shadow-md);\n\toutline: none;\n\n\t&[data-state='closed'] {\n\t\topacity: 0;\n\t\tvisibility: hidden;\n\t\tpointer-events: none;\n\t}\n\n\t&[data-state='open'] {\n\t\topacity: 1;\n\t\tvisibility: visible;\n\t\tpointer-events: auto;\n\t}\n\n\t// 集中式视口模式下内容为正常流布局\n\t&[data-viewport='true'] {\n\t\tposition: static;\n\t\twidth: 100%;\n\t\tmin-width: 0;\n\t\tmargin-top: 0;\n\t\tborder: none;\n\t\tpadding: 0;\n\t\tbox-shadow: none;\n\t}\n}\n\n// 集中式视口\n.viewport {\n\tposition: absolute;\n\ttop: 100%;\n\tleft: 0;\n\tz-index: $z-dropdown;\n\tdisplay: flex;\n\tjustify-content: center;\n\tmargin-top: $spacing-1_5;\n\toverflow: hidden;\n\tborder-radius: var(--tile-navigation-panel-radius, $radius);\n\tborder: 1px solid var(--tile-navigation-panel-border, $border);\n\tbackground-color: var(--tile-navigation-panel-background, $popover);\n\tcolor: var(--tile-navigation-panel-foreground, $popover-foreground);\n\tbox-shadow: var(--tile-navigation-panel-shadow, $shadow-md);\n\ttransition:\n\t\topacity $transition-fast,\n\t\tvisibility $transition-fast,\n\t\ttransform $transition-fast;\n\n\t&[data-state='closed'] {\n\t\topacity: 0;\n\t\tvisibility: hidden;\n\t\tpointer-events: none;\n\t\ttransform: scale(0.95);\n\t}\n\n\t&[data-state='open'] {\n\t\topacity: 1;\n\t\tvisibility: visible;\n\t\tpointer-events: auto;\n\t\ttransform: scale(1);\n\t}\n}\n\n// 视口内部容器\n.viewportInner {\n\tposition: relative;\n\twidth: 100%;\n}\n\n// 指示器\n.indicator {\n\tposition: absolute;\n\ttop: 100%;\n\tz-index: 1;\n\tdisplay: flex;\n\theight: $spacing-1_5;\n\talign-items: flex-end;\n\tjustify-content: center;\n\toverflow: hidden;\n\ttransition:\n\t\topacity $transition-fast,\n\t\ttransform $transition-fast;\n\n\t&[data-state='hidden'] {\n\t\topacity: 0;\n\t}\n\n\t&[data-state='visible'] {\n\t\topacity: 1;\n\t}\n}\n\n// 指示器箭头\n.indicatorArrow {\n\tposition: relative;\n\ttop: 60%;\n\twidth: $spacing-2;\n\theight: $spacing-2;\n\ttransform: rotate(45deg);\n\tborder-radius: $radius-xs 0 0 0;\n\tbackground-color: var(--tile-navigation-panel-border, $border);\n\tbox-shadow: var(--tile-navigation-panel-shadow, $shadow-md);\n}\n\n// 链接\n.link {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: $spacing-1;\n\tborder-radius: var(--tile-navigation-link-radius, $radius-sm);\n\tpadding: var(--tile-navigation-link-padding, $spacing-2);\n\tfont-size: var(--tile-navigation-font-size, $text-sm);\n\tcolor: var(--tile-navigation-panel-foreground, $popover-foreground);\n\ttext-decoration: none;\n\toutline: none;\n\ttransition:\n\t\tbackground-color $transition-fast,\n\t\tcolor $transition-fast;\n\n\t&:hover {\n\t\tbackground-color: var(--tile-navigation-hover-background, $accent);\n\t\tcolor: var(--tile-navigation-hover-foreground, $accent-foreground);\n\t}\n\n\t&[data-active='true'] {\n\t\tbackground-color: var(--tile-navigation-active-background, color-mix(in srgb, $accent 50%, transparent));\n\t\tcolor: var(--tile-navigation-hover-foreground, $accent-foreground);\n\t}\n\n\t&:focus-visible {\n\t\tbox-shadow: var(--tile-navigation-focus-shadow, none);\n\t}\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.trigger,\n\t.chevron,\n\t.viewport,\n\t.indicator,\n\t.link {\n\t\ttransition: none;\n\t}\n}\n"
    },
    {
      "path": "packages/react/src/components/navigation-menu/index.ts",
      "type": "registry:ui",
      "target": "components/ui/navigation-menu/index.ts",
      "content": "export {\n\tNavigationMenu,\n\tNavigationMenuList,\n\tNavigationMenuItem,\n\tNavigationMenuTrigger,\n\tNavigationMenuContent,\n\tNavigationMenuViewport,\n\tNavigationMenuIndicator,\n\tNavigationMenuLink,\n\ttype NavigationMenuProps,\n\ttype NavigationMenuListProps,\n\ttype NavigationMenuItemProps,\n\ttype NavigationMenuTriggerProps,\n\ttype NavigationMenuContentProps,\n\ttype NavigationMenuViewportProps,\n\ttype NavigationMenuIndicatorProps,\n\ttype NavigationMenuLinkProps,\n} from './navigation-menu';\n"
    }
  ]
}
