{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "lightbox",
  "title": "Lightbox",
  "author": "ncdai <dai@chanhdai.com>",
  "description": "Image viewer that morphs from the thumbnail, with swipe, zoom, and pull to close.",
  "dependencies": [
    "@ramka/react",
    "lucide-react"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "src/registry/components/lightbox/lightbox.tsx",
      "content": "\"use client\"\n\nimport type { ComponentProps } from \"react\"\nimport * as LightboxPrimitive from \"@ramka/react/lightbox\"\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  XIcon,\n  ZoomInIcon,\n  ZoomOutIcon,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\ntype ButtonStyleProps = Pick<ComponentProps<typeof Button>, \"variant\" | \"size\">\n\ntype LightboxHandle = LightboxPrimitive.LightboxHandle\n\nconst createLightboxHandle = LightboxPrimitive.createHandle\n\nfunction Lightbox(props: ComponentProps<typeof LightboxPrimitive.Root>) {\n  return <LightboxPrimitive.Root {...props} />\n}\n\nfunction LightboxTrigger({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Trigger>) {\n  return (\n    <LightboxPrimitive.Trigger\n      data-slot=\"lightbox-trigger\"\n      className={cn(\n        \"cursor-zoom-in outline-none focus-visible:ring-3 focus-visible:ring-ring/50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxPortal(\n  props: ComponentProps<typeof LightboxPrimitive.Portal>\n) {\n  return <LightboxPrimitive.Portal data-slot=\"lightbox-portal\" {...props} />\n}\n\nfunction LightboxBackdrop({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Backdrop>) {\n  return (\n    <LightboxPrimitive.Backdrop\n      data-slot=\"lightbox-backdrop\"\n      className={cn(\n        \"fixed inset-0 z-50 bg-background opacity-0\",\n        // Pull-snap timing exists only while a cancelled pull springs back.\n        \"transition-opacity duration-(--lightbox-pull-snap-duration,var(--lightbox-vt-duration,360ms)) ease-(--lightbox-pull-snap-easing,cubic-bezier(0.16,1,0.3,1))\",\n        \"data-open:opacity-[calc(1-var(--lightbox-pull-progress,0)*0.6)] data-open:starting:opacity-0\",\n        \"data-pulling:transition-none\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxContent({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Content>) {\n  return (\n    <LightboxPortal>\n      <LightboxBackdrop />\n      <LightboxPrimitive.Content\n        data-slot=\"lightbox-content\"\n        className={cn(\n          \"group/lightbox fixed inset-0 z-50 flex flex-col overscroll-contain opacity-0 outline-none\",\n          \"transition-opacity duration-(--lightbox-vt-duration,360ms) ease-[cubic-bezier(0.16,1,0.3,1)]\",\n          \"data-open:opacity-100 data-open:starting:opacity-0\",\n          // Shared by the slides, header, and footer so they stay aligned.\n          \"[--lb-inset-bottom:calc(4rem+env(safe-area-inset-bottom,0px))] [--lb-inset-top:calc(4rem+env(safe-area-inset-top,0px))] [--lb-inset-x:0px]\",\n          \"md:[--lb-inset-bottom:calc(4.5rem+env(safe-area-inset-bottom,0px))] md:[--lb-inset-top:calc(4.5rem+env(safe-area-inset-top,0px))] md:[--lb-inset-x:4.5rem]\",\n          \"has-data-[slot=lightbox-thumbnail-strip]:[--lb-inset-bottom:calc(8rem+env(safe-area-inset-bottom,0px))]\",\n          className\n        )}\n        {...props}\n      />\n    </LightboxPortal>\n  )\n}\n\nfunction LightboxStage(props: ComponentProps<typeof LightboxPrimitive.Stage>) {\n  return <LightboxPrimitive.Stage data-slot=\"lightbox-stage\" {...props} />\n}\n\nfunction LightboxSlides({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Slides>) {\n  return (\n    <LightboxPrimitive.Slides\n      data-slot=\"lightbox-slides\"\n      className={cn(\n        \"flex-1 scrollbar-none gap-4 md:gap-0 [&::-webkit-scrollbar]:hidden\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxSlide({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Slide>) {\n  return (\n    <LightboxPrimitive.Slide\n      data-slot=\"lightbox-slide\"\n      className={cn(\n        \"h-full min-w-full flex-[0_0_100%] px-(--lb-inset-x) pt-(--lb-inset-top) pb-(--lb-inset-bottom)\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxItem(props: ComponentProps<typeof LightboxPrimitive.Item>) {\n  return <LightboxPrimitive.Item data-slot=\"lightbox-item\" {...props} />\n}\n\nfunction LightboxZoom({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Zoom>) {\n  return (\n    <LightboxPrimitive.Zoom\n      data-slot=\"lightbox-zoom\"\n      className={cn(\"cursor-zoom-in\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxMedia({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Media>) {\n  return (\n    <LightboxPrimitive.Media\n      data-slot=\"lightbox-media\"\n      className={cn(\n        \"overflow-hidden md:rounded-xl [&_img]:select-none\",\n        // Keeps image edges from blending into the backdrop.\n        \"after:pointer-events-none after:absolute after:inset-0 after:z-10 after:rounded-[inherit] md:after:inset-ring md:after:inset-ring-foreground/10\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxHeader({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"lightbox-header\"\n      className={cn(\n        \"pointer-events-none absolute inset-x-0 top-0 z-10 flex h-(--lb-inset-top) items-center justify-end gap-2 px-3 pt-[env(safe-area-inset-top,0px)] *:pointer-events-auto\",\n        \"transition-[opacity,visibility] duration-200\",\n        \"group-data-pull-dismissing/lightbox:invisible group-data-pull-dismissing/lightbox:opacity-0 group-data-pulling/lightbox:invisible group-data-pulling/lightbox:opacity-0\",\n        // Zoomed media fills small screens.\n        \"max-md:group-data-zoomed/lightbox:invisible max-md:group-data-zoomed/lightbox:opacity-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxFooter({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"lightbox-footer\"\n      className={cn(\n        \"pointer-events-none absolute inset-x-0 bottom-0 z-10 flex h-(--lb-inset-bottom) flex-col items-center justify-center gap-3 px-4 pb-[env(safe-area-inset-bottom,0px)] text-center *:pointer-events-auto\",\n        \"transition-[opacity,visibility] duration-200\",\n        \"group-data-pull-dismissing/lightbox:invisible group-data-pull-dismissing/lightbox:opacity-0 group-data-pulling/lightbox:invisible group-data-pulling/lightbox:opacity-0\",\n        \"group-data-zoomed/lightbox:invisible group-data-zoomed/lightbox:opacity-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxClose({\n  variant = \"secondary\",\n  size = \"icon\",\n  children,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Close> & ButtonStyleProps) {\n  return (\n    <LightboxPrimitive.Close\n      data-slot=\"lightbox-close\"\n      render={<Button variant={variant} size={size} />}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <XIcon />\n          <span className=\"sr-only\">Close</span>\n        </>\n      )}\n    </LightboxPrimitive.Close>\n  )\n}\n\nfunction LightboxZoomIn({\n  variant = \"secondary\",\n  size = \"icon\",\n  children,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.ZoomIn> & ButtonStyleProps) {\n  return (\n    <LightboxPrimitive.ZoomIn\n      data-slot=\"lightbox-zoom-in\"\n      render={<Button variant={variant} size={size} />}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <ZoomInIcon />\n          <span className=\"sr-only\">Zoom in</span>\n        </>\n      )}\n    </LightboxPrimitive.ZoomIn>\n  )\n}\n\nfunction LightboxZoomOut({\n  variant = \"secondary\",\n  size = \"icon\",\n  children,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.ZoomOut> & ButtonStyleProps) {\n  return (\n    <LightboxPrimitive.ZoomOut\n      data-slot=\"lightbox-zoom-out\"\n      render={<Button variant={variant} size={size} />}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <ZoomOutIcon />\n          <span className=\"sr-only\">Zoom out</span>\n        </>\n      )}\n    </LightboxPrimitive.ZoomOut>\n  )\n}\n\nfunction LightboxPrevious({\n  className,\n  variant = \"secondary\",\n  size = \"icon\",\n  children,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Previous> & ButtonStyleProps) {\n  return (\n    <LightboxPrimitive.Previous\n      data-slot=\"lightbox-previous\"\n      render={<Button variant={variant} size={size} />}\n      className={cn(\n        \"absolute inset-s-4 top-1/2 z-10 -translate-y-1/2 max-md:hidden rtl:[&_svg]:rotate-180\",\n        \"group-data-pull-dismissing/lightbox:invisible group-data-pull-dismissing/lightbox:opacity-0 group-data-pulling/lightbox:invisible group-data-pulling/lightbox:opacity-0\",\n        className\n      )}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <ChevronLeftIcon />\n          <span className=\"sr-only\">Previous</span>\n        </>\n      )}\n    </LightboxPrimitive.Previous>\n  )\n}\n\nfunction LightboxNext({\n  className,\n  variant = \"secondary\",\n  size = \"icon\",\n  children,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Next> & ButtonStyleProps) {\n  return (\n    <LightboxPrimitive.Next\n      data-slot=\"lightbox-next\"\n      render={<Button variant={variant} size={size} />}\n      className={cn(\n        \"absolute inset-e-4 top-1/2 z-10 -translate-y-1/2 max-md:hidden rtl:[&_svg]:rotate-180\",\n        \"group-data-pull-dismissing/lightbox:invisible group-data-pull-dismissing/lightbox:opacity-0 group-data-pulling/lightbox:invisible group-data-pulling/lightbox:opacity-0\",\n        className\n      )}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <ChevronRightIcon />\n          <span className=\"sr-only\">Next</span>\n        </>\n      )}\n    </LightboxPrimitive.Next>\n  )\n}\n\nfunction LightboxCounter({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Counter>) {\n  return (\n    <LightboxPrimitive.Counter\n      data-slot=\"lightbox-counter\"\n      className={cn(\"text-sm text-muted-foreground tabular-nums\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxCaption({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Caption>) {\n  return (\n    <LightboxPrimitive.Caption\n      data-slot=\"lightbox-caption\"\n      className={cn(\n        \"max-w-full truncate text-sm font-medium text-foreground select-text\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxThumbnailGroup(\n  props: ComponentProps<typeof LightboxPrimitive.ThumbnailGroup>\n) {\n  return (\n    <LightboxPrimitive.ThumbnailGroup\n      data-slot=\"lightbox-thumbnail-group\"\n      {...props}\n    />\n  )\n}\n\nfunction LightboxThumbnailStrip({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.ThumbnailStrip>) {\n  return (\n    <LightboxPrimitive.ThumbnailStrip\n      data-slot=\"lightbox-thumbnail-strip\"\n      className={cn(\n        \"w-full py-1\",\n        \"mask-[linear-gradient(to_right,transparent,black_2.5rem,black_calc(100%-2.5rem),transparent)]\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxThumbnailStripTrack({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.ThumbnailStripTrack>) {\n  return (\n    <LightboxPrimitive.ThumbnailStripTrack\n      data-slot=\"lightbox-thumbnail-strip-track\"\n      className={cn(\"flex w-max items-center gap-2\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction LightboxThumbnail({\n  className,\n  ...props\n}: ComponentProps<typeof LightboxPrimitive.Thumbnail>) {\n  return (\n    <LightboxPrimitive.Thumbnail\n      data-slot=\"lightbox-thumbnail\"\n      className={cn(\n        \"block size-14 shrink-0 cursor-pointer overflow-hidden rounded-md outline-none\",\n        \"ring-offset-2 ring-offset-background focus-visible:ring-3 focus-visible:ring-ring/50 data-active:ring-2 data-active:ring-foreground\",\n        \"[&_img]:size-full [&_img]:object-cover\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  createLightboxHandle,\n  Lightbox,\n  LightboxBackdrop,\n  LightboxCaption,\n  LightboxClose,\n  LightboxContent,\n  LightboxCounter,\n  LightboxFooter,\n  LightboxHeader,\n  LightboxItem,\n  LightboxMedia,\n  LightboxNext,\n  LightboxPortal,\n  LightboxPrevious,\n  LightboxSlide,\n  LightboxSlides,\n  LightboxStage,\n  LightboxThumbnail,\n  LightboxThumbnailGroup,\n  LightboxThumbnailStrip,\n  LightboxThumbnailStripTrack,\n  LightboxTrigger,\n  LightboxZoom,\n  LightboxZoomIn,\n  LightboxZoomOut,\n}\nexport type { LightboxHandle }\n",
      "type": "registry:component",
      "target": "@components/lightbox.tsx"
    }
  ],
  "docs": "https://chanhdai.com/components/lightbox",
  "categories": [
    "data-display"
  ],
  "type": "registry:component"
}