import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks'
import * as SelectStories from './Select.stories'

<Meta of={SelectStories} />

# Select

One value for a form field: a button that shows the choice and opens a
portalled listbox under itself. The list keeps itself on screen — clamped
to the viewport's sides, height capped to the room it actually has,
opening upward when the room below is worse — so it works in cramped
corners and scrolling panels.

<Canvas of={SelectStories.Default} />

## When

- A field with one answer out of a known set: a status, a stage, a person.
- Options may carry a `leading` node — a face beside a name — shown in the
  trigger and the list alike.
- Two sizes; `small` for dense rows and rails (it sits well inside
  **Property**).

<Canvas of={SelectStories.WithLeading} />

## When not

- A list of actions → **Menu** — a Select changes a value, it does not do
  things.
- Several values → **ChipInput**.
- Switching views of one thing → **Tabs**.
- `disabled` explains nothing by itself — give it `disabledReason`, which says
  why and keeps it reachable by Tab. Busy for a moment while an action runs
  needs no reason.
- Ticking several on and off in a list → **Checkbox** with `row`.

## How

```tsx
import { Select } from '@estiva-app/ui'

<Select
  value={status}
  onChange={setStatus}
  options={[
    { value: 'open', label: 'Open' },
    { value: 'done', label: 'Done' },
  ]}
  ariaLabel="Status"
/>
```

- The trigger truncates long labels instead of stretching its row — give
  it room with your layout, not with `white-space` tricks.
- **The list hangs under the trigger**, 4px below it, and flips above when
  the room below is worse. Base UI would rather lay it *over* the trigger
  with the chosen option covering the trigger's own text, the way macOS
  does; that is off, so every Select opens below its trigger.
- It caps at 288px, or at the room the screen actually has, whichever is
  smaller — and scrolls inside that. Measured in a bottom-right corner:
  the list flips above the trigger, slides left to stay on screen, and
  stands at its full 288.
- **`disabledReason="Read only"`** holds it shut and keeps it reachable by Tab,
  showing the reason on hover and on keyboard focus — as `Button` does. Plain
  `disabled` is a native disabled control: Tab skips it, so it cannot say why.
  Given both, the reason wins, as on `Button`.
- **The trigger is a `combobox`, not a `button`.** That is the ARIA pattern
  for this control and Base UI's doing; a test looking for it by role has
  to ask for `combobox`.

## What it owns

It does these by itself. Write none of them around it.

| It does | So you never write | Refused when written by hand |
|---|---|---|
| Floats on top of the page | a portal, or a `z-index` to win | ✓ |
| Stays attached to its anchor on scroll and resize, and hides when its trigger scrolls out of sight | a scroll or resize listener, or placement arithmetic | ✓ |
| Closes on a press outside | a click listener on the page | ✓ |
| Takes its keys by itself: Enter and Space pick, Escape and Tab close | a key listener on the page | ✓ |
| Moves through its items with the arrow keys, and jumps to an option by its first letters | an arrow-key handler | ✓ |
| Says what it is to assistive technology: a list of options | `role="listbox"` or `role="option"` | ✓ |
| Keeps the highlighted option in view, and gives focus back to the trigger | scroll or focus code |  |
| Keeps its height in a scrolling column | `shrink-0` |  |

## Keys

| Input | What happens |
|---|---|
| Enter / Space / click, closed | opens the list |
| ↑ / ↓ | move the highlight, kept scrolled into view |
| Home / End | jump to the first / last option |
| **typing a label's first letters** | **jumps to that option** — new, and nothing here implements it |
| Enter / Space, open | picks the highlighted option |
| Escape | closes, returns focus to the trigger |
| Tab | closes |
| click outside | closes |
| page scroll or resize | **the list follows its trigger**, and disappears if the trigger scrolls out of sight. It used to close, because a list fixed to where the trigger *had been* had no other way not to be left behind |

## Props

<Controls of={SelectStories.Default} />
