{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"popover","type":"registry:ui","title":"Popover","description":"Popover from react-fancy","package":"react-fancy","dependencies":[],"registryDependencies":["portal"],"files":[{"path":"components\/fancy\/popover\/Popover.context.ts","content":"import { createContext, useContext } from \"react\";\r\nimport type { PopoverContextValue } from \".\/Popover.types\";\r\n\r\nexport const PopoverContext = createContext<PopoverContextValue | null>(null);\r\n\r\nexport function usePopover(): PopoverContextValue {\r\n  const ctx = useContext(PopoverContext);\r\n  if (!ctx) {\r\n    throw new Error(\"Popover compound components must be used within <Popover>\");\r\n  }\r\n  return ctx;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/popover\/Popover.context.ts"},{"path":"components\/fancy\/popover\/Popover.tsx","content":"import { useCallback, useMemo, useRef, useState } from \"react\";\r\nimport { PopoverContext } from \".\/Popover.context\";\r\nimport { PopoverTrigger } from \".\/PopoverTrigger\";\r\nimport { PopoverContent } from \".\/PopoverContent\";\r\nimport type { PopoverProps } from \".\/Popover.types\";\r\n\r\nfunction PopoverRoot({\r\n  children,\r\n  open: controlledOpen,\r\n  defaultOpen = false,\r\n  onOpenChange,\r\n  placement = \"bottom\",\r\n  offset = 8,\r\n  hover = false,\r\n  hoverDelay = 200,\r\n  hoverCloseDelay = 300,\r\n}: PopoverProps) {\r\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\r\n  const isControlled = controlledOpen !== undefined;\r\n  const open = isControlled ? controlledOpen : internalOpen;\r\n\r\n  const anchorRef = useRef<HTMLElement>(null);\r\n  const floatingRef = useRef<HTMLDivElement>(null);\r\n  const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\r\n\r\n  const setOpen = useCallback(\r\n    (next: boolean) => {\r\n      if (!isControlled) setInternalOpen(next);\r\n      onOpenChange?.(next);\r\n    },\r\n    [isControlled, onOpenChange],\r\n  );\r\n\r\n  const onHoverEnter = useCallback(() => {\r\n    if (!hover) return;\r\n    clearTimeout(hoverTimeoutRef.current);\r\n    hoverTimeoutRef.current = setTimeout(() => setOpen(true), hoverDelay);\r\n  }, [hover, hoverDelay, setOpen]);\r\n\r\n  const onHoverLeave = useCallback(() => {\r\n    if (!hover) return;\r\n    clearTimeout(hoverTimeoutRef.current);\r\n    hoverTimeoutRef.current = setTimeout(() => setOpen(false), hoverCloseDelay);\r\n  }, [hover, hoverCloseDelay, setOpen]);\r\n\r\n  const ctx = useMemo(\r\n    () => ({ open, setOpen, anchorRef, floatingRef, placement, offset, hover, onHoverEnter, onHoverLeave }),\r\n    [open, setOpen, placement, offset, hover, onHoverEnter, onHoverLeave],\r\n  );\r\n\r\n  return (\r\n    <PopoverContext.Provider value={ctx}>{children}<\/PopoverContext.Provider>\r\n  );\r\n}\r\n\r\nexport const Popover = Object.assign(PopoverRoot, {\r\n  Trigger: PopoverTrigger,\r\n  Content: PopoverContent,\r\n});\r\n","type":"registry:ui","target":"components\/fancy\/popover\/Popover.tsx"},{"path":"components\/fancy\/popover\/Popover.types.ts","content":"import type { HTMLAttributes, ReactNode } from \"react\";\r\nimport type { Placement } from \"..\/..\/utils\/types\";\r\n\r\nexport interface PopoverContextValue {\r\n  open: boolean;\r\n  setOpen: (open: boolean) => void;\r\n  anchorRef: React.RefObject<HTMLElement | null>;\r\n  floatingRef: React.RefObject<HTMLDivElement | null>;\r\n  placement: Placement;\r\n  offset: number;\r\n  hover: boolean;\r\n  onHoverEnter: () => void;\r\n  onHoverLeave: () => void;\r\n}\r\n\r\nexport interface PopoverProps {\r\n  children: ReactNode;\r\n  \/** Controlled open state *\/\r\n  open?: boolean;\r\n  \/** Default open state (uncontrolled) *\/\r\n  defaultOpen?: boolean;\r\n  \/** Callback when open state changes *\/\r\n  onOpenChange?: (open: boolean) => void;\r\n  \/** Placement of the popover relative to the trigger *\/\r\n  placement?: Placement;\r\n  \/** Pixel offset from the trigger *\/\r\n  offset?: number;\r\n  \/** Open on hover instead of click *\/\r\n  hover?: boolean;\r\n  \/** Delay in ms before opening on hover. Default: 200 *\/\r\n  hoverDelay?: number;\r\n  \/** Delay in ms before closing after hover leaves. Default: 300 *\/\r\n  hoverCloseDelay?: number;\r\n}\r\n\r\n\/**\r\n * The trigger renders a `<span>` \u2014 it extends its attributes so consumers can\r\n * forward `aria-*`, `data-*`, `id`, `title`, etc. onto the actual anchor node\r\n * (e.g. `aria-label` for an icon-only trigger). Popover-owned props (ref,\r\n * onClick, aria-expanded\/haspopup) always win over forwarded ones.\r\n *\/\r\nexport interface PopoverTriggerProps extends HTMLAttributes<HTMLSpanElement> {\r\n  children: ReactNode;\r\n}\r\n\r\n\/**\r\n * The content renders a `<div>` \u2014 it extends its attributes so consumers can\r\n * forward `aria-*`\/`data-*`\/`role`\/`id` onto the floating panel. Consumer\r\n * `style` is merged under the Popover's positioning (left\/top always win).\r\n *\/\r\nexport interface PopoverContentProps extends HTMLAttributes<HTMLDivElement> {\r\n  children: ReactNode;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/popover\/Popover.types.ts"},{"path":"components\/fancy\/popover\/PopoverContent.tsx","content":"import { useCallback, useRef } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { Portal } from \"..\/Portal\";\r\nimport { usePopover } from \".\/Popover.context\";\r\nimport { useFloatingPosition } from \"..\/..\/hooks\/use-floating-position\";\r\nimport { useOutsideClick } from \"..\/..\/hooks\/use-outside-click\";\r\nimport { useEscapeKey } from \"..\/..\/hooks\/use-escape-key\";\r\nimport type { PopoverContentProps } from \".\/Popover.types\";\r\n\r\nexport function PopoverContent({ children, className, style, ...rest }: PopoverContentProps) {\r\n  const { open, setOpen, anchorRef, floatingRef, placement, offset, hover, onHoverEnter, onHoverLeave } = usePopover();\r\n  const outsideRef = useRef<HTMLDivElement>(null);\r\n\r\n  const position = useFloatingPosition(anchorRef, floatingRef, {\r\n    placement,\r\n    offset,\r\n    enabled: open,\r\n  });\r\n\r\n  const close = useCallback(() => setOpen(false), [setOpen]);\r\n  useOutsideClick(outsideRef, close, open && !hover, anchorRef);\r\n  useEscapeKey(close, open);\r\n\r\n  \/\/ Hide until position is calculated to prevent flash at (-9999, -9999)\r\n  const positioned = position.x !== -9999 && position.y !== -9999;\r\n\r\n  if (!open) return null;\r\n\r\n  return (\r\n    <Portal>\r\n      <div\r\n        {...rest}\r\n        ref={(node) => {\r\n          outsideRef.current = node;\r\n          (floatingRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\r\n        }}\r\n        data-react-fancy-popover=\"\"\r\n        className={cn(\r\n          \"fixed z-50 rounded-xl border border-zinc-200 bg-white p-4 text-zinc-700 shadow-lg dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:shadow-zinc-950\/50\",\r\n          positioned ? \"fancy-scale-in\" : \"invisible\",\r\n          className,\r\n        )}\r\n        style={{ ...style, left: position.x, top: position.y }}\r\n        onMouseEnter={hover ? onHoverEnter : undefined}\r\n        onMouseLeave={hover ? onHoverLeave : undefined}\r\n      >\r\n        {children}\r\n      <\/div>\r\n    <\/Portal>\r\n  );\r\n}\r\n\r\nPopoverContent.displayName = \"PopoverContent\";\r\n","type":"registry:ui","target":"components\/fancy\/popover\/PopoverContent.tsx"},{"path":"components\/fancy\/popover\/PopoverTrigger.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport { usePopover } from \".\/Popover.context\";\r\nimport type { PopoverTriggerProps } from \".\/Popover.types\";\r\n\r\nexport function PopoverTrigger({ children, className, ...rest }: PopoverTriggerProps) {\r\n  const { setOpen, open, anchorRef, hover, onHoverEnter, onHoverLeave } = usePopover();\r\n\r\n  return (\r\n    <span\r\n      {...rest}\r\n      ref={anchorRef as React.RefObject<HTMLSpanElement>}\r\n      data-react-fancy-popover-trigger=\"\"\r\n      className={cn(\"inline-flex\", className)}\r\n      onClick={hover ? undefined : () => setOpen(!open)}\r\n      onMouseEnter={hover ? onHoverEnter : undefined}\r\n      onMouseLeave={hover ? onHoverLeave : undefined}\r\n      aria-expanded={open}\r\n      aria-haspopup\r\n    >\r\n      {children}\r\n    <\/span>\r\n  );\r\n}\r\n\r\nPopoverTrigger.displayName = \"PopoverTrigger\";\r\n","type":"registry:ui","target":"components\/fancy\/popover\/PopoverTrigger.tsx"},{"path":"components\/fancy\/popover\/index.ts","content":"export { Popover } from \".\/Popover\";\r\nexport { usePopover } from \".\/Popover.context\";\r\nexport type {\r\n  PopoverProps,\r\n  PopoverTriggerProps,\r\n  PopoverContentProps,\r\n  PopoverContextValue,\r\n} from \".\/Popover.types\";\r\n","type":"registry:ui","target":"components\/fancy\/popover\/index.ts"}]}