{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "nested-menu",
  "type": "registry:ui",
  "title": "Nested Menu",
  "description": "A hover-activated nested menu component for multi-level navigation with smooth animations.",
  "dependencies": [
    "@radix-ui/react-popover"
  ],
  "files": [
    {
      "path": "registry/new-york/ui/nested-menu.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useState, useCallback, useRef, useEffect } from \"react\";\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\nimport { cn } from \"@/lib/utils\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface NestedMenuItem {\n\t/** Unique key for the item */\n\tkey: string;\n\t/** Display label */\n\tlabel: string;\n\t/** Icon component */\n\ticon?: React.ComponentType<{ className?: string }>;\n\t/** Click handler */\n\tonSelect?: () => void;\n\t/** Whether this item has a submenu */\n\thasSubmenu?: boolean;\n\t/** Submenu items (if hasSubmenu is true) */\n\tsubmenuItems?: NestedMenuItem[];\n\t/** Color variant */\n\tvariant?: \"default\" | \"danger\";\n\t/** Additional className for custom styling */\n\tclassName?: string;\n\t/** Icon color (CSS color value) */\n\ticonColor?: string;\n\t/** Separator after this item */\n\tseparator?: boolean;\n}\n\nexport interface NestedMenuSectionProps {\n\t/** Section title */\n\ttitle?: string;\n\t/** Items in this section */\n\titems: NestedMenuItem[];\n\t/** Show divider after section */\n\tshowDivider?: boolean;\n}\n\n// ============================================================================\n// Hook: useNestedMenu\n// ============================================================================\n\nexport function useNestedMenu() {\n\tconst [isOpen, setIsOpen] = useState(false);\n\tconst [itemRef, setItemRef] = useState<HTMLElement | null>(null);\n\tconst timeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n\tconst handleMouseEnter = useCallback((e: React.MouseEvent<HTMLElement>) => {\n\t\tif (timeoutRef.current) {\n\t\t\tclearTimeout(timeoutRef.current);\n\t\t\ttimeoutRef.current = null;\n\t\t}\n\t\tsetItemRef(e.currentTarget as HTMLElement);\n\t\tsetIsOpen(true);\n\t}, []);\n\n\tconst handleMouseLeave = useCallback(() => {\n\t\t// Longer delay to allow moving to submenu\n\t\ttimeoutRef.current = setTimeout(() => {\n\t\t\tsetIsOpen(false);\n\t\t}, 300);\n\t}, []);\n\n\tconst cancelClose = useCallback(() => {\n\t\tif (timeoutRef.current) {\n\t\t\tclearTimeout(timeoutRef.current);\n\t\t\ttimeoutRef.current = null;\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (timeoutRef.current) {\n\t\t\t\tclearTimeout(timeoutRef.current);\n\t\t\t}\n\t\t};\n\t}, []);\n\n\treturn {\n\t\tisOpen,\n\t\tsetIsOpen,\n\t\titemRef,\n\t\thandleMouseEnter,\n\t\thandleMouseLeave,\n\t\tcancelClose,\n\t};\n}\n\n// ============================================================================\n// NestedMenuTooltip Component\n// ============================================================================\n\nexport interface NestedMenuTooltipProps {\n\t/** Whether the tooltip is open */\n\tisOpen: boolean;\n\t/** Callback when open state changes */\n\tonOpenChange: (open: boolean) => void;\n\t/** Reference element to anchor the tooltip */\n\titemRef: HTMLElement | null;\n\t/** Menu items to display */\n\tmenuItems: NestedMenuItem[];\n\t/** Icon className */\n\ticonClassName?: string;\n\t/** Container className */\n\tclassName?: string;\n\t/** Called when mouse enters the tooltip */\n\tonMouseEnter?: () => void;\n\t/** Called when mouse leaves the tooltip */\n\tonMouseLeave?: () => void;\n}\n\nexport function NestedMenuTooltip({\n\tisOpen,\n\tonOpenChange,\n\titemRef,\n\tmenuItems,\n\ticonClassName = \"h-4 w-4\",\n\tclassName,\n\tonMouseEnter,\n\tonMouseLeave,\n}: NestedMenuTooltipProps) {\n\tif (!itemRef || !isOpen) return null;\n\n\tconst rect = itemRef.getBoundingClientRect();\n\n\treturn (\n\t\t<PopoverPrimitive.Root open={isOpen} onOpenChange={onOpenChange}>\n\t\t\t<PopoverPrimitive.Anchor\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"fixed\",\n\t\t\t\t\tleft: rect.right,\n\t\t\t\t\ttop: rect.top,\n\t\t\t\t\twidth: 1,\n\t\t\t\t\theight: 1,\n\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PopoverPrimitive.Portal>\n\t\t\t\t<PopoverPrimitive.Content\n\t\t\t\t\tside=\"right\"\n\t\t\t\t\talign=\"start\"\n\t\t\t\t\tsideOffset={10}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"z-50 min-w-[180px] overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95\",\n\t\t\t\t\t\t\"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n\t\t\t\t\t\tclassName,\n\t\t\t\t\t)}\n\t\t\t\t\tonMouseEnter={onMouseEnter}\n\t\t\t\t\tonMouseLeave={onMouseLeave}\n\t\t\t\t\tonOpenAutoFocus={(e) => e.preventDefault()}\n\t\t\t\t\tonCloseAutoFocus={(e) => e.preventDefault()}\n\t\t\t\t>\n\t\t\t\t\t{menuItems.map((item) => {\n\t\t\t\t\t\tconst Icon = item.icon;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tkey={item.key}\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm outline-none transition-colors\",\n\t\t\t\t\t\t\t\t\t\"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\",\n\t\t\t\t\t\t\t\t\titem.variant === \"danger\" &&\n\t\t\t\t\t\t\t\t\t\t\"text-destructive hover:bg-destructive/10 hover:text-destructive focus:bg-destructive/10\",\n\t\t\t\t\t\t\t\t\titem.className,\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\titem.onSelect?.();\n\t\t\t\t\t\t\t\t\tonOpenChange(false);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{Icon && (\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\tstyle={\n\t\t\t\t\t\t\t\t\t\t\titem.iconColor ? { color: item.iconColor } : undefined\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<Icon className={cn(iconClassName)} />\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t<span className=\"flex-1 text-left\">{item.label}</span>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t\t</PopoverPrimitive.Content>\n\t\t\t</PopoverPrimitive.Portal>\n\t\t</PopoverPrimitive.Root>\n\t);\n}\n\n// ============================================================================\n// NestedMenuItem Component\n// ============================================================================\n\nexport interface NestedMenuItemComponentProps {\n\t/** Menu item data */\n\titem: NestedMenuItem;\n\t/** Icon className */\n\ticonClassName?: string;\n\t/** Whether to show arrow for submenu */\n\tshowSubmenuArrow?: boolean;\n\t/** Arrow icon component */\n\tarrowIcon?: React.ComponentType<{ className?: string }>;\n\t/** Mouse enter handler (for submenu trigger) */\n\tonMouseEnter?: (e: React.MouseEvent<HTMLElement>) => void;\n\t/** Mouse leave handler */\n\tonMouseLeave?: () => void;\n}\n\nexport function NestedMenuItemComponent({\n\titem,\n\ticonClassName = \"h-6 w-6\",\n\tshowSubmenuArrow = true,\n\tarrowIcon: ArrowIcon,\n\tonMouseEnter,\n\tonMouseLeave,\n}: NestedMenuItemComponentProps) {\n\tconst Icon = item.icon;\n\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm outline-none transition-colors\",\n\t\t\t\t\"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\",\n\t\t\t\titem.variant === \"danger\" &&\n\t\t\t\t\t\"text-destructive hover:bg-destructive/10 hover:text-destructive focus:bg-destructive/10\",\n\t\t\t\titem.className,\n\t\t\t)}\n\t\t\tonClick={() => !item.hasSubmenu && item.onSelect?.()}\n\t\t\tonMouseEnter={onMouseEnter}\n\t\t\tonMouseLeave={onMouseLeave}\n\t\t>\n\t\t\t{Icon && (\n\t\t\t\t<span style={item.iconColor ? { color: item.iconColor } : undefined}>\n\t\t\t\t\t<Icon className={cn(iconClassName)} />\n\t\t\t\t</span>\n\t\t\t)}\n\t\t\t<span className=\"flex-1 text-left\">{item.label}</span>\n\t\t\t{item.hasSubmenu && showSubmenuArrow && ArrowIcon && (\n\t\t\t\t<ArrowIcon className=\"h-4 w-4 text-muted-foreground\" />\n\t\t\t)}\n\t\t</button>\n\t);\n}\n\n// ============================================================================\n// NestedMenuSection Component\n// ============================================================================\n\nexport interface NestedMenuSectionComponentProps\n\textends NestedMenuSectionProps {\n\t/** Icon className */\n\ticonClassName?: string;\n\t/** Arrow icon for submenu items */\n\tarrowIcon?: React.ComponentType<{ className?: string }>;\n\t/** Callback when item with submenu is hovered */\n\tonSubmenuHover?: (\n\t\titem: NestedMenuItem,\n\t\te: React.MouseEvent<HTMLElement>,\n\t) => void;\n\t/** Callback when mouse leaves submenu trigger */\n\tonSubmenuLeave?: () => void;\n}\n\nexport function NestedMenuSection({\n\ttitle,\n\titems,\n\tshowDivider = false,\n\ticonClassName = \"h-4 w-4\",\n\tarrowIcon,\n\tonSubmenuHover,\n\tonSubmenuLeave,\n}: NestedMenuSectionComponentProps) {\n\treturn (\n\t\t<div className=\"py-1\">\n\t\t\t{title && (\n\t\t\t\t<div className=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n\t\t\t\t\t{title}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{items.map((item) => (\n\t\t\t\t<React.Fragment key={item.key}>\n\t\t\t\t\t<NestedMenuItemComponent\n\t\t\t\t\t\titem={item}\n\t\t\t\t\t\ticonClassName={iconClassName}\n\t\t\t\t\t\tarrowIcon={arrowIcon}\n\t\t\t\t\t\tonMouseEnter={\n\t\t\t\t\t\t\titem.hasSubmenu ? (e) => onSubmenuHover?.(item, e) : undefined\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonMouseLeave={item.hasSubmenu ? onSubmenuLeave : undefined}\n\t\t\t\t\t/>\n\t\t\t\t\t{item.separator && <div className=\"my-1 h-px bg-border\" />}\n\t\t\t\t</React.Fragment>\n\t\t\t))}\n\t\t\t{showDivider && <div className=\"my-1 h-px bg-border\" />}\n\t\t</div>\n\t);\n}\n\n// ============================================================================\n// NestedMenu Component (Full Menu with built-in submenu handling)\n// ============================================================================\n\nexport interface NestedMenuProps {\n\t/** Menu sections */\n\tsections: NestedMenuSectionProps[];\n\t/** Trigger element */\n\ttrigger: React.ReactNode;\n\t/** Icon className */\n\ticonClassName?: string;\n\t/** Arrow icon for submenu items */\n\tarrowIcon?: React.ComponentType<{ className?: string }>;\n\t/** Menu placement */\n\tside?: \"top\" | \"right\" | \"bottom\" | \"left\";\n\t/** Alignment relative to trigger */\n\talign?: \"start\" | \"center\" | \"end\";\n\t/** Offset from trigger */\n\tsideOffset?: number;\n\t/** Container className */\n\tclassName?: string;\n\t/** Controlled open state */\n\topen?: boolean;\n\t/** Callback when open state changes */\n\tonOpenChange?: (open: boolean) => void;\n}\n\nexport function NestedMenu({\n\tsections,\n\ttrigger,\n\ticonClassName = \"h-4 w-4\",\n\tarrowIcon,\n\tside = \"right\",\n\talign = \"start\",\n\tsideOffset = 8,\n\tclassName,\n\topen: controlledOpen,\n\tonOpenChange,\n}: NestedMenuProps) {\n\tconst [internalOpen, setInternalOpen] = useState(false);\n\tconst isControlled = controlledOpen !== undefined;\n\tconst open = isControlled ? controlledOpen : internalOpen;\n\n\tconst handleOpenChange = useCallback(\n\t\t(newOpen: boolean) => {\n\t\t\tif (!isControlled) {\n\t\t\t\tsetInternalOpen(newOpen);\n\t\t\t}\n\t\t\tonOpenChange?.(newOpen);\n\t\t},\n\t\t[isControlled, onOpenChange],\n\t);\n\n\t// Submenu state\n\tconst [activeSubmenu, setActiveSubmenu] = useState<NestedMenuItem | null>(\n\t\tnull,\n\t);\n\tconst [submenuAnchor, setSubmenuAnchor] = useState<HTMLElement | null>(null);\n\tconst submenuTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n\tconst handleSubmenuHover = useCallback(\n\t\t(item: NestedMenuItem, e: React.MouseEvent<HTMLElement>) => {\n\t\t\tif (submenuTimeoutRef.current) {\n\t\t\t\tclearTimeout(submenuTimeoutRef.current);\n\t\t\t\tsubmenuTimeoutRef.current = null;\n\t\t\t}\n\t\t\tsetActiveSubmenu(item);\n\t\t\tsetSubmenuAnchor(e.currentTarget);\n\t\t},\n\t\t[],\n\t);\n\n\tconst handleSubmenuLeave = useCallback(() => {\n\t\tsubmenuTimeoutRef.current = setTimeout(() => {\n\t\t\tsetActiveSubmenu(null);\n\t\t\tsetSubmenuAnchor(null);\n\t\t}, 300);\n\t}, []);\n\n\tconst handleSubmenuEnter = useCallback(() => {\n\t\tif (submenuTimeoutRef.current) {\n\t\t\tclearTimeout(submenuTimeoutRef.current);\n\t\t\tsubmenuTimeoutRef.current = null;\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (submenuTimeoutRef.current) {\n\t\t\t\tclearTimeout(submenuTimeoutRef.current);\n\t\t\t}\n\t\t};\n\t}, []);\n\n\t// Close submenu when main menu closes\n\tuseEffect(() => {\n\t\tif (!open) {\n\t\t\tsetActiveSubmenu(null);\n\t\t\tsetSubmenuAnchor(null);\n\t\t}\n\t}, [open]);\n\n\treturn (\n\t\t<>\n\t\t\t<PopoverPrimitive.Root open={open} onOpenChange={handleOpenChange}>\n\t\t\t\t<PopoverPrimitive.Trigger asChild>{trigger}</PopoverPrimitive.Trigger>\n\t\t\t\t<PopoverPrimitive.Portal>\n\t\t\t\t\t<PopoverPrimitive.Content\n\t\t\t\t\t\tside={side}\n\t\t\t\t\t\talign={align}\n\t\t\t\t\t\tsideOffset={sideOffset}\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"z-50 min-w-[200px] overflow-hidden rounded-xl px-2 py-1 bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95\",\n\t\t\t\t\t\t\t\"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n\t\t\t\t\t\t\tclassName,\n\t\t\t\t\t\t)}\n\t\t\t\t\t\tonOpenAutoFocus={(e) => e.preventDefault()}\n\t\t\t\t\t\tonCloseAutoFocus={(e) => e.preventDefault()}\n\t\t\t\t\t\tonInteractOutside={(e) => {\n\t\t\t\t\t\t\t// Prevent closing when interacting with submenu\n\t\t\t\t\t\t\tconst target = e.target as HTMLElement;\n\t\t\t\t\t\t\tif (target?.closest(\"[data-radix-popper-content-wrapper]\")) {\n\t\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{sections.map((section, index) => (\n\t\t\t\t\t\t\t<NestedMenuSection\n\t\t\t\t\t\t\t\tkey={section.title || `section-${index}`}\n\t\t\t\t\t\t\t\t{...section}\n\t\t\t\t\t\t\t\ticonClassName={iconClassName}\n\t\t\t\t\t\t\t\tarrowIcon={arrowIcon}\n\t\t\t\t\t\t\t\tonSubmenuHover={handleSubmenuHover}\n\t\t\t\t\t\t\t\tonSubmenuLeave={handleSubmenuLeave}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</PopoverPrimitive.Content>\n\t\t\t\t</PopoverPrimitive.Portal>\n\t\t\t</PopoverPrimitive.Root>\n\n\t\t\t{/* Submenu */}\n\t\t\t{activeSubmenu?.submenuItems && (\n\t\t\t\t<NestedMenuTooltip\n\t\t\t\t\tisOpen={!!activeSubmenu}\n\t\t\t\t\tonOpenChange={(isOpen) => {\n\t\t\t\t\t\tif (!isOpen) {\n\t\t\t\t\t\t\tsetActiveSubmenu(null);\n\t\t\t\t\t\t\tsetSubmenuAnchor(null);\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t\titemRef={submenuAnchor}\n\t\t\t\t\tmenuItems={activeSubmenu.submenuItems}\n\t\t\t\t\ticonClassName={iconClassName}\n\t\t\t\t\tonMouseEnter={handleSubmenuEnter}\n\t\t\t\t\tonMouseLeave={handleSubmenuLeave}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</>\n\t);\n}\n\nexport default NestedMenu;\n",
      "type": "registry:ui"
    }
  ]
}