All Taste components
Productivity
File list
Shared files with their type, size, last change and a menu of actions per file.
type tiles · size & modified columns · owner · row menu · upload · stacks on narrow widths
Shared files
5 files · 317.5 MB of 50 GB used
PDF
Q3 GST returns.pdf1.2 MB · 14 Oct
XLSX
Price list 2026.xlsx86 KB · 12 Oct
PNG
Storefront banner.png3.4 MB · 9 Oct
ZIP
Menu photos, October.zip248 MB · 8 Oct
MP4
Booking walkthrough.mp464.8 MB · 2 Oct
"use client";
import { Download, MoreHorizontal, Pencil, Search, Share2, Trash2, Upload } from "lucide-react";
import { cn } from "@/lib/utils";
export interface FileRow { id: string; name: string; size: string; modified: string; owner: string }
const sample: FileRow[] = [
{ id: "1", name: "Q3 GST returns.pdf", size: "1.2 MB", modified: "14 Oct", owner: "Ritika Shah" },
{ id: "2", name: "Price list 2026.xlsx", size: "86 KB", modified: "12 Oct", owner: "Asha Raman" },
{ id: "3", name: "Storefront banner.png", size: "3.4 MB", modified: "9 Oct", owner: "Arjun Mehta" },
{ id: "4", name: "Menu photos, October.zip", size: "248 MB", modified: "8 Oct", owner: "Maya Nair" },
{ id: "5", name: "Booking walkthrough.mp4", size: "64.8 MB", modified: "2 Oct", owner: "Asha Raman" },
];
const typeTone: Record<string, string> = {
pdf: "bg-danger-soft text-danger-fg",
xlsx: "bg-success-soft text-success-fg", csv: "bg-success-soft text-success-fg",
png: "bg-info-soft text-info-fg", jpg: "bg-info-soft text-info-fg",
zip: "bg-warning-soft text-warning-fg",
};
type Action = "download" | "share" | "rename" | "delete";
function RowMenu({ name, onAction }: { name: string; onAction?: (a: Action) => void }) {
const items: [Action, string, typeof Download][] = [["download", "Download", Download], ["share", "Share", Share2], ["rename", "Rename", Pencil]];
return (
<details className="group relative">
<summary aria-label={"Actions for " + name} className="grid size-7 cursor-pointer list-none place-items-center rounded text-fg-muted hover:bg-surface-2 group-open:bg-surface-2 [&::-webkit-details-marker]:hidden">
<MoreHorizontal className="size-4" />
</summary>
<div role="menu" className="absolute right-0 top-[calc(100%+4px)] z-10 grid min-w-[168px] rounded border border-border bg-bg p-1 shadow-lg">
{items.map(([key, label, Icon]) => (
<button key={key} role="menuitem" onClick={() => onAction?.(key)} className="flex h-8 items-center gap-2 rounded-sm px-2 text-left text-[13px] hover:bg-surface-2">
<Icon className="size-3.5 text-fg-subtle" aria-hidden />{label}
</button>
))}
<hr className="my-1 border-border" />
<button role="menuitem" onClick={() => onAction?.("delete")} className="flex h-8 items-center gap-2 rounded-sm px-2 text-left text-[13px] text-danger hover:bg-surface-2">
<Trash2 className="size-3.5" aria-hidden />Delete
</button>
</div>
</details>
);
}
export function FileList({ files = sample, usage = "317.5 MB of 50 GB used", onUpload, onAction }: {
files?: FileRow[]; usage?: string; onUpload?: () => void; onAction?: (id: string, a: Action) => void;
}) {
const cols = "grid grid-cols-[32px_minmax(0,1fr)_28px] items-center gap-3 px-4 @[520px]:grid-cols-[32px_minmax(0,1fr)_72px_92px_112px_28px]";
const meta = "hidden whitespace-nowrap text-[13px] tabular-nums text-fg-muted @[520px]:block";
return (
// Uses Tailwind container queries (@tailwindcss/container-queries, built into v4).
<section className="@container w-full rounded-lg border border-border bg-bg">
<header className="flex flex-wrap items-center justify-between gap-3 p-4">
<div>
<h3 className="text-sm font-medium">Shared files</h3>
<p className="text-[12.5px] text-fg-subtle">{files.length} files · {usage}</p>
</div>
<div className="flex gap-2">
<button aria-label="Search files" className="grid size-[30px] place-items-center rounded border border-border-strong hover:bg-surface"><Search className="size-3.5" /></button>
<button onClick={onUpload} className="inline-flex h-[30px] items-center gap-1.5 rounded bg-primary px-2.5 text-[13px] font-medium text-primary-fg hover:bg-primary-hover">
<Upload className="size-3.5" aria-hidden /> Upload
</button>
</div>
</header>
<div className={cn(cols, "border-t border-border py-2 text-xs font-medium text-fg-subtle")} aria-hidden>
<span /><span>Name</span><span className={meta}>Size</span><span className={meta}>Modified</span><span className={meta}>Owner</span><span />
</div>
<ul>
{files.map((f) => {
const ext = f.name.split(".").pop()?.toLowerCase() ?? "";
return (
<li key={f.id} className={cn(cols, "border-t border-border py-2.5 text-[13.5px] hover:bg-surface")}>
<span className={cn("grid size-8 place-items-center rounded-sm text-[9.5px] font-semibold uppercase tracking-wide", typeTone[ext] ?? "bg-surface-2 text-fg-muted")}>{ext}</span>
<div className="min-w-0">
<p className="truncate font-medium" title={f.name}>{f.name}</p>
<p className="text-xs tabular-nums text-fg-subtle @[520px]:hidden">{f.size} · {f.modified}</p>
</div>
<span className={meta}>{f.size}</span>
<span className={meta}>{f.modified}</span>
<span className={cn(meta, "truncate")}>{f.owner}</span>
<RowMenu name={f.name} onAction={(a) => onAction?.(f.id, a)} />
</li>
);
})}
</ul>
</section>
);
}
The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.