

<Preview className="min-h-[520px]" name="FilesInteractiveStatesExample" />

## Overview [#overview]

`Files` is a compound surface for composing file and folder UI from consumer-owned data. Cubitt owns the visual treatment, grid/list layout, controlled selection interactions, inline rename mechanics, item slots, upload affordances, and accessibility details.

Consumers own file schemas, folder hierarchy, navigation, filtering, sorting, URL state, permissions, rename persistence, actions, and upload lifecycle. Folder support is display-only: pass `kind: "folder"` and handle open/navigation callbacks in your app.

## Usage [#usage]

```tsx
import {
  Files,
  type FilesItem,
} from "@tilt-legal/cubitt-components/files";

const items: FilesItem[] = [
  { id: "contracts", kind: "folder", name: "Contracts" },
  {
    id: "contract-1",
    kind: "file",
    name: "Client Contract.pdf",
    mediaType: "application/pdf",
    sizeLabel: "612 KB",
  },
];

<Files
  selectedIds={selectedIds}
  onSelectedIdsChange={setSelectedIds}
  onRename={renameItem}
  selectionMode="multiple"
>
  <Files.Grid>
    {items.map((item) => (
      <Files.Item
        item={item}
        key={item.id}
        onOpen={(openedItem) => {
          if (openedItem.kind === "folder") {
            navigateToFolder(openedItem.id);
          }
        }}
      />
    ))}
  </Files.Grid>
</Files>;
```

The root keeps selection controlled and stores only the transient inline rename draft. Collection components only lay out children, so the consuming app decides which items exist, how they are ordered, which view renders, and how a rename mutation updates source data.

Selection clears from `Files.Root` when the user clicks anything that is not a
selection-preserving surface. `Files.Item`, `Files.Details`, Cubitt `Toolbar`
inside the `Files` root, Base UI portals, and elements marked with
`data-files-selection-preserve` preserve the current selection; blank
grid/list/content space clears it. Use `data-files-selection-preserve` for
external toolbar islands that sit outside the `Files` root.

| Surface                         | Selection behavior                                                                  |
| ------------------------------- | ----------------------------------------------------------------------------------- |
| `Files.Item`                    | Preserves selection so item clicks can update selection intentionally.              |
| `Files.Details`                 | Preserves selection while users inspect or rename details.                          |
| `Toolbar` inside `Files`        | Preserves selection while users search, filter, sort, upload, or open action menus. |
| Base UI portals                 | Preserves selection while users interact with menus, popovers, and dialogs.         |
| `data-files-selection-preserve` | Preserves selection for external toolbar/action islands outside the `Files` root.   |

Use `canSelect` when some items should be navigable but should not enter
`selectedIds`. For example, a single-file picker can keep files selectable while
folders only activate/open:

```tsx
<Files
  selectedIds={selectedIds}
  onSelectedIdsChange={setSelectedIds}
  canSelect={(item) => item.kind !== "folder"}
  selectionBehavior="toggle"
  selectionMode="single"
>
  {/* collections */}
</Files>
```

For explorer-style surfaces, leave folders selectable and clear selection in
your folder navigation handler. For picker-style surfaces, use `canSelect` only
when a folder should be navigable without becoming a selected value.

For right-click menus, compose the `ContextMenu` components with
`useFilesContextMenu` so your app owns menu actions while Cubitt keeps item
selection semantics consistent.

## Files.Item [#filesitem]

### Contract [#contract]

`FilesItem` is the display contract shared by `Files.Item`, `Files.Grid`,
`Files.List`, `Files.Details`, and custom item slots.

```ts
type FilesItem<TSource = unknown> = {
  id: string;
  kind: "file" | "folder";
  name: string;
  size?: number;
  mediaType?: string;
  sizeLabel?: string;
  description?: string;
  badge?: ReactNode;
  icon?: ReactNode;
  thumbnailUrl?: string;
  state?: "default" | "loading" | "uploading" | "error";
  progress?: number;
  disabled?: boolean;
  source?: TSource;
};
```

`source` is a passthrough for consumer data. Cubitt does not read it.
Use `size` when Cubitt should calculate multi-file details totals; use
`sizeLabel` when the consuming app already has a formatted display value.

Use `state: "loading"` for skeleton placeholders, `state: "uploading"` with
`progress` for upload display, and `state: "error"` for failed upload display.
These states are visual only; the consuming app still owns retry, cancel,
persistence, and upload queue state.

### Composition [#composition]

`Files.Item` renders one display-only `FilesItem`. Default grid and list
rendering is built from the same item slots consumers can use when composing
custom item content.

The same item context powers the slots used by grid rows, list rows, and
details surfaces.

