{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <edu@smoothui.dev>","css":{},"dependencies":["@base-ui/react","motion"],"description":"Hover preview card for SmoothUI with Base UI / Radix HoverCard twins and Motion overlay.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { PreviewCard as PreviewCardPrimitive } from \"@base-ui/react/preview-card\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport {\n  type ComponentProps,\n  createContext,\n  isValidElement,\n  type ReactElement,\n  type ReactNode,\n  useContext,\n} from \"react\";\nimport {\n  OVERLAY_ENTER,\n  OVERLAY_ENTER_INITIAL,\n  OVERLAY_EXIT,\n  OVERLAY_TRANSITION,\n} from \"@/components/smoothui/lib/animation\";\n\ntype Side = \"top\" | \"right\" | \"bottom\" | \"left\";\ntype Align = \"start\" | \"center\" | \"end\";\n\nexport interface PreviewCardProps {\n  /** Alignment of the card relative to the trigger */\n  align?: Align;\n  /** Card content */\n  children?: ReactNode;\n  /** Additional CSS class names for the card */\n  className?: string;\n  /** Delay in ms before the card closes after the pointer leaves */\n  closeDelay?: number;\n  /** Delay in ms before the card opens on hover/focus */\n  delay?: number;\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?: Side;\n  /** Distance in px from the trigger */\n  sideOffset?: number;\n  /** Trigger element (usually a link) that reveals the card */\n  trigger?: ReactNode;\n}\n\nexport interface PreviewCardContentProps {\n  /** Alignment of the card relative to the trigger */\n  align?: Align;\n  /** Show an arrow pointing at the trigger */\n  arrow?: boolean;\n  /** Card content */\n  children?: ReactNode;\n  /** Additional CSS class names */\n  className?: string;\n  /** Preferred side of the trigger */\n  side?: Side;\n  /** Distance in px from the trigger */\n  sideOffset?: number;\n}\n\nexport type PreviewCardRootProps = Omit<\n  ComponentProps<typeof PreviewCardPrimitive.Root>,\n  \"onOpenChange\"\n> & {\n  /** Delay in ms before the card closes after the pointer leaves */\n  closeDelay?: number;\n  /** Delay in ms before the card opens on hover/focus */\n  delay?: number;\n  onOpenChange?: (open: boolean) => void;\n};\nexport type PreviewCardTriggerProps = ComponentProps<\n  typeof PreviewCardPrimitive.Trigger\n>;\nexport type PreviewCardPortalProps = ComponentProps<\n  typeof PreviewCardPrimitive.Portal\n>;\nexport type PreviewCardPositionerProps = Omit<\n  ComponentProps<typeof PreviewCardPrimitive.Positioner>,\n  \"className\"\n> & { className?: string };\nexport type PreviewCardPopupProps = Omit<\n  ComponentProps<typeof PreviewCardPrimitive.Popup>,\n  \"className\"\n> & { className?: string };\nexport type PreviewCardArrowProps = Omit<\n  ComponentProps<typeof PreviewCardPrimitive.Arrow>,\n  \"className\"\n> & { className?: string };\n\nconst POPUP_CLASS =\n  \"w-64 min-w-0 origin-[var(--transform-origin)] rounded-md border overlay-surface bg-popover p-4 text-popover-foreground outline-none\";\n\nconst ARROW_CLASS =\n  \"size-2 rotate-45 border-border bg-popover data-[side=bottom]:top-[-5px] data-[side=bottom]:border-t data-[side=bottom]:border-l data-[side=left]:right-[-5px] data-[side=left]:border-t data-[side=left]:border-r data-[side=right]:left-[-5px] data-[side=right]:border-b data-[side=right]:border-l data-[side=top]:bottom-[-5px] data-[side=top]:border-r data-[side=top]:border-b\";\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\ninterface DelayContextValue {\n  closeDelay?: number;\n  delay?: number;\n}\n\nconst DelayContext = createContext<DelayContextValue>({});\n\n/**\n * Preview card root. `delay` / `closeDelay` live here (same as the Radix twin)\n * and are forwarded to the trigger.\n */\nexport const PreviewCardRoot = ({\n  closeDelay,\n  delay,\n  onOpenChange,\n  ...props\n}: PreviewCardRootProps) => (\n  <DelayContext.Provider value={{ closeDelay, delay }}>\n    <PreviewCardPrimitive.Root\n      onOpenChange={(next) => onOpenChange?.(next)}\n      {...props}\n    />\n  </DelayContext.Provider>\n);\n\nexport const PreviewCardTrigger = ({\n  closeDelay,\n  delay,\n  ...props\n}: PreviewCardTriggerProps) => {\n  const ctx = useContext(DelayContext);\n\n  return (\n    <PreviewCardPrimitive.Trigger\n      closeDelay={closeDelay ?? ctx.closeDelay}\n      data-slot=\"preview-card-trigger\"\n      delay={delay ?? ctx.delay}\n      {...props}\n    />\n  );\n};\n\nexport const PreviewCardPortal = (props: PreviewCardPortalProps) => (\n  <PreviewCardPrimitive.Portal {...props} />\n);\n\nexport const PreviewCardPositioner = ({\n  className,\n  ...props\n}: PreviewCardPositionerProps) => (\n  <PreviewCardPrimitive.Positioner\n    className={cn(\"z-50 outline-none\", className)}\n    data-slot=\"preview-card-positioner\"\n    {...props}\n  />\n);\n\n/** Animated card surface — Motion fade/scale from `OVERLAY_*`. */\nexport const PreviewCardPopup = ({\n  className,\n  ...props\n}: PreviewCardPopupProps) => {\n  const shouldReduceMotion = useReducedMotion();\n  const motionProps = overlayMotion(shouldReduceMotion);\n\n  return (\n    <PreviewCardPrimitive.Popup\n      className={cn(POPUP_CLASS, className)}\n      data-slot=\"preview-card-popup\"\n      render={\n        <motion.div\n          animate={motionProps.animate}\n          initial={motionProps.initial}\n          transition={motionProps.transition}\n        />\n      }\n      {...props}\n    />\n  );\n};\n\nexport const PreviewCardArrow = ({\n  className,\n  ...props\n}: PreviewCardArrowProps) => (\n  <PreviewCardPrimitive.Arrow\n    className={cn(ARROW_CLASS, className)}\n    data-slot=\"preview-card-arrow\"\n    {...props}\n  />\n);\n\n/**\n * Anchored card — Portal + Positioner + animated Popup (+ optional Arrow).\n */\nexport const PreviewCardContent = ({\n  align = \"center\",\n  arrow = false,\n  children,\n  className,\n  side = \"bottom\",\n  sideOffset = 8,\n}: PreviewCardContentProps) => (\n  <PreviewCardPortal>\n    <PreviewCardPositioner align={align} side={side} sideOffset={sideOffset}>\n      <PreviewCardPopup className={className}>\n        {children}\n        {arrow ? <PreviewCardArrow /> : null}\n      </PreviewCardPopup>\n    </PreviewCardPositioner>\n  </PreviewCardPortal>\n);\n\nconst renderTrigger = (trigger: ReactNode) => {\n  if (!trigger) {\n    return null;\n  }\n  if (isValidElement(trigger)) {\n    return <PreviewCardTrigger render={trigger as ReactElement} />;\n  }\n  return <PreviewCardTrigger>{trigger}</PreviewCardTrigger>;\n};\n\n/**\n * SmoothUI Preview Card — Base UI twin (default).\n * Hover/focus card for link previews; convenience API with trigger + content.\n */\nexport default function PreviewCard({\n  align = \"center\",\n  children,\n  className,\n  closeDelay,\n  delay,\n  onOpenChange,\n  open,\n  side = \"bottom\",\n  sideOffset = 8,\n  trigger,\n}: PreviewCardProps) {\n  return (\n    <PreviewCardRoot\n      closeDelay={closeDelay}\n      delay={delay}\n      onOpenChange={onOpenChange}\n      open={open}\n    >\n      {renderTrigger(trigger)}\n      <PreviewCardContent\n        align={align}\n        className={className}\n        side={side}\n        sideOffset={sideOffset}\n      >\n        {children}\n      </PreviewCardContent>\n    </PreviewCardRoot>\n  );\n}\n","path":"index.tsx","target":"components/smoothui/preview-card/index.tsx","type":"registry:ui"}],"name":"preview-card","registryDependencies":["https://smoothui.dev/r/lib.json","https://smoothui.dev/r/tokens.json"],"title":"Preview Card","type":"registry:ui"}