{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "navigation-menu",
  "title": "NavigationMenu",
  "description": "shadcn/ui(new-york-v4) navigation-menu의 유니버설(웹+네이티브 한 벌 소스) 포팅. (사이클 40, T-1e). 원본: shadcn-ref/ui/navigation-menu.tsx — radix NavigationMenu 엔진 제거.",
  "dependencies": [
    "@unikit-bridge/ui",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "utils",
    "animations"
  ],
  "files": [
    {
      "path": "registry/ui/navigation-menu.tsx",
      "content": "// NavigationMenu — shadcn/ui（new-york-v4） navigation-menu.tsx 유니버설 포팅\n// （사이클 40, T-1e）. 원본: shadcn-ref/ui/navigation-menu.tsx — radix\n// NavigationMenu 엔진 제거.\n//\n// 포팅 판단（원본 대비 차이이유）:\n// - 재구성: 상단 바（List） + 각 아이템 트리거에 앵커링된 AnchoredOverlay\n//   （placement=\"bottom\"） 콘텐츠로 구성한다. 활성 아이템은 원본 Root의 value/\n//   onValueChange 계약 그대로 관리한다（제어/비제어 통합 — 콘텐츠가 있는\n//   아이템의 Trigger 토글로 value가 해당 아이템으로 바뀌고 Content가 뜬다）.\n//   viewport 없이도（항상） 각 아이템 AnchoredOverlay가 자체 동작한다.\n// - NavigationMenuViewport: 원본은 활성 콘텐츠를 루트 하단 공용 카드에 모아\n//   --radix--viewport-width/height CSS 변수로 크기 전환 애니메이션을 준다.\n//   콘텐츠가 각자 포털（AnchoredOverlay）로 뜨는 구조라 이 배치 트릭을 재현할\n//   수 없어 API 호환용 no-op（렌더하지 않음）로 두고 viewport prop은 장식으로\n//   유지된다 — 콘텐츠는 항상 트리거 바로 아래 카드로 뜬다.\n// - NavigationMenuIndicator: 활성 트리거 아래 화살표를 indicator x 이동\n//   트릭으로 배치하는데 트리거 위치 측정 계약이 없어 재현 불가 — 생략（no-op）.\n// - Trigger: hover 개방은 브리지 onHoverIn 계약 부재로 눌림 토글 근사\n//   （dropdown-menu와 동일 판단）. ChevronDownIcon（브리지 lucide 아이콘 프리미티브）는\n//   개방 시 web:rotate-180 회전 근사（원본 300ms 전이 유지, 원본 top-［1px］ 임의값과\n//   size-3은 text-xs로 근사）.\n// - navigationMenuTriggerStyle: 원본 cva 문자열을 유지하되 data-［state=open］\n//   토큰은 상태를 모르는 cva에 넣을 수 없어 Trigger에서 조건부 전개한다.\n//   transition-［color,box-shadow］→web:transition-colorsring-［3px］→ring-3\n//   （비대괄호 계단）outline-hidden→web:outline-none, 원본 focus-visible:outline-1은\n//   outline-ring 없이 근사（G1 F-1）. 임의값 금지.\n// - Link: radix Link의 <a href>는 브리지 프리미티브 계약 밖이라 Pressable\n//   role=\"link\" + onPress로 근사한다（라우팅은 사용처 onPress 담당）. active\n//   prop（data-［active=true］）은 유지한다. 원본 focus:text-accent-foreground\n//   등 글자색 토큰은 이중 지정 원칙상 내부 Text가 대신한다（색 전환 생략）.\n// - Content: 원본의 motion/viewport 그룹 셀렉터 토큰（data-［motion=］,\n//   group-data-［viewport=false］/）은 네이티브 data 속성 부재그룹 셀렉터\n//   불가로 전부 제거하고, viewport=false 모드 카드 토큰（borderbg-popover\n//   p-2 pr-2.5shadow）을 기본으로 쓴다（위치크기 전환은 AnchoredOverlay가\n//   담당）. slide-in-from-right-52 등 방향 애니메이션도 미지원.\n// - 미지원: 화살표키 트리거 순회hover 지연 개방트리거 폭 추종 콘텐츠\n//   （w-full 토큰 제거 — 카드 폭은 내용 결정, 사용처 className으로 조정）.\n// - 아이템 value: radix는 자동 생성한다 — 포팅도 useId로 자동 생성하되\n//   value prop 명시를 허용해 Root value/onValueChange 제어와 연결하게 했다.\n// - 접근성: 트리거 aria-expandedaria-controls（useId 토대로 Content nativeID와\n//   결정적 연결）, Link role=\"link\". 원본 ul/list 스타일（list-none）은 View\n//   구조에서 무의미해 제거, role list/listitem 계약도 생략했다（웹 div 기반\n//   근사 — 필요시 사용처 지정）.\nimport {\n  Children,\n  createContext,\n  forwardRef,\n  useCallback,\n  useContext,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  type ForwardedRef,\n  type ReactNode,\n} from \"react\";\nimport {\n  ChevronDownIcon,\n  Pressable,\n  Text,\n  View,\n  type BridgePressableRef,\n  type BridgeViewRef,\n  type PressableProps,\n  type ViewProps,\n} from \"@unikit-bridge/ui/primitives\";\nimport { cva } from \"class-variance-authority\";\n\nimport { AnchoredOverlay } from \"../lib/overlay\";\nimport { cn } from \"@/lib/utils\";\n\n// ---------------------------------------------------------------------------\n// 내부 유틸\n// ---------------------------------------------------------------------------\n\ntype PressableRefObject = { current: BridgePressableRef | null };\n\n/** 외부 forwardRef와 앵커 측정용 내부 ref를 한 콜백으로 합성한다（dropdown 관행） */\nfunction composeRefs(\n  ...refs: Array<ForwardedRef<BridgePressableRef> | PressableRefObject>\n) {\n  return (node: BridgePressableRef | null) => {\n    for (const ref of refs) {\n      if (!ref) continue;\n      if (typeof ref === \"function\") ref(node);\n      else ref.current = node;\n    }\n  };\n}\n\n/** 자식 중 문자열/숫자를 내부 Text로 감싼다 — 네이티브 색 상속 불가 대응 */\nfunction wrapTextChildren(children: ReactNode, textClassName: string) {\n  return Children.map(children, (child) =>\n    typeof child === \"string\" || typeof child === \"number\" ? (\n      <Text className={textClassName}>{child}</Text>\n    ) : (\n      child\n    ),\n  );\n}\n\n// ---------------------------------------------------------------------------\n// 상태 context — 루트（활성 아이템）아이템（값트리거 ref）\n// ---------------------------------------------------------------------------\n\ntype NavigationMenuContextValue = {\n  value: string | null;\n  setValue: (next: string | null) => void;\n};\n\nconst NavigationMenuContext = createContext<NavigationMenuContextValue | null>(\n  null,\n);\n\nfunction useNavigationMenuContext(\n  component: string,\n): NavigationMenuContextValue {\n  const ctx = useContext(NavigationMenuContext);\n  if (!ctx) {\n    throw new Error(`<${component}>는 <NavigationMenu> 안에서 사용해야 합니다.`);\n  }\n  return ctx;\n}\n\ntype NavigationMenuItemContextValue = {\n  itemValue: string;\n  /** Trigger 요소 ref — Content（AnchoredOverlay）의 앵커포커스 복귀 공용 */\n  triggerRef: PressableRefObject;\n  /** 트리거 aria-controls ↔ 콘텐츠 nativeID 연결 */\n  contentId: string;\n};\n\nconst NavigationMenuItemContext =\n  createContext<NavigationMenuItemContextValue | null>(null);\n\nfunction useNavigationMenuItemContext(\n  component: string,\n): NavigationMenuItemContextValue {\n  const ctx = useContext(NavigationMenuItemContext);\n  if (!ctx) {\n    throw new Error(\n      `<${component}>는 <NavigationMenuItem> 안에서 사용해야 합니다.`,\n    );\n  }\n  return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// 루트리스트아이템\n// ---------------------------------------------------------------------------\n\nexport type NavigationMenuProps = ViewProps & {\n  /** 활성（콘텐츠 전개） 아이템 value（제어） — null이면 전부 닫힘 */\n  value?: string | null;\n  defaultValue?: string | null;\n  onValueChange?: (value: string | null) => void;\n  /** API 호환용 — 포팅에서는 무시（콘텐츠가 각자 앵커링, 머리 주석 참조） */\n  viewport?: boolean;\n};\n\nconst NavigationMenu = forwardRef<BridgeViewRef, NavigationMenuProps>(\n  (\n    {\n      value,\n      defaultValue = null,\n      onValueChange,\n      viewport = true,\n      className,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    // 비제어 상태 — 제어（value 지정）면 무시된다.\n    const [internalValue, setInternalValue] = useState(defaultValue);\n    const current = value !== undefined ? value : internalValue;\n    const stateRef = useRef({ value, current, onValueChange });\n    stateRef.current = { value, current, onValueChange };\n    const setValue = useCallback((next: string | null) => {\n      const state = stateRef.current;\n      if (state.value === undefined) setInternalValue(next);\n      if (next !== state.current) state.onValueChange?.(next);\n    }, []);\n    const contextValue = useMemo<NavigationMenuContextValue>(\n      () => ({ value: current, setValue }),\n      [current, setValue],\n    );\n    return (\n      <NavigationMenuContext.Provider value={contextValue}>\n        {/* 원본 group/navigation-menu 그룹 셀렉터는 미사용 — 조건부 클래스로 대체 */}\n        <View\n          ref={ref}\n          className={cn(\n            \"relative flex-row max-w-max flex-1 items-center justify-center\",\n            className,\n          )}\n          {...props}\n        >\n          {children}\n          {viewport ? <NavigationMenuViewport /> : null}\n        </View>\n      </NavigationMenuContext.Provider>\n    );\n  },\n);\nNavigationMenu.displayName = \"NavigationMenu\";\n\nexport type NavigationMenuListProps = ViewProps;\n\nconst NavigationMenuList = forwardRef<BridgeViewRef, NavigationMenuListProps>(\n  ({ className, ...props }, ref) => (\n    <View\n      ref={ref}\n      className={cn(\n        \"flex-row flex-1 items-center justify-center gap-1\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n);\nNavigationMenuList.displayName = \"NavigationMenuList\";\n\nexport type NavigationMenuItemProps = ViewProps & {\n  /** 아이템 식별자 — Root value 제어와 연결（미지정 시 자동 생성） */\n  value?: string;\n};\n\nconst NavigationMenuItem = forwardRef<BridgeViewRef, NavigationMenuItemProps>(\n  ({ value, className, children, ...props }, ref) => {\n    const id = useId();\n    const triggerRef = useRef<BridgePressableRef | null>(null);\n    const itemValue = value ?? id;\n    const itemContextValue = useMemo<NavigationMenuItemContextValue>(\n      () => ({\n        itemValue,\n        triggerRef,\n        contentId: `${id}-navigation-menu-content`,\n      }),\n      [itemValue, id],\n    );\n    return (\n      <NavigationMenuItemContext.Provider value={itemContextValue}>\n        <View ref={ref} className={cn(\"relative\", className)} {...props}>\n          {children}\n        </View>\n      </NavigationMenuItemContext.Provider>\n    );\n  },\n);\nNavigationMenuItem.displayName = \"NavigationMenuItem\";\n\n// ---------------------------------------------------------------------------\n// 트리거콘텐츠링크\n// ---------------------------------------------------------------------------\n\n/** 원본 cva 재현 — data-［state=open］transition 임의값은 Trigger 측 근사（머리 주석） */\nconst navigationMenuTriggerStyle = cva(\n  \"flex h-9 w-max flex-row items-center justify-center rounded-md bg-background px-4 py-2 active:bg-accent web:hover:bg-accent web:focus-visible:bg-accent web:focus-visible:outline-1 web:focus-visible:ring-3 web:focus-visible:ring-ring/50 web:outline-none web:transition-colors\",\n);\n\nexport type NavigationMenuTriggerProps = PressableProps;\n\nconst NavigationMenuTrigger = forwardRef<\n  BridgePressableRef,\n  NavigationMenuTriggerProps\n>(({ className, children, disabled, onPress, ...props }, ref) => {\n  const nav = useNavigationMenuContext(\"NavigationMenuTrigger\");\n  const item = useNavigationMenuItemContext(\"NavigationMenuTrigger\");\n  const isOpen = nav.value === item.itemValue;\n  return (\n    <Pressable\n      ref={composeRefs(ref, item.triggerRef)}\n      accessibilityRole=\"button\"\n      aria-expanded={isOpen}\n      aria-controls={item.contentId}\n      disabled={disabled}\n      onPress={() => {\n        nav.setValue(isOpen ? null : item.itemValue);\n        onPress?.();\n      }}\n      className={cn(\n        navigationMenuTriggerStyle(),\n        // 원본 data-［state=open］:bg-accent → 조건부 클래스\n        isOpen && \"bg-accent/50\",\n        disabled && \"web:pointer-events-none opacity-50\",\n        className,\n      )}\n      {...props}\n    >\n      {wrapTextChildren(children, \"font-sans text-sm font-medium text-foreground\")}\n      <ChevronDownIcon\n        className={cn(\n          \"ml-1 size-3 text-muted-foreground web:transition-transform web:duration-300\",\n          isOpen && \"web:rotate-180\",\n        )}\n      />\n    </Pressable>\n  );\n});\nNavigationMenuTrigger.displayName = \"NavigationMenuTrigger\";\n\nexport type NavigationMenuContentProps = ViewProps;\n\nconst NavigationMenuContent = forwardRef<\n  BridgeViewRef,\n  NavigationMenuContentProps\n>(({ className, children, nativeID, ...props }, ref) => {\n  const nav = useNavigationMenuContext(\"NavigationMenuContent\");\n  const item = useNavigationMenuItemContext(\"NavigationMenuContent\");\n  const isOpen = nav.value === item.itemValue;\n  return (\n    // 각 아이템 앵커링 팝업 — 원본 viewport=false 카드 토큰을 기본으로 사용.\n    // ESC바깥 눌림 닫기포커스 이동/복귀는 AnchoredOverlay 골격이 제공한다.\n    <AnchoredOverlay\n      visible={isOpen}\n      onClose={() => nav.setValue(null)}\n      anchorRef={item.triggerRef}\n      placement=\"bottom\"\n      align=\"start\"\n      offset={6}\n      triggerRef={item.triggerRef}\n      className=\"z-50\"\n    >\n      <View\n        ref={ref}\n        nativeID={nativeID ?? item.contentId}\n        className={cn(\n          \"pointer-events-auto rounded-md border border-border bg-popover p-2 pr-2.5 text-popover-foreground unikit-popover-in web:shadow-md\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </View>\n    </AnchoredOverlay>\n  );\n});\nNavigationMenuContent.displayName = \"NavigationMenuContent\";\n\nexport type NavigationMenuLinkProps = PressableProps & {\n  /** 현재 위치 표시（원본 data-［active=true］） */\n  active?: boolean;\n};\n\nconst NavigationMenuLink = forwardRef<\n  BridgePressableRef,\n  NavigationMenuLinkProps\n>(({ className, active, disabled, onPress, children, ...props }, ref) => {\n  const nav = useNavigationMenuContext(\"NavigationMenuLink\");\n  return (\n    <Pressable\n      ref={ref}\n      accessibilityRole=\"link\"\n      disabled={disabled}\n      onPress={() => {\n        // 이동 선택 시 메뉴（콘텐츠 팝업）를 닫는다\n        nav.setValue(null);\n        onPress?.();\n      }}\n      className={cn(\n        \"flex flex-col gap-1 rounded-sm p-2 active:bg-accent web:hover:bg-accent web:focus-visible:bg-accent web:focus-visible:outline-1 web:focus-visible:ring-3 web:focus-visible:ring-ring/50 web:transition-colors\",\n        active &&\n          \"bg-accent/50 active:bg-accent web:hover:bg-accent web:focus-visible:bg-accent\",\n        disabled && \"web:pointer-events-none opacity-50\",\n        className,\n      )}\n      {...props}\n    >\n      {wrapTextChildren(children, \"font-sans text-sm text-foreground\")}\n    </Pressable>\n  );\n});\nNavigationMenuLink.displayName = \"NavigationMenuLink\";\n\n// ---------------------------------------------------------------------------\n// IndicatorViewport — 원본 배치 트릭 재현 불가로 no-op（머리 주석 참조）\n// ---------------------------------------------------------------------------\n\nexport type NavigationMenuIndicatorProps = ViewProps;\n\nconst NavigationMenuIndicator = forwardRef<\n  BridgeViewRef,\n  NavigationMenuIndicatorProps\n>(() => null);\nNavigationMenuIndicator.displayName = \"NavigationMenuIndicator\";\n\nexport type NavigationMenuViewportProps = ViewProps;\n\nconst NavigationMenuViewport = forwardRef<\n  BridgeViewRef,\n  NavigationMenuViewportProps\n>(() => null);\nNavigationMenuViewport.displayName = \"NavigationMenuViewport\";\n\nexport {\n  NavigationMenu,\n  NavigationMenuList,\n  NavigationMenuItem,\n  NavigationMenuContent,\n  NavigationMenuTrigger,\n  NavigationMenuLink,\n  NavigationMenuIndicator,\n  NavigationMenuViewport,\n  navigationMenuTriggerStyle,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/navigation-menu.tsx"
    }
  ],
  "type": "registry:ui"
}
