{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "preview-card",
  "title": "Preview-card",
  "description": "A preview-card component.",
  "registryDependencies": [
    "@cubby-ui/elevated"
  ],
  "files": [
    {
      "path": "registry/default/preview-card/preview-card.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { PreviewCard as BasePreviewCard } from \"@base-ui/react/preview-card\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  solidSurface,\n  type SurfaceLevel,\n} from \"@/registry/default/lib/elevated\";\n\nfunction PreviewCard<Payload = unknown>({\n  ...props\n}: BasePreviewCard.Root.Props<Payload>) {\n  return <BasePreviewCard.Root data-slot=\"preview-card\" {...props} />;\n}\n\nfunction PreviewCardTrigger<Payload = unknown>({\n  // Base UI's 600ms feels laggy; 0 opens a card for every link the pointer crosses.\n  delay = 400,\n  // Long enough to reach a neighbouring trigger on the same handle, so the card\n  // glides to it instead of closing.\n  closeDelay = 200,\n  ...props\n}: BasePreviewCard.Trigger.Props<Payload>) {\n  return (\n    <BasePreviewCard.Trigger\n      data-slot=\"preview-card-trigger\"\n      delay={delay}\n      closeDelay={closeDelay}\n      {...props}\n    />\n  );\n}\n\nfunction PreviewCardPortal({ ...props }: BasePreviewCard.Portal.Props) {\n  return <BasePreviewCard.Portal data-slot=\"preview-card-portal\" {...props} />;\n}\n\nfunction PreviewCardArrow({ ...props }: BasePreviewCard.Arrow.Props) {\n  return <BasePreviewCard.Arrow data-slot=\"preview-card-arrow\" {...props} />;\n}\n\nfunction PreviewCardContent({\n  children,\n  className,\n  side = \"bottom\",\n  align = \"center\",\n  sideOffset = 8,\n  alignOffset = 0,\n  collisionBoundary,\n  collisionPadding = 10,\n  sticky = false,\n  positionMethod = \"absolute\",\n  anchor,\n  viewportClassName,\n  arrow = false,\n  arrowPadding,\n  container,\n  level = 3,\n  shadowLevel = 3,\n  ...props\n}: BasePreviewCard.Popup.Props & {\n  side?: BasePreviewCard.Positioner.Props[\"side\"];\n  align?: BasePreviewCard.Positioner.Props[\"align\"];\n  sideOffset?: BasePreviewCard.Positioner.Props[\"sideOffset\"];\n  alignOffset?: BasePreviewCard.Positioner.Props[\"alignOffset\"];\n  collisionBoundary?: BasePreviewCard.Positioner.Props[\"collisionBoundary\"];\n  collisionPadding?: BasePreviewCard.Positioner.Props[\"collisionPadding\"];\n  sticky?: BasePreviewCard.Positioner.Props[\"sticky\"];\n  positionMethod?: BasePreviewCard.Positioner.Props[\"positionMethod\"];\n  /** Positions against this element instead of the trigger. */\n  anchor?: BasePreviewCard.Positioner.Props[\"anchor\"];\n  /** Classes for the inner viewport. Set padding with `[--viewport-padding:…]`. */\n  viewportClassName?: string;\n  arrow?: boolean;\n  arrowPadding?: number;\n  container?: BasePreviewCard.Portal.Props[\"container\"];\n  /** Surface elevation level for the card bg (1-8). Bump when nesting inside a Dialog or other elevated container. Defaults to 3. */\n  level?: SurfaceLevel;\n  /** Shadow weight (1-8). Pinned to 3 by default so the card reads the same regardless of nesting depth. */\n  shadowLevel?: SurfaceLevel;\n}) {\n  return (\n    <PreviewCardPortal container={container}>\n      <BasePreviewCard.Positioner\n        data-slot=\"preview-card-positioner\"\n        side={side}\n        align={align}\n        sideOffset={sideOffset}\n        alignOffset={alignOffset}\n        collisionBoundary={collisionBoundary}\n        collisionPadding={collisionPadding}\n        sticky={sticky}\n        positionMethod={positionMethod}\n        anchor={anchor}\n        arrowPadding={arrowPadding}\n        // Glides between triggers on the size morph's 200ms curve, so the card\n        // moves as one object.\n        className=\"z-50 h-(--positioner-height) max-h-(--available-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] data-instant:transition-none motion-reduce:transition-none\"\n      >\n        <BasePreviewCard.Popup\n          data-slot=\"preview-card-content\"\n          data-level={level}\n          className={cn(\n            // Base styles\n            \"text-popover-foreground relative rounded-xl text-sm outline-none\",\n            \"h-(--popup-height,auto) w-(--popup-width,auto)\",\n            \"max-h-(--available-height) max-w-[min(var(--available-width),20rem)]\",\n            \"origin-(--transform-origin)\",\n            // Surface elevation\n            solidSurface(level, shadowLevel),\n            // Enter: scale and a 1px blur from the trigger side. Width and\n            // height only transition when the card switches triggers.\n            \"transition-[width,height,scale,opacity,filter] duration-[200ms,200ms,100ms,100ms,100ms] ease-[cubic-bezier(0.22,1,0.36,1),cubic-bezier(0.22,1,0.36,1),var(--ease-out-expo),var(--ease-out-expo),var(--ease-out-expo)]\",\n            \"data-starting-style:scale-[0.98] data-starting-style:opacity-0 data-starting-style:blur-[1px]\",\n            // Exit\n            \"data-ending-style:scale-[0.98] data-ending-style:opacity-0 data-ending-style:duration-100 data-ending-style:ease-out\",\n            // Chrome re-rasters text while width/height transition, which\n            // shifts it as the entrance ends. A promoted layer rasters once.\n            \"will-change-transform\",\n            // Keyboard opens and Escape/press closes skip the animation\n            \"data-instant:transition-none\",\n            // Reduced motion: fade only\n            \"motion-reduce:transition-opacity motion-reduce:duration-150 motion-reduce:will-change-auto\",\n            \"motion-reduce:data-ending-style:scale-100 motion-reduce:data-starting-style:scale-100 motion-reduce:data-starting-style:blur-none\",\n            className,\n          )}\n          {...props}\n        >\n          {arrow && (\n            <PreviewCardArrow className=\"transition-[left,right,top,bottom] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] data-instant:transition-none data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180 motion-reduce:transition-none\">\n              <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\">\n                <path\n                  d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V9H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n                  className=\"fill-(--popup-surface,var(--popover))\"\n                />\n                <path\n                  d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n                  className=\"fill-border/70\"\n                />\n              </svg>\n            </PreviewCardArrow>\n          )}\n          <BasePreviewCard.Viewport\n            data-slot=\"preview-card-viewport\"\n            className={cn(\n              // Clips here, not on the popup, so the arrow isn't cut off.\n              // `h-full` and the max-h cap are both needed; see PopoverContent.\n              \"relative h-full max-h-(--available-height) w-full overflow-clip rounded-[inherit] p-(--viewport-padding) [--viewport-padding:1rem]\",\n              \"overscroll-contain not-data-transitioning:overflow-y-auto\",\n              // Content width (edge to edge minus padding)\n              \"**:data-current:w-[calc(var(--popup-width)-2*var(--viewport-padding))]\",\n              \"**:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-padding))]\",\n              // Trigger switch: crossfade with an 8px nudge from the direction\n              // of travel. A switch that interrupts another restarts the old\n              // content from rest; the 2px blur merges the two into one card.\n              \"**:data-current:opacity-100 **:data-previous:opacity-100\",\n              \"**:data-current:transition-[translate,opacity,filter] **:data-current:duration-150 **:data-current:ease-[cubic-bezier(0.22,1,0.36,1)]\",\n              \"**:data-previous:transition-[translate,opacity,filter] **:data-previous:duration-150 **:data-previous:ease-[cubic-bezier(0.22,1,0.36,1)]\",\n              \"**:data-current:data-starting-style:opacity-0 **:data-current:data-starting-style:blur-[2px]\",\n              \"**:data-previous:data-ending-style:opacity-0 **:data-previous:data-ending-style:blur-[2px]\",\n              \"data-[activation-direction~=right]:**:data-current:data-starting-style:translate-x-2\",\n              \"data-[activation-direction~=left]:**:data-current:data-starting-style:-translate-x-2\",\n              // Vertical nudge only on a purely vertical move. Base UI reports\n              // diagonals as \"right down\", which nudge sideways.\n              \"data-[activation-direction~=down]:not-data-[activation-direction~=left]:not-data-[activation-direction~=right]:**:data-current:data-starting-style:translate-y-2\",\n              \"data-[activation-direction~=up]:not-data-[activation-direction~=left]:not-data-[activation-direction~=right]:**:data-current:data-starting-style:-translate-y-2\",\n              // Reduced motion: plain crossfade\n              \"motion-reduce:**:data-current:transition-opacity motion-reduce:**:data-previous:transition-opacity\",\n              \"motion-reduce:**:data-previous:data-ending-style:blur-none motion-reduce:**:data-current:data-starting-style:blur-none\",\n              // Keyboard opens skip the swap, like the popup\n              \"data-instant:**:data-current:transition-none data-instant:**:data-previous:transition-none\",\n              viewportClassName,\n            )}\n          >\n            {children}\n          </BasePreviewCard.Viewport>\n        </BasePreviewCard.Popup>\n      </BasePreviewCard.Positioner>\n    </PreviewCardPortal>\n  );\n}\n\n/** Edge-to-edge image at the top of the card, in the 1.91:1 Open Graph ratio. */\nfunction PreviewCardMedia({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"preview-card-media\"\n      className={cn(\n        \"bg-muted relative -mx-(--viewport-padding) -mt-(--viewport-padding) mb-3 aspect-[1.91/1] overflow-hidden\",\n        \"*:[img,video]:size-full *:[img,video]:object-cover\",\n        // Bottom hairline, so light images don't dissolve into the card\n        \"after:pointer-events-none after:absolute after:inset-0 after:shadow-[inset_0_-1px_0_oklch(0_0_0/0.1)] dark:after:shadow-[inset_0_-1px_0_oklch(1_0_0/0.1)]\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction PreviewCardTitle({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"preview-card-title\"\n      className={cn(\n        \"text-foreground text-sm leading-snug font-semibold text-balance\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction PreviewCardDescription({\n  className,\n  ...props\n}: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"preview-card-description\"\n      className={cn(\n        \"text-muted-foreground mt-1 text-sm leading-relaxed text-pretty\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nconst createPreviewCardHandle = BasePreviewCard.createHandle;\n\nexport {\n  PreviewCard,\n  PreviewCardTrigger,\n  PreviewCardContent,\n  PreviewCardMedia,\n  PreviewCardTitle,\n  PreviewCardDescription,\n  createPreviewCardHandle,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/preview-card.tsx"
    }
  ],
  "type": "registry:ui"
}