{ "$schema": "https://blode.co/ui/schema/registry-item.json", "name": "file-thumbnail", "title": "File Thumbnail", "author": "Matthew Blode", "description": "A square preview tile for an attached file — an image thumbnail or a file icon with name.", "files": [ { "path": "ui/file-thumbnail.tsx", "content": "\"use client\";\n\nimport { FileIcon } from \"blode-icons-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface FileThumbnailProps extends Omit, \"style\"> {\n /** The file to preview. Images render a thumbnail; everything else shows a\n * file icon with the truncated filename. */\n file: File;\n /** Side length of the square tile in pixels. Defaults to 64. */\n size?: number;\n}\n\n// ─── FileThumbnail ──────────────────────────────────────────────────────────\n// A square preview tile for an attached file. Images render via an object URL\n// (revoked on unmount); all other types fall back to a file icon + name. No\n// PDF rasterization — keeps the component dependency-free.\nconst FileThumbnail = ({ file, size = 64, className, ...props }: FileThumbnailProps) => {\n const isImage = file.type.startsWith(\"image/\");\n const url = React.useMemo(() => (isImage ? URL.createObjectURL(file) : null), [file, isImage]);\n\n React.useEffect(() => {\n if (!url) {\n return;\n }\n return () => URL.revokeObjectURL(url);\n }, [url]);\n\n return (\n \n {isImage && url ? (\n // eslint-disable-next-line next/no-img-element -- object-URL blob preview, not a remote asset\n {file.name}\n ) : (\n <>\n \n \n {file.name}\n \n \n )}\n \n );\n};\n\nexport { FileThumbnail };\nexport type { FileThumbnailProps };\n", "type": "registry:ui", "target": "" } ], "type": "registry:ui" }