# alouette 24

Breaking changes of 24.0.0, then what changed within 24.x without breaking.

## To 24.0.0

### FormItem: `render` takes an object, and the input shows the error

`FormItem`'s `render` prop received the label id as a string. It now receives
`FormItemRenderParams`, `{ labelId, describedBy, invalid, required }`, so the
input can carry the state the label and the message around it already show.
`FormField`'s `render` params gain the same fields beside `field` and `labelId`.

Wire all four onto the input. `InputText`, `TextArea`, `Select` and
`InputTextAutocomplete` take `invalid` (the danger border, through the
`interactive-outlined-*` tokens, plus `aria-invalid`), `aria-describedby` and
`aria-required`:

```tsx
// Before
<FormItem
  label="Email"
  render={(labelId) => <InputText aria-labelledby={labelId} />}
/>

// After
<FormItem
  label="Email"
  render={({ labelId, describedBy, invalid, required }) => (
    <InputText
      aria-labelledby={labelId}
      aria-describedby={describedBy}
      aria-required={required}
      invalid={invalid}
    />
  )}
/>
```

A `FormField` input adds the same three props next to `ref={field.ref}` and
`aria-labelledby={labelId}`. `describedBy` holds the ids of `details` and
`error`, whichever are rendered, and is `undefined` without either. The three
ARIA attributes are web only in effect: React Native has no equivalent and
ignores them.

### alouette-icons: one module per glyph

`alouette-icons` 12.0.0 has one module per Phosphor glyph instead of one per
weight. Each glyph module exports the regular, duotone and fill weights. The
export names are unchanged; only the import path changes. Upgrade `alouette-icons`
together with `alouette`.

```tsx
// Before
import { HouseDuotoneIcon } from "alouette-icons/phosphor-icons/HouseDuotoneIcon";
import { HouseRegularIcon } from "alouette-icons/phosphor-icons/HouseRegularIcon";

// After
import {
  HouseDuotoneIcon,
  HouseRegularIcon,
} from "alouette-icons/phosphor-icons/House";
```

Rewrite the paths from the app root:

```sh
perl -pi -e 's#(alouette-icons/phosphor-icons/)(\w+?)(?:Regular|Duotone|Fill)Icon"#$1$2"#g' $(git grep -l alouette-icons/phosphor-icons)
```

This leaves one import per weight from the same module. Merge them with
`eslint --fix` and the `import-x/no-duplicates` rule (or `import/no-duplicates`),
then run your formatter.

A bundler that tree-shakes keeps only the weights you import. Metro keeps the
whole glyph module, which is three small components.

The fill weight (`HouseFillIcon`), previously generated for `Star` only, now
exists for every glyph.

## Within 24.x — recommended: DecisionBar for action rows

Not breaking: `Modal`'s `footer` still takes any content. A row of buttons
ending a decision — a `Modal` footer, an inline form's Cancel and Save — moves
to `DecisionBar`, which owns its alignment and order (dismiss, `alternative`,
`primary`) instead of each call site picking its own. The dismiss action is
rendered by the bar, as the neutral `soft` button.

```tsx
// Before
<View className="flex-row gap-xs">
  <ActionButton text="Save" onPress={save} errorToMessage={toMessage} />
  <Button accent="neutral" variant="soft" text="Cancel" onPress={close} />
</View>

<Modal
  footer={
    <>
      <Button accent="neutral" variant="soft" text="Discard" onPress={discard} />
      <Button text="Save" onPress={save} />
    </>
  }
/>

// After
<DecisionBar
  dismissText="Cancel"
  onDismiss={close}
  primary={<ActionButton text="Save" onPress={save} errorToMessage={toMessage} />}
/>

<Modal
  footer={
    <DecisionBar
      dismissText="Discard"
      onDismiss={discard}
      primary={<Button text="Save" onPress={save} />}
    />
  }
/>
```

A second way to commit ("Save draft" beside "Publish") is `alternative`; a
Cancel carrying the weight (neutral `filled`, beside a `soft` primary) is
`emphasis="dismiss"`.
