{
  "name": "dropdown-menu",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "dropdown-menu.tsx",
      "content": "\"use client\";\r\n\r\nimport { computePosition, css, scaleIn, scaleOut, themeVars as theme, type Placement } from \"@yugnex/core\";\r\nimport { useClickOutside, useEscapeKey, usePortal, usePresence } from \"@yugnex/core/client\";\r\nimport {\r\n  cloneElement,\r\n  createContext,\r\n  useContext,\r\n  useEffect,\r\n  useRef,\r\n  useState,\r\n  type HTMLAttributes,\r\n  type KeyboardEvent,\r\n  type MutableRefObject,\r\n  type ReactElement,\r\n  type ReactNode,\r\n  type Ref,\r\n} from \"react\";\r\nimport { createPortal } from \"react-dom\";\r\n\r\ninterface DropdownMenuContextValue {\r\n  open: boolean;\r\n  setOpen: (open: boolean) => void;\r\n  anchorRef: MutableRefObject<HTMLElement | null>;\r\n  itemsRef: MutableRefObject<HTMLElement[]>;\r\n}\r\n\r\nconst DropdownMenuContext = createContext<DropdownMenuContextValue | null>(null);\r\n\r\nfunction useDropdownMenuContext(component: string): DropdownMenuContextValue {\r\n  const ctx = useContext(DropdownMenuContext);\r\n  if (!ctx) throw new Error(`<${component}> must be used within <DropdownMenu>`);\r\n  return ctx;\r\n}\r\n\r\nfunction mergeRefs<T>(...refs: Array<Ref<T> | undefined>): (node: T | null) => void {\r\n  return (node) => {\r\n    for (const ref of refs) {\r\n      if (typeof ref === \"function\") ref(node);\r\n      else if (ref) (ref as { current: T | null }).current = node;\r\n    }\r\n  };\r\n}\r\n\r\nconst contentClass = css({\r\n  position: \"fixed\",\r\n  top: 0,\r\n  left: 0,\r\n  zIndex: theme.zIndex.dropdown,\r\n  minWidth: \"10rem\",\r\n  padding: theme.space[1],\r\n  borderRadius: theme.radius.md,\r\n  border: `1px solid ${theme.color.border}`,\r\n  backgroundColor: theme.color.popover,\r\n  color: theme.color.popoverForeground,\r\n  boxShadow: theme.shadow.lg,\r\n  '&[data-state=\"open\"]': { animation: `${scaleIn} ${theme.duration.fast} ${theme.easing.decelerate}` },\r\n  '&[data-state=\"closed\"]': { animation: `${scaleOut} ${theme.duration.fast} ${theme.easing.accelerate}` },\r\n});\r\n\r\nconst itemClass = css({\r\n  display: \"flex\",\r\n  alignItems: \"center\",\r\n  width: \"100%\",\r\n  padding: `${theme.space[2]} ${theme.space[2.5]}`,\r\n  borderRadius: theme.radius.sm,\r\n  border: \"none\",\r\n  backgroundColor: \"transparent\",\r\n  color: theme.color.popoverForeground,\r\n  fontFamily: theme.fontFamily.sans,\r\n  fontSize: theme.fontSize.sm,\r\n  textAlign: \"left\",\r\n  cursor: \"pointer\",\r\n  \"&:hover, &:focus\": {\r\n    backgroundColor: theme.color.accent,\r\n    color: theme.color.accentForeground,\r\n    outline: \"none\",\r\n  },\r\n  '&[aria-disabled=\"true\"]': {\r\n    opacity: 0.5,\r\n    cursor: \"not-allowed\",\r\n  },\r\n});\r\n\r\nexport interface DropdownMenuProps {\r\n  open: boolean;\r\n  onOpenChange: (open: boolean) => void;\r\n  children: ReactNode;\r\n}\r\n\r\nexport function DropdownMenu({ open, onOpenChange, children }: DropdownMenuProps) {\r\n  const anchorRef = useRef<HTMLElement | null>(null);\r\n  const itemsRef = useRef<HTMLElement[]>([]);\r\n\r\n  return (\r\n    <DropdownMenuContext.Provider value={{ open, setOpen: onOpenChange, anchorRef, itemsRef }}>\r\n      {children}\r\n    </DropdownMenuContext.Provider>\r\n  );\r\n}\r\n\r\nexport interface DropdownMenuTriggerProps {\r\n  children: ReactElement;\r\n}\r\n\r\nexport function DropdownMenuTrigger({ children }: DropdownMenuTriggerProps) {\r\n  const { open, setOpen, anchorRef } = useDropdownMenuContext(\"DropdownMenuTrigger\");\r\n  const element = children as ReactElement<Record<string, unknown>>;\r\n  const childRef = (element as { ref?: Ref<HTMLElement> }).ref;\r\n\r\n  return cloneElement(element, {\r\n    ref: mergeRefs(anchorRef, childRef),\r\n    \"aria-haspopup\": \"menu\",\r\n    \"aria-expanded\": open,\r\n    onClick: () => setOpen(!open),\r\n  });\r\n}\r\n\r\nexport interface DropdownMenuContentProps extends HTMLAttributes<HTMLDivElement> {\r\n  placement?: Placement;\r\n  children: ReactNode;\r\n}\r\n\r\nexport function DropdownMenuContent({\r\n  placement = \"bottom\",\r\n  className,\r\n  children,\r\n  ...props\r\n}: DropdownMenuContentProps) {\r\n  const { open, setOpen, anchorRef, itemsRef } = useDropdownMenuContext(\"DropdownMenuContent\");\r\n  const { mounted, dataState } = usePresence(open, { exitDuration: 150 });\r\n  const portalNode = usePortal();\r\n  const contentRef = useRef<HTMLDivElement | null>(null);\r\n  const [pos, setPos] = useState({ x: 0, y: 0 });\r\n\r\n  useEscapeKey(() => setOpen(false), open);\r\n  useClickOutside(contentRef, () => setOpen(false), open);\r\n\r\n  useEffect(() => {\r\n    // Keyed on `mounted`, not `open`: the content node only exists once\r\n    // `mounted` flips true one tick after `open` does (see usePresence).\r\n    if (!mounted) return;\r\n    const raf = requestAnimationFrame(() => {\r\n      if (contentRef.current) {\r\n        itemsRef.current = Array.from(contentRef.current.querySelectorAll<HTMLElement>('[role=\"menuitem\"]'));\r\n        itemsRef.current[0]?.focus();\r\n      }\r\n    });\r\n    return () => cancelAnimationFrame(raf);\r\n  }, [mounted, itemsRef]);\r\n\r\n  useEffect(() => {\r\n    if (!open || !anchorRef.current || !contentRef.current) return;\r\n    const update = () => {\r\n      if (!anchorRef.current || !contentRef.current) return;\r\n      const result = computePosition(anchorRef.current, contentRef.current, { placement, align: \"start\" });\r\n      setPos({ x: result.x, y: result.y });\r\n    };\r\n    update();\r\n    window.addEventListener(\"scroll\", update, true);\r\n    window.addEventListener(\"resize\", update);\r\n    return () => {\r\n      window.removeEventListener(\"scroll\", update, true);\r\n      window.removeEventListener(\"resize\", update);\r\n    };\r\n  }, [open, placement, mounted, anchorRef]);\r\n\r\n  const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\r\n    const items = itemsRef.current;\r\n    if (items.length === 0) return;\r\n    const currentIndex = items.indexOf(document.activeElement as HTMLElement);\r\n\r\n    if (event.key === \"ArrowDown\") {\r\n      event.preventDefault();\r\n      items[(currentIndex + 1 + items.length) % items.length]?.focus();\r\n    } else if (event.key === \"ArrowUp\") {\r\n      event.preventDefault();\r\n      items[(currentIndex - 1 + items.length) % items.length]?.focus();\r\n    } else if (event.key === \"Home\") {\r\n      event.preventDefault();\r\n      items[0]?.focus();\r\n    } else if (event.key === \"End\") {\r\n      event.preventDefault();\r\n      items[items.length - 1]?.focus();\r\n    } else if (event.key === \"Enter\" || event.key === \" \") {\r\n      // Items are plain divs (role=\"menuitem\"), not buttons, so they don't\r\n      // get native Enter/Space activation — wire it up explicitly.\r\n      event.preventDefault();\r\n      items[currentIndex]?.click();\r\n    } else if (event.key.length === 1 && /\\S/.test(event.key)) {\r\n      const char = event.key.toLowerCase();\r\n      const start = (currentIndex + 1) % items.length;\r\n      for (let i = 0; i < items.length; i++) {\r\n        const item = items[(start + i) % items.length]!;\r\n        if (item.textContent?.trim().toLowerCase().startsWith(char)) {\r\n          item.focus();\r\n          break;\r\n        }\r\n      }\r\n    }\r\n  };\r\n\r\n  if (!mounted || !portalNode) return null;\r\n\r\n  return createPortal(\r\n    <div\r\n      ref={contentRef}\r\n      role=\"menu\"\r\n      data-state={dataState}\r\n      className={className ? `${contentClass} ${className}` : contentClass}\r\n      style={{ transform: `translate(${pos.x}px, ${pos.y}px)` }}\r\n      onKeyDown={handleKeyDown}\r\n      {...props}\r\n    >\r\n      {children}\r\n    </div>,\r\n    portalNode,\r\n  );\r\n}\r\n\r\nexport interface DropdownMenuItemProps extends HTMLAttributes<HTMLDivElement> {\r\n  onSelect?: () => void;\r\n  disabled?: boolean;\r\n}\r\n\r\nexport function DropdownMenuItem({\r\n  onSelect,\r\n  disabled,\r\n  className,\r\n  onClick,\r\n  ...props\r\n}: DropdownMenuItemProps) {\r\n  const { setOpen } = useDropdownMenuContext(\"DropdownMenuItem\");\r\n\r\n  return (\r\n    <div\r\n      role=\"menuitem\"\r\n      tabIndex={-1}\r\n      aria-disabled={disabled || undefined}\r\n      className={className ? `${itemClass} ${className}` : itemClass}\r\n      onClick={(event) => {\r\n        if (disabled) return;\r\n        onClick?.(event);\r\n        onSelect?.();\r\n        setOpen(false);\r\n      }}\r\n      {...props}\r\n    />\r\n  );\r\n}\r\n",
      "type": "registry:component"
    }
  ]
}