All components

Settings Storage

Settings Storage is a React and Tailwind CSS component from HextaUI, licensed MIT. Copy it and paste it into your project.

HextaUI MIT other

What it is

Settings Storage is a React and Tailwind CSS component from HextaUI, licensed MIT. Copy it and paste it into your project.

How to use it

Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.

Open in the catalogue

Five free copies a month. No card.

No packages beyond the project baseline

Licence

This component comes from HextaUI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.

Settings Storage

registry/new-york/blocks/settings/settings-storage.tsx
"use client";

import {
  Database,
  Loader2,
  type LucideIcon,
  Trash2,
  TrendingDown,
} from "lucide-react";
import { useState } from "react";
import { cn } from "@/lib/utils";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/registry/new-york/ui/alert-dialog";
import { Button } from "@/registry/new-york/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/registry/new-york/ui/card";
import { FieldDescription, FieldLabel } from "@/registry/new-york/ui/field";
import { Progress } from "@/registry/new-york/ui/progress";
import { Separator } from "@/registry/new-york/ui/separator";

export interface StorageCategory {
  id: string;
  name: string;
  icon: LucideIcon;
  used: number;
  total?: number;
  color: string;
}

export interface SettingsStorageProps {
  totalUsed?: number;
  totalLimit?: number;
  categories?: StorageCategory[];
  onCleanup?: (categoryId?: string) => Promise<void>;
  className?: string;
}

function formatBytes(bytes: number): string {
  if (bytes === 0) return "0 Bytes";
  const k = 1024;
  const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;
}

