{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "file-thumbnail",
  "title": "File Thumbnail",
  "description": "A compact image preview with automatic browser File support, loading, and failure states.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/file-thumbnail/file-thumbnail.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { FileImage } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\nexport type FileThumbnailFile = {\n  name: string\n  type?: string\n}\n\nexport type FileThumbnailProps = Omit<\n  React.HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  file: FileThumbnailFile | File\n  previewImageUrl?: string | null\n  previewAspectRatio?: number\n  previewClassName?: string\n  fit?: \"cover\" | \"contain\"\n  alt?: string\n  isLoading?: boolean\n  hasError?: boolean\n}\n\nfunction extensionFromName(name: string) {\n  const extension = name.includes(\".\") ? name.split(\".\").pop() : undefined\n  return extension?.slice(0, 5).toUpperCase() || \"IMAGE\"\n}\n\nfunction isImageFile(file: FileThumbnailFile | File) {\n  const extension = extensionFromName(file.name).toLowerCase()\n  return (\n    file.type?.toLowerCase().startsWith(\"image/\") === true ||\n    /^(png|jpe?g|gif|webp|svg|avif)$/.test(extension)\n  )\n}\n\nexport const FileThumbnail = React.forwardRef<\n  HTMLDivElement,\n  FileThumbnailProps\n>(function FileThumbnail(\n  {\n    file,\n    previewImageUrl,\n    previewAspectRatio = 1,\n    previewClassName,\n    fit = \"cover\",\n    alt = \"\",\n    isLoading = false,\n    hasError = false,\n    className,\n    style,\n    ...rootProps\n  },\n  forwardedRef\n) {\n  const [filePreview, setFilePreview] = React.useState<{\n    file: File\n    url: string\n  }>()\n  const [imageResult, setImageResult] = React.useState<{\n    url: string\n    state: \"loaded\" | \"error\"\n  }>()\n  const browserFile =\n    typeof File !== \"undefined\" && file instanceof File ? file : undefined\n\n  React.useEffect(() => {\n    if (\n      previewImageUrl !== undefined ||\n      !browserFile ||\n      !isImageFile(browserFile)\n    ) {\n      return\n    }\n\n    const nextUrl = URL.createObjectURL(browserFile)\n    const frame = requestAnimationFrame(() => {\n      setFilePreview({ file: browserFile, url: nextUrl })\n    })\n\n    return () => {\n      cancelAnimationFrame(frame)\n      URL.revokeObjectURL(nextUrl)\n    }\n  }, [browserFile, previewImageUrl])\n\n  const fileUrl = filePreview?.file === file ? filePreview.url : undefined\n  const imageUrl =\n    previewImageUrl === undefined ? fileUrl : previewImageUrl || undefined\n  const imageState =\n    imageUrl && imageResult?.url === imageUrl\n      ? imageResult.state\n      : imageUrl\n        ? \"loading\"\n        : \"error\"\n  const showImage = Boolean(imageUrl) && !hasError && imageState !== \"error\"\n  const showLoading =\n    isLoading ||\n    (!hasError &&\n      ((Boolean(browserFile) &&\n        previewImageUrl === undefined &&\n        !filePreview) ||\n        (Boolean(imageUrl) && imageState === \"loading\")))\n  const showFallback = !showImage && !showLoading\n  const handleImageRef = React.useCallback(\n    (image: HTMLImageElement | null) => {\n      if (!image || !imageUrl || !image.complete) return\n      const state = image.naturalWidth > 0 ? \"loaded\" : \"error\"\n      setImageResult((current) =>\n        current?.url === imageUrl && current.state === state\n          ? current\n          : { url: imageUrl, state }\n      )\n    },\n    [imageUrl]\n  )\n\n  return (\n    <div\n      className={cn(\n        \"border-border bg-muted text-muted-foreground relative isolate overflow-hidden rounded-[var(--radius)] border\",\n        className\n      )}\n      data-slot=\"file-thumbnail\"\n      data-state={showLoading ? \"loading\" : showFallback ? \"fallback\" : \"ready\"}\n      style={{ aspectRatio: previewAspectRatio, ...style }}\n      ref={forwardedRef}\n      {...rootProps}\n    >\n      {imageUrl && !hasError ? (\n        // A native image keeps this registry item independent of framework image loaders.\n        // eslint-disable-next-line @next/next/no-img-element\n        <img\n          data-slot=\"file-thumbnail-image\"\n          ref={handleImageRef}\n          alt={alt}\n          className={cn(\n            \"absolute inset-0 size-full transition-opacity duration-200 motion-reduce:transition-none\",\n            fit === \"contain\" ? \"object-contain\" : \"object-cover\",\n            imageState === \"loaded\" ? \"opacity-100\" : \"opacity-0\",\n            previewClassName\n          )}\n          src={imageUrl}\n          onError={() => setImageResult({ url: imageUrl, state: \"error\" })}\n          onLoad={() => setImageResult({ url: imageUrl, state: \"loaded\" })}\n        />\n      ) : null}\n\n      {showLoading ? (\n        <div\n          data-slot=\"file-thumbnail-loading\"\n          aria-label={`Loading preview for ${file.name}`}\n          className=\"absolute inset-0 overflow-hidden\"\n          role=\"status\"\n        >\n          <span className=\"sr-only\">Loading preview</span>\n          <span className=\"bg-muted absolute inset-0\" />\n          <span className=\"bg-background/60 absolute inset-y-0 -left-1/2 w-1/2 skew-x-[-12deg] motion-safe:animate-[mischief-file-thumbnail-shimmer_1.4s_ease-in-out_infinite]\" />\n        </div>\n      ) : null}\n\n      {showFallback ? (\n        <div\n          data-slot=\"file-thumbnail-fallback\"\n          aria-label={`${file.name} image preview unavailable`}\n          className=\"absolute inset-0 flex flex-col items-center justify-center gap-2\"\n          role=\"img\"\n        >\n          <FileImage aria-hidden=\"true\" size={24} strokeWidth={1.7} />\n          <span className=\"bg-background/80 border-border rounded-md border px-1.5 py-0.5 text-[0.625rem] leading-none font-semibold tracking-[0.08em]\">\n            {extensionFromName(file.name)}\n          </span>\n          <span\n            aria-hidden=\"true\"\n            className=\"border-border bg-background absolute top-0 right-0 size-6 origin-top-right translate-x-3 -translate-y-3 rotate-45 border\"\n          />\n        </div>\n      ) : null}\n    </div>\n  )\n})\n",
      "type": "registry:ui"
    }
  ],
  "css": {
    "@keyframes mischief-file-thumbnail-shimmer": {
      "to": {
        "transform": "translateX(300%) skewX(-12deg)"
      }
    }
  },
  "categories": [
    "file",
    "thumbnail",
    "preview",
    "loading"
  ],
  "type": "registry:ui"
}