import { Canvas, Controls, Meta } from "@storybook/addon-docs/blocks";

import * as CheckboxStories from "./Checkbox.stories";

<Meta of={CheckboxStories} />

# Checkbox

A single boolean, on or off, that owns nothing but itself.

This is the primitive mux-magic's `BooleanField` hand-rolled because the library had no
checkbox to reach for. Its box was `bg-slate-700 border-slate-500 accent-blue-500` — three
palette colours with no light mode, the exact M6 defect the token layer exists to delete.
Here the box is `bg-surface-sunken`, its border `border-border-strong`, and the fill
`bg-intent-accent-solid`, so one control reads correctly in `daylight`, `midnight`, and on
the kiosk with no per-app override. `BooleanField` becomes a thin wrapper that binds this
to a `CommandField`.

<Canvas of={CheckboxStories.Playground} />

## Props

<Controls />

## The `<label>` wraps the control

A checkbox reads to the **left** of its text, which is why it is not a `Field`. `Field`
stacks label above control in a `flex-col` — right for a text input, wrong for a boolean —
and stacking every checkbox in a step card (`isRecursive`, `isDryRun`, …) would double the
card's height for no gain.

Wrapping is also the one label association that needs no `for` at all: the control is a
descendant of its `<label>`, so there is no id to mint, clone, and get wrong. That is the
whole defect [`Field`](/docs/components-field--docs) exists to prevent, avoided here by
construction rather than by wiring.

<Canvas of={CheckboxStories.AllVariants} />

## The `<input>` is the store

`defaultChecked`, not `checked`. The DOM owns a checkbox's state natively, so there is no
second copy to keep in step and no controlled round-trip — reading the value back is the
`<input>`'s job, and `onChange` reports each change to a consumer that wants to persist it.
`isChecked` seeds the first paint and nothing after.

This is the same refusal `SegmentedControl` makes about `selectedValue`: a controlled prop
is the thing this library does not have, because a control that cannot hold its own state
is a control every consumer has to hold it for.

<Canvas of={CheckboxStories.AllStates} />

## Three ways a box can be inactive

`isDisabled` and `isReadOnly` are different states, and the box shows all three:

- **Enabled** — the accent intent, a crisp `border-strong` edge.
- **Disabled** — the same box turned down with `opacity-60`. It dims rather than fading a
  token because the scale has no step between `border-default` and `border-strong`, so a
  muted outline was either invisible or indistinguishable from enabled.
- **Read-only** — full contrast, because the whole point is that the value stays legible,
  but in the **neutral** intent with a softer edge: an informational value you may read
  here and change elsewhere, not an actionable accent. It announces `aria-readonly`, and
  the toggle is blocked on both pointer and `Space`.

## A group of boxes needs `value`

`value` is the `<input>`'s `value` attribute: **which member of a group** this box is, as
opposed to whether it is ticked. A lone boolean should omit it.

A group needs it, because a group is read back with one query over its container rather
than by keeping a callback's worth of state per box:

```ts
const chosen = [...group.querySelectorAll<HTMLInputElement>("input[type=checkbox]")]
  .filter((input) => input.checked)
  .map((input) => input.value)
```

Without a `value` every box in the group answers the UA default `"on"`, so that read
returns N copies of one meaningless string. This is why queuepilot's library and ratings
pickers were still hand-rolling a raw `<input type="checkbox">` instead of using this
component — the component was correct and simply could not express the case.

It is passed straight through and is otherwise inert: it is **not** the checked state and
does not become one. `isChecked` still seeds the box, `onChange` still reports a boolean.

<Canvas of={CheckboxStories.WithValues} />

## A hint follows the box

`description` is standing help bound with `aria-describedby`, so a screen reader reads it
**with** the control rather than announcing it from nowhere — the same slot, the same words
and the same announcement order as [`Field`](/docs/components-field--docs)'s.

`Field` has had one since it shipped and this component had none, which is a gap you only
feel in a mixed box: a hint after a checkbox had to be the app's own paragraph while the
hint after the `Field` two rows down came from the library, and the two disagreed about how
big a hint is. One control's help looking unlike its neighbour's, in the same editor.

The hint renders **outside** the `<label>`, which is the only place it can go. A `<label>`'s
text content *is* the control's accessible name, so a hint inside it would be announced
twice — once inside the name, once again as the description — and a pointer press on a
sentence of explanation would toggle the box. So the `<label>` is no longer the outermost
element this component renders: a `<div>` holds the row and the hint, and `className` moves
out to it, because `className` is the outermost box a component renders and that rule does
not bend for this one.

Two smaller decisions inside it:

- **`opacity-60` stays on the `<label>`** and does not reach the hint. axe exempts a
  disabled control and its own label from the colour-contrast rule and exempts nothing else,
  so dimming the hint with the box measures **3.77:1** on `midnight` against a 4.5
  threshold. A hint nobody can read is worse than a hint that does not dim, and a turned-off
  box is exactly when that sentence gets read.
- **There is no `error` to go with it.** `Field` derives `aria-invalid` from its error, and a
  lone boolean is almost never the invalid thing — a *group* of them is, and a group's error
  belongs on the `FieldGroup` around it, described rather than asserted.

<Canvas of={CheckboxStories.WithDescriptions} />

## Checkbox or `Switch`

Same state kind, different meaning. A checkbox is a value you **submit** — "delete
originals", "I agree" — and a [`Switch`](/docs/components-switch--docs) is a setting that
**takes effect the moment it is flipped**. A screen reader announces
"checked/unchecked" for one and "on/off" for the other, which is the line to hold when
choosing between them.