| Slot                  | Default                                                                                                    |
| --------------------- | ---------------------------------------------------------------------------------------------------------- |
| `Files.ItemSelection` | Selection check indicator. Hidden when `selectionMode="none"` or `canSelect` returns `false` for the item. |
| `Files.ItemIcon`      | Folder icon, image icon, file icon, `item.icon`, or `item.thumbnailUrl`.                                   |
| `Files.ItemName`      | Truncated `item.name` with a title attribute, or inline rename textarea.                                   |
| `Files.ItemMeta`      | `item.description` and `item.sizeLabel`.                                                                   |
| `Files.ItemBadge`     | `item.badge` rendered as the compact icon indicator used in grid and list items.                           |
| `Files.ItemProgress`  | Upload/error status text for the item.                                                                     |

Use `badge` for small icon indicators, such as shared, reviewed, or
confidential states. If an app needs explanatory labels, keep that label in
app-owned data and compose a custom `Files.ItemBadge` slot with the right
accessible label or tooltip.

Use `Files.FolderIcon` or the named `FilesFolderIcon` export when surrounding
file UI, such as toolbar breadcrumbs, should use the same custom folder artwork
as `Files.Item` and `Files.Details`.

## API Reference [#api-reference]

### Files [#files]

Root provider for controlled selection and shared item interaction state.