export default function SettingsStorage({
  totalUsed = 0,
  totalLimit = 100 * 1024 * 1024 * 1024, // 100 GB default
  categories = [],
  onCleanup,
  className,
}: SettingsStorageProps) {
  const [isCleaning, setIsCleaning] = useState<string | null>(null);

  const usagePercentage = (totalUsed / totalLimit) * 100;
  const remaining = totalLimit - totalUsed;

  const handleCleanup = async (categoryId?: string) => {
    setIsCleaning(categoryId || "all");
    try {
      await onCleanup?.(categoryId);
    } finally {
      setIsCleaning(null);
    }
  };

  return (
    <Card className={cn("w-full shadow-xs", className)}>
      <CardHeader>
        <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
          <div className="flex min-w-0 flex-1 flex-col gap-2">
            <CardTitle className="wrap-break-word">Storage</CardTitle>
            <CardDescription className="wrap-break-word">
              Manage your storage usage and quotas
            </CardDescription>
          </div>
        </div>
      </CardHeader>
      <CardContent>
        <div className="flex flex-col gap-6">
          {/* Overall Usage */}
          <div className="flex flex-col gap-4 rounded-lg border p-4">
            <div className="flex items-center gap-2">
              <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                <Database className="size-4" />
              </div>
              <FieldLabel className="mb-0">Storage Usage</FieldLabel>
            </div>
            <div className="flex flex-col gap-3">
              <div className="flex items-center justify-between">
                <span className="text-muted-foreground text-sm">
                  {formatBytes(totalUsed)} of {formatBytes(totalLimit)} used
                </span>
                <span className="font-medium text-sm">
                  {usagePercentage.toFixed(1)}%
                </span>
              </div>
              <Progress value={usagePercentage} />
              <p className="text-muted-foreground text-xs">
                {formatBytes(remaining)} remaining
              </p>
            </div>
          </div>

          <Separator />

          {/* Storage by Category */}
          <div className="flex flex-col gap-4">
            <h3 className="font-semibold text-base">Storage by Category</h3>
            {categories.length === 0 ? (
              <p className="text-muted-foreground text-sm">
                No storage data available
              </p>
            ) : (
              <div className="grid gap-4 md:grid-cols-2">
                {categories.map((category) => {
                  const Icon = category.icon;
                  const categoryPercentage = category.total
                    ? (category.used / category.total) * 100
                    : 0;

                  return (
                    <div
                      className="flex flex-col gap-4 rounded-lg border p-4"
                      key={category.id}
                    >
                      <div className="flex items-center justify-between">
                        <div className="flex items-center gap-2">
                          <div
                            className={cn(
                              "flex size-8 items-center justify-center rounded-lg",
                              category.color
                            )}
                          >
                            <Icon className="size-4" />
                          </div>
                          <FieldLabel className="mb-0">
                            {category.name}
                          </FieldLabel>
                        </div>
                        {category.total && (
                          <span className="font-medium text-sm">
                            {categoryPercentage.toFixed(1)}%
                          </span>
                        )}
                      </div>
                      <div className="flex flex-col gap-2">
                        <div className="flex items-center justify-between text-muted-foreground text-xs">
                          <span>{formatBytes(category.used)}</span>
                          {category.total && (
                            <span>of {formatBytes(category.total)}</span>
                          )}
                        </div>
                        {category.total && (
                          <Progress value={categoryPercentage} />
                        )}
                      </div>
                      <AlertDialog>
                        <AlertDialogTrigger asChild>
                          <Button
                            className="w-full"
                            disabled={isCleaning === category.id}
                            type="button"
                            variant="outline"
                          >
                            {isCleaning === category.id ? (
                              <>
                                <Loader2 className="size-4 animate-spin" />
                                Cleaning…
                              </>
                            ) : (
                              <>
                                <Trash2 className="size-4" />
                                Clean Up
                              </>
                            )}
                          </Button>
                        </AlertDialogTrigger>
                        <AlertDialogContent>
                          <AlertDialogHeader>
                            <AlertDialogTitle>
                              Clean Up {category.name}?
                            </AlertDialogTitle>
                            <AlertDialogDescription>
                              This will delete old and unused files from{" "}
                              {category.name}. This action cannot be undone.
                            </AlertDialogDescription>
                          </AlertDialogHeader>
                          <AlertDialogFooter>
                            <AlertDialogCancel>Cancel</AlertDialogCancel>
                            <AlertDialogAction
                              onClick={() => handleCleanup(category.id)}
                            >
                              Clean Up
                            </AlertDialogAction>
                          </AlertDialogFooter>
                        </AlertDialogContent>
                      </AlertDialog>
                    </div>
                  );
                })}
              </div>
            )}
          </div>

          <Separator />

          {/* Cleanup Actions */}
          <div className="flex flex-col gap-4 rounded-lg border p-4">
            <div className="flex items-center gap-2">
              <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                <TrendingDown className="size-4" />
              </div>
              <FieldLabel className="mb-0">Storage Optimization</FieldLabel>
            </div>
            <div className="flex flex-col gap-3">
              <AlertDialog>
                <AlertDialogTrigger asChild>
                  <Button
                    className="w-full sm:w-auto"
                    disabled={isCleaning === "all"}
                    type="button"
                    variant="outline"
                  >
                    {isCleaning === "all" ? (
                      <>
                        <Loader2 className="size-4 animate-spin" />
                        Cleaning…
                      </>
                    ) : (
                      <>
                        <Trash2 className="size-4" />
                        Empty Trash
                      </>
                    )}
                  </Button>
                </AlertDialogTrigger>
                <AlertDialogContent>
                  <AlertDialogHeader>
                    <AlertDialogTitle>Empty Trash?</AlertDialogTitle>
                    <AlertDialogDescription>
                      This will permanently delete all files in your trash. This
                      action cannot be undone.
                    </AlertDialogDescription>
                  </AlertDialogHeader>
                  <AlertDialogFooter>
                    <AlertDialogCancel>Cancel</AlertDialogCancel>
                    <AlertDialogAction onClick={() => handleCleanup()}>
                      Empty Trash
                    </AlertDialogAction>
                  </AlertDialogFooter>
                </AlertDialogContent>
              </AlertDialog>
              <FieldDescription>
                Remove old and unused files to free up storage space
              </FieldDescription>
            </div>
          </div>
        </div>
      </CardContent>
    </Card>
  );
}

Similar components