@charcuterie/ui 2.14.0 → 2.15.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/Picker/Picker.d.ts +83 -0
- package/dist/Picker/Picker.d.ts.map +1 -0
- package/dist/Picker/Picker.js +85 -0
- package/dist/Picker/Picker.js.map +1 -0
- package/dist/QueryBuilder/QueryBuilderCombinator.d.ts +12 -33
- package/dist/QueryBuilder/QueryBuilderCombinator.d.ts.map +1 -1
- package/dist/QueryBuilder/QueryBuilderCombinator.js +32 -50
- package/dist/QueryBuilder/QueryBuilderCombinator.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Picker/Picker.mdx +63 -0
- package/src/Picker/Picker.stories.tsx +184 -0
- package/src/Picker/Picker.test.tsx +98 -0
- package/src/Picker/Picker.tsx +186 -0
- package/src/QueryBuilder/QueryBuilder.stories.tsx +14 -63
- package/src/QueryBuilder/QueryBuilderCombinator.tsx +40 -79
- package/src/index.ts +5 -0
- package/src/sourceRules.test.ts +7 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ControlSize, IntentName } from "@charcuterie/tokens";
|
|
2
|
+
import type { Placement } from "@floating-ui/react";
|
|
3
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
4
|
+
import type { IntentAppearance } from "../intentStyles.ts";
|
|
5
|
+
import type { ListboxItem } from "../Listbox/Listbox.tsx";
|
|
6
|
+
export type PickerOption = ListboxItem;
|
|
7
|
+
export type PickerProps = Omit<ComponentPropsWithRef<"button">, "disabled" | "onChange" | "value"> & {
|
|
8
|
+
appearance?: IntentAppearance;
|
|
9
|
+
className?: string;
|
|
10
|
+
intent?: IntentName;
|
|
11
|
+
isDisabled?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* What the control is FOR — "Language", "Quantifier", "Rule type".
|
|
14
|
+
* Not the current value; that is read off `options`.
|
|
15
|
+
*/
|
|
16
|
+
label: string;
|
|
17
|
+
/**
|
|
18
|
+
* Defaults to a chevron. Pass `null` to drop it — a picker rendered
|
|
19
|
+
* inside a denser control row may not want the affordance, and a
|
|
20
|
+
* refactor moving an existing hand-rolled trigger onto `Picker`
|
|
21
|
+
* stays pixel-identical by passing `null` rather than re-baselining
|
|
22
|
+
* every screenshot that contains it.
|
|
23
|
+
*/
|
|
24
|
+
iconEnd?: ReactNode;
|
|
25
|
+
onChange: (value: string) => void;
|
|
26
|
+
options: readonly PickerOption[];
|
|
27
|
+
placement?: Placement;
|
|
28
|
+
/** Read by the trigger when `value` matches no option. */
|
|
29
|
+
placeholder?: string;
|
|
30
|
+
size?: ControlSize;
|
|
31
|
+
value?: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* A `Listbox` with the trigger already attached — the shape every app
|
|
35
|
+
* reaches for when it wants "a select, but ours".
|
|
36
|
+
*
|
|
37
|
+
* ### Four repos wrote this, and the fourth is this package
|
|
38
|
+
*
|
|
39
|
+
* `Listbox` is deliberately trigger-agnostic: it takes a `trigger`
|
|
40
|
+
* element and owns none of the open state, which is what lets it hang
|
|
41
|
+
* off a button, a tile, a table header. The cost is that the *common*
|
|
42
|
+
* case — a button showing the current value — is about thirty lines,
|
|
43
|
+
* and the fleet wrote them four times: queuepilot's `SelectListbox`,
|
|
44
|
+
* board-games' `SelectMenu` (on `useState` rather than
|
|
45
|
+
* `useVisibility`), mux-magic's `ListboxPicker`, and — the tell —
|
|
46
|
+
* **twice inside this package**, in `QueryBuilderCombinator` and in
|
|
47
|
+
* `QueryBuilder`'s own story. Each also hand-rolled the same chevron.
|
|
48
|
+
*
|
|
49
|
+
* `Listbox` stays exactly as it is. This is the assembled default.
|
|
50
|
+
*
|
|
51
|
+
* ### Why it is not called `SinglePicker`
|
|
52
|
+
*
|
|
53
|
+
* The state layer has `useSinglePicker` and `useMultiplePicker`, so a
|
|
54
|
+
* name near "picker" invites the question of whether this is their
|
|
55
|
+
* component. It is not, and no component is: `useSinglePicker` is a
|
|
56
|
+
* state kind that `Listbox`, `Combobox` and `Tabs` all compose. This
|
|
57
|
+
* component owns a `useVisibility` and delegates selection to
|
|
58
|
+
* `Listbox`, which is where the picker state actually lives. The name
|
|
59
|
+
* is the owner's, chosen over `SelectListbox` and `ListboxButton`.
|
|
60
|
+
*
|
|
61
|
+
* ### The accessible name carries the current value
|
|
62
|
+
*
|
|
63
|
+
* `"Language: English"`, not `"Language"`. The trigger's visible text
|
|
64
|
+
* IS the current value, and WCAG 2.5.3 requires the visible text to be
|
|
65
|
+
* contained in the accessible name — so a bare `aria-label={label}`
|
|
66
|
+
* fails it, which is a latent defect in one of the hand-rolled
|
|
67
|
+
* versions this replaces. It also makes each control uniquely findable
|
|
68
|
+
* when a form has several, which is the property `expectAgentDrivable`
|
|
69
|
+
* exists to protect.
|
|
70
|
+
*
|
|
71
|
+
* Query it as `getByRole("button", { name: /^Language: / })`.
|
|
72
|
+
*
|
|
73
|
+
* ### `id` does not survive; `data-testid` does
|
|
74
|
+
*
|
|
75
|
+
* `useAnchoredOverlay` overwrites the trigger's `id` with a generated
|
|
76
|
+
* one, so the portalled listbox can point `aria-labelledby` across at
|
|
77
|
+
* it. An `id` passed here is therefore silently replaced — queuepilot
|
|
78
|
+
* found this the hard way and switched its e2e handles to
|
|
79
|
+
* `data-testid`, which nothing injects. Everything else on the button
|
|
80
|
+
* (`data-*`, `onClick`, `form`, …) passes straight through.
|
|
81
|
+
*/
|
|
82
|
+
export declare const Picker: ({ appearance, className, iconEnd, intent, isDisabled, label, onChange, options, placement, placeholder, size, value, ...buttonProps }: PickerProps) => ReactNode;
|
|
83
|
+
//# sourceMappingURL=Picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Picker.d.ts","sourceRoot":"","sources":["../../src/Picker/Picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAGd,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAGzD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAA;AAwBtC,MAAM,MAAM,WAAW,GAAG,IAAI,CAC5B,qBAAqB,CAAC,QAAQ,CAAC,EAC/B,UAAU,GAAG,UAAU,GAAG,OAAO,CAClC,GAAG;IACF,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IAChC,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,MAAM,GAAI,uIAcpB,WAAW,KAAG,SAoDhB,CAAA"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useVisibility } from "@charcuterie/logic";
|
|
3
|
+
import { Button } from "../Button/Button.js";
|
|
4
|
+
import { Listbox } from "../Listbox/Listbox.js";
|
|
5
|
+
/**
|
|
6
|
+
* A constant element rather than a component, for the same reason
|
|
7
|
+
* `Select` inlines its own: a second component in this file trips the
|
|
8
|
+
* house rule, and this never varies.
|
|
9
|
+
*/
|
|
10
|
+
const CHEVRON_DOWN = (_jsx("svg", { "aria-hidden": "true", className: "size-4", fill: "none", focusable: false, stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.75, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "m6 9 6 6 6-6" }) }));
|
|
11
|
+
/**
|
|
12
|
+
* A `Listbox` with the trigger already attached — the shape every app
|
|
13
|
+
* reaches for when it wants "a select, but ours".
|
|
14
|
+
*
|
|
15
|
+
* ### Four repos wrote this, and the fourth is this package
|
|
16
|
+
*
|
|
17
|
+
* `Listbox` is deliberately trigger-agnostic: it takes a `trigger`
|
|
18
|
+
* element and owns none of the open state, which is what lets it hang
|
|
19
|
+
* off a button, a tile, a table header. The cost is that the *common*
|
|
20
|
+
* case — a button showing the current value — is about thirty lines,
|
|
21
|
+
* and the fleet wrote them four times: queuepilot's `SelectListbox`,
|
|
22
|
+
* board-games' `SelectMenu` (on `useState` rather than
|
|
23
|
+
* `useVisibility`), mux-magic's `ListboxPicker`, and — the tell —
|
|
24
|
+
* **twice inside this package**, in `QueryBuilderCombinator` and in
|
|
25
|
+
* `QueryBuilder`'s own story. Each also hand-rolled the same chevron.
|
|
26
|
+
*
|
|
27
|
+
* `Listbox` stays exactly as it is. This is the assembled default.
|
|
28
|
+
*
|
|
29
|
+
* ### Why it is not called `SinglePicker`
|
|
30
|
+
*
|
|
31
|
+
* The state layer has `useSinglePicker` and `useMultiplePicker`, so a
|
|
32
|
+
* name near "picker" invites the question of whether this is their
|
|
33
|
+
* component. It is not, and no component is: `useSinglePicker` is a
|
|
34
|
+
* state kind that `Listbox`, `Combobox` and `Tabs` all compose. This
|
|
35
|
+
* component owns a `useVisibility` and delegates selection to
|
|
36
|
+
* `Listbox`, which is where the picker state actually lives. The name
|
|
37
|
+
* is the owner's, chosen over `SelectListbox` and `ListboxButton`.
|
|
38
|
+
*
|
|
39
|
+
* ### The accessible name carries the current value
|
|
40
|
+
*
|
|
41
|
+
* `"Language: English"`, not `"Language"`. The trigger's visible text
|
|
42
|
+
* IS the current value, and WCAG 2.5.3 requires the visible text to be
|
|
43
|
+
* contained in the accessible name — so a bare `aria-label={label}`
|
|
44
|
+
* fails it, which is a latent defect in one of the hand-rolled
|
|
45
|
+
* versions this replaces. It also makes each control uniquely findable
|
|
46
|
+
* when a form has several, which is the property `expectAgentDrivable`
|
|
47
|
+
* exists to protect.
|
|
48
|
+
*
|
|
49
|
+
* Query it as `getByRole("button", { name: /^Language: / })`.
|
|
50
|
+
*
|
|
51
|
+
* ### `id` does not survive; `data-testid` does
|
|
52
|
+
*
|
|
53
|
+
* `useAnchoredOverlay` overwrites the trigger's `id` with a generated
|
|
54
|
+
* one, so the portalled listbox can point `aria-labelledby` across at
|
|
55
|
+
* it. An `id` passed here is therefore silently replaced — queuepilot
|
|
56
|
+
* found this the hard way and switched its e2e handles to
|
|
57
|
+
* `data-testid`, which nothing injects. Everything else on the button
|
|
58
|
+
* (`data-*`, `onClick`, `form`, …) passes straight through.
|
|
59
|
+
*/
|
|
60
|
+
export const Picker = ({ appearance = "outline", className, iconEnd = CHEVRON_DOWN, intent = "neutral", isDisabled = false, label, onChange, options, placement, placeholder, size, value, ...buttonProps }) => {
|
|
61
|
+
const { hide, isVisible, toggle } = useVisibility();
|
|
62
|
+
const currentOption = options.find((option) => option.value === value);
|
|
63
|
+
const currentText = currentOption === undefined
|
|
64
|
+
? placeholder
|
|
65
|
+
: (currentOption.textValue ??
|
|
66
|
+
(typeof currentOption.label === "string"
|
|
67
|
+
? currentOption.label
|
|
68
|
+
: undefined));
|
|
69
|
+
const triggerText = currentText ?? placeholder ?? "";
|
|
70
|
+
return (_jsx(Listbox
|
|
71
|
+
// A disabled control must not open, and hiding the panel is a
|
|
72
|
+
// truer way to say that than trusting the trigger's click alone —
|
|
73
|
+
// Escape and outside-press both route through `onDismiss`.
|
|
74
|
+
, {
|
|
75
|
+
// A disabled control must not open, and hiding the panel is a
|
|
76
|
+
// truer way to say that than trusting the trigger's click alone —
|
|
77
|
+
// Escape and outside-press both route through `onDismiss`.
|
|
78
|
+
isVisible: isVisible && !isDisabled, onDismiss: hide, onSelect: onChange, options: options, placement: placement, selectedValue: value, trigger: _jsx(Button, { ...buttonProps, appearance: appearance, "aria-label": `${label}: ${triggerText}`, className: className, iconEnd: iconEnd, intent: intent, isDisabled: isDisabled, onClick: (clickEvent) => {
|
|
79
|
+
buttonProps.onClick?.(clickEvent);
|
|
80
|
+
toggle();
|
|
81
|
+
}, size: size, type: "button", children: currentOption === undefined
|
|
82
|
+
? placeholder
|
|
83
|
+
: currentOption.label }) }));
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=Picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Picker.js","sourceRoot":"","sources":["../../src/Picker/Picker.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAWlD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAG7C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAwB,CAAA;AAIhD;;;;GAIG;AACH,MAAM,YAAY,GAAG,CACnB,6BACc,MAAM,EAClB,SAAS,EAAC,QAAQ,EAClB,IAAI,EAAC,MAAM,EACX,SAAS,EAAE,KAAK,EAChB,MAAM,EAAC,cAAc,EACrB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,WAAW,EAAE,IAAI,EACjB,OAAO,EAAC,WAAW,EACnB,KAAK,EAAC,4BAA4B,YAElC,eAAM,CAAC,EAAC,cAAc,GAAG,GACrB,CACP,CAAA;AAgCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,UAAU,GAAG,SAAS,EACtB,SAAS,EACT,OAAO,GAAG,YAAY,EACtB,MAAM,GAAG,SAAS,EAClB,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACX,IAAI,EACJ,KAAK,EACL,GAAG,WAAW,EACF,EAAa,EAAE;IAC3B,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAAA;IAEnD,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAChC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,KAAK,CACnC,CAAA;IAED,MAAM,WAAW,GACf,aAAa,KAAK,SAAS;QACzB,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,CAAC,aAAa,CAAC,SAAS;YACxB,CAAC,OAAO,aAAa,CAAC,KAAK,KAAK,QAAQ;gBACtC,CAAC,CAAC,aAAa,CAAC,KAAK;gBACrB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;IAErB,MAAM,WAAW,GAAG,WAAW,IAAI,WAAW,IAAI,EAAE,CAAA;IAEpD,OAAO,CACL,KAAC,OAAO;IACN,8DAA8D;IAC9D,kEAAkE;IAClE,2DAA2D;;QAF3D,8DAA8D;QAC9D,kEAAkE;QAClE,2DAA2D;QAC3D,SAAS,EAAE,SAAS,IAAI,CAAC,UAAU,EACnC,SAAS,EAAE,IAAI,EACf,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,KAAK,EACpB,OAAO,EACL,KAAC,MAAM,OACD,WAAW,EACf,UAAU,EAAE,UAAU,gBACV,GAAG,KAAK,KAAK,WAAW,EAAE,EACtC,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,CAAC,UAAU,EAAE,EAAE;gBACtB,WAAW,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,CAAA;gBAEjC,MAAM,EAAE,CAAA;YACV,CAAC,EACD,IAAI,EAAE,IAAI,EACV,IAAI,EAAC,QAAQ,YAEZ,aAAa,KAAK,SAAS;gBAC1B,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,aAAa,CAAC,KAAK,GAChB,GAEX,CACH,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -12,42 +12,21 @@ export type QueryBuilderCombinatorProps<Combinator> = {
|
|
|
12
12
|
/**
|
|
13
13
|
* One group's combinator picker.
|
|
14
14
|
*
|
|
15
|
-
* A `
|
|
15
|
+
* A `Picker` — a `Listbox` with its trigger attached — rather than a
|
|
16
|
+
* native `Select`, per
|
|
16
17
|
* [the 2026-08-10 demotion](../../../../docs/decisions/2026-08-10-listbox-and-combobox-are-the-default-and-select-is-demoted.md).
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* catch it because the component-choice block is scoped to app
|
|
20
|
-
* packages and exempts this one — right for a primitive, wrong for a
|
|
21
|
-
* composite an app consumes whole.
|
|
18
|
+
* This file used to assemble the trigger by hand; `Picker` exists
|
|
19
|
+
* because it was the fourth place in the fleet doing so.
|
|
22
20
|
*
|
|
23
|
-
* ###
|
|
21
|
+
* ### What is left here, and why it is still a component
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* `Field` wires `<label htmlFor>` by cloning its `id` onto the
|
|
33
|
-
* control. `Listbox`'s trigger cannot take one: `useAnchoredOverlay`
|
|
34
|
-
* overwrites the cloned trigger's `id` with the one the panel points
|
|
35
|
-
* `aria-labelledby` at, so the label would address an element that no
|
|
36
|
-
* longer carries that id — the exact defect `Field` exists to
|
|
37
|
-
* prevent. So the caption is plain text and the trigger is named with
|
|
38
|
-
* `aria-label`.
|
|
39
|
-
*
|
|
40
|
-
* That name is `"<label>: <current value>"` rather than bare
|
|
41
|
-
* `"<label>"` on purpose: the button's visible text is the current
|
|
42
|
-
* combinator, and WCAG 2.5.3 wants the visible text contained in the
|
|
43
|
-
* accessible name. "Match" alone would fail it; "Match: ALL — every
|
|
44
|
-
* condition" reads correctly and keeps each group's control findable
|
|
45
|
-
* by name.
|
|
46
|
-
*
|
|
47
|
-
* The combinator stays opaque here exactly as it is in the rest of
|
|
48
|
-
* the component: `String(value)` bridges it to the string-valued
|
|
49
|
-
* `Listbox`, and a selection is mapped back to the real `Combinator`
|
|
50
|
-
* before it leaves.
|
|
23
|
+
* Only the bridging. `Combinator` is opaque to `QueryBuilder`, so it
|
|
24
|
+
* cannot reach `Picker`'s string-valued API directly: `String(value)`
|
|
25
|
+
* carries it out and the matching option carries it back, so the
|
|
26
|
+
* generic never leaks. The caption is plain text rather than a
|
|
27
|
+
* `Field`, because `useAnchoredOverlay` overwrites the trigger's `id`
|
|
28
|
+
* and a `<label htmlFor>` would then address an element that no longer
|
|
29
|
+
* carries it — the exact defect `Field` exists to prevent.
|
|
51
30
|
*/
|
|
52
31
|
export declare const QueryBuilderCombinator: <Combinator>({ label, onChange, options, value, }: QueryBuilderCombinatorProps<Combinator>) => ReactNode;
|
|
53
32
|
//# sourceMappingURL=QueryBuilderCombinator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QueryBuilderCombinator.d.ts","sourceRoot":"","sources":["../../src/QueryBuilder/QueryBuilderCombinator.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"QueryBuilderCombinator.d.ts","sourceRoot":"","sources":["../../src/QueryBuilder/QueryBuilderCombinator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,MAAM,MAAM,2BAA2B,CAAC,UAAU,IAAI;IACpD,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAA;IAC1C,OAAO,EAAE,SAAS;QAChB,KAAK,EAAE,MAAM,CAAA;QACb,KAAK,EAAE,UAAU,CAAA;KAClB,EAAE,CAAA;IACH,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sBAAsB,GAAI,UAAU,EAAG,sCAKjD,2BAA2B,CAAC,UAAU,CAAC,KAAG,SA6B5C,CAAA"}
|
|
@@ -1,59 +1,41 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { Button } from "../Button/Button.js";
|
|
4
|
-
import { Listbox } from "../Listbox/Listbox.js";
|
|
2
|
+
import { Picker } from "../Picker/Picker.js";
|
|
5
3
|
/**
|
|
6
4
|
* One group's combinator picker.
|
|
7
5
|
*
|
|
8
|
-
* A `
|
|
6
|
+
* A `Picker` — a `Listbox` with its trigger attached — rather than a
|
|
7
|
+
* native `Select`, per
|
|
9
8
|
* [the 2026-08-10 demotion](../../../../docs/decisions/2026-08-10-listbox-and-combobox-are-the-default-and-select-is-demoted.md).
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* catch it because the component-choice block is scoped to app
|
|
13
|
-
* packages and exempts this one — right for a primitive, wrong for a
|
|
14
|
-
* composite an app consumes whole.
|
|
9
|
+
* This file used to assemble the trigger by hand; `Picker` exists
|
|
10
|
+
* because it was the fourth place in the fleet doing so.
|
|
15
11
|
*
|
|
16
|
-
* ###
|
|
12
|
+
* ### What is left here, and why it is still a component
|
|
17
13
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* `Field` wires `<label htmlFor>` by cloning its `id` onto the
|
|
26
|
-
* control. `Listbox`'s trigger cannot take one: `useAnchoredOverlay`
|
|
27
|
-
* overwrites the cloned trigger's `id` with the one the panel points
|
|
28
|
-
* `aria-labelledby` at, so the label would address an element that no
|
|
29
|
-
* longer carries that id — the exact defect `Field` exists to
|
|
30
|
-
* prevent. So the caption is plain text and the trigger is named with
|
|
31
|
-
* `aria-label`.
|
|
32
|
-
*
|
|
33
|
-
* That name is `"<label>: <current value>"` rather than bare
|
|
34
|
-
* `"<label>"` on purpose: the button's visible text is the current
|
|
35
|
-
* combinator, and WCAG 2.5.3 wants the visible text contained in the
|
|
36
|
-
* accessible name. "Match" alone would fail it; "Match: ALL — every
|
|
37
|
-
* condition" reads correctly and keeps each group's control findable
|
|
38
|
-
* by name.
|
|
39
|
-
*
|
|
40
|
-
* The combinator stays opaque here exactly as it is in the rest of
|
|
41
|
-
* the component: `String(value)` bridges it to the string-valued
|
|
42
|
-
* `Listbox`, and a selection is mapped back to the real `Combinator`
|
|
43
|
-
* before it leaves.
|
|
14
|
+
* Only the bridging. `Combinator` is opaque to `QueryBuilder`, so it
|
|
15
|
+
* cannot reach `Picker`'s string-valued API directly: `String(value)`
|
|
16
|
+
* carries it out and the matching option carries it back, so the
|
|
17
|
+
* generic never leaks. The caption is plain text rather than a
|
|
18
|
+
* `Field`, because `useAnchoredOverlay` overwrites the trigger's `id`
|
|
19
|
+
* and a `<label htmlFor>` would then address an element that no longer
|
|
20
|
+
* carries it — the exact defect `Field` exists to prevent.
|
|
44
21
|
*/
|
|
45
|
-
export const QueryBuilderCombinator = ({ label, onChange, options, value, }) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
22
|
+
export const QueryBuilderCombinator = ({ label, onChange, options, value, }) => (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: "font-medium text-content-primary text-sm", children: label }), _jsx(Picker
|
|
23
|
+
// No chevron: this is a refactor onto the shared component, and
|
|
24
|
+
// the trigger that shipped in 2.14.0 had none. Adding the
|
|
25
|
+
// affordance here is a visual change and belongs in its own PR,
|
|
26
|
+
// not smuggled in under an extraction.
|
|
27
|
+
, {
|
|
28
|
+
// No chevron: this is a refactor onto the shared component, and
|
|
29
|
+
// the trigger that shipped in 2.14.0 had none. Adding the
|
|
30
|
+
// affordance here is a visual change and belongs in its own PR,
|
|
31
|
+
// not smuggled in under an extraction.
|
|
32
|
+
iconEnd: null, label: label, onChange: (nextValue) => {
|
|
33
|
+
const match = options.find((option) => String(option.value) === nextValue);
|
|
34
|
+
if (match) {
|
|
35
|
+
onChange(match.value);
|
|
36
|
+
}
|
|
37
|
+
}, options: options.map((option) => ({
|
|
38
|
+
label: option.label,
|
|
39
|
+
value: String(option.value),
|
|
40
|
+
})), value: String(value) })] }));
|
|
59
41
|
//# sourceMappingURL=QueryBuilderCombinator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QueryBuilderCombinator.js","sourceRoot":"","sources":["../../src/QueryBuilder/QueryBuilderCombinator.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"QueryBuilderCombinator.js","sourceRoot":"","sources":["../../src/QueryBuilder/QueryBuilderCombinator.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAa7C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAc,EAClD,KAAK,EACL,QAAQ,EACR,OAAO,EACP,KAAK,GACmC,EAAa,EAAE,CAAC,CACxD,eAAK,SAAS,EAAC,uBAAuB,aACpC,eAAM,SAAS,EAAC,0CAA0C,YACvD,KAAK,GACD,EAEP,KAAC,MAAM;QACL,gEAAgE;QAChE,0DAA0D;QAC1D,gEAAgE;QAChE,uCAAuC;;YAHvC,gEAAgE;YAChE,0DAA0D;YAC1D,gEAAgE;YAChE,uCAAuC;YACvC,OAAO,EAAE,IAAI,EACb,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,SAAS,EAAE,EAAE;gBACtB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CACxB,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,SAAS,CAC/C,CAAA;gBAED,IAAI,KAAK,EAAE,CAAC;oBACV,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;gBACvB,CAAC;YACH,CAAC,EACD,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAChC,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;aAC5B,CAAC,CAAC,EACH,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GACpB,IACE,CACP,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -92,6 +92,8 @@ export type { MenuItem, MenuProps, } from "./Menu/Menu.tsx";
|
|
|
92
92
|
export { Menu } from "./Menu/Menu.tsx";
|
|
93
93
|
export type { ModalProps } from "./Modal/Modal.tsx";
|
|
94
94
|
export { Modal } from "./Modal/Modal.tsx";
|
|
95
|
+
export type { PickerOption, PickerProps, } from "./Picker/Picker.tsx";
|
|
96
|
+
export { Picker } from "./Picker/Picker.tsx";
|
|
95
97
|
export type { PopoverProps } from "./Popover/Popover.tsx";
|
|
96
98
|
export { Popover } from "./Popover/Popover.tsx";
|
|
97
99
|
export type { ProgressBarProps, ProgressBarSize, } from "./ProgressBar/ProgressBar.tsx";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -61,6 +61,7 @@ export { MediaTile } from "./MediaTile/MediaTile.js";
|
|
|
61
61
|
export { mediaTransitions } from "./MediaTile/mediaStatus.js";
|
|
62
62
|
export { Menu } from "./Menu/Menu.js";
|
|
63
63
|
export { Modal } from "./Modal/Modal.js";
|
|
64
|
+
export { Picker } from "./Picker/Picker.js";
|
|
64
65
|
export { Popover } from "./Popover/Popover.js";
|
|
65
66
|
export { ProgressBar } from "./ProgressBar/ProgressBar.js";
|
|
66
67
|
export { getProgressIntent, toProgressPercent, toProgressValue, } from "./ProgressBar/progressValue.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAiC,CAAA;AAE9D,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,0BAA0B,EAC1B,iCAAiC,EACjC,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,iCAAiC,CAAA;AAKxC,OAAO,EACL,6BAA6B,EAC7B,gBAAgB,EAChB,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,sCAAsC,CAAA;AAK7C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAE5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAMxD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAElD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA+C,CAAA;AAKnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA2C,CAAA;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAElD,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAK3B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAK5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAwB,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAiC,CAAA;AAE9D,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAE5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAExD,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAKlD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAuB,CAAA;AAK/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA+C,CAAA;AAKnF,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAErD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAKtC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAK7D,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAEtC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Canvas, Meta } from "@storybook/addon-docs/blocks"
|
|
2
|
+
|
|
3
|
+
import * as PickerStories from "./Picker.stories.tsx"
|
|
4
|
+
|
|
5
|
+
<Meta of={PickerStories} />
|
|
6
|
+
|
|
7
|
+
# Picker
|
|
8
|
+
|
|
9
|
+
A `Listbox` with its trigger already attached — the shape an app reaches for when it wants
|
|
10
|
+
"a select, but ours".
|
|
11
|
+
|
|
12
|
+
<Canvas of={PickerStories.Default} />
|
|
13
|
+
|
|
14
|
+
## Why this exists when `Listbox` already does
|
|
15
|
+
|
|
16
|
+
`Listbox` is deliberately trigger-agnostic. It takes a `trigger` element and owns none of the
|
|
17
|
+
open state, which is what lets it hang off a button, a tile, or a table header. The cost is
|
|
18
|
+
that the *common* case — a button showing the current value, with a chevron — is about thirty
|
|
19
|
+
lines, and the fleet wrote those lines **four separate times**: queuepilot's
|
|
20
|
+
`SelectListbox`, board-games' `SelectMenu` (on `useState` rather than `useVisibility`),
|
|
21
|
+
mux-magic's `ListboxPicker`, and twice inside this package (`QueryBuilderCombinator` and
|
|
22
|
+
`QueryBuilder`'s own story). Each hand-rolled the same chevron.
|
|
23
|
+
|
|
24
|
+
`Listbox` is unchanged. `Picker` is the assembled default; reach for `Listbox` directly when
|
|
25
|
+
the trigger is not a button.
|
|
26
|
+
|
|
27
|
+
## The accessible name carries the current value
|
|
28
|
+
|
|
29
|
+
`"Language: English"`, not `"Language"`. The trigger's visible text **is** the value, and
|
|
30
|
+
WCAG 2.5.3 requires the visible text to be contained in the accessible name — a bare
|
|
31
|
+
`aria-label={label}` fails it. It also keeps each control uniquely findable when a form has
|
|
32
|
+
several:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
getByRole("button", { name: /^Language: / })
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
<Canvas of={PickerStories.Interactive} />
|
|
39
|
+
|
|
40
|
+
## Rich options need a `textValue`
|
|
41
|
+
|
|
42
|
+
`label` takes a `ReactNode` — a flag, a badge, two lines. When it is not a plain string, give
|
|
43
|
+
the option a `textValue`: it is the type-ahead target, and it is what the trigger and the
|
|
44
|
+
accessible name read, so the button says `Audio track: English` rather than the flattened
|
|
45
|
+
text of the whole node.
|
|
46
|
+
|
|
47
|
+
<Canvas of={PickerStories.AllStates} />
|
|
48
|
+
|
|
49
|
+
## `id` does not survive; `data-testid` does
|
|
50
|
+
|
|
51
|
+
`useAnchoredOverlay` overwrites the trigger's `id` with a generated one so the portalled
|
|
52
|
+
listbox can point `aria-labelledby` across at it. An `id` passed to `Picker` is therefore
|
|
53
|
+
silently replaced — queuepilot found this the hard way and moved its e2e handles to
|
|
54
|
+
`data-testid`, which nothing injects. Every other button prop (`data-*`, `onClick`, `form`,
|
|
55
|
+
…) passes straight through; an `onClick` you pass runs before the panel toggles, which is how
|
|
56
|
+
a picker inside a clickable row stops the event reaching its parent.
|
|
57
|
+
|
|
58
|
+
## Not the component of `useSinglePicker`
|
|
59
|
+
|
|
60
|
+
The state layer has `useSinglePicker` and `useMultiplePicker`, and the name invites the
|
|
61
|
+
question. `useSinglePicker` is a **state kind** that `Listbox`, `Combobox` and `Tabs` all
|
|
62
|
+
compose; it has no component of its own. `Picker` owns a `useVisibility` and delegates
|
|
63
|
+
selection to `Listbox`, which is where that state actually lives.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import type { ReactNode } from "react"
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
controlSizeArgType,
|
|
7
|
+
intentArgType,
|
|
8
|
+
placementArgType,
|
|
9
|
+
} from "../argTypes.storyHelpers.ts"
|
|
10
|
+
import { Badge } from "../Badge/Badge.tsx"
|
|
11
|
+
import {
|
|
12
|
+
StoryRow,
|
|
13
|
+
StorySection,
|
|
14
|
+
} from "../board.storyHelpers.tsx"
|
|
15
|
+
import type { PickerOption } from "./Picker.tsx"
|
|
16
|
+
import { Picker } from "./Picker.tsx"
|
|
17
|
+
|
|
18
|
+
const LANGUAGES: PickerOption[] = [
|
|
19
|
+
{ label: "English", value: "eng" },
|
|
20
|
+
{ label: "Spanish", value: "spa" },
|
|
21
|
+
{ label: "French", value: "fra" },
|
|
22
|
+
{ isDisabled: true, label: "German", value: "deu" },
|
|
23
|
+
{ label: "Japanese", value: "jpn" },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The rich options a native `<option>` cannot render — a trailing
|
|
28
|
+
* intent-coloured badge. No emoji: the VRT runner has no emoji font, so
|
|
29
|
+
* a flag would snapshot as a blank box and make the shot font-dependent.
|
|
30
|
+
*/
|
|
31
|
+
const TRACKS: PickerOption[] = [
|
|
32
|
+
{
|
|
33
|
+
label: (
|
|
34
|
+
<span className="flex items-center gap-2">
|
|
35
|
+
<span>English</span>
|
|
36
|
+
<Badge intent="neutral" size="sm">
|
|
37
|
+
5.1
|
|
38
|
+
</Badge>
|
|
39
|
+
</span>
|
|
40
|
+
),
|
|
41
|
+
textValue: "English",
|
|
42
|
+
value: "eng",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: (
|
|
46
|
+
<span className="flex items-center gap-2">
|
|
47
|
+
<span>Japanese</span>
|
|
48
|
+
<Badge intent="warning" size="sm">
|
|
49
|
+
forced
|
|
50
|
+
</Badge>
|
|
51
|
+
</span>
|
|
52
|
+
),
|
|
53
|
+
textValue: "Japanese",
|
|
54
|
+
value: "jpn",
|
|
55
|
+
},
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
const meta = {
|
|
59
|
+
title: "Components/Picker",
|
|
60
|
+
component: Picker,
|
|
61
|
+
parameters: { layout: "padded" },
|
|
62
|
+
argTypes: {
|
|
63
|
+
intent: intentArgType,
|
|
64
|
+
placement: placementArgType,
|
|
65
|
+
size: controlSizeArgType,
|
|
66
|
+
},
|
|
67
|
+
args: {
|
|
68
|
+
label: "Language",
|
|
69
|
+
onChange: () => {},
|
|
70
|
+
options: LANGUAGES,
|
|
71
|
+
},
|
|
72
|
+
} satisfies Meta<typeof Picker>
|
|
73
|
+
|
|
74
|
+
export default meta
|
|
75
|
+
|
|
76
|
+
type Story = StoryObj<typeof meta>
|
|
77
|
+
|
|
78
|
+
const PickerHarness = ({
|
|
79
|
+
initialValue,
|
|
80
|
+
label,
|
|
81
|
+
options,
|
|
82
|
+
placeholder,
|
|
83
|
+
}: {
|
|
84
|
+
initialValue?: string
|
|
85
|
+
label: string
|
|
86
|
+
options: PickerOption[]
|
|
87
|
+
placeholder?: string
|
|
88
|
+
}): ReactNode => {
|
|
89
|
+
const [value, setValue] = useState<string | undefined>(
|
|
90
|
+
initialValue,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Picker
|
|
95
|
+
label={label}
|
|
96
|
+
onChange={setValue}
|
|
97
|
+
options={options}
|
|
98
|
+
placeholder={placeholder}
|
|
99
|
+
value={value}
|
|
100
|
+
/>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The assembled default: a button showing the current value, a
|
|
106
|
+
* chevron, and the `Listbox` panel already wired to it.
|
|
107
|
+
*/
|
|
108
|
+
export const Default: Story = {
|
|
109
|
+
render: () => (
|
|
110
|
+
<PickerHarness
|
|
111
|
+
initialValue="eng"
|
|
112
|
+
label="Language"
|
|
113
|
+
options={LANGUAGES}
|
|
114
|
+
/>
|
|
115
|
+
),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Nothing chosen yet, a disabled option, and rich option labels —
|
|
120
|
+
* each with a `textValue` so type-ahead and the trigger's text stay
|
|
121
|
+
* plain strings.
|
|
122
|
+
*/
|
|
123
|
+
export const AllStates: Story = {
|
|
124
|
+
render: () => (
|
|
125
|
+
<div className="flex flex-col gap-6">
|
|
126
|
+
<StorySection title="Seeded">
|
|
127
|
+
<StoryRow>
|
|
128
|
+
<PickerHarness
|
|
129
|
+
initialValue="spa"
|
|
130
|
+
label="Language"
|
|
131
|
+
options={LANGUAGES}
|
|
132
|
+
/>
|
|
133
|
+
</StoryRow>
|
|
134
|
+
</StorySection>
|
|
135
|
+
|
|
136
|
+
<StorySection title="Empty — reads its placeholder">
|
|
137
|
+
<StoryRow>
|
|
138
|
+
<PickerHarness
|
|
139
|
+
label="Language"
|
|
140
|
+
options={LANGUAGES}
|
|
141
|
+
placeholder="Choose a language…"
|
|
142
|
+
/>
|
|
143
|
+
</StoryRow>
|
|
144
|
+
</StorySection>
|
|
145
|
+
|
|
146
|
+
<StorySection title="Rich options">
|
|
147
|
+
<StoryRow>
|
|
148
|
+
<PickerHarness
|
|
149
|
+
initialValue="eng"
|
|
150
|
+
label="Audio track"
|
|
151
|
+
options={TRACKS}
|
|
152
|
+
/>
|
|
153
|
+
</StoryRow>
|
|
154
|
+
</StorySection>
|
|
155
|
+
|
|
156
|
+
<StorySection title="Disabled">
|
|
157
|
+
<StoryRow>
|
|
158
|
+
<Picker
|
|
159
|
+
isDisabled
|
|
160
|
+
label="Language"
|
|
161
|
+
onChange={() => {}}
|
|
162
|
+
options={LANGUAGES}
|
|
163
|
+
value="eng"
|
|
164
|
+
/>
|
|
165
|
+
</StoryRow>
|
|
166
|
+
</StorySection>
|
|
167
|
+
</div>
|
|
168
|
+
),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Drive it: open, choose, and watch the trigger's accessible name
|
|
173
|
+
* follow the value — `"Language: English"` becomes
|
|
174
|
+
* `"Language: French"`.
|
|
175
|
+
*/
|
|
176
|
+
export const Interactive: Story = {
|
|
177
|
+
render: () => (
|
|
178
|
+
<PickerHarness
|
|
179
|
+
initialValue="eng"
|
|
180
|
+
label="Language"
|
|
181
|
+
options={LANGUAGES}
|
|
182
|
+
/>
|
|
183
|
+
),
|
|
184
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { composeStories } from "@storybook/react"
|
|
2
|
+
import { expect, userEvent, waitFor } from "storybook/test"
|
|
3
|
+
import { test } from "vitest"
|
|
4
|
+
|
|
5
|
+
import { expectNoAxeViolations } from "../expectNoAxeViolations.testHelpers.ts"
|
|
6
|
+
import { mountStory } from "../mountStory.testHelpers.ts"
|
|
7
|
+
import { expectAgentDrivable } from "../testing/index.ts"
|
|
8
|
+
import * as stories from "./Picker.stories.tsx"
|
|
9
|
+
|
|
10
|
+
const { AllStates, Default, Interactive } =
|
|
11
|
+
composeStories(stories)
|
|
12
|
+
|
|
13
|
+
test("the trigger's accessible name carries the current value", async () => {
|
|
14
|
+
const { canvas, canvasElement } =
|
|
15
|
+
await mountStory(Default)
|
|
16
|
+
|
|
17
|
+
// Not a bare "Language": the button's VISIBLE text is the value, and
|
|
18
|
+
// WCAG 2.5.3 wants the visible text inside the accessible name.
|
|
19
|
+
const trigger = expectAgentDrivable(canvas, {
|
|
20
|
+
name: "Language: English",
|
|
21
|
+
role: "button",
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
await expect(trigger).toHaveAttribute(
|
|
25
|
+
"aria-haspopup",
|
|
26
|
+
"listbox",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
await expectNoAxeViolations(canvasElement)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test("choosing an option updates the trigger", async () => {
|
|
33
|
+
const { body, canvas } = await mountStory(Interactive)
|
|
34
|
+
|
|
35
|
+
await userEvent.click(
|
|
36
|
+
expectAgentDrivable(canvas, {
|
|
37
|
+
name: "Language: English",
|
|
38
|
+
role: "button",
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
await userEvent.click(
|
|
43
|
+
expectAgentDrivable(body, {
|
|
44
|
+
name: "French",
|
|
45
|
+
role: "option",
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
await waitFor(() => {
|
|
50
|
+
expectAgentDrivable(canvas, {
|
|
51
|
+
name: "Language: French",
|
|
52
|
+
role: "button",
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test("a picker with no value reads its placeholder", async () => {
|
|
58
|
+
const { canvas } = await mountStory(AllStates)
|
|
59
|
+
|
|
60
|
+
expectAgentDrivable(canvas, {
|
|
61
|
+
name: "Language: Choose a language…",
|
|
62
|
+
role: "button",
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("a disabled picker does not open", async () => {
|
|
67
|
+
const { body, canvas } = await mountStory(AllStates)
|
|
68
|
+
|
|
69
|
+
const disabledTrigger = canvas
|
|
70
|
+
.getAllByRole("button", { name: "Language: English" })
|
|
71
|
+
.find((button: HTMLElement) =>
|
|
72
|
+
button.hasAttribute("disabled"),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
await expect(disabledTrigger).toBeDefined()
|
|
76
|
+
|
|
77
|
+
await userEvent.click(disabledTrigger as HTMLElement, {
|
|
78
|
+
pointerEventsCheck: 0,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
await expect(body.queryAllByRole("listbox")).toHaveLength(
|
|
82
|
+
0,
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("a rich option label keeps a plain-string trigger via textValue", async () => {
|
|
87
|
+
const { canvas, canvasElement } =
|
|
88
|
+
await mountStory(AllStates)
|
|
89
|
+
|
|
90
|
+
// The label is JSX (a flag, a badge); `textValue` is what the name
|
|
91
|
+
// and the type-ahead use, so the trigger does not read "🇬🇧 English 5.1".
|
|
92
|
+
expectAgentDrivable(canvas, {
|
|
93
|
+
name: "Audio track: English",
|
|
94
|
+
role: "button",
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
await expectNoAxeViolations(canvasElement)
|
|
98
|
+
})
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { useVisibility } from "@charcuterie/logic"
|
|
2
|
+
import type {
|
|
3
|
+
ControlSize,
|
|
4
|
+
IntentName,
|
|
5
|
+
} from "@charcuterie/tokens"
|
|
6
|
+
import type { Placement } from "@floating-ui/react"
|
|
7
|
+
import type {
|
|
8
|
+
ComponentPropsWithRef,
|
|
9
|
+
ReactNode,
|
|
10
|
+
} from "react"
|
|
11
|
+
|
|
12
|
+
import { Button } from "../Button/Button.tsx"
|
|
13
|
+
import type { IntentAppearance } from "../intentStyles.ts"
|
|
14
|
+
import type { ListboxItem } from "../Listbox/Listbox.tsx"
|
|
15
|
+
import { Listbox } from "../Listbox/Listbox.tsx"
|
|
16
|
+
|
|
17
|
+
export type PickerOption = ListboxItem
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A constant element rather than a component, for the same reason
|
|
21
|
+
* `Select` inlines its own: a second component in this file trips the
|
|
22
|
+
* house rule, and this never varies.
|
|
23
|
+
*/
|
|
24
|
+
const CHEVRON_DOWN = (
|
|
25
|
+
<svg
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
className="size-4"
|
|
28
|
+
fill="none"
|
|
29
|
+
focusable={false}
|
|
30
|
+
stroke="currentColor"
|
|
31
|
+
strokeLinecap="round"
|
|
32
|
+
strokeLinejoin="round"
|
|
33
|
+
strokeWidth={1.75}
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
36
|
+
>
|
|
37
|
+
<path d="m6 9 6 6 6-6" />
|
|
38
|
+
</svg>
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
export type PickerProps = Omit<
|
|
42
|
+
ComponentPropsWithRef<"button">,
|
|
43
|
+
"disabled" | "onChange" | "value"
|
|
44
|
+
> & {
|
|
45
|
+
appearance?: IntentAppearance
|
|
46
|
+
className?: string
|
|
47
|
+
intent?: IntentName
|
|
48
|
+
isDisabled?: boolean
|
|
49
|
+
/**
|
|
50
|
+
* What the control is FOR — "Language", "Quantifier", "Rule type".
|
|
51
|
+
* Not the current value; that is read off `options`.
|
|
52
|
+
*/
|
|
53
|
+
label: string
|
|
54
|
+
/**
|
|
55
|
+
* Defaults to a chevron. Pass `null` to drop it — a picker rendered
|
|
56
|
+
* inside a denser control row may not want the affordance, and a
|
|
57
|
+
* refactor moving an existing hand-rolled trigger onto `Picker`
|
|
58
|
+
* stays pixel-identical by passing `null` rather than re-baselining
|
|
59
|
+
* every screenshot that contains it.
|
|
60
|
+
*/
|
|
61
|
+
iconEnd?: ReactNode
|
|
62
|
+
onChange: (value: string) => void
|
|
63
|
+
options: readonly PickerOption[]
|
|
64
|
+
placement?: Placement
|
|
65
|
+
/** Read by the trigger when `value` matches no option. */
|
|
66
|
+
placeholder?: string
|
|
67
|
+
size?: ControlSize
|
|
68
|
+
value?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A `Listbox` with the trigger already attached — the shape every app
|
|
73
|
+
* reaches for when it wants "a select, but ours".
|
|
74
|
+
*
|
|
75
|
+
* ### Four repos wrote this, and the fourth is this package
|
|
76
|
+
*
|
|
77
|
+
* `Listbox` is deliberately trigger-agnostic: it takes a `trigger`
|
|
78
|
+
* element and owns none of the open state, which is what lets it hang
|
|
79
|
+
* off a button, a tile, a table header. The cost is that the *common*
|
|
80
|
+
* case — a button showing the current value — is about thirty lines,
|
|
81
|
+
* and the fleet wrote them four times: queuepilot's `SelectListbox`,
|
|
82
|
+
* board-games' `SelectMenu` (on `useState` rather than
|
|
83
|
+
* `useVisibility`), mux-magic's `ListboxPicker`, and — the tell —
|
|
84
|
+
* **twice inside this package**, in `QueryBuilderCombinator` and in
|
|
85
|
+
* `QueryBuilder`'s own story. Each also hand-rolled the same chevron.
|
|
86
|
+
*
|
|
87
|
+
* `Listbox` stays exactly as it is. This is the assembled default.
|
|
88
|
+
*
|
|
89
|
+
* ### Why it is not called `SinglePicker`
|
|
90
|
+
*
|
|
91
|
+
* The state layer has `useSinglePicker` and `useMultiplePicker`, so a
|
|
92
|
+
* name near "picker" invites the question of whether this is their
|
|
93
|
+
* component. It is not, and no component is: `useSinglePicker` is a
|
|
94
|
+
* state kind that `Listbox`, `Combobox` and `Tabs` all compose. This
|
|
95
|
+
* component owns a `useVisibility` and delegates selection to
|
|
96
|
+
* `Listbox`, which is where the picker state actually lives. The name
|
|
97
|
+
* is the owner's, chosen over `SelectListbox` and `ListboxButton`.
|
|
98
|
+
*
|
|
99
|
+
* ### The accessible name carries the current value
|
|
100
|
+
*
|
|
101
|
+
* `"Language: English"`, not `"Language"`. The trigger's visible text
|
|
102
|
+
* IS the current value, and WCAG 2.5.3 requires the visible text to be
|
|
103
|
+
* contained in the accessible name — so a bare `aria-label={label}`
|
|
104
|
+
* fails it, which is a latent defect in one of the hand-rolled
|
|
105
|
+
* versions this replaces. It also makes each control uniquely findable
|
|
106
|
+
* when a form has several, which is the property `expectAgentDrivable`
|
|
107
|
+
* exists to protect.
|
|
108
|
+
*
|
|
109
|
+
* Query it as `getByRole("button", { name: /^Language: / })`.
|
|
110
|
+
*
|
|
111
|
+
* ### `id` does not survive; `data-testid` does
|
|
112
|
+
*
|
|
113
|
+
* `useAnchoredOverlay` overwrites the trigger's `id` with a generated
|
|
114
|
+
* one, so the portalled listbox can point `aria-labelledby` across at
|
|
115
|
+
* it. An `id` passed here is therefore silently replaced — queuepilot
|
|
116
|
+
* found this the hard way and switched its e2e handles to
|
|
117
|
+
* `data-testid`, which nothing injects. Everything else on the button
|
|
118
|
+
* (`data-*`, `onClick`, `form`, …) passes straight through.
|
|
119
|
+
*/
|
|
120
|
+
export const Picker = ({
|
|
121
|
+
appearance = "outline",
|
|
122
|
+
className,
|
|
123
|
+
iconEnd = CHEVRON_DOWN,
|
|
124
|
+
intent = "neutral",
|
|
125
|
+
isDisabled = false,
|
|
126
|
+
label,
|
|
127
|
+
onChange,
|
|
128
|
+
options,
|
|
129
|
+
placement,
|
|
130
|
+
placeholder,
|
|
131
|
+
size,
|
|
132
|
+
value,
|
|
133
|
+
...buttonProps
|
|
134
|
+
}: PickerProps): ReactNode => {
|
|
135
|
+
const { hide, isVisible, toggle } = useVisibility()
|
|
136
|
+
|
|
137
|
+
const currentOption = options.find(
|
|
138
|
+
(option) => option.value === value,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
const currentText =
|
|
142
|
+
currentOption === undefined
|
|
143
|
+
? placeholder
|
|
144
|
+
: (currentOption.textValue ??
|
|
145
|
+
(typeof currentOption.label === "string"
|
|
146
|
+
? currentOption.label
|
|
147
|
+
: undefined))
|
|
148
|
+
|
|
149
|
+
const triggerText = currentText ?? placeholder ?? ""
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<Listbox
|
|
153
|
+
// A disabled control must not open, and hiding the panel is a
|
|
154
|
+
// truer way to say that than trusting the trigger's click alone —
|
|
155
|
+
// Escape and outside-press both route through `onDismiss`.
|
|
156
|
+
isVisible={isVisible && !isDisabled}
|
|
157
|
+
onDismiss={hide}
|
|
158
|
+
onSelect={onChange}
|
|
159
|
+
options={options}
|
|
160
|
+
placement={placement}
|
|
161
|
+
selectedValue={value}
|
|
162
|
+
trigger={
|
|
163
|
+
<Button
|
|
164
|
+
{...buttonProps}
|
|
165
|
+
appearance={appearance}
|
|
166
|
+
aria-label={`${label}: ${triggerText}`}
|
|
167
|
+
className={className}
|
|
168
|
+
iconEnd={iconEnd}
|
|
169
|
+
intent={intent}
|
|
170
|
+
isDisabled={isDisabled}
|
|
171
|
+
onClick={(clickEvent) => {
|
|
172
|
+
buttonProps.onClick?.(clickEvent)
|
|
173
|
+
|
|
174
|
+
toggle()
|
|
175
|
+
}}
|
|
176
|
+
size={size}
|
|
177
|
+
type="button"
|
|
178
|
+
>
|
|
179
|
+
{currentOption === undefined
|
|
180
|
+
? placeholder
|
|
181
|
+
: currentOption.label}
|
|
182
|
+
</Button>
|
|
183
|
+
}
|
|
184
|
+
/>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import type { SerializedTree } from "@charcuterie/logic"
|
|
2
|
-
import {
|
|
3
|
-
createTree,
|
|
4
|
-
useTree,
|
|
5
|
-
useVisibility,
|
|
6
|
-
} from "@charcuterie/logic"
|
|
2
|
+
import { createTree, useTree } from "@charcuterie/logic"
|
|
7
3
|
import type { Meta, StoryObj } from "@storybook/react"
|
|
8
4
|
import type { ReactNode } from "react"
|
|
9
5
|
import { useState } from "react"
|
|
10
|
-
import { Button } from "../Button/Button.tsx"
|
|
11
6
|
import { StorySection } from "../board.storyHelpers.tsx"
|
|
12
|
-
import {
|
|
7
|
+
import { Picker } from "../Picker/Picker.tsx"
|
|
13
8
|
import { QueryBuilder } from "./QueryBuilder.tsx"
|
|
14
9
|
|
|
15
10
|
/**
|
|
@@ -52,58 +47,6 @@ const createLeafValue = (): DemoLeaf => ({
|
|
|
52
47
|
value: "",
|
|
53
48
|
})
|
|
54
49
|
|
|
55
|
-
/**
|
|
56
|
-
* A leaf's own picker — a `Listbox`, like the combinator above it,
|
|
57
|
-
* because [the 2026-08-10 demotion](../../../../docs/decisions/2026-08-10-listbox-and-combobox-are-the-default-and-select-is-demoted.md)
|
|
58
|
-
* applies to an app's leaf UI exactly as it does to this component's
|
|
59
|
-
* own controls. This story is the example apps copy, so it must not
|
|
60
|
-
* teach the native `Select` the record demoted.
|
|
61
|
-
*
|
|
62
|
-
* It is a component rather than inline JSX for the same reason
|
|
63
|
-
* `QueryBuilderCombinator` is: `Listbox` needs a visibility state, and
|
|
64
|
-
* a leaf is rendered inside a `.map` where a hook cannot be called.
|
|
65
|
-
* An app adopting `QueryBuilder` writes this same small wrapper — see
|
|
66
|
-
* the note in `QueryBuilder.mdx`.
|
|
67
|
-
*/
|
|
68
|
-
const LeafPicker = ({
|
|
69
|
-
label,
|
|
70
|
-
onChange,
|
|
71
|
-
options,
|
|
72
|
-
value,
|
|
73
|
-
}: {
|
|
74
|
-
label: string
|
|
75
|
-
onChange: (value: string) => void
|
|
76
|
-
options: readonly { label: string; value: string }[]
|
|
77
|
-
value: string
|
|
78
|
-
}): ReactNode => {
|
|
79
|
-
const { hide, isVisible, toggle } = useVisibility()
|
|
80
|
-
|
|
81
|
-
const currentLabel =
|
|
82
|
-
options.find((option) => option.value === value)
|
|
83
|
-
?.label ?? ""
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<Listbox
|
|
87
|
-
isVisible={isVisible}
|
|
88
|
-
onDismiss={hide}
|
|
89
|
-
onSelect={onChange}
|
|
90
|
-
options={options}
|
|
91
|
-
selectedValue={value}
|
|
92
|
-
trigger={
|
|
93
|
-
<Button
|
|
94
|
-
appearance="outline"
|
|
95
|
-
aria-label={`${label}: ${currentLabel}`}
|
|
96
|
-
intent="neutral"
|
|
97
|
-
onClick={toggle}
|
|
98
|
-
size="sm"
|
|
99
|
-
>
|
|
100
|
-
{currentLabel}
|
|
101
|
-
</Button>
|
|
102
|
-
}
|
|
103
|
-
/>
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
50
|
const renderLeaf = ({
|
|
108
51
|
onChange,
|
|
109
52
|
value,
|
|
@@ -113,21 +56,25 @@ const renderLeaf = ({
|
|
|
113
56
|
value: DemoLeaf
|
|
114
57
|
}): ReactNode => (
|
|
115
58
|
<div className="flex flex-wrap items-center gap-2">
|
|
116
|
-
<
|
|
59
|
+
<Picker
|
|
60
|
+
iconEnd={null}
|
|
117
61
|
label="Field"
|
|
118
62
|
onChange={(field) => {
|
|
119
63
|
onChange({ ...value, field })
|
|
120
64
|
}}
|
|
121
65
|
options={FIELD_OPTIONS}
|
|
66
|
+
size="sm"
|
|
122
67
|
value={value.field}
|
|
123
68
|
/>
|
|
124
69
|
|
|
125
|
-
<
|
|
70
|
+
<Picker
|
|
71
|
+
iconEnd={null}
|
|
126
72
|
label="Operator"
|
|
127
73
|
onChange={(operator) => {
|
|
128
74
|
onChange({ ...value, operator })
|
|
129
75
|
}}
|
|
130
76
|
options={OPERATOR_OPTIONS}
|
|
77
|
+
size="sm"
|
|
131
78
|
value={value.operator}
|
|
132
79
|
/>
|
|
133
80
|
|
|
@@ -357,7 +304,8 @@ const CombinatorPair = ({
|
|
|
357
304
|
</span>
|
|
358
305
|
|
|
359
306
|
<div className="flex items-center gap-1.5">
|
|
360
|
-
<
|
|
307
|
+
<Picker
|
|
308
|
+
iconEnd={null}
|
|
361
309
|
label="Quantifier"
|
|
362
310
|
onChange={(nextQuantifier) => {
|
|
363
311
|
setQuantifier(nextQuantifier)
|
|
@@ -374,10 +322,12 @@ const CombinatorPair = ({
|
|
|
374
322
|
)
|
|
375
323
|
}}
|
|
376
324
|
options={QUANTIFIER_OPTIONS}
|
|
325
|
+
size="sm"
|
|
377
326
|
value={quantifier}
|
|
378
327
|
/>
|
|
379
328
|
|
|
380
|
-
<
|
|
329
|
+
<Picker
|
|
330
|
+
iconEnd={null}
|
|
381
331
|
label="Target"
|
|
382
332
|
onChange={(nextTarget) => {
|
|
383
333
|
setTarget(nextTarget)
|
|
@@ -389,6 +339,7 @@ const CombinatorPair = ({
|
|
|
389
339
|
)
|
|
390
340
|
}}
|
|
391
341
|
options={targetOptions}
|
|
342
|
+
size="sm"
|
|
392
343
|
value={resolvedTarget}
|
|
393
344
|
/>
|
|
394
345
|
</div>
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { useVisibility } from "@charcuterie/logic"
|
|
2
1
|
import type { ReactNode } from "react"
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
import { Listbox } from "../Listbox/Listbox.tsx"
|
|
3
|
+
import { Picker } from "../Picker/Picker.tsx"
|
|
6
4
|
|
|
7
5
|
export type QueryBuilderCombinatorProps<Combinator> = {
|
|
8
6
|
/** The visible caption above the control, e.g. "Match". */
|
|
@@ -18,91 +16,54 @@ export type QueryBuilderCombinatorProps<Combinator> = {
|
|
|
18
16
|
/**
|
|
19
17
|
* One group's combinator picker.
|
|
20
18
|
*
|
|
21
|
-
* A `
|
|
19
|
+
* A `Picker` — a `Listbox` with its trigger attached — rather than a
|
|
20
|
+
* native `Select`, per
|
|
22
21
|
* [the 2026-08-10 demotion](../../../../docs/decisions/2026-08-10-listbox-and-combobox-are-the-default-and-select-is-demoted.md).
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* catch it because the component-choice block is scoped to app
|
|
26
|
-
* packages and exempts this one — right for a primitive, wrong for a
|
|
27
|
-
* composite an app consumes whole.
|
|
22
|
+
* This file used to assemble the trigger by hand; `Picker` exists
|
|
23
|
+
* because it was the fourth place in the fleet doing so.
|
|
28
24
|
*
|
|
29
|
-
* ###
|
|
25
|
+
* ### What is left here, and why it is still a component
|
|
30
26
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* `Field` wires `<label htmlFor>` by cloning its `id` onto the
|
|
39
|
-
* control. `Listbox`'s trigger cannot take one: `useAnchoredOverlay`
|
|
40
|
-
* overwrites the cloned trigger's `id` with the one the panel points
|
|
41
|
-
* `aria-labelledby` at, so the label would address an element that no
|
|
42
|
-
* longer carries that id — the exact defect `Field` exists to
|
|
43
|
-
* prevent. So the caption is plain text and the trigger is named with
|
|
44
|
-
* `aria-label`.
|
|
45
|
-
*
|
|
46
|
-
* That name is `"<label>: <current value>"` rather than bare
|
|
47
|
-
* `"<label>"` on purpose: the button's visible text is the current
|
|
48
|
-
* combinator, and WCAG 2.5.3 wants the visible text contained in the
|
|
49
|
-
* accessible name. "Match" alone would fail it; "Match: ALL — every
|
|
50
|
-
* condition" reads correctly and keeps each group's control findable
|
|
51
|
-
* by name.
|
|
52
|
-
*
|
|
53
|
-
* The combinator stays opaque here exactly as it is in the rest of
|
|
54
|
-
* the component: `String(value)` bridges it to the string-valued
|
|
55
|
-
* `Listbox`, and a selection is mapped back to the real `Combinator`
|
|
56
|
-
* before it leaves.
|
|
27
|
+
* Only the bridging. `Combinator` is opaque to `QueryBuilder`, so it
|
|
28
|
+
* cannot reach `Picker`'s string-valued API directly: `String(value)`
|
|
29
|
+
* carries it out and the matching option carries it back, so the
|
|
30
|
+
* generic never leaks. The caption is plain text rather than a
|
|
31
|
+
* `Field`, because `useAnchoredOverlay` overwrites the trigger's `id`
|
|
32
|
+
* and a `<label htmlFor>` would then address an element that no longer
|
|
33
|
+
* carries it — the exact defect `Field` exists to prevent.
|
|
57
34
|
*/
|
|
58
35
|
export const QueryBuilderCombinator = <Combinator,>({
|
|
59
36
|
label,
|
|
60
37
|
onChange,
|
|
61
38
|
options,
|
|
62
39
|
value,
|
|
63
|
-
}: QueryBuilderCombinatorProps<Combinator>): ReactNode =>
|
|
64
|
-
|
|
40
|
+
}: QueryBuilderCombinatorProps<Combinator>): ReactNode => (
|
|
41
|
+
<div className="flex flex-col gap-1.5">
|
|
42
|
+
<span className="font-medium text-content-primary text-sm">
|
|
43
|
+
{label}
|
|
44
|
+
</span>
|
|
65
45
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
46
|
+
<Picker
|
|
47
|
+
// No chevron: this is a refactor onto the shared component, and
|
|
48
|
+
// the trigger that shipped in 2.14.0 had none. Adding the
|
|
49
|
+
// affordance here is a visual change and belongs in its own PR,
|
|
50
|
+
// not smuggled in under an extraction.
|
|
51
|
+
iconEnd={null}
|
|
52
|
+
label={label}
|
|
53
|
+
onChange={(nextValue) => {
|
|
54
|
+
const match = options.find(
|
|
55
|
+
(option) => String(option.value) === nextValue,
|
|
56
|
+
)
|
|
69
57
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<div className="flex flex-col gap-1.5">
|
|
74
|
-
<span className="font-medium text-content-primary text-sm">
|
|
75
|
-
{label}
|
|
76
|
-
</span>
|
|
77
|
-
|
|
78
|
-
<Listbox
|
|
79
|
-
isVisible={isVisible}
|
|
80
|
-
onDismiss={hide}
|
|
81
|
-
onSelect={(nextValue) => {
|
|
82
|
-
const match = options.find(
|
|
83
|
-
(option) => String(option.value) === nextValue,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
if (match) {
|
|
87
|
-
onChange(match.value)
|
|
88
|
-
}
|
|
89
|
-
}}
|
|
90
|
-
options={options.map((option) => ({
|
|
91
|
-
label: option.label,
|
|
92
|
-
value: String(option.value),
|
|
93
|
-
}))}
|
|
94
|
-
selectedValue={String(value)}
|
|
95
|
-
trigger={
|
|
96
|
-
<Button
|
|
97
|
-
appearance="outline"
|
|
98
|
-
aria-label={`${label}: ${currentLabel}`}
|
|
99
|
-
intent="neutral"
|
|
100
|
-
onClick={toggle}
|
|
101
|
-
>
|
|
102
|
-
{currentLabel}
|
|
103
|
-
</Button>
|
|
58
|
+
if (match) {
|
|
59
|
+
onChange(match.value)
|
|
104
60
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
61
|
+
}}
|
|
62
|
+
options={options.map((option) => ({
|
|
63
|
+
label: option.label,
|
|
64
|
+
value: String(option.value),
|
|
65
|
+
}))}
|
|
66
|
+
value={String(value)}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
package/src/index.ts
CHANGED
|
@@ -160,6 +160,11 @@ export type {
|
|
|
160
160
|
export { Menu } from "./Menu/Menu.tsx"
|
|
161
161
|
export type { ModalProps } from "./Modal/Modal.tsx"
|
|
162
162
|
export { Modal } from "./Modal/Modal.tsx"
|
|
163
|
+
export type {
|
|
164
|
+
PickerOption,
|
|
165
|
+
PickerProps,
|
|
166
|
+
} from "./Picker/Picker.tsx"
|
|
167
|
+
export { Picker } from "./Picker/Picker.tsx"
|
|
163
168
|
export type { PopoverProps } from "./Popover/Popover.tsx"
|
|
164
169
|
export { Popover } from "./Popover/Popover.tsx"
|
|
165
170
|
export type {
|
package/src/sourceRules.test.ts
CHANGED
|
@@ -373,7 +373,13 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
373
373
|
// both are rendered inside a `.map` — and stay out of this count and
|
|
374
374
|
// the barrel by the `<Name>/<Name>.tsx` rule, exactly as
|
|
375
375
|
// `SegmentedOption` and `ListboxOption` do.
|
|
376
|
-
|
|
376
|
+
//
|
|
377
|
+
// `Picker` — a `Listbox` with its trigger already attached — is +1 ->
|
|
378
|
+
// 45. It is a component rather than a recipe in the docs because the
|
|
379
|
+
// fleet wrote it four separate times (plex-channels' `SelectListbox`,
|
|
380
|
+
// board-games' `SelectMenu`, mux-magic's `ListboxPicker`, and twice
|
|
381
|
+
// inside this package), each with its own hand-rolled chevron.
|
|
382
|
+
expect(componentNames.length).toBe(45)
|
|
377
383
|
|
|
378
384
|
for (const name of componentNames) {
|
|
379
385
|
expect(barrel).toContain(`export { ${name} }`)
|