{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "version-history",
  "type": "registry:component",
  "title": "Version History",
  "description": "A version timeline for one key with one-click restore (versioning plugin).",
  "dependencies": [
    "files-sdk",
    "lucide-react"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "registry/files-sdk/version-history/version-history.tsx",
      "type": "registry:component",
      "target": "components/files-sdk/version-history.tsx",
      "content": "\"use client\";\n\nimport type { FileVersion, UseFilesResult } from \"files-sdk/react\";\nimport { HistoryIcon, Loader2Icon, RotateCcwIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface VersionHistoryProps {\n  /** A `useFiles()` instance backed by a gateway with the `versioning()` plugin. */\n  files: UseFilesResult;\n  /** The key whose history to show. */\n  fileKey: string;\n  /** Called after a successful restore. */\n  onRestored?: (file: { key: string }) => void;\n  className?: string;\n}\n\nconst formatBytes = (bytes: number): string => {\n  if (bytes === 0) {\n    return \"0 B\";\n  }\n  const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n  const exponent = Math.min(\n    Math.floor(Math.log(bytes) / Math.log(1024)),\n    units.length - 1\n  );\n  return `${(bytes / 1024 ** exponent).toFixed(exponent === 0 ? 0 : 1)} ${units[exponent]}`;\n};\n\n/**\n * A version timeline for one key, backed by the `versioning()` plugin. Lists the\n * saved snapshots (newest first) and restores any of them through the same\n * `useFiles()` instance. Restoring snapshots the current bytes first, so it's\n * itself reversible.\n */\nexport const VersionHistory = ({\n  files,\n  fileKey,\n  onRestored,\n  className,\n}: VersionHistoryProps) => {\n  const [versions, setVersions] = useState<FileVersion[]>([]);\n  const [isLoading, setIsLoading] = useState(true);\n  const [restoring, setRestoring] = useState<string>();\n\n  const filesRef = useRef(files);\n  filesRef.current = files;\n\n  const refresh = useCallback(async () => {\n    setIsLoading(true);\n    try {\n      setVersions(await filesRef.current.versions(fileKey));\n    } catch {\n      // The hook mirrors the error to `files.error` for display.\n    } finally {\n      setIsLoading(false);\n    }\n  }, [fileKey]);\n\n  useEffect(() => {\n    void refresh();\n  }, [refresh]);\n\n  const restore = useCallback(\n    async (versionId: string) => {\n      setRestoring(versionId);\n      try {\n        const file = await filesRef.current.restoreVersion(fileKey, versionId);\n        await refresh();\n        onRestored?.(file);\n      } catch {\n        // Mirrored to `files.error`.\n      } finally {\n        setRestoring(undefined);\n      }\n    },\n    [fileKey, refresh, onRestored]\n  );\n\n  if (isLoading && !versions.length) {\n    return (\n      <div\n        className={cn(\n          \"flex items-center justify-center gap-2 p-8 text-muted-foreground text-sm\",\n          className\n        )}\n      >\n        <Loader2Icon className=\"size-4 animate-spin\" /> Loading…\n      </div>\n    );\n  }\n\n  if (!versions.length) {\n    return (\n      <div\n        className={cn(\n          \"flex flex-col items-center gap-1 p-8 text-center text-muted-foreground text-sm\",\n          className\n        )}\n      >\n        <HistoryIcon className=\"size-6\" />\n        No version history yet.\n      </div>\n    );\n  }\n\n  return (\n    <ol className={cn(\"flex flex-col gap-2\", className)}>\n      {versions.map((version, index) => (\n        <li\n          className=\"flex items-center gap-3 rounded-lg border border-border p-2\"\n          key={version.versionId}\n        >\n          <span className=\"flex size-9 shrink-0 items-center justify-center rounded bg-muted text-muted-foreground\">\n            <HistoryIcon className=\"size-4\" />\n          </span>\n          <div className=\"min-w-0 flex-1\">\n            <p className=\"truncate font-medium text-sm\">\n              {new Date(version.lastModified).toLocaleString()}\n              {index === 0 && (\n                <span className=\"ml-2 text-muted-foreground text-xs\">\n                  latest\n                </span>\n              )}\n            </p>\n            <p className=\"text-muted-foreground text-xs\">\n              {formatBytes(version.size)}\n              {version.etag ? ` · ${version.etag.slice(0, 12)}` : \"\"}\n            </p>\n          </div>\n          <Button\n            disabled={restoring !== undefined}\n            onClick={() => void restore(version.versionId)}\n            size=\"sm\"\n            type=\"button\"\n            variant=\"outline\"\n          >\n            {restoring === version.versionId ? (\n              <Loader2Icon className=\"animate-spin\" />\n            ) : (\n              <RotateCcwIcon />\n            )}\n            Restore\n          </Button>\n        </li>\n      ))}\n    </ol>\n  );\n};\n"
    }
  ]
}