| Prop                  | Type                                    | Default      | Description                                                                                                                                     |
| --------------------- | --------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`               | `FilesItem[]`                           | `[]`         | Optional item data map for root-level features such as `Files.Details` resolving selected IDs without relying on mounted item registration.     |
| `detailsItem`         | `FilesItem \| null`                     | `null`       | Item used by `Files.Details` when nothing is selected, usually the current directory.                                                           |
| `selectedIds`         | `string[]`                              | `[]`         | Controlled selected item ids.                                                                                                                   |
| `onSelectedIdsChange` | `(ids: string[]) => void`               | -            | Called when item interaction commits a selection change.                                                                                        |
| `selectionMode`       | `"none" \| "single" \| "multiple"`      | `"multiple"` | Selection mode for item interactions and selection affordances.                                                                                 |
| `selectionBehavior`   | `"replace" \| "toggle"`                 | `"replace"`  | Whether normal clicks replace selection or toggle items. Modifier and range selection are still handled by `Files.Item`.                        |
| `canSelect`           | `(item) => boolean`                     | -            | Optional per-item predicate for items that can enter `selectedIds`. Use it for navigation-only folders in single-file pickers.                  |
| `disabled`            | `boolean`                               | `false`      | Disables item interaction for the whole surface.                                                                                                |
| `onRename`            | `(item, name) => Promise<void> \| void` | -            | Enables inline item rename. Press Enter on a single selected item to edit its name; blur or Enter submits. The consuming app updates item data. |
| `canRename`           | `(item) => boolean`                     | -            | Optional per-item predicate for disabling inline rename while keeping other item interactions available.                                        |
| `onMove`              | `(event) => Promise<void> \| void`      | -            | Enables drag-and-drop moves. Receives the active item, all moved file/folder items, and the folder or collection destination.                   |
| `canMove`             | `(event) => boolean`                    | -            | Optional predicate for preventing invalid moves, such as moving a folder into itself or one of its descendants.                                 |
| `className`           | `string`                                | -            | Overrides or extends the root layout.                                                                                                           |
| `children`            | `ReactNode`                             | -            | Files collections, content, details, or custom composition.                                                                                     |

Also accepts standard `div` props.

### Files.Content [#filescontent]

| Prop                | Type             | Default | Description                                                                                             |
| ------------------- | ---------------- | ------- | ------------------------------------------------------------------------------------------------------- |
| `moveDestinationId` | `string \| null` | `null`  | Id emitted when items are dropped onto the content area rather than a folder, usually the directory id. |
| `className`         | `string`         | -       | Overrides or extends the flexible content wrapper.                                                      |
| `children`          | `ReactNode`      | -       | Files collection, empty state, or custom content.                                                       |

Also accepts standard `div` props.

### Files.Item [#filesitem-1]

`Files.Item` must be rendered inside `Files.Grid` or `Files.List` so it can
inherit the active layout.

| Prop        | Type                    | Default | Description                                                                                                       |
| ----------- | ----------------------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
| `item`      | `FilesItem<TSource>`    | -       | Required display data.                                                                                            |
| `onOpen`    | `(item, event) => void` | -       | Called on double click, or Enter when inline rename is unavailable. Use this for file open and folder navigation. |
| `disabled`  | `boolean`               | `false` | Disables the item in addition to root and item-level disabled state.                                              |
| `children`  | `ReactNode`             | -       | Custom item content composed from item slots or app-specific cells.                                               |
| `className` | `string`                | -       | Overrides or extends the item root styles.                                                                        |

Also accepts standard `button` props.

### Files.Item Slots [#filesitem-slots]

| Export                | Props                    | Description                                                                |
| --------------------- | ------------------------ | -------------------------------------------------------------------------- |
| `Files.ItemSelection` | `ComponentProps<"span">` | Selection check indicator. Hidden when selection is disabled for the item. |
| `Files.ItemIcon`      | `ComponentProps<"div">`  | Folder icon, image icon, file icon, `item.icon`, or `item.thumbnailUrl`.   |
| `Files.ItemName`      | `ComponentProps<"span">` | Truncated `item.name`, or inline rename textarea for the active item.      |
| `Files.ItemMeta`      | `ComponentProps<"span">` | `item.description` and `item.sizeLabel`.                                   |
| `Files.ItemBadge`     | `ComponentProps<"span">` | Compact badge indicator from `item.badge` or custom children.              |
| `Files.ItemProgress`  | `ComponentProps<"span">` | Upload/error status text for the item.                                     |

### Files.UploadButton [#filesuploadbutton]

Stateless file picker action. It does not create upload queues or persist files.

| Prop              | Type                                      | Default  | Description                                                           |
| ----------------- | ----------------------------------------- | -------- | --------------------------------------------------------------------- |
| `accept`          | `string \| readonly string[]`             | -        | Accepted MIME patterns or extensions.                                 |
| `directory`       | `boolean`                                 | `false`  | Enables folder selection where the browser supports directory inputs. |
| `maxFiles`        | `number`                                  | -        | Maximum accepted files before excess entries are rejected.            |
| `maxSizeMB`       | `number \| null`                          | `null`   | Maximum accepted file size in megabytes.                              |
| `multiple`        | `boolean`                                 | `true`   | Allows multiple file selection.                                       |
| `onFilesAccepted` | `(entries: FilesUploadEntry[]) => void`   | -        | Called with accepted file entries.                                    |
| `onFilesRejected` | `(entries: FilesRejectedEntry[]) => void` | -        | Called with rejected file entries.                                    |
| `children`        | `ReactNode`                               | `Upload` | Custom button content.                                                |
| `disabled`        | `boolean`                                 | `false`  | Disables the button and hidden file input.                            |

Also accepts `Button` props except `onChange` and `onClick`.

### Types And Hooks [#types-and-hooks]

| Export                | Purpose                                                                                    |
| --------------------- | ------------------------------------------------------------------------------------------ |
| `FilesItem`           | Display-only file/folder item data.                                                        |
| `FilesCanSelect`      | Per-item predicate used by `Files` to keep navigation-only items out of `selectedIds`.     |
| `FilesViewMode`       | `"grid" \| "list"`. Useful for consumer-owned view state.                                  |
| `FilesSelectionMode`  | `"none" \| "single" \| "multiple"`.                                                        |
| `FilesUploadEntry`    | Accepted file entry emitted by `Files.UploadButton`.                                       |
| `FilesRejectedEntry`  | Rejected file entry emitted by `Files.UploadButton`.                                       |
| `FilesFolderIcon`     | Custom folder SVG used by Files items/details and reusable in surrounding file UI.         |
| `useFilesContext`     | Optional custom-slot hook for root selection state and inline rename actions.              |
| `useFilesContextMenu` | Optional helper for consumer-owned context menus that need Files item-selection semantics. |
| `useFilesItemContext` | Optional custom-slot hook for the current item.                                            |

## Components [#components]

<Cards>
  <Card title="Grid View" href="./grid-view">
    Grid layout for consumer-owned item children.
  </Card>

  <Card title="List View" href="./list-view">
    Table-like rows for consumer-owned item children.
  </Card>

  <Card title="Details" href="./details">
    Presentational details surface with item-aware slots.
  </Card>
</Cards>

## Examples [#examples]

<Cards>
  <Card title="Details Panel" href="./details-panel">
    Build a file browser with a consumer-owned details sidebar, search, view
    switching, and inline rename.
  </Card>

  <Card title="Attachment Picker" href="./attachment-picker">
    Build a reusable file attachment picker from Dialog, Toolbar, Files, and
    primitive components.
  </Card>

  <Card title="Generate Transcript Picker" href="./generate-transcript-picker">
    Build a single-file transcript generation picker with provider selection and
    async confirmation.
  </Card>

  <Card title="Context Menu" href="./context-menu">
    Build a consumer-owned right-click menu with file, folder, and
    multi-selection rules.
  </Card>
</Cards>
