@charcuterie/ui 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/Checkbox/Checkbox.d.ts +59 -0
  2. package/dist/Checkbox/Checkbox.d.ts.map +1 -0
  3. package/dist/Checkbox/Checkbox.js +61 -0
  4. package/dist/Checkbox/Checkbox.js.map +1 -0
  5. package/dist/Field/Field.d.ts.map +1 -1
  6. package/dist/Field/Field.js +12 -1
  7. package/dist/Field/Field.js.map +1 -1
  8. package/dist/Menu/Menu.d.ts +60 -2
  9. package/dist/Menu/Menu.d.ts.map +1 -1
  10. package/dist/Menu/Menu.js +58 -11
  11. package/dist/Menu/Menu.js.map +1 -1
  12. package/dist/RadioGroup/RadioGroup.d.ts +47 -0
  13. package/dist/RadioGroup/RadioGroup.d.ts.map +1 -0
  14. package/dist/RadioGroup/RadioGroup.js +114 -0
  15. package/dist/RadioGroup/RadioGroup.js.map +1 -0
  16. package/dist/RadioGroup/RadioGroupOption.d.ts +26 -0
  17. package/dist/RadioGroup/RadioGroupOption.d.ts.map +1 -0
  18. package/dist/RadioGroup/RadioGroupOption.js +52 -0
  19. package/dist/RadioGroup/RadioGroupOption.js.map +1 -0
  20. package/dist/Switch/Switch.d.ts +54 -0
  21. package/dist/Switch/Switch.d.ts.map +1 -0
  22. package/dist/Switch/Switch.js +94 -0
  23. package/dist/Switch/Switch.js.map +1 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/slotWiring.d.ts +9 -7
  29. package/dist/slotWiring.d.ts.map +1 -1
  30. package/dist/slotWiring.js +10 -70
  31. package/dist/slotWiring.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/Checkbox/Checkbox.mdx +58 -0
  34. package/src/Checkbox/Checkbox.stories.tsx +100 -0
  35. package/src/Checkbox/Checkbox.test.tsx +80 -0
  36. package/src/Checkbox/Checkbox.tsx +146 -0
  37. package/src/Combobox/Combobox.stories.tsx +19 -4
  38. package/src/Field/Field.stories.tsx +19 -0
  39. package/src/Field/Field.test.tsx +30 -2
  40. package/src/Field/Field.tsx +13 -1
  41. package/src/Menu/Menu.stories.tsx +80 -2
  42. package/src/Menu/Menu.test.tsx +102 -0
  43. package/src/Menu/Menu.tsx +156 -17
  44. package/src/RadioGroup/RadioGroup.mdx +58 -0
  45. package/src/RadioGroup/RadioGroup.stories.tsx +130 -0
  46. package/src/RadioGroup/RadioGroup.test.tsx +191 -0
  47. package/src/RadioGroup/RadioGroup.tsx +204 -0
  48. package/src/RadioGroup/RadioGroupOption.tsx +126 -0
  49. package/src/Switch/Switch.mdx +53 -0
  50. package/src/Switch/Switch.stories.tsx +94 -0
  51. package/src/Switch/Switch.test.tsx +86 -0
  52. package/src/Switch/Switch.tsx +162 -0
  53. package/src/index.ts +9 -0
  54. package/src/slotWiring.ts +15 -107
  55. package/src/sourceRules.test.ts +11 -1
  56. package/src/tailwindCandidates.test.ts +9 -7
