Details Panel
Compose a file list with a consumer-owned details sidebar.
This example keeps Files.Details focused on metadata display and inline rename
mechanics. The consumer owns the file collection, selected IDs, rename mutation,
product-specific metadata, and the surrounding split layout.
import { useMemo, useState } from "react";
import { Files } from "@tilt-legal/cubitt-components/files";
import { Separator } from "@tilt-legal/cubitt-components/separator";
import { toast } from "@tilt-legal/cubitt-components/toast";
import { formatPlural } from "@tilt-legal/cubitt-components/utilities/formatters";
import { MatterFileDetailsPanel } from "@/components/files-details-panel/details-sidebar";
import {
matterFiles,
type MatterFileItem,
} from "@/components/files-details-panel/data";
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function FilesDetailsPanel() {
const [items, setItems] = useState(matterFiles);
const [selectedIds, setSelectedIds] = useState<string[]>([
"financial-report",
]);
const selectedItems = useMemo(
() =>
selectedIds
.map((id) => items.find((item) => item.id === id))
.filter((item): item is MatterFileItem => Boolean(item)),
[items, selectedIds],
);
const currentDetailsItem = useMemo<MatterFileItem>(
() => ({
id: "current-files",
kind: "folder",
name: "Files",
itemCount: items.length,
matter: "Bennett v Carter",
createdBy: "Matter team",
workflowStatus: "Reviewed",
updatedAt: "2026-04-22",
}),
[items.length],
);
const detailsItems =
selectedItems.length > 0 ? selectedItems : [currentDetailsItem];
async function renameItem(item: MatterFileItem, name: string) {
await sleep(300);
setItems((currentItems) =>
currentItems.map((currentItem) =>
currentItem.id === item.id ? { ...currentItem, name } : currentItem,
),
);
toast.success("File renamed", { description: name });
}
return (
<Files
className="h-[420px] w-full max-w-4xl flex-row gap-6"
canRename={(item) => item.id !== currentDetailsItem.id}
detailsItem={currentDetailsItem}
items={items}
onRename={renameItem}
onSelectedIdsChange={setSelectedIds}
selectedIds={selectedIds}
selectionMode="multiple"
>
<Files.Content className="overflow-auto">
<Files.List>
{items.map((item) => (
<Files.Item item={item} key={item.id} />
))}
</Files.List>
</Files.Content>
<Separator orientation="vertical" />
<MatterFileDetailsPanel
className="min-h-0 min-w-[400px] basis-[400px] shrink-0"
selectedItems={detailsItems}
/>
</Files>
);
}import { Files } from "@tilt-legal/cubitt-components/files";
import { formatPlural } from "@tilt-legal/cubitt-components/utilities/formatters";
import type { MatterFileItem } from "@/components/files-details-panel/data";
type Props = {
className?: string;
selectedItems: MatterFileItem[];
};
export function MatterFileDetailsPanel({
className,
selectedItems,
}: Props) {
const singleItem = selectedItems.length === 1 ? selectedItems[0] : null;
const matterCount = new Set(
selectedItems.map((item) => item.matter).filter(Boolean),
).size;
const ownerCount = new Set(
selectedItems.map((item) => item.createdBy).filter(Boolean),
).size;
return (
<Files.Details className={className}>
<Files.Details.Icon />
<Files.Details.Header>
<Files.Details.Heading>
<Files.Details.Name />
<Files.Details.Type />
</Files.Details.Heading>
<Files.Details.EditNameButton />
</Files.Details.Header>
<Files.Details.Panels>
<Files.Details.Panel label="Details" value="details">
<Files.Details.Properties>
<Files.Details.Metadata />
{singleItem?.matter ? (
<Files.Details.Property>
<Files.Details.Property.Label>
Matter
</Files.Details.Property.Label>
<Files.Details.Property.Value>
{singleItem.matter}
</Files.Details.Property.Value>
</Files.Details.Property>
) : null}
{singleItem?.workflowStatus ? (
<Files.Details.Property>
<Files.Details.Property.Label>
Status
</Files.Details.Property.Label>
<Files.Details.Property.Value>
{singleItem.workflowStatus}
</Files.Details.Property.Value>
</Files.Details.Property>
) : null}
{selectedItems.length > 1 ? (
<Files.Details.Property>
<Files.Details.Property.Label>
Matters
</Files.Details.Property.Label>
<Files.Details.Property.Value>
{formatPlural(matterCount, "matter")}
</Files.Details.Property.Value>
</Files.Details.Property>
) : null}
{selectedItems.length > 1 ? (
<Files.Details.Property>
<Files.Details.Property.Label>
Owners
</Files.Details.Property.Label>
<Files.Details.Property.Value>
{formatPlural(ownerCount, "owner")}
</Files.Details.Property.Value>
</Files.Details.Property>
) : null}
</Files.Details.Properties>
</Files.Details.Panel>
</Files.Details.Panels>
</Files.Details>
);
}import type { FilesItem } from "@tilt-legal/cubitt-components/files";
export type MatterFileItem = FilesItem & {
matter: string;
workflowStatus: "Draft" | "Reviewed" | "Filed";
};
export const matterFiles: MatterFileItem[] = [
{
id: "financial-report",
kind: "file",
name: "Q4FinancialReportBennettVCarterLitigationBudgetAndSettlementExposureAnalysisFinalReviewedCopy.pdf",
mimeType: "application/pdf",
size: 2_458_624,
matter: "Bennett v Carter",
createdBy: "Sarah Chen",
workflowStatus: "Reviewed",
updatedAt: "2026-04-20",
},
{
id: "deposition-outline",
kind: "file",
name: "Deposition Outline.docx",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
size: 884_736,
matter: "Hudson Contract Review",
createdBy: "Ava Johnson",
workflowStatus: "Draft",
updatedAt: "2026-04-18",
},
{
id: "folder-discovery",
kind: "folder",
name: "Discovery",
itemCount: 12,
matter: "Bennett v Carter",
createdBy: "Sarah Chen",
workflowStatus: "Draft",
updatedAt: "2026-04-22",
},
];