# Accordion gallery

Panels side by side, one open at a time: a gallery of projects, plans or places, each a picture with a title. The panel under the pointer, the one with focus, or else the open one widens; the others narrow to their titles, which stay readable down their sides. Enter, Space or a click opens a panel (its button says whether it is open), and each panel's button is one stop for the keyboard. While focus is in the open panel's content, that panel stays wide whatever the pointer is over; narrow, its content is out of sight but still in the tab order, and reaching it widens the panel. Below 40rem of its own width the gallery is a column, every title across its panel. The first panel is open at first, unless `defaultValue` names another.

- Registry name: `accordion-gallery` (`@candor/accordion-gallery`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/accordion-gallery

## Install

```bash
npx candor-ui add accordion-gallery
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/accordion-gallery`.
It brings in: bidi, motion, tokens, utils.

## Example

```tsx
"use client"

import { AccordionGallery } from "@/components/candor/ui/accordion-gallery"

const offices = [
  { id: "north", title: "North office", content: <p>12 desks and 3 meeting rooms. Open 8 to 6.</p> },
  { id: "harbour", title: "Harbour office", content: <p>20 desks and 5 meeting rooms, with a quiet floor.</p> },
  { id: "east", title: "East office", content: <p>8 desks and 1 meeting room. Booked through the front desk.</p> },
  { id: "remote", title: "Remote", content: <p>A monthly allowance for a desk near home.</p> },
]

// The panel under the pointer or with focus widens; Enter, Space or a click opens it. Below 40rem of its own width the
// gallery is a column.
export default function Example() {
  return <AccordionGallery label="Offices" items={offices} className="w-full" />
}
```

## <AccordionGallery>

Props:

- `items: AccordionGalleryItem[]`: The panels, in order.
- `label: string`: What the gallery is: the name of its group, "Projects".
- `value?: string | undefined`: The open panel's id, when you control it.
- `defaultValue?: string | undefined`: The open panel's id at first, when you don't control it. Defaults to the first panel's.
- `onValueChange?: ((id: string) => void) | undefined`: Called with the id of the panel the person opened.
- `className?: string | undefined`: Classes for the root, merged with Candor's.

## Types

### AccordionGalleryItem

One panel of an AccordionGallery.

```ts
{
  /** Stable while the panel is in the gallery. */
  id: string;
  /** The panel's name, always shown: on its button, and down its side while it is narrow. Drawn as text. */
  title: string;
  /** A picture behind the panel, decorative (its alt is empty): put what it shows in `title` or `content`. */
  image?: string | undefined;
  /** What the panel shows when it is open. */
  content: React.ReactNode;
}
```

## Motion

- **widen** (reveal, size): The panel the pointer is over, the one with focus, or else the one the person opened widens to show its picture and title (and its content, when it is the open one) while the others narrow to their titles. Under reduced motion or the calm level the widths snap. A gallery drawn with a panel open is drawn still.

Set with `--cd-motion-accordion-gallery-<moment>-*` in your brand file; the families are in the design reference.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
