# ListDetail

> Master-detail: a list of items beside the detail of the selected one.

Source: https://ui.jcsoftdev.com/docs/components/list-detail

Master-detail: a list of items beside the detail of the selected one. Under 641px of its own
width it collapses to a single pane: the list until an item is chosen, then the detail with a
back action (focus follows the change). Panes render flush to the container's inline edges.

## Import

```ts
import { ListDetail } from "@jcsoftdev/ui/containers";
```

Single-component entry points tree-shake to just this component: `@jcsoftdev/ui/containers/list-detail`.

## Usage

```tsx
export const SideBySide: Story = {
  decorators: inWidth("48rem"),
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const list = canvas.getByRole("list", { name: "Students" });
    await expect(list).toBeVisible();
    await expect(canvas.getByText("Pick a student.")).toBeVisible();
    await expect(canvas.queryByRole("button", { name: "Back to list" })).toBeNull();
    await userEvent.click(canvas.getByRole("button", { name: /Luis/ }));
    await expect(canvas.getByRole("button", { name: /Luis/ })).toHaveAttribute(
      "aria-current",
      "true",
    );
    await expect(canvas.getByRole("region", { name: "Luis Ramírez" })).toBeVisible();
    // Beside the list, the list stays visible and keeps focus.
    await expect(list).toBeVisible();
    await expect(canvas.getByRole("button", { name: /Luis/ })).toHaveFocus();
    await expect(
      getComputedStyle(list.closest("[data-view]") as Element).gridTemplateColumns.split(" "),
    ).toHaveLength(2);
  },
};
```

_Source of the Storybook story._

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string \| null` |  | Controlled selected item id; `null` means none (the single-pane view then shows the list). |
| `defaultValue` | `string \| null` | `null` |  |
| `onValueChange` | `((value: string) => void)` |  | Called with the id the user chose. It is never called with `null`: going back to the list calls `onBack` instead, so a handler that loads the selected record never sees "no record". |
| `onBack` | `(() => void)` |  | Called when the back action (or `select(null)` from `useListDetail`) clears the selection. Uncontrolled, the list shows again on its own; controlled, set `value` to `null` here. |
| `aria-label` * | `string` |  | Names the list of items. |
| `listWidth` | `string \| number` |  | Fixed width of the list column beside the detail, from 641px of the component's own width: a number is pixels, a string any CSS length (`"20rem"`). A rail of 280-360px reads better than an even split for most catalogues. Wins over `ratio`. |
| `ratio` | `"1:1" \| "1:2" \| "1:3"` | `"1:1"` | Proportional split when `listWidth` is not set. Defaults to `"1:1"` (the original even split). |
| `surface` | `"card" \| "flat"` | `"flat"` | `"flat"` (default) keeps the panes flush with a divider between them. `"card"` draws each pane as a raised card with a gap between them. |
| `stickyList` | `boolean` | `false` | Keeps the list column in view while the page scrolls and gives it its own scroll, from 641px of the component's own width. The list header stays at the top of the column. |
| `stickyOffset` | `string \| number` |  | Distance the sticky list keeps from the top of its scroll container (number = pixels). Default `0`. |
| `children` * | `ReactNode` |  |  |

`*` marks a required prop.

It also accepts the native props of the element or primitive it renders (for example `className`, `id`, `aria-*` and event handlers).

## Storybook stories

- [Side By Side](/storybook/?path=/story/containers-listdetail--side-by-side)
- [Collapses To Single Pane](/storybook/?path=/story/containers-listdetail--collapses-to-single-pane)
- [Controlled](/storybook/?path=/story/containers-listdetail--controlled)
- [Reports Selection](/storybook/?path=/story/containers-listdetail--reports-selection)
- [Spanish Back](/storybook/?path=/story/containers-listdetail--spanish-back)
- [Back Calls On Back](/storybook/?path=/story/containers-listdetail--back-calls-on-back)
- [List Rail](/storybook/?path=/story/containers-listdetail--list-rail)
- [Narrow Ratio](/storybook/?path=/story/containers-listdetail--narrow-ratio)
- [Card With Header](/storybook/?path=/story/containers-listdetail--card-with-header)

## Source

`packages/ui/src/containers/list-detail/list-detail.tsx`
