{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <edu@smoothui.dev>","css":{},"dependencies":["@base-ui/react","motion"],"description":"Animated Popover component for SmoothUI with Base UI / Radix twins and Motion enter animations.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { Popover as PopoverPrimitive } from \"@base-ui/react/popover\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport {\n  type ComponentProps,\n  isValidElement,\n  type ReactElement,\n  type ReactNode,\n  useState,\n} from \"react\";\nimport {\n  OVERLAY_ENTER,\n  OVERLAY_ENTER_INITIAL,\n  OVERLAY_EXIT,\n  OVERLAY_TRANSITION,\n} from \"@/components/smoothui/lib/animation\";\n\nexport interface PopoverProps {\n  /** Alignment of the popover relative to the trigger */\n  align?: \"start\" | \"center\" | \"end\";\n  /** Popover content */\n  children?: ReactNode;\n  /** Additional CSS class names for the content */\n  className?: string;\n  /** Callback when the open state changes */\n  onOpenChange?: (open: boolean) => void;\n  /** Controlled open state */\n  open?: boolean;\n  /** Preferred side of the trigger */\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n  /** Distance in px from the trigger */\n  sideOffset?: number;\n  /** Trigger element that opens the popover */\n  trigger?: ReactNode;\n}\n\nexport interface PopoverContentProps {\n  /** Alignment of the popover relative to the trigger */\n  align?: \"start\" | \"center\" | \"end\";\n  /** Popover content */\n  children?: ReactNode;\n  /** Additional CSS class names */\n  className?: string;\n  /** Preferred side of the trigger */\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n  /** Distance in px from the trigger */\n  sideOffset?: number;\n}\n\nconst POPUP_CLASS =\n  \"w-72 origin-[var(--transform-origin)] rounded-md border overlay-surface bg-popover p-4 text-popover-foreground outline-none\";\n\nconst overlayMotion = (shouldReduceMotion: boolean | null) => ({\n  animate: shouldReduceMotion\n    ? { opacity: 1 }\n    : { opacity: OVERLAY_ENTER.opacity, scale: OVERLAY_ENTER.scale },\n  exit: shouldReduceMotion\n    ? { opacity: 0, transition: { duration: 0 } }\n    : {\n        opacity: OVERLAY_EXIT.opacity,\n        scale: OVERLAY_EXIT.scale,\n        transition: { duration: 0.15 },\n      },\n  initial: shouldReduceMotion\n    ? { opacity: 1 }\n    : {\n        opacity: OVERLAY_ENTER_INITIAL.opacity,\n        scale: OVERLAY_ENTER_INITIAL.scale,\n      },\n  transition: shouldReduceMotion ? { duration: 0 } : OVERLAY_TRANSITION,\n});\n\ntype TriggerComponent = (props: {\n  children?: ReactNode;\n  nativeButton?: boolean;\n  render?: ReactElement;\n}) => ReactElement | null;\n\nconst renderTrigger = (Trigger: TriggerComponent, trigger: ReactNode) => {\n  if (!trigger) {\n    return null;\n  }\n  if (isValidElement(trigger)) {\n    const element = trigger as ReactElement;\n    const nativeButton =\n      typeof element.type === \"string\" ? element.type === \"button\" : true;\n    return <Trigger nativeButton={nativeButton} render={element} />;\n  }\n  return <Trigger>{trigger}</Trigger>;\n};\n\nexport const PopoverTrigger = PopoverPrimitive.Trigger;\nexport const PopoverClose = PopoverPrimitive.Close;\n\nexport const PopoverRoot = ({\n  ...props\n}: ComponentProps<typeof PopoverPrimitive.Root>) => (\n  <PopoverPrimitive.Root data-slot=\"popover\" {...props} />\n);\n\n/**\n * Anchored popover content — Portal + Positioner + animated Popup.\n */\nexport const PopoverContent = ({\n  className,\n  side = \"bottom\",\n  align = \"center\",\n  sideOffset = 4,\n  children,\n}: PopoverContentProps) => {\n  const shouldReduceMotion = useReducedMotion();\n  const motionProps = overlayMotion(shouldReduceMotion);\n\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverPrimitive.Positioner\n        align={align}\n        className=\"z-50 outline-none\"\n        data-slot=\"popover-positioner\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <PopoverPrimitive.Popup\n          className={cn(POPUP_CLASS, className)}\n          data-slot=\"popover-content\"\n          render={\n            <motion.div\n              animate={motionProps.animate}\n              initial={motionProps.initial}\n              transition={motionProps.transition}\n            />\n          }\n        >\n          {children}\n        </PopoverPrimitive.Popup>\n      </PopoverPrimitive.Positioner>\n    </PopoverPrimitive.Portal>\n  );\n};\n\n/**\n * SmoothUI Popover — Base UI twin (default).\n * Convenience API with trigger + content; compounds available for advanced use.\n */\nexport default function Popover({\n  open,\n  onOpenChange,\n  trigger,\n  children,\n  className,\n  side = \"bottom\",\n  align = \"center\",\n  sideOffset = 4,\n}: PopoverProps) {\n  const [internalOpen, setInternalOpen] = useState(false);\n  const isControlled = open !== undefined;\n  const isOpen = isControlled ? open : internalOpen;\n\n  const handleOpenChange = (next: boolean) => {\n    if (!isControlled) {\n      setInternalOpen(next);\n    }\n    onOpenChange?.(next);\n  };\n\n  return (\n    <PopoverPrimitive.Root onOpenChange={handleOpenChange} open={isOpen}>\n      {renderTrigger(PopoverPrimitive.Trigger, trigger)}\n      <PopoverContent\n        align={align}\n        className={className}\n        side={side}\n        sideOffset={sideOffset}\n      >\n        {children}\n      </PopoverContent>\n    </PopoverPrimitive.Root>\n  );\n}\n","path":"index.tsx","target":"components/smoothui/popover/index.tsx","type":"registry:ui"}],"name":"popover","registryDependencies":["https://smoothui.dev/r/lib.json","https://smoothui.dev/r/tokens.json"],"title":"Popover","type":"registry:ui"}