{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "popover",
  "title": "Popover",
  "author": "andongmin94",
  "description": "A floating panel for contextual content and lightweight controls.",
  "dependencies": [
    "@base-ui/react@^1.6.0"
  ],
  "files": [
    {
      "path": "src/components/ui/popover.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Popover as PopoverPrimitive } from \"@base-ui/react/popover\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype AutoFocusEventHandler = (event: Event) => void;\ntype FocusTarget = PopoverPrimitive.Popup.Props[\"initialFocus\"];\ntype PointerDownOutsideEvent = CustomEvent<{ originalEvent: PointerEvent }>;\ntype FocusOutsideEvent = CustomEvent<{ originalEvent: FocusEvent }>;\ntype DismissableLayerHandlers = {\n  onEscapeKeyDown?: (event: KeyboardEvent) => void;\n  onFocusOutside?: (event: FocusOutsideEvent) => void;\n  onInteractOutside?: (event: FocusOutsideEvent | PointerDownOutsideEvent) => void;\n  onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;\n};\ntype CollisionBoundary =\n  | PopoverPrimitive.Positioner.Props[\"collisionBoundary\"]\n  | null\n  | Array<Element | null>;\ntype PopoverPortalProps = PopoverPrimitive.Portal.Props & {\n  forceMount?: boolean;\n};\ntype PopoverContentProps = PopoverPrimitive.Popup.Props &\n  Pick<\n    PopoverPrimitive.Positioner.Props,\n    | \"align\"\n    | \"alignOffset\"\n    | \"arrowPadding\"\n    | \"collisionAvoidance\"\n    | \"collisionPadding\"\n    | \"disableAnchorTracking\"\n    | \"positionMethod\"\n    | \"side\"\n    | \"sideOffset\"\n  > & {\n    asChild?: boolean;\n    avoidCollisions?: boolean;\n    collisionBoundary?: CollisionBoundary;\n    forceMount?: boolean;\n    hideWhenDetached?: boolean;\n    onCloseAutoFocus?: AutoFocusEventHandler;\n    onOpenAutoFocus?: AutoFocusEventHandler;\n    onPlaced?: () => void;\n    sticky?: \"partial\" | \"always\";\n    updatePositionStrategy?: \"optimized\" | \"always\";\n  } & DismissableLayerHandlers;\ntype PopoverContextProps = {\n  dismissableLayerHandlersRef: React.RefObject<DismissableLayerHandlers>;\n  open: boolean;\n};\n\ntype PreventableBaseUIEvent = {\n  readonly baseUIHandlerPrevented?: boolean;\n  readonly defaultPrevented?: boolean;\n  preventBaseUIHandler?: () => void;\n};\ntype EventHandler = (...args: unknown[]) => unknown;\ntype RenderFunction = (props: Record<string, unknown>, state: unknown) => React.ReactElement;\n\nfunction adaptEventHandlers(props: Record<string, unknown>, beforeBaseUIHandler: boolean) {\n  const eventHandlers: Record<string, EventHandler> = {};\n\n  for (const [name, handler] of Object.entries(props)) {\n    if (!/^on[A-Z]/.test(name) || typeof handler !== \"function\") {\n      continue;\n    }\n\n    eventHandlers[name] = (...args) => {\n      const event = args[0] as PreventableBaseUIEvent | undefined;\n      if (beforeBaseUIHandler && event?.defaultPrevented) {\n        event.preventBaseUIHandler?.();\n      }\n      if (beforeBaseUIHandler && event?.baseUIHandlerPrevented) {\n        return undefined;\n      }\n\n      const result = handler(...args);\n      if (!beforeBaseUIHandler && event?.defaultPrevented) {\n        event.preventBaseUIHandler?.();\n      }\n      return result;\n    };\n  }\n\n  return eventHandlers;\n}\n\nfunction adaptRenderEventHandlers<Render>(render: Render): Render {\n  if (React.isValidElement(render)) {\n    const element = render as React.ReactElement<Record<string, unknown>>;\n    const eventHandlers = adaptEventHandlers(element.props, false);\n    return (Object.keys(eventHandlers).length === 0\n      ? render\n      : React.cloneElement(element, eventHandlers)) as unknown as Render;\n  }\n  if (typeof render === \"function\") {\n    const renderFunction = render as RenderFunction;\n    return ((props: Record<string, unknown>, state: unknown) =>\n      renderFunction({ ...props, ...adaptEventHandlers(props, true) }, state)) as unknown as Render;\n  }\n  return render;\n}\n\ntype CSSPropertiesWithVariables = React.CSSProperties & {\n  [name: `--${string}`]: string | number | undefined;\n};\n\nconst popoverCssVariables: CSSPropertiesWithVariables = {\n  \"--radix-popover-content-available-height\": \"var(--available-height)\",\n  \"--radix-popover-content-available-width\": \"var(--available-width)\",\n  \"--radix-popover-content-transform-origin\": \"var(--transform-origin)\",\n  \"--radix-popover-trigger-height\": \"var(--anchor-height)\",\n  \"--radix-popover-trigger-width\": \"var(--anchor-width)\",\n};\n\nconst PopoverContext = React.createContext<PopoverContextProps | null>(null);\n\nfunction usePopoverContext() {\n  const context = React.useContext(PopoverContext);\n\n  if (!context) {\n    throw new Error(\"Popover components must be used within Popover.\");\n  }\n\n  return context;\n}\n\nfunction mergePopupStyle(\n  style: PopoverPrimitive.Popup.Props[\"style\"],\n): PopoverPrimitive.Popup.Props[\"style\"] {\n  if (typeof style === \"function\") {\n    return (state) => ({ ...popoverCssVariables, ...style(state) });\n  }\n\n  return { ...popoverCssVariables, ...style };\n}\n\nfunction getAsChildElement(children: React.ReactNode, componentName: string) {\n  const child = React.Children.toArray(children).find(React.isValidElement);\n\n  if (!child) {\n    throw new Error(`${componentName} with asChild requires a valid React element child.`);\n  }\n\n  return child as React.ReactElement<{\n    [key: string]: unknown;\n    children?: React.ReactNode;\n  }>;\n}\n\nfunction createPointerEvent(event: MouseEvent | PointerEvent | TouchEvent): PointerEvent {\n  if (\"pointerId\" in event) {\n    return event;\n  }\n\n  const touch = \"changedTouches\" in event ? event.changedTouches[0] : undefined;\n  const ownerDocument = event.target instanceof Node ? event.target.ownerDocument : null;\n  const view = ownerDocument?.defaultView ?? window;\n  const PointerEventConstructor = view?.PointerEvent;\n\n  if (!PointerEventConstructor) {\n    return event as PointerEvent;\n  }\n\n  return new PointerEventConstructor(\"pointerdown\", {\n    bubbles: event.bubbles,\n    button: \"button\" in event ? event.button : 0,\n    buttons: \"buttons\" in event ? event.buttons : 1,\n    cancelable: event.cancelable,\n    clientX: touch?.clientX ?? (\"clientX\" in event ? event.clientX : 0),\n    clientY: touch?.clientY ?? (\"clientY\" in event ? event.clientY : 0),\n    composed: event.composed,\n    ctrlKey: \"ctrlKey\" in event && event.ctrlKey,\n    metaKey: \"metaKey\" in event && event.metaKey,\n    pointerType: \"changedTouches\" in event ? \"touch\" : \"mouse\",\n    shiftKey: \"shiftKey\" in event && event.shiftKey,\n  });\n}\n\nfunction createPointerDownOutsideEvent(\n  originalEvent: MouseEvent | PointerEvent | TouchEvent,\n): PointerDownOutsideEvent {\n  const event = new CustomEvent<{ originalEvent: PointerEvent }>(\n    \"dismissableLayer.pointerDownOutside\",\n    {\n      cancelable: true,\n      detail: { originalEvent: createPointerEvent(originalEvent) },\n    },\n  );\n  Object.defineProperty(event, \"target\", { configurable: true, value: originalEvent.target });\n  return event;\n}\n\nfunction createFocusOutsideEvent(originalEvent: FocusEvent | KeyboardEvent): FocusOutsideEvent {\n  const focusEvent =\n    \"relatedTarget\" in originalEvent\n      ? originalEvent\n      : new FocusEvent(\"focusin\", { bubbles: true, cancelable: true });\n  const event = new CustomEvent<{ originalEvent: FocusEvent }>(\"dismissableLayer.focusOutside\", {\n    cancelable: true,\n    detail: { originalEvent: focusEvent },\n  });\n  Object.defineProperty(event, \"target\", { configurable: true, value: originalEvent.target });\n  return event;\n}\n\nfunction adaptDismissableLayerEvent(\n  eventDetails: PopoverPrimitive.Root.ChangeEventDetails,\n  handlers: DismissableLayerHandlers,\n) {\n  if (eventDetails.reason === \"escape-key\") {\n    handlers.onEscapeKeyDown?.(eventDetails.event);\n    if (eventDetails.event.defaultPrevented) {\n      eventDetails.cancel();\n    }\n    return;\n  }\n\n  if (eventDetails.reason === \"outside-press\") {\n    const event = createPointerDownOutsideEvent(eventDetails.event);\n    handlers.onPointerDownOutside?.(event);\n    handlers.onInteractOutside?.(event);\n    if (event.defaultPrevented) {\n      eventDetails.cancel();\n    }\n    return;\n  }\n\n  if (eventDetails.reason === \"focus-out\") {\n    const event = createFocusOutsideEvent(eventDetails.event);\n    handlers.onFocusOutside?.(event);\n    handlers.onInteractOutside?.(event);\n    if (event.defaultPrevented) {\n      eventDetails.cancel();\n    }\n  }\n}\n\nfunction hasForceMountedChild(children: React.ReactNode) {\n  return React.Children.toArray(children).some(\n    (child) => React.isValidElement<{ forceMount?: boolean }>(child) && child.props.forceMount,\n  );\n}\n\nfunction adaptAutoFocus(\n  handler: AutoFocusEventHandler | undefined,\n  focus: FocusTarget,\n  eventName: string,\n): FocusTarget {\n  if (!handler) {\n    return focus;\n  }\n\n  return (interactionType) => {\n    const event = new Event(eventName, { cancelable: true });\n    handler(event);\n\n    if (event.defaultPrevented) {\n      return false;\n    }\n\n    if (typeof focus === \"function\") {\n      return focus(interactionType);\n    }\n\n    if (typeof focus === \"object\" && focus !== null) {\n      return focus.current;\n    }\n\n    return typeof focus === \"boolean\" ? focus : true;\n  };\n}\n\nfunction normalizeCollisionBoundary(\n  collisionBoundary: CollisionBoundary | undefined,\n): PopoverPrimitive.Positioner.Props[\"collisionBoundary\"] {\n  if (Array.isArray(collisionBoundary)) {\n    const boundaries: Element[] = [];\n\n    for (const boundary of collisionBoundary) {\n      if (boundary) {\n        boundaries.push(boundary);\n      }\n    }\n\n    return boundaries;\n  }\n\n  return collisionBoundary ?? undefined;\n}\n\nfunction PopoverPortal({ forceMount, keepMounted, ...props }: PopoverPortalProps) {\n  const keepPortalMounted = forceMount ?? (hasForceMountedChild(props.children) || keepMounted);\n\n  return (\n    <PopoverPrimitive.Portal\n      data-slot=\"popover-portal\"\n      keepMounted={keepPortalMounted}\n      {...props}\n    />\n  );\n}\n\nfunction PopoverPositioner({\n  onPlaced,\n  ...props\n}: PopoverPrimitive.Positioner.Props & { onPlaced?: () => void }) {\n  const positionerRef = React.useRef<HTMLDivElement>(null);\n\n  React.useEffect(() => {\n    const positioner = positionerRef.current;\n\n    if (!positioner || !onPlaced) {\n      return;\n    }\n\n    let animationFrame = 0;\n    let notified = false;\n    const notifyWhenOpen = () => {\n      if (!positioner.hasAttribute(\"data-open\")) {\n        notified = false;\n        return;\n      }\n\n      if (!notified) {\n        notified = true;\n        animationFrame = window.requestAnimationFrame(onPlaced);\n      }\n    };\n\n    notifyWhenOpen();\n    const observer = new MutationObserver(notifyWhenOpen);\n    observer.observe(positioner, { attributeFilter: [\"data-open\"], attributes: true });\n\n    return () => {\n      window.cancelAnimationFrame(animationFrame);\n      observer.disconnect();\n    };\n  }, [onPlaced]);\n\n  return <PopoverPrimitive.Positioner ref={positionerRef} {...props} />;\n}\n\nfunction Popover({\n  defaultOpen = false,\n  onOpenChange,\n  open: openProp,\n  ...props\n}: PopoverPrimitive.Root.Props) {\n  const dismissableLayerHandlersRef = React.useRef<DismissableLayerHandlers>({});\n  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);\n  const open = openProp ?? uncontrolledOpen;\n  const contextValue = React.useMemo(() => ({ dismissableLayerHandlersRef, open }), [open]);\n\n  const handleOpenChange: NonNullable<PopoverPrimitive.Root.Props[\"onOpenChange\"]> = (\n    open,\n    eventDetails,\n  ) => {\n    if (!open) {\n      adaptDismissableLayerEvent(eventDetails, dismissableLayerHandlersRef.current);\n    }\n    if (eventDetails.isCanceled) {\n      return;\n    }\n    onOpenChange?.(open, eventDetails);\n    if (!eventDetails.isCanceled && openProp === undefined) {\n      setUncontrolledOpen(open);\n    }\n  };\n\n  return (\n    <PopoverContext.Provider value={contextValue}>\n      <PopoverPrimitive.Root\n        data-slot=\"popover\"\n        defaultOpen={defaultOpen}\n        open={openProp}\n        {...props}\n        onOpenChange={handleOpenChange}\n      />\n    </PopoverContext.Provider>\n  );\n}\n\nfunction PopoverTrigger({\n  asChild = false,\n  children,\n  render,\n  ...props\n}: PopoverPrimitive.Trigger.Props & {\n  asChild?: boolean;\n  children?: React.ReactNode;\n}) {\n  const { open } = usePopoverContext();\n  const renderElement = asChild ? getAsChildElement(children, \"PopoverTrigger\") : render;\n\n  return (\n    <PopoverPrimitive.Trigger\n      data-slot=\"popover-trigger\"\n      data-state={open ? \"open\" : \"closed\"}\n      render={adaptRenderEventHandlers(renderElement)}\n      {...props}\n    >\n      {asChild ? undefined : children}\n    </PopoverPrimitive.Trigger>\n  );\n}\n\nfunction PopoverContent({\n  align = \"center\",\n  alignOffset = 0,\n  arrowPadding = 0,\n  asChild = false,\n  avoidCollisions = true,\n  className,\n  children,\n  collisionAvoidance,\n  collisionBoundary,\n  collisionPadding = 0,\n  disableAnchorTracking,\n  finalFocus,\n  forceMount,\n  hideWhenDetached = false,\n  initialFocus,\n  onCloseAutoFocus,\n  onEscapeKeyDown,\n  onFocusOutside,\n  onInteractOutside,\n  onOpenAutoFocus,\n  onPointerDownOutside,\n  onPlaced,\n  positionMethod,\n  render,\n  side = \"bottom\",\n  sideOffset = 4,\n  sticky,\n  style,\n  updatePositionStrategy,\n  ...props\n}: PopoverContentProps) {\n  const { dismissableLayerHandlersRef, open } = usePopoverContext();\n  const renderElement = asChild ? getAsChildElement(children, \"PopoverContent\") : render;\n  const dismissableLayerHandlers = React.useMemo(\n    () => ({ onEscapeKeyDown, onFocusOutside, onInteractOutside, onPointerDownOutside }),\n    [onEscapeKeyDown, onFocusOutside, onInteractOutside, onPointerDownOutside],\n  );\n  dismissableLayerHandlersRef.current = dismissableLayerHandlers;\n\n  React.useEffect(\n    () => () => {\n      if (dismissableLayerHandlersRef.current === dismissableLayerHandlers) {\n        dismissableLayerHandlersRef.current = {};\n      }\n    },\n    [dismissableLayerHandlers, dismissableLayerHandlersRef],\n  );\n\n  return (\n    <PopoverPortal forceMount={forceMount}>\n      <PopoverPositioner\n        align={align}\n        alignOffset={alignOffset}\n        arrowPadding={arrowPadding}\n        collisionAvoidance={\n          avoidCollisions\n            ? collisionAvoidance\n            : { align: \"none\", fallbackAxisSide: \"none\", side: \"none\" }\n        }\n        collisionBoundary={normalizeCollisionBoundary(collisionBoundary)}\n        collisionPadding={collisionPadding}\n        disableAnchorTracking={updatePositionStrategy === \"always\" ? false : disableAnchorTracking}\n        onPlaced={onPlaced}\n        positionMethod={positionMethod}\n        side={side}\n        sideOffset={sideOffset}\n        sticky={sticky === undefined ? undefined : sticky === \"always\"}\n        className={cn(\n          \"isolate z-50\",\n          hideWhenDetached && \"data-anchor-hidden:pointer-events-none data-anchor-hidden:invisible\",\n        )}\n      >\n        <PopoverPrimitive.Popup\n          data-slot=\"popover-content\"\n          data-state={open ? \"open\" : \"closed\"}\n          initialFocus={adaptAutoFocus(\n            onOpenAutoFocus,\n            initialFocus,\n            \"focusScope.autoFocusOnMount\",\n          )}\n          finalFocus={adaptAutoFocus(onCloseAutoFocus, finalFocus, \"focusScope.autoFocusOnUnmount\")}\n          render={adaptRenderEventHandlers(renderElement)}\n          style={mergePopupStyle(style)}\n          className={cn(\n            \"z-50 w-72 origin-(--transform-origin) rounded-base border-2 border-border bg-main p-4 text-foreground outline-none duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\n            className,\n          )}\n          {...props}\n        >\n          {asChild ? undefined : children}\n        </PopoverPrimitive.Popup>\n      </PopoverPositioner>\n    </PopoverPortal>\n  );\n}\n\nfunction PopoverHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"popover-header\"\n      className={cn(\"flex flex-col gap-0.5 text-sm\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction PopoverTitle({\n  asChild = false,\n  children,\n  className,\n  render,\n  ...props\n}: PopoverPrimitive.Title.Props & { asChild?: boolean }) {\n  const renderElement = asChild ? getAsChildElement(children, \"PopoverTitle\") : render;\n\n  return (\n    <PopoverPrimitive.Title\n      data-slot=\"popover-title\"\n      render={adaptRenderEventHandlers(renderElement)}\n      className={cn(\"font-heading\", className)}\n      {...props}\n    >\n      {asChild ? undefined : children}\n    </PopoverPrimitive.Title>\n  );\n}\n\nfunction PopoverDescription({\n  asChild = false,\n  children,\n  className,\n  render,\n  ...props\n}: PopoverPrimitive.Description.Props & { asChild?: boolean }) {\n  const renderElement = asChild ? getAsChildElement(children, \"PopoverDescription\") : render;\n\n  return (\n    <PopoverPrimitive.Description\n      data-slot=\"popover-description\"\n      render={adaptRenderEventHandlers(renderElement)}\n      className={cn(\"text-main-foreground\", className)}\n      {...props}\n    >\n      {asChild ? undefined : children}\n    </PopoverPrimitive.Description>\n  );\n}\n\nexport { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger };\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "overlay"
  ],
  "type": "registry:ui"
}