@@ -0,0 +1,59 @@
1
+ import type { ControlSize } from "@charcuterie/tokens";
2
+ import type { ReactNode } from "react";
3
+ export type CheckboxProps = {
4
+ className?: string;
5
+ /**
6
+ * The caller's own id, when it has one — a stable target for an
7
+ * autofill hint or a deep link. Left to React's `useId` on the
8
+ * `<input>` otherwise, which is enough for the wrapping `<label>`.
9
+ */
10
+ id?: string;
11
+ /**
12
+ * **Initial** only. The `<input>` is the store from then on — the
13
+ * DOM owns a checkbox's checkedness, and a controlled `checked`
14
+ * prop is the thing this library refuses to have
15
+ * (`SegmentedControl` says the same about `selectedValue`).
16
+ */
17
+ isChecked?: boolean;
18
+ isDisabled?: boolean;
19
+ /**
20
+ * The visible text beside the box, and the checkbox's accessible
21
+ * name — the `<label>` wraps the control, so there is no `for` to
22
+ * get wrong. A box with no label is a control a screen reader
23
+ * cannot announce, which is why this is required.
24
+ */
25
+ label: ReactNode;
26
+ onChange?: (isChecked: boolean) => void;
27
+ size?: ControlSize;
28
+ };
29
+ /**
30
+ * A single boolean, on or off, that owns nothing but itself.
31
+ *
32
+ * This is the primitive mux-magic's `BooleanField` hand-rolled in
33
+ * `bg-slate-700 accent-blue-500` because the library had no checkbox
34
+ * to reach for — palette colours with no light mode, the exact M6
35
+ * defect the token layer exists to delete. Here the box is
36
+ * `bg-surface-sunken` and the fill is `bg-intent-accent-solid`, so
37
+ * one control reads correctly in `daylight`, `midnight`, and on the
38
+ * kiosk with no per-app override.
39
+ *
40
+ * ### The `<label>` wraps the control
41
+ *
42
+ * A checkbox reads to the *left* of its text, which is why it is not
43
+ * a `Field` — `Field` stacks label above control in a `flex-col`,
44
+ * right for a text input and wrong for a boolean, and stacking every
45
+ * checkbox in a step card would double the card's height. Wrapping is
46
+ * also the one label association that needs no `for` at all, so there
47
+ * is no minted-then-dropped id to get wrong.
48
+ *
49
+ * ### The `<input>` is the store
50
+ *
51
+ * `defaultChecked`, not `checked`. The DOM owns a checkbox's state
52
+ * natively, so there is no second copy to keep in step and no
53
+ * controlled-prop round-trip — `onChange` reports the new state to a
54
+ * consumer that wants to persist it, and reading it back is the
55
+ * `<input>`'s job. `isChecked` seeds the first paint and nothing
56
+ * after.
57
+ */
58
+ export declare const Checkbox: ({ className, id, isChecked, isDisabled, label, onChange, size, }: CheckboxProps) => ReactNode;
59
+ //# sourceMappingURL=Checkbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../src/Checkbox/Checkbox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAe,SAAS,EAAE,MAAM,OAAO,CAAA;AAKnD,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;IACX;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IACvC,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,QAAQ,GAAI,kEAQtB,aAAa,KAAG,SAyDlB,CAAA"}
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FOCUS_RING_CLASS } from "../intentStyles.js";
3
+ import { toClassName } from "../toClassName.js";
4
+ const BOX_SIZE_CLASS = {
5
+ sm: "size-3.5",
6
+ md: "size-4",
7
+ lg: "size-5",
8
+ };
9
+ const CHECK_SIZE_CLASS = {
10
+ sm: "size-2.5",
11
+ md: "size-3",
12
+ lg: "size-3.5",
13
+ };
14
+ const TEXT_SIZE_CLASS = {
15
+ sm: "text-sm",
16
+ md: "text-md",
17
+ lg: "text-lg",
18
+ };
19
+ /**
20
+ * A single boolean, on or off, that owns nothing but itself.
21
+ *
22
+ * This is the primitive mux-magic's `BooleanField` hand-rolled in
23
+ * `bg-slate-700 accent-blue-500` because the library had no checkbox
24
+ * to reach for — palette colours with no light mode, the exact M6
25
+ * defect the token layer exists to delete. Here the box is
26
+ * `bg-surface-sunken` and the fill is `bg-intent-accent-solid`, so
27
+ * one control reads correctly in `daylight`, `midnight`, and on the
28
+ * kiosk with no per-app override.
29
+ *
30
+ * ### The `<label>` wraps the control
31
+ *
32
+ * A checkbox reads to the *left* of its text, which is why it is not
33
+ * a `Field` — `Field` stacks label above control in a `flex-col`,
34
+ * right for a text input and wrong for a boolean, and stacking every
35
+ * checkbox in a step card would double the card's height. Wrapping is
36
+ * also the one label association that needs no `for` at all, so there
37
+ * is no minted-then-dropped id to get wrong.
38
+ *
39
+ * ### The `<input>` is the store
40
+ *
41
+ * `defaultChecked`, not `checked`. The DOM owns a checkbox's state
42
+ * natively, so there is no second copy to keep in step and no
43
+ * controlled-prop round-trip — `onChange` reports the new state to a
44
+ * consumer that wants to persist it, and reading it back is the
45
+ * `<input>`'s job. `isChecked` seeds the first paint and nothing
46
+ * after.
47
+ */
48
+ export const Checkbox = ({ className, id, isChecked = false, isDisabled = false, label, onChange, size = "md", }) => (_jsxs("label", { className: toClassName("inline-flex cursor-pointer items-center gap-2 text-content-secondary select-none", TEXT_SIZE_CLASS[size], isDisabled &&
49
+ "cursor-not-allowed text-content-disabled", className), children: [_jsxs("span", { className: "relative grid shrink-0 place-items-center", children: [_jsx("input", { className: toClassName("peer cursor-pointer appearance-none rounded-sm border border-border-strong bg-surface-sunken transition-colors duration-(--duration-fast) ease-standard", "checked:border-intent-accent-solid checked:bg-intent-accent-solid",
50
+ // Disabled mutes the border and the cursor, and — unlike the
51
+ // shared `DISABLED_CLASS` — does *not* touch the background.
52
+ // The generic bundle repaints the box `surface-sunken`, which
53
+ // erased both the checked accent fill (white tick on grey)
54
+ // and, in a light scheme, the unchecked box entirely: a
55
+ // `border-subtle` outline on `surface-sunken` on
56
+ // `surface-base` is three near-identical creams. A disabled
57
+ // box is still a box the user has to see.
58
+ "disabled:cursor-not-allowed disabled:border-border-default", BOX_SIZE_CLASS[size], FOCUS_RING_CLASS), defaultChecked: isChecked, disabled: isDisabled, id: id, onChange: (event) => {
59
+ onChange?.(event.target.checked);
60
+ }, type: "checkbox" }), _jsx("svg", { "aria-hidden": "true", className: toClassName("pointer-events-none invisible absolute text-intent-accent-on-solid peer-checked:visible", CHECK_SIZE_CLASS[size]), fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 3, viewBox: "0 0 24 24", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) })] }), _jsx("span", { children: label })] }));
61
+ //# sourceMappingURL=Checkbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../src/Checkbox/Checkbox.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA6B/C,MAAM,cAAc,GAAgC;IAClD,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,QAAQ;CACb,CAAA;AAED,MAAM,gBAAgB,GAAgC;IACpD,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,UAAU;CACf,CAAA;AAED,MAAM,eAAe,GAAgC;IACnD,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;CACd,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EACvB,SAAS,EACT,EAAE,EACF,SAAS,GAAG,KAAK,EACjB,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,QAAQ,EACR,IAAI,GAAG,IAAI,GACG,EAAa,EAAE,CAAC,CAC9B,iBACE,SAAS,EAAE,WAAW,CACpB,kFAAkF,EAClF,eAAe,CAAC,IAAI,CAAC,EACrB,UAAU;QACR,0CAA0C,EAC5C,SAAS,CACV,aAED,gBAAM,SAAS,EAAC,2CAA2C,aACzD,gBACE,SAAS,EAAE,WAAW,CACpB,yJAAyJ,EACzJ,mEAAmE;oBACnE,6DAA6D;oBAC7D,6DAA6D;oBAC7D,8DAA8D;oBAC9D,2DAA2D;oBAC3D,wDAAwD;oBACxD,iDAAiD;oBACjD,4DAA4D;oBAC5D,0CAA0C;oBAC1C,4DAA4D,EAC5D,cAAc,CAAC,IAAI,CAAC,EACpB,gBAAgB,CACjB,EACD,cAAc,EAAE,SAAS,EACzB,QAAQ,EAAE,UAAU,EACpB,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,CACR,KAAoC,EACpC,EAAE;wBACF,QAAQ,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;oBAClC,CAAC,EACD,IAAI,EAAC,UAAU,GACf,EAEF,6BACc,MAAM,EAClB,SAAS,EAAE,WAAW,CACpB,yFAAyF,EACzF,gBAAgB,CAAC,IAAI,CAAC,CACvB,EACD,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,WAAW,EAAE,CAAC,EACd,OAAO,EAAC,WAAW,YAEnB,eAAM,CAAC,EAAC,iBAAiB,GAAG,GACxB,IACD,EAEP,yBAAO,KAAK,GAAQ,IACd,CACT,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"Field.d.ts","sourceRoot":"","sources":["../../src/Field/Field.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAIhD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG;IACnC;;;;;OAKG;IACH,QAAQ,EAAE,YAAY,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,eAAO,MAAM,KAAK,GAAI,sFAQnB,UAAU,KAAG,SA4Ff,CAAA"}
1
+ {"version":3,"file":"Field.d.ts","sourceRoot":"","sources":["../../src/Field/Field.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAIhD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG;IACnC;;;;;OAKG;IACH,QAAQ,EAAE,YAAY,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,eAAO,MAAM,KAAK,GAAI,sFAQnB,UAAU,KAAG,SAwGf,CAAA"}
@@ -86,8 +86,19 @@ export const Field = ({ children, className, description, error, isRequired = fa
86
86
  * link, a server-rendered error summary linking to the control.
87
87
  * `useUniqueId` is still called: hooks are unconditional, and it
88
88
  * costs one counter read.
89
+ *
90
+ * Precedence is `Field` prop → **the child's own `id`** →
91
+ * generated. The child's is read because a control written as
92
+ * `<input id="rename-pattern" />` puts the id where the author can
93
+ * see it, not on the `Field`; overwriting it with a minted
94
+ * `-control` id used to break exactly the outside-in references the
95
+ * `Field` prop exists to keep — a deep link, an autofill hint, a
96
+ * consumer's own selector. The `Field` prop still wins when both
97
+ * are set: it is the outer, later declaration, and last-writer-wins
98
+ * is the slot's rule for a value.
89
99
  */
90
- const controlId = receivedSlotProps.id ?? `${baseId}-control`;
100
+ const childId = children.props.id;
101
+ const controlId = receivedSlotProps.id ?? childId ?? `${baseId}-control`;
91
102
  const descriptionId = `${baseId}-description`;
92
103
  const errorId = `${baseId}-error`;
93
104
  const describedBy = [
@@ -1 +1 @@
1
- {"version":3,"file":"Field.js","sourceRoot":"","sources":["../../src/Field/Field.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA2B/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,QAAQ,EACR,SAAS,EACT,WAAW,EACX,KAAK,EACL,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,GAAG,iBAAiB,EACT,EAAa,EAAE;IAC1B,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAE5B;;;;;;OAMG;IACH,MAAM,SAAS,GACb,iBAAiB,CAAC,EAAE,IAAI,GAAG,MAAM,UAAU,CAAA;IAE7C,MAAM,aAAa,GAAG,GAAG,MAAM,cAAc,CAAA;IAE7C,MAAM,OAAO,GAAG,GAAG,MAAM,QAAQ,CAAA;IAEjC,MAAM,WAAW,GAAG;QAClB,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa;QAChD,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;KACrC;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAA;IAEZ,MAAM,aAAa,GAAG,cAAc,CAClC,QAAQ,EACR,cAAc,CAAC,iBAAiB,EAAE;QAChC,kBAAkB,EAChB,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW;QAC9C,6DAA6D;QAC7D,+DAA+D;QAC/D,iDAAiD;QACjD,cAAc,EACZ,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;QACxC,sDAAsD;QACtD,8DAA8D;QAC9D,8DAA8D;QAC9D,gDAAgD;QAChD,eAAe,EAAE,UAAU,IAAI,SAAS;QACxC,EAAE,EAAE,SAAS;QACb,QAAQ,EAAE,UAAU,IAAI,SAAS;KAClC,CAAC,CACH,CAAA;IAED,OAAO,CACL,eACE,SAAS,EAAE,WAAW,CACpB,uBAAuB,EACvB,SAAS,CACV,aAED,iBACE,SAAS,EAAC,0CAA0C,EACpD,OAAO,EAAE,SAAS,aAEjB,KAAK,EAEL,UAAU,CAAC,CAAC,CAAC,CACZ,8BAKc,MAAM,EAClB,SAAS,EAAC,iCAAiC,kBAGtC,CACR,CAAC,CAAC,CAAC,IAAI,IACF,EAEP,aAAa,EAEb,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAClC,YACE,SAAS,EAAC,gCAAgC,EAC1C,EAAE,EAAE,aAAa,YAEhB,WAAW,GACV,CACL,EAEA,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC5B,YACE,SAAS,EAAC,oCAAoC,EAC9C,EAAE,EAAE,OAAO,YAEV,KAAK,GACJ,CACL,IACG,CACP,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Field.js","sourceRoot":"","sources":["../../src/Field/Field.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA2B/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,QAAQ,EACR,SAAS,EACT,WAAW,EACX,KAAK,EACL,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,GAAG,iBAAiB,EACT,EAAa,EAAE;IAC1B,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAE5B;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,OAAO,GAAI,QAAQ,CAAC,KAAyB,CAAC,EAAE,CAAA;IAEtD,MAAM,SAAS,GACb,iBAAiB,CAAC,EAAE,IAAI,OAAO,IAAI,GAAG,MAAM,UAAU,CAAA;IAExD,MAAM,aAAa,GAAG,GAAG,MAAM,cAAc,CAAA;IAE7C,MAAM,OAAO,GAAG,GAAG,MAAM,QAAQ,CAAA;IAEjC,MAAM,WAAW,GAAG;QAClB,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa;QAChD,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;KACrC;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAA;IAEZ,MAAM,aAAa,GAAG,cAAc,CAClC,QAAQ,EACR,cAAc,CAAC,iBAAiB,EAAE;QAChC,kBAAkB,EAChB,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW;QAC9C,6DAA6D;QAC7D,+DAA+D;QAC/D,iDAAiD;QACjD,cAAc,EACZ,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;QACxC,sDAAsD;QACtD,8DAA8D;QAC9D,8DAA8D;QAC9D,gDAAgD;QAChD,eAAe,EAAE,UAAU,IAAI,SAAS;QACxC,EAAE,EAAE,SAAS;QACb,QAAQ,EAAE,UAAU,IAAI,SAAS;KAClC,CAAC,CACH,CAAA;IAED,OAAO,CACL,eACE,SAAS,EAAE,WAAW,CACpB,uBAAuB,EACvB,SAAS,CACV,aAED,iBACE,SAAS,EAAC,0CAA0C,EACpD,OAAO,EAAE,SAAS,aAEjB,KAAK,EAEL,UAAU,CAAC,CAAC,CAAC,CACZ,8BAKc,MAAM,EAClB,SAAS,EAAC,iCAAiC,kBAGtC,CACR,CAAC,CAAC,CAAC,IAAI,IACF,EAEP,aAAa,EAEb,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAClC,YACE,SAAS,EAAC,gCAAgC,EAC1C,EAAE,EAAE,aAAa,YAEhB,WAAW,GACV,CACL,EAEA,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC5B,YACE,SAAS,EAAC,oCAAoC,EAC9C,EAAE,EAAE,OAAO,YAEV,KAAK,GACJ,CACL,IACG,CACP,CAAA;AACH,CAAC,CAAA"}
@@ -6,11 +6,53 @@ export type MenuItem = {
6
6
  key: string;
7
7
  label: ReactNode;
8
8
  onSelect: () => void;
9
+ /**
10
+ * The default kind, and optional so the common `{ key, label,
11
+ * onSelect }` still type-checks. Present only to discriminate this
12
+ * from a `separator` or a `group`.
13
+ */
14
+ type?: "item";
9
15
  };
16
+ /**
17
+ * A rule between groups of items — `role="separator"`, and nothing a
18
+ * keyboard user lands on. It never registers with the roving group,
19
+ * so the arrow keys pass straight over it, the same mechanism a
20
+ * disabled item uses.
21
+ */
22
+ export type MenuSeparator = {
23
+ key: string;
24
+ type: "separator";
25
+ };
26
+ /**
27
+ * A named set of items — `role="group"` with the `label` as its
28
+ * accessible name (a screen reader announces "Danger, group" before
29
+ * its members). The heading itself is not focusable; only the items
30
+ * inside it register.
31
+ */
32
+ export type MenuGroup = {
33
+ items: MenuItem[];
34
+ key: string;
35
+ label: ReactNode;
36
+ type: "group";
37
+ };
38
+ /**
39
+ * What a menu is made of. A bare `MenuItem` is still valid on its
40
+ * own, so existing `items` arrays need no change; a `separator` or a
41
+ * `group` opts in by its `type`.
42
+ */
43
+ export type MenuEntry = MenuGroup | MenuItem | MenuSeparator;
10
44
  export type MenuProps = {
11
45
  className?: string;
46
+ /**
47
+ * Shown when there is no item to show — a `group` with no members
48
+ * and a bare separator list both count as empty. Rendered as a
49
+ * **disabled** `menuitem` (a `role="menu"` must own one), so it is
50
+ * announced but never focused. Without it an empty menu renders
51
+ * nothing.
52
+ */
53
+ emptyState?: ReactNode;
12
54
  isVisible: boolean;
13
- items: MenuItem[];
55
+ items: MenuEntry[];
14
56
  /** Outside press, Escape, and choosing an item all land here. */
15
57
  onDismiss: () => void;
16
58
  placement?: Placement;
@@ -69,6 +111,22 @@ export type MenuProps = {
69
111
  * focus is whichever one `RovingFocus` says is active, and that is
70
112
  * not known until the items have registered.
71
113
  *
114
+ * ### Separators, groups, and an empty state
115
+ *
116
+ * `items` is a union, not a flat `MenuItem[]`: a `separator`
117
+ * (`role="separator"`) and a `group` (`role="group"`, named by its
118
+ * `label`) opt in by their `type`, and a bare item still type-checks
119
+ * unchanged. The keyboard model needs no new code for them — a
120
+ * separator and a group heading register nothing, so the roving
121
+ * group never sees them and the arrow keys skip straight over, the
122
+ * same mechanism a disabled item already used. A `group`'s items
123
+ * register normally, so focus moves through them in DOM order as if
124
+ * the heading were not there. When there is nothing to show, the
125
+ * `emptyState` renders as a **disabled** `menuitem` — a `role="menu"`
126
+ * with no `menuitem` child fails `aria-required-children`, so the
127
+ * note has to be one, `aria-disabled` and out of the roving group:
128
+ * announced as "No actions available, dimmed", focusable by nothing.
129
+ *
72
130
  * ### No type-ahead, deliberately
73
131
  *
74
132
  * The APG lists type-ahead as optional for a menu and required for a
@@ -77,5 +135,5 @@ export type MenuProps = {
77
135
  * buffer and a timer in a component that currently has neither, for
78
136
  * a menu you can cross in three arrow presses.
79
137
  */
80
- export declare const Menu: ({ className, isVisible, items, onDismiss, placement, trigger, }: MenuProps) => ReactNode;
138
+ export declare const Menu: ({ className, emptyState, isVisible, items, onDismiss, placement, trigger, }: MenuProps) => ReactNode;
81
139
  //# sourceMappingURL=Menu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Menu.d.ts","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAKnD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQpD,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,iEAAiE;IACjE,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,+DAA+D;IAC/D,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,eAAO,MAAM,IAAI,GAAI,iEAOlB,SAAS,KAAG,SAuId,CAAA"}
1
+ {"version":3,"file":"Menu.d.ts","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAKnD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQpD,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,WAAW,CAAA;CAClB,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,SAAS,CAAA;IAChB,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,aAAa,CAAA;AAE5D,MAAM,MAAM,SAAS,GAAG;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,iEAAiE;IACjE,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,+DAA+D;IAC/D,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,eAAO,MAAM,IAAI,GAAI,6EAQlB,SAAS,KAAG,SAyMd,CAAA"}
package/dist/Menu/Menu.js CHANGED
@@ -1,11 +1,14 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { selectTabIndex, useRovingFocus, } from "@charcuterie/logic";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { selectTabIndex, useRovingFocus, useUniqueId, } from "@charcuterie/logic";
3
3
  import { FloatingFocusManager, FloatingPortal, } from "@floating-ui/react";
4
4
  import { useEffect, useRef } from "react";
5
5
  import { PANEL_SURFACE_CLASS } from "../Overlay/overlayPanelClass.js";
6
6
  import { useAnchoredOverlay } from "../Overlay/useAnchoredOverlay.js";
7
7
  import { toClassName } from "../toClassName.js";
8
8
  import { MenuAction } from "./MenuAction.js";
9
+ const isMenuItem = (entry) => entry.type === undefined || entry.type === "item";
10
+ const hasAnyItem = (items) => items.some((entry) => isMenuItem(entry) ||
11
+ (entry.type === "group" && entry.items.length > 0));
9
12
  /**
10
13
  * A menu is **not** a listbox, and mux-magic's `TypePicker` is the
11
14
  * fleet's one attempt at telling them apart — it renders
@@ -58,6 +61,22 @@ import { MenuAction } from "./MenuAction.js";
58
61
  * focus is whichever one `RovingFocus` says is active, and that is
59
62
  * not known until the items have registered.
60
63
  *
64
+ * ### Separators, groups, and an empty state
65
+ *
66
+ * `items` is a union, not a flat `MenuItem[]`: a `separator`
67
+ * (`role="separator"`) and a `group` (`role="group"`, named by its
68
+ * `label`) opt in by their `type`, and a bare item still type-checks
69
+ * unchanged. The keyboard model needs no new code for them — a
70
+ * separator and a group heading register nothing, so the roving
71
+ * group never sees them and the arrow keys skip straight over, the
72
+ * same mechanism a disabled item already used. A `group`'s items
73
+ * register normally, so focus moves through them in DOM order as if
74
+ * the heading were not there. When there is nothing to show, the
75
+ * `emptyState` renders as a **disabled** `menuitem` — a `role="menu"`
76
+ * with no `menuitem` child fails `aria-required-children`, so the
77
+ * note has to be one, `aria-disabled` and out of the roving group:
78
+ * announced as "No actions available, dimmed", focusable by nothing.
79
+ *
61
80
  * ### No type-ahead, deliberately
62
81
  *
63
82
  * The APG lists type-ahead as optional for a menu and required for a
@@ -66,7 +85,8 @@ import { MenuAction } from "./MenuAction.js";
66
85
  * buffer and a timer in a component that currently has neither, for
67
86
  * a menu you can cross in three arrow presses.
68
87
  */
69
- export const Menu = ({ className, isVisible, items, onDismiss, placement = "bottom-start", trigger, }) => {
88
+ export const Menu = ({ className, emptyState, isVisible, items, onDismiss, placement = "bottom-start", trigger, }) => {
89
+ const menuId = useUniqueId();
70
90
  const itemElements = useRef(new Map());
71
91
  const focus = useRovingFocus();
72
92
  const { activeValue, registeredValues, setActiveValue } = focus;
@@ -107,6 +127,38 @@ export const Menu = ({ className, isVisible, items, onDismiss, placement = "bott
107
127
  }
108
128
  itemElements.current.get(activeValue)?.focus();
109
129
  }, [activeValue, isVisible]);
130
+ // One member of the roving group, whether it sits at the top level
131
+ // or inside a `group`. The registration lives in `MenuAction`'s
132
+ // effect, so an item nested in a group joins the same arrow-key
133
+ // sequence in DOM order — the heading above it registers nothing.
134
+ const renderItem = (item) => (_jsx(MenuAction, { item: item, onDismiss: onDismiss, register: focus.register, tabIndex: selectTabIndex(focus, item.key), trackElement: (key, element) => {
135
+ if (element) {
136
+ itemElements.current.set(key, element);
137
+ }
138
+ else {
139
+ itemElements.current.delete(key);
140
+ }
141
+ } }, item.key));
142
+ const renderEntry = (entry) => {
143
+ if (entry.type === "separator") {
144
+ // A native `<hr>` — implicit `role="separator"`, and the one
145
+ // that does *not* need `aria-valuenow` (that is the focusable
146
+ // splitter kind, not a static rule between menu groups).
147
+ return (_jsx("hr", { className: "-mx-1 my-1 h-px border-0 bg-border-subtle" }, entry.key));
148
+ }
149
+ if (entry.type === "group") {
150
+ const headingId = `${menuId}-${entry.key}`;
151
+ return (_jsxs("div", { "aria-labelledby": headingId, role: "group", children: [_jsx("div", { className: "px-2 pt-1.5 pb-0.5 font-medium text-content-secondary text-xs", id: headingId, children: entry.label }), entry.items.map(renderItem)] }, entry.key));
152
+ }
153
+ return renderItem(entry);
154
+ };
155
+ // A **disabled** `menuitem`, not inert text: a `role="menu"` with
156
+ // no `menuitem` child fails `aria-required-children`, so the empty
157
+ // note has to be one — `aria-disabled` and `tabindex="-1"`, so it
158
+ // is announced ("No actions available, dimmed") but takes no tab
159
+ // stop and never joins the roving group. `null` when the consumer
160
+ // gave no `emptyState`, so an empty menu renders nothing.
161
+ const emptyElement = emptyState === undefined ? null : (_jsx("div", { "aria-disabled": "true", className: "px-2 py-1.5 text-content-disabled text-sm", role: "menuitem", tabIndex: -1, children: emptyState }));
110
162
  return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingPortal, { children: _jsx(FloatingFocusManager, { context: context,
111
163
  // `false`, so Tab leaves the menu and lands on the next
112
164
  // control in the page — which is what closes it, through
@@ -137,13 +189,8 @@ export const Menu = ({ className, isVisible, items, onDismiss, placement = "bott
137
189
  // "menu" })` in the hook is where it comes from. Stated
138
190
  // here because a linter cannot see a role through a
139
191
  // spread.
140
- role: "menu", style: floatingStyles, children: items.map((item) => (_jsx(MenuAction, { item: item, onDismiss: onDismiss, register: focus.register, tabIndex: selectTabIndex(focus, item.key), trackElement: (key, element) => {
141
- if (element) {
142
- itemElements.current.set(key, element);
143
- }
144
- else {
145
- itemElements.current.delete(key);
146
- }
147
- } }, item.key))) }) }) })) : null] }));
192
+ role: "menu", style: floatingStyles, children: hasAnyItem(items)
193
+ ? items.map(renderEntry)
194
+ : emptyElement }) }) })) : null] }));
148
195
  };
