@charcuterie/ui 2.4.1 → 2.6.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.
- package/dist/Checkbox/Checkbox.d.ts +68 -0
- package/dist/Checkbox/Checkbox.d.ts.map +1 -0
- package/dist/Checkbox/Checkbox.js +108 -0
- package/dist/Checkbox/Checkbox.js.map +1 -0
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +12 -1
- package/dist/Field/Field.js.map +1 -1
- package/dist/Menu/Menu.d.ts +60 -2
- package/dist/Menu/Menu.d.ts.map +1 -1
- package/dist/Menu/Menu.js +58 -11
- package/dist/Menu/Menu.js.map +1 -1
- package/dist/RadioGroup/RadioGroup.d.ts +54 -0
- package/dist/RadioGroup/RadioGroup.d.ts.map +1 -0
- package/dist/RadioGroup/RadioGroup.js +121 -0
- package/dist/RadioGroup/RadioGroup.js.map +1 -0
- package/dist/RadioGroup/RadioGroupOption.d.ts +27 -0
- package/dist/RadioGroup/RadioGroupOption.d.ts.map +1 -0
- package/dist/RadioGroup/RadioGroupOption.js +75 -0
- package/dist/RadioGroup/RadioGroupOption.js.map +1 -0
- package/dist/Switch/Switch.d.ts +60 -0
- package/dist/Switch/Switch.d.ts.map +1 -0
- package/dist/Switch/Switch.js +106 -0
- package/dist/Switch/Switch.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/slotWiring.d.ts +9 -7
- package/dist/slotWiring.d.ts.map +1 -1
- package/dist/slotWiring.js +10 -70
- package/dist/slotWiring.js.map +1 -1
- package/package.json +2 -2
- package/src/Checkbox/Checkbox.mdx +71 -0
- package/src/Checkbox/Checkbox.stories.tsx +122 -0
- package/src/Checkbox/Checkbox.test.tsx +109 -0
- package/src/Checkbox/Checkbox.tsx +205 -0
- package/src/Combobox/Combobox.stories.tsx +19 -4
- package/src/Field/Field.stories.tsx +19 -0
- package/src/Field/Field.test.tsx +30 -2
- package/src/Field/Field.tsx +13 -1
- package/src/Menu/Menu.stories.tsx +80 -2
- package/src/Menu/Menu.test.tsx +102 -0
- package/src/Menu/Menu.tsx +156 -17
- package/src/RadioGroup/RadioGroup.mdx +67 -0
- package/src/RadioGroup/RadioGroup.stories.tsx +140 -0
- package/src/RadioGroup/RadioGroup.test.tsx +232 -0
- package/src/RadioGroup/RadioGroup.tsx +223 -0
- package/src/RadioGroup/RadioGroupOption.tsx +148 -0
- package/src/Switch/Switch.mdx +55 -0
- package/src/Switch/Switch.stories.tsx +116 -0
- package/src/Switch/Switch.test.tsx +115 -0
- package/src/Switch/Switch.tsx +183 -0
- package/src/index.ts +9 -0
- package/src/slotWiring.ts +15 -107
- package/src/sourceRules.test.ts +11 -1
- package/src/tailwindCandidates.test.ts +9 -7
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
* Shows the value at full contrast but refuses to change it — a
|
|
21
|
+
* setting you may read here and edit elsewhere. Distinct from
|
|
22
|
+
* `isDisabled`, which dims: a read-only box is not turned down,
|
|
23
|
+
* because the whole point is that you can still read it. Announced
|
|
24
|
+
* as `aria-readonly`, and the toggle is blocked on both pointer and
|
|
25
|
+
* `Space`.
|
|
26
|
+
*/
|
|
27
|
+
isReadOnly?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The visible text beside the box, and the checkbox's accessible
|
|
30
|
+
* name — the `<label>` wraps the control, so there is no `for` to
|
|
31
|
+
* get wrong. A box with no label is a control a screen reader
|
|
32
|
+
* cannot announce, which is why this is required.
|
|
33
|
+
*/
|
|
34
|
+
label: ReactNode;
|
|
35
|
+
onChange?: (isChecked: boolean) => void;
|
|
36
|
+
size?: ControlSize;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* A single boolean, on or off, that owns nothing but itself.
|
|
40
|
+
*
|
|
41
|
+
* This is the primitive mux-magic's `BooleanField` hand-rolled in
|
|
42
|
+
* `bg-slate-700 accent-blue-500` because the library had no checkbox
|
|
43
|
+
* to reach for — palette colours with no light mode, the exact M6
|
|
44
|
+
* defect the token layer exists to delete. Here the box is
|
|
45
|
+
* `bg-surface-sunken` and the fill is `bg-intent-accent-solid`, so
|
|
46
|
+
* one control reads correctly in `daylight`, `midnight`, and on the
|
|
47
|
+
* kiosk with no per-app override.
|
|
48
|
+
*
|
|
49
|
+
* ### The `<label>` wraps the control
|
|
50
|
+
*
|
|
51
|
+
* A checkbox reads to the *left* of its text, which is why it is not
|
|
52
|
+
* a `Field` — `Field` stacks label above control in a `flex-col`,
|
|
53
|
+
* right for a text input and wrong for a boolean, and stacking every
|
|
54
|
+
* checkbox in a step card would double the card's height. Wrapping is
|
|
55
|
+
* also the one label association that needs no `for` at all, so there
|
|
56
|
+
* is no minted-then-dropped id to get wrong.
|
|
57
|
+
*
|
|
58
|
+
* ### The `<input>` is the store
|
|
59
|
+
*
|
|
60
|
+
* `defaultChecked`, not `checked`. The DOM owns a checkbox's state
|
|
61
|
+
* natively, so there is no second copy to keep in step and no
|
|
62
|
+
* controlled-prop round-trip — `onChange` reports the new state to a
|
|
63
|
+
* consumer that wants to persist it, and reading it back is the
|
|
64
|
+
* `<input>`'s job. `isChecked` seeds the first paint and nothing
|
|
65
|
+
* after.
|
|
66
|
+
*/
|
|
67
|
+
export declare const Checkbox: ({ className, id, isChecked, isDisabled, isReadOnly, label, onChange, size, }: CheckboxProps) => ReactNode;
|
|
68
|
+
//# 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;;;;;;;OAOG;IACH,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,8EAStB,aAAa,KAAG,SA0GlB,CAAA"}
|
|
@@ -0,0 +1,108 @@
|
|
|
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, isReadOnly = false, label, onChange, size = "md", }) => (_jsxs("label", { className: toClassName("inline-flex items-center gap-2 text-content-secondary select-none", TEXT_SIZE_CLASS[size],
|
|
49
|
+
// One cursor, chosen by state — two cursor utilities of the same
|
|
50
|
+
// specificity do not resolve by class-list order.
|
|
51
|
+
isDisabled
|
|
52
|
+
? "cursor-not-allowed"
|
|
53
|
+
: isReadOnly
|
|
54
|
+
? "cursor-default"
|
|
55
|
+
: "cursor-pointer",
|
|
56
|
+
// Disabled dims the whole control with opacity rather than fading
|
|
57
|
+
// the box to a quieter token. The token scale has no step between
|
|
58
|
+
// `border-default` and `border-strong`, so a muted outline was
|
|
59
|
+
// either invisible or indistinguishable from enabled; `opacity-60`
|
|
60
|
+
// keeps the full shape and colour and just turns them down, which
|
|
61
|
+
// is the "present but inactive" the box actually is. Read-only is
|
|
62
|
+
// *not* dimmed — its whole point is that the value stays readable.
|
|
63
|
+
// The same treatment is on `Switch` and `RadioGroup`.
|
|
64
|
+
isDisabled && "opacity-60", className), children: [_jsxs("span", { className: "relative grid shrink-0 place-items-center", children: [_jsx("input", { "aria-readonly": isReadOnly || undefined, className: toClassName(
|
|
65
|
+
// `border-2` is the family's shared weight — the `Switch`
|
|
66
|
+
// track's `ring-2` and the `RadioGroup` ring match it, so the
|
|
67
|
+
// three read as one set — and a 2px edge is also what keeps a
|
|
68
|
+
// disabled box visible where a 1px one washed out.
|
|
69
|
+
"peer appearance-none rounded-sm border-2 bg-surface-sunken transition-colors duration-(--duration-fast) ease-standard",
|
|
70
|
+
// Read-only wears the **neutral** intent, not the accent: full
|
|
71
|
+
// contrast (so the value reads) but plainly not an actionable
|
|
72
|
+
// accent control, with a softer `border-default` resting edge.
|
|
73
|
+
// Three distinct states result — enabled (accent, crisp),
|
|
74
|
+
// disabled (accent, dimmed by the wrapper), read-only (neutral).
|
|
75
|
+
// No `disabled:` colour override; the wrapper's `opacity-60`
|
|
76
|
+
// does the dimming, and doubling it on the box is what made a
|
|
77
|
+
// disabled checkbox vanish.
|
|
78
|
+
isReadOnly
|
|
79
|
+
? "border-border-default checked:border-intent-neutral-solid checked:bg-intent-neutral-solid"
|
|
80
|
+
: "border-border-strong checked:border-intent-accent-solid checked:bg-intent-accent-solid", isDisabled
|
|
81
|
+
? "cursor-not-allowed"
|
|
82
|
+
: isReadOnly
|
|
83
|
+
? "cursor-default"
|
|
84
|
+
: "cursor-pointer", BOX_SIZE_CLASS[size], FOCUS_RING_CLASS), defaultChecked: isChecked, disabled: isDisabled, id: id, onChange: (event) => {
|
|
85
|
+
if (isReadOnly) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
onChange?.(event.target.checked);
|
|
89
|
+
}, onClick: (clickEvent) => {
|
|
90
|
+
// Native checkboxes have no `readonly`, so the toggle is
|
|
91
|
+
// blocked here — `preventDefault` on the click stops the
|
|
92
|
+
// checked flip a pointer would make.
|
|
93
|
+
if (isReadOnly) {
|
|
94
|
+
clickEvent.preventDefault();
|
|
95
|
+
}
|
|
96
|
+
}, onKeyDown: (keyEvent) => {
|
|
97
|
+
// …and Space, the keyboard toggle a click block does not
|
|
98
|
+
// cover.
|
|
99
|
+
if (isReadOnly && keyEvent.key === " ") {
|
|
100
|
+
keyEvent.preventDefault();
|
|
101
|
+
}
|
|
102
|
+
}, type: "checkbox" }), _jsx("svg", { "aria-hidden": "true", className: toClassName("pointer-events-none invisible absolute peer-checked:visible",
|
|
103
|
+
// Matches whichever fill the box wears, so the tick keeps its
|
|
104
|
+
// guaranteed on-solid contrast in either intent.
|
|
105
|
+
isReadOnly
|
|
106
|
+
? "text-intent-neutral-on-solid"
|
|
107
|
+
: "text-intent-accent-on-solid", 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 })] }));
|
|
108
|
+
//# sourceMappingURL=Checkbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../src/Checkbox/Checkbox.tsx"],"names":[],"mappings":"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"}
|
|
@@ -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,
|
|
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"}
|
package/dist/Field/Field.js
CHANGED
|
@@ -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
|
|
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 = [
|
package/dist/Field/Field.js.map
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/Menu/Menu.d.ts
CHANGED
|
@@ -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:
|
|
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
|
package/dist/Menu/Menu.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Menu.d.ts","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"
|
|
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,
|
|
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
|
|
141
|
-
|
|
142
|
-
|
|
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
|
package/dist/Menu/Menu.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Menu.js","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,cAAc,EACd,cAAc,
|
|
1
|
+
{"version":3,"file":"Menu.js","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* Shows which option is chosen at full contrast but refuses to move
|
|
19
|
+
* the choice — the arrow keys and clicks no longer select, and the
|
|
20
|
+
* group announces `aria-readonly`. Focus can still travel the
|
|
21
|
+
* options so a keyboard user can read them.
|
|
22
|
+
*/
|
|
23
|
+
isReadOnly?: boolean;
|
|
24
|
+
/** The group's accessible name. Required. */
|
|
25
|
+
label: string;
|
|
26
|
+
onChange?: (selectedValue: string | null) => void;
|
|
27
|
+
/** **Initial** only. Charcuterie owns it from then on. */
|
|
28
|
+
selectedValue?: string;
|
|
29
|
+
size?: ControlSize;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* One choice out of many, each on its own row with a label the size
|
|
33
|
+
* of prose — the stacked sibling of `SegmentedControl`.
|
|
34
|
+
*
|
|
35
|
+
* The two are the *same* composition — `SinglePicker` + `RovingFocus`,
|
|
36
|
+
* a `radiogroup` of `radio`s where selection follows focus — and a
|
|
37
|
+
* different shape. `SegmentedControl` is a compact strip for a
|
|
38
|
+
* handful of short options that share a control row; `RadioGroup` is
|
|
39
|
+
* the form-length list where each option carries a real sentence and
|
|
40
|
+
* an in-line strip would wrap. Picking between them is layout, not
|
|
41
|
+
* behaviour, which is why the keyboard model here is identical and
|
|
42
|
+
* lives in the same two hooks.
|
|
43
|
+
*
|
|
44
|
+
* ### `radiogroup`, not a stack of checkboxes
|
|
45
|
+
*
|
|
46
|
+
* A column of `<input type="checkbox">` is the **independent
|
|
47
|
+
* toggles** pattern: nothing says the options are mutually
|
|
48
|
+
* exclusive, and a screen reader never announces "1 of 4". A single
|
|
49
|
+
* choice from a named set is a radio group, and its arrow keys both
|
|
50
|
+
* move focus and check — APG's one activation mode, the same reason
|
|
51
|
+
* `SegmentedControl` has no `manual` prop.
|
|
52
|
+
*/
|
|
53
|
+
export declare const RadioGroup: ({ className, items, isReadOnly, label, onChange, selectedValue, size, }: RadioGroupProps) => ReactNode;
|
|
54
|
+
//# 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;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,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,yEAQxB,eAAe,KAAG,SAgJpB,CAAA"}
|
|
@@ -0,0 +1,121 @@
|
|
|
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, isReadOnly = false, 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
|
+
// Focus may still land on the option so it can be read; only the
|
|
57
|
+
// choice is frozen when read-only.
|
|
58
|
+
setActiveValue(value);
|
|
59
|
+
if (!isReadOnly) {
|
|
60
|
+
select(value);
|
|
61
|
+
}
|
|
62
|
+
}, [isReadOnly, select, setActiveValue]);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
// Selection follows focus — the radio-group rule, and the one
|
|
65
|
+
// line a `manual` mode this component deliberately lacks would
|
|
66
|
+
// remove. Read-only severs exactly this link: focus still travels
|
|
67
|
+
// for reading, but moving it no longer moves the choice.
|
|
68
|
+
if (activeValue !== null && !isReadOnly) {
|
|
69
|
+
select(activeValue);
|
|
70
|
+
}
|
|
71
|
+
}, [activeValue, isReadOnly, select]);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const group = groupRef.current;
|
|
74
|
+
if (!group ||
|
|
75
|
+
activeValue === null ||
|
|
76
|
+
!group.contains(document.activeElement)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
optionElements.current.get(activeValue)?.focus();
|
|
80
|
+
}, [activeValue]);
|
|
81
|
+
return (_jsx("div", { "aria-label": label, "aria-readonly": isReadOnly || undefined, className: toClassName("flex flex-col", GAP_CLASS[size], className), onKeyDown: (keyEvent) => {
|
|
82
|
+
const commands = {
|
|
83
|
+
ArrowDown: focus.next,
|
|
84
|
+
ArrowLeft: focus.previous,
|
|
85
|
+
ArrowRight: focus.next,
|
|
86
|
+
ArrowUp: focus.previous,
|
|
87
|
+
End: focus.last,
|
|
88
|
+
Home: focus.first,
|
|
89
|
+
};
|
|
90
|
+
const command = commands[keyEvent.key];
|
|
91
|
+
if (command) {
|
|
92
|
+
// Arrow keys scroll the page by default, and a control
|
|
93
|
+
// that scrolls the window while it moves focus is the
|
|
94
|
+
// hand-rolled version of this component.
|
|
95
|
+
keyEvent.preventDefault();
|
|
96
|
+
command();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Space checks the focused option. Selection already follows
|
|
100
|
+
// focus, so this is almost always a no-op — one idempotent
|
|
101
|
+
// command, not a second code path — and read-only skips it.
|
|
102
|
+
if (keyEvent.key === " " &&
|
|
103
|
+
activeValue !== null &&
|
|
104
|
+
!isReadOnly) {
|
|
105
|
+
keyEvent.preventDefault();
|
|
106
|
+
select(activeValue);
|
|
107
|
+
}
|
|
108
|
+
}, ref: groupRef, role: "radiogroup", children: items.map((item) => (_jsx(RadioGroupOption, { isChecked: checkedValue === item.value, isReadOnly: isReadOnly, item: item, onChoose: chooseOption, registerFocus: focus.register, registerSelection: selection.register, size: size,
|
|
109
|
+
// The roving-tabindex rule read from the core rather than
|
|
110
|
+
// restated here: exactly one option is in the tab order, so
|
|
111
|
+
// Tab enters and leaves the group once.
|
|
112
|
+
tabIndex: selectTabIndex(focus, item.value), trackElement: (value, element) => {
|
|
113
|
+
if (element) {
|
|
114
|
+
optionElements.current.set(value, element);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
optionElements.current.delete(value);
|
|
118
|
+
}
|
|
119
|
+
} }, item.value))) }));
|
|
120
|
+
};
|
|
121
|
+
//# 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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"}
|