Details Panel

Compose a file list with a consumer-owned details sidebar.

Discovery
Folder12 itemsSarah Chen22 Apr 2026
Exhibits
Folder8 itemsAva Johnson21 Apr 2026
Q4FinancialReportBennettVCarterLitigationBudgetAndSettlementExposureAnalysisFinalReviewedCopy.pdf
PDF Document2 MBSarah Chen20 Apr 2026
Deposition Outline.docx
Word Document864 KBAva Johnson18 Apr 2026
Settlement Strategy.pptx
PowerPoint Presentation3 MBNoah Martinez16 Apr 2026
Filed Order.pdf
PDF Document627 KBEmma Hughes12 Apr 2026

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.

components/files-details-panel/index.tsx
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>
  );
}
components/files-details-panel/details-sidebar.tsx
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>
  );
}
components/files-details-panel/data.ts
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",
  },
];