149
196
  //# sourceMappingURL=Menu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Menu.js","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Menu.js","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import type { ControlSize } from "@charcuterie/tokens";
2
+ import type { ReactNode } from "react";
3
+ export type RadioItem = {
4
+ /**
5
+ * A disabled option is simply **not registered** with the roving
6
+ * group, so the arrow keys skip it without any command in
7
+ * `RovingFocus` having to know what "disabled" means. It stays
8
+ * registered with the picker: it is still one of the options.
9
+ */
10
+ isDisabled?: boolean;
11
+ label: ReactNode;
12
+ value: string;
13
+ };
14
+ export type RadioGroupProps = {
15
+ className?: string;
16
+ items: readonly RadioItem[];
17
+ /** The group's accessible name. Required. */
18
+ label: string;
19
+ onChange?: (selectedValue: string | null) => void;
20
+ /** **Initial** only. Charcuterie owns it from then on. */
21
+ selectedValue?: string;
22
+ size?: ControlSize;
23
+ };
24
+ /**
25
+ * One choice out of many, each on its own row with a label the size
26
+ * of prose — the stacked sibling of `SegmentedControl`.
27
+ *
28
+ * The two are the *same* composition — `SinglePicker` + `RovingFocus`,
29
+ * a `radiogroup` of `radio`s where selection follows focus — and a
30
+ * different shape. `SegmentedControl` is a compact strip for a
31
+ * handful of short options that share a control row; `RadioGroup` is
32
+ * the form-length list where each option carries a real sentence and
33
+ * an in-line strip would wrap. Picking between them is layout, not
34
+ * behaviour, which is why the keyboard model here is identical and
35
+ * lives in the same two hooks.
36
+ *
37
+ * ### `radiogroup`, not a stack of checkboxes
38
+ *
39
+ * A column of `<input type="checkbox">` is the **independent
40
+ * toggles** pattern: nothing says the options are mutually
41
+ * exclusive, and a screen reader never announces "1 of 4". A single
42
+ * choice from a named set is a radio group, and its arrow keys both
43
+ * move focus and check — APG's one activation mode, the same reason
44
+ * `SegmentedControl` has no `manual` prop.
45
+ */
46
+ export declare const RadioGroup: ({ className, items, label, onChange, selectedValue, size, }: RadioGroupProps) => ReactNode;
47
+ //# sourceMappingURL=RadioGroup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RadioGroup.d.ts","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroup.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,SAAS,GAAG;IACtB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IAC3B,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACjD,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAQD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,UAAU,GAAI,6DAOxB,eAAe,KAAG,SAqIpB,CAAA"}
@@ -0,0 +1,114 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { selectTabIndex, useRovingFocus, useSinglePicker, } from "@charcuterie/logic";
3
+ import { useCallback, useEffect, useRef } from "react";
4
+ import { toClassName } from "../toClassName.js";
5
+ import { RadioGroupOption } from "./RadioGroupOption.js";
6
+ const GAP_CLASS = {
7
+ sm: "gap-1",
8
+ md: "gap-1.5",
9
+ lg: "gap-2",
10
+ };
11
+ /**
12
+ * One choice out of many, each on its own row with a label the size
13
+ * of prose — the stacked sibling of `SegmentedControl`.
14
+ *
15
+ * The two are the *same* composition — `SinglePicker` + `RovingFocus`,
16
+ * a `radiogroup` of `radio`s where selection follows focus — and a
17
+ * different shape. `SegmentedControl` is a compact strip for a
18
+ * handful of short options that share a control row; `RadioGroup` is
19
+ * the form-length list where each option carries a real sentence and
20
+ * an in-line strip would wrap. Picking between them is layout, not
21
+ * behaviour, which is why the keyboard model here is identical and
22
+ * lives in the same two hooks.
23
+ *
24
+ * ### `radiogroup`, not a stack of checkboxes
25
+ *
26
+ * A column of `<input type="checkbox">` is the **independent
27
+ * toggles** pattern: nothing says the options are mutually
28
+ * exclusive, and a screen reader never announces "1 of 4". A single
29
+ * choice from a named set is a radio group, and its arrow keys both
30
+ * move focus and check — APG's one activation mode, the same reason
31
+ * `SegmentedControl` has no `manual` prop.
32
+ */
33
+ export const RadioGroup = ({ className, items, label, onChange, selectedValue, size = "md", }) => {
34
+ const groupRef = useRef(null);
35
+ const optionElements = useRef(new Map());
36
+ const initialValue = selectedValue ??
37
+ items.find((one) => !one.isDisabled)?.value ??
38
+ null;
39
+ const selection = useSinglePicker({
40
+ onChange,
41
+ selectedValue: initialValue,
42
+ });
43
+ const focus = useRovingFocus({
44
+ activeValue: initialValue,
45
+ });
46
+ const { select } = selection;
47
+ const { activeValue, setActiveValue } = focus;
48
+ /**
49
+ * Selected **or** pending. Members register from an effect, so on
50
+ * the first paint `selectedValue` is still null and the intent
51
+ * lives in `pendingValue` — reading only the former renders a
52
+ * group with nothing checked, then corrects itself a frame later.
53
+ */
54
+ const checkedValue = selection.selectedValue ?? selection.pendingValue;
55
+ const chooseOption = useCallback((value) => {
56
+ setActiveValue(value);
57
+ select(value);
58
+ }, [select, setActiveValue]);
59
+ useEffect(() => {
60
+ // Selection follows focus — the radio-group rule, and the one
61
+ // line a `manual` mode this component deliberately lacks would
62
+ // remove.
63
+ if (activeValue !== null) {
64
+ select(activeValue);
65
+ }
66
+ }, [activeValue, select]);
67
+ useEffect(() => {
68
+ const group = groupRef.current;
69
+ if (!group ||
70
+ activeValue === null ||
71
+ !group.contains(document.activeElement)) {
72
+ return;
73
+ }
74
+ optionElements.current.get(activeValue)?.focus();
75
+ }, [activeValue]);
76
+ return (_jsx("div", { "aria-label": label, className: toClassName("flex flex-col", GAP_CLASS[size], className), onKeyDown: (keyEvent) => {
77
+ const commands = {
78
+ ArrowDown: focus.next,
79
+ ArrowLeft: focus.previous,
80
+ ArrowRight: focus.next,
81
+ ArrowUp: focus.previous,
82
+ End: focus.last,
83
+ Home: focus.first,
84
+ };
85
+ const command = commands[keyEvent.key];
86
+ if (command) {
87
+ // Arrow keys scroll the page by default, and a control
88
+ // that scrolls the window while it moves focus is the
89
+ // hand-rolled version of this component.
90
+ keyEvent.preventDefault();
91
+ command();
92
+ return;
93
+ }
94
+ // Space checks the focused option. Selection already follows
95
+ // focus, so this is almost always a no-op — one idempotent
96
+ // command, not a second code path.
97
+ if (keyEvent.key === " " && activeValue !== null) {
98
+ keyEvent.preventDefault();
99
+ select(activeValue);
100
+ }
101
+ }, ref: groupRef, role: "radiogroup", children: items.map((item) => (_jsx(RadioGroupOption, { isChecked: checkedValue === item.value, item: item, onChoose: chooseOption, registerFocus: focus.register, registerSelection: selection.register, size: size,
102
+ // The roving-tabindex rule read from the core rather than
103
+ // restated here: exactly one option is in the tab order, so
104
+ // Tab enters and leaves the group once.
105
+ tabIndex: selectTabIndex(focus, item.value), trackElement: (value, element) => {
106
+ if (element) {
107
+ optionElements.current.set(value, element);
108
+ }
109
+ else {
110
+ optionElements.current.delete(value);
111
+ }
112
+ } }, item.value))) }));
113
+ };
114
+ //# sourceMappingURL=RadioGroup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RadioGroup.js","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroup.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { ControlSize } from "@charcuterie/tokens";
2
+ import type { ReactNode } from "react";
3
+ import type { RadioItem } from "./RadioGroup.tsx";
4
+ export type RadioGroupOptionProps = {
5
+ isChecked: boolean;
6
+ item: RadioItem;
7
+ onChoose: (value: string) => void;
8
+ /** `RovingFocus.register` — membership of the arrow-key group. */
9
+ registerFocus: (value: string) => () => void;
10
+ /** `SinglePicker.register` — membership of the choice. */
11
+ registerSelection: (value: string) => () => void;
12
+ size: ControlSize;
13
+ tabIndex: number;
14
+ trackElement: (value: string, element: HTMLButtonElement | null) => void;
15
+ };
16
+ /**
17
+ * Its own file for the same reason `SegmentedOption` is: **both
18
+ * registrations are effects**, and an effect cannot run in a loop.
19
+ *
20
+ * The ring and the dot are two elements rather than a bordered box
21
+ * with a background, so the checked dot can be centred by the grid
22
+ * and sized independently of the ring — a filled `bg-intent-accent`
23
+ * ring with no inner dot reads as a checkbox, not a radio.
24
+ */
25
+ export declare const RadioGroupOption: ({ isChecked, item, onChoose, registerFocus, registerSelection, size, tabIndex, trackElement, }: RadioGroupOptionProps) => ReactNode;
26
+ //# sourceMappingURL=RadioGroupOption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RadioGroupOption.d.ts","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroupOption.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,kEAAkE;IAClE,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,IAAI,CAAA;IAC5C,0DAA0D;IAC1D,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,IAAI,CAAA;IAChD,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,CACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,iBAAiB,GAAG,IAAI,KAC9B,IAAI,CAAA;CACV,CAAA;AAoBD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,gGAS9B,qBAAqB,KAAG,SAiE1B,CAAA"}