@stapel/search-react 0.32.7 → 0.32.8
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/CHANGELOG.md +16 -0
- package/README.md +7 -2
- package/dist/default/PartitionChips.d.ts +7 -8
- package/dist/default/PartitionChips.d.ts.map +1 -1
- package/dist/default/PartitionChips.js +54 -32
- package/dist/default/PartitionChips.js.map +1 -1
- package/llms.txt +1 -1
- package/manifest.json +1 -1
- package/nav-manifest.json +1 -1
- package/package.json +4 -4
- package/src/analytics/generated/events.json +1 -1
- package/src/default/PartitionChips.tsx +84 -44
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @stapel/search-react
|
|
2
2
|
|
|
3
|
+
## 0.32.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e4797ce: `<PartitionChips variant="segmented">` renders the control it declares.
|
|
8
|
+
|
|
9
|
+
The row carried `data-variant="segmented"` and `role="radiogroup"`, and inside it the walker counted `.ant-segmented` **0** and `input[type=radio]` **0** over plain `ant-btn` cells — on both axes of two categories (D304). The ARIA was real (`role="radio"`, `aria-checked`, a roving tabindex, arrow keys), but the control named in the DOM did not exist, and ~30 lines of this file re-decided joined-row geometry the design system already owns.
|
|
10
|
+
|
|
11
|
+
The segmented arm is antd's `Segmented` now: `.ant-segmented`, one real `input[type=radio]` per cell under a shared `name`, and with it the browser's own single Tab stop and arrow keys. The declaration is unchanged (`data-variant="segmented"`, `role="radiogroup"`, the group's `aria-label` still the axis's own name) and so is the contract — `value: string | null`, `onChange` reporting `null` for the parent, controlled, nothing kept here.
|
|
12
|
+
|
|
13
|
+
What moves for anything reading the DOM: the chosen cell is the radio's **`checked`** rather than `aria-checked` on a button (`aria-checked` is how a button fakes what a radio has), and it carries `.ant-segmented-item-selected`. The per-cell test ids (`partition-chip-<path>`, `partition-chip-all`) stay where they were, on the cell's own label, now with `data-checked` beside them so a probe reading a snapshot still has the chosen cell without asking the accessibility tree. A keydown belongs to the cell's input.
|
|
14
|
+
|
|
15
|
+
The `chips` variant — the phone's wrapping pill row — is untouched: same buttons, same `role="radio"`, same `aria-checked`, same roving tabindex.
|
|
16
|
+
|
|
17
|
+
Ceiling raised 31 -> 31.25 KB. Measured with dependencies held constant, this package's src before and after: 30.95 -> 31.05 KB.
|
|
18
|
+
|
|
3
19
|
## 0.32.7
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -532,8 +532,13 @@ a tile grid: an "all" cell plus one per child, from `{id, path, name}`,
|
|
|
532
532
|
controlled (the choice is a `category` in the URL), a real `radiogroup` with
|
|
533
533
|
roving tabindex and arrow keys — because exactly one of them is true at a time,
|
|
534
534
|
and `aria-pressed` toggles say the opposite. `variant="segmented"` is the
|
|
535
|
-
desktop rail's shape
|
|
536
|
-
|
|
535
|
+
desktop rail's shape: **antd's `Segmented`**, one joined control under its own
|
|
536
|
+
label, so its radiogroup is the browser's — real `input[type=radio]` under one
|
|
537
|
+
`name`, the chosen cell's own `checked` (not `aria-checked`, which is how a
|
|
538
|
+
button fakes what a radio has), and the arrow keys and single Tab stop that
|
|
539
|
+
come with them. The contract does not vary with the variant, and the per-cell
|
|
540
|
+
test ids (`partition-chip-<path>`, `partition-chip-all`, each with
|
|
541
|
+
`data-checked`) are the same in both.
|
|
537
542
|
|
|
538
543
|
```tsx
|
|
539
544
|
<PartitionChips items={children} value={state.category ?? null}
|
|
@@ -21,15 +21,14 @@ export interface PartitionChipsProps {
|
|
|
21
21
|
/**
|
|
22
22
|
* Which shape the row takes. `"chips"` (the default) is the phone's: a
|
|
23
23
|
* wrapping row of rounded pills above the feed. `"segmented"` is the
|
|
24
|
-
* desktop RAIL's — one joined control under the axis's
|
|
25
|
-
* how the reference classified draws the same choice (a
|
|
26
|
-
* all, used, new) and what a 280px column has room for.
|
|
24
|
+
* desktop RAIL's — antd's `Segmented`, one joined control under the axis's
|
|
25
|
+
* own label, which is how the reference classified draws the same choice (a
|
|
26
|
+
* car-type row: all, used, new) and what a 280px column has room for.
|
|
27
27
|
*
|
|
28
|
-
* The SEMANTICS do not vary with it
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* accessible half of this control for the visible half.
|
|
28
|
+
* The SEMANTICS do not vary with it: both are a `radiogroup` naming itself,
|
|
29
|
+
* with one Tab stop and the arrow keys moving the choice. What varies is
|
|
30
|
+
* who provides them — this file's `role="radio"` buttons in `chips`, the
|
|
31
|
+
* browser's own `input[type=radio]` in `segmented`.
|
|
33
32
|
*/
|
|
34
33
|
readonly variant?: "chips" | "segmented";
|
|
35
34
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PartitionChips.d.ts","sourceRoot":"","sources":["../../src/default/PartitionChips.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PartitionChips.d.ts","sourceRoot":"","sources":["../../src/default/PartitionChips.tsx"],"names":[],"mappings":"AAmDA,OAAO,KAAK,EAGV,YAAY,EACZ,SAAS,EACV,MAAM,OAAO,CAAC;AAMf;;oDAEoD;AACpD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C;iFAC6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACjD,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAC9B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;CAC1C;AA8BD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,YAAY,CAsHvE"}
|
|
@@ -26,43 +26,47 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
26
26
|
* with roving tabindex is the pattern for "one of these": Tab reaches the row
|
|
27
27
|
* once and lands on the chosen chip, the arrow keys move along it, and the
|
|
28
28
|
* group's own name says what is being chosen.
|
|
29
|
+
*
|
|
30
|
+
* ── The two variants are two CONTROLS, and each one is real ───────────────
|
|
31
|
+
*
|
|
32
|
+
* `chips` is this file's own row of `role="radio"` buttons — pills that wrap,
|
|
33
|
+
* the phone's shape.
|
|
34
|
+
*
|
|
35
|
+
* `segmented` is antd's `Segmented`, and it used to be this same row with a
|
|
36
|
+
* shared border drawn round it. That was a claim nothing backed: the walker
|
|
37
|
+
* read `data-variant="segmented"` with `role="radiogroup"` and found
|
|
38
|
+
* `.ant-segmented` zero times, `input[type=radio]` zero times, and plain
|
|
39
|
+
* `ant-btn` inside — on both axes of two categories (D304). A row that names
|
|
40
|
+
* a control it does not render sends the next reader looking for a bug in the
|
|
41
|
+
* wrong place, and hand-rolling the joined look was ~30 lines re-deciding
|
|
42
|
+
* geometry the design system already owns.
|
|
43
|
+
*
|
|
44
|
+
* So the segmented arm IS the design system's control now, and its radiogroup
|
|
45
|
+
* is the browser's rather than ours: real `input[type=radio]` sharing one
|
|
46
|
+
* `name`, so the selected state is the radio's own `checked` (`aria-checked`
|
|
47
|
+
* is how a BUTTON fakes what a radio has), the arrow keys are the control's,
|
|
48
|
+
* and one Tab stop is the platform's doing. The per-cell test ids stay where
|
|
49
|
+
* they were, plus `data-checked`, so a probe that reads a snapshot still has
|
|
50
|
+
* the chosen cell without asking the accessibility tree.
|
|
29
51
|
*/
|
|
30
52
|
import { useRef } from "react";
|
|
31
|
-
import { Button } from "antd";
|
|
53
|
+
import { Button, Segmented } from "antd";
|
|
32
54
|
import { useT } from "@stapel/core";
|
|
33
|
-
import {
|
|
55
|
+
import { radii, spacing } from "@stapel/tokens";
|
|
34
56
|
import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
|
|
57
|
+
/**
|
|
58
|
+
* The `value` of the parent cell inside the segmented control. `null` is this
|
|
59
|
+
* component's word for "the parent, unnarrowed" and a radio's value is a
|
|
60
|
+
* string, so the sentinel exists only between here and antd — it never
|
|
61
|
+
* reaches {@link PartitionChipsProps.onChange}, which still reports `null`.
|
|
62
|
+
*/
|
|
63
|
+
const ALL = "__all__";
|
|
35
64
|
const CHIP = { borderRadius: radii.full };
|
|
36
65
|
const ROW = {
|
|
37
66
|
display: "flex",
|
|
38
67
|
flexWrap: "wrap",
|
|
39
68
|
gap: spacing[2],
|
|
40
69
|
};
|
|
41
|
-
/**
|
|
42
|
-
* The segmented row: one joined control, no gaps, the group's own outline.
|
|
43
|
-
*
|
|
44
|
-
* `gap: 0` plus a shared border is what makes three buttons read as one
|
|
45
|
-
* control — the thing a rail needs, because a wrapping pill row in a 280px
|
|
46
|
-
* column is two ragged lines. `overflow: hidden` clips the cells' own corners
|
|
47
|
-
* to the group's radius so the ends are round and the joins are square.
|
|
48
|
-
*/
|
|
49
|
-
const SEGMENTED_ROW = {
|
|
50
|
-
display: "flex",
|
|
51
|
-
gap: 0,
|
|
52
|
-
inlineSize: "100%",
|
|
53
|
-
border: `1px solid ${cssVar("border")}`,
|
|
54
|
-
borderRadius: cssVar("radius-md"),
|
|
55
|
-
overflow: "hidden",
|
|
56
|
-
};
|
|
57
|
-
/** One cell of the segmented row: an equal share of the width, square joins,
|
|
58
|
-
* no border of its own — the group draws the outline. */
|
|
59
|
-
const SEGMENTED_CELL = {
|
|
60
|
-
flex: "1 1 0",
|
|
61
|
-
minInlineSize: 0,
|
|
62
|
-
borderRadius: 0,
|
|
63
|
-
borderInline: "none",
|
|
64
|
-
borderBlock: "none",
|
|
65
|
-
};
|
|
66
70
|
/** The row's cells, as `[value, label]` — the parent first, then the
|
|
67
71
|
* children in catalogue order. */
|
|
68
72
|
function cells(items, allLabel) {
|
|
@@ -106,15 +110,33 @@ export function PartitionChips(props) {
|
|
|
106
110
|
// parent chip rather than vanishing.
|
|
107
111
|
const active = options.findIndex(([value]) => value === props.value);
|
|
108
112
|
const stop = active >= 0 ? active : 0;
|
|
109
|
-
const
|
|
110
|
-
|
|
113
|
+
const name = props.label ?? t(SEARCH_I18N_KEYS.partitionLabel);
|
|
114
|
+
if (props.variant === "segmented") {
|
|
115
|
+
// antd's own control: `.ant-segmented`, one `input[type=radio]` per cell
|
|
116
|
+
// under a shared `name`, the selected cell's `checked`, and the arrow keys
|
|
117
|
+
// — the radiogroup this variant used to only claim to be. `role` and
|
|
118
|
+
// `aria-label` reach the root because the component spreads what it is
|
|
119
|
+
// given over its own defaults (which are `radiogroup` and the string
|
|
120
|
+
// "segmented control").
|
|
121
|
+
return (_jsx(Segmented, { block: true, size: "small", role: "radiogroup", "aria-label": name, "data-variant": "segmented", "data-testid": "partition-chips", value: props.value ?? ALL, options: options.map(([value, label]) => ({
|
|
122
|
+
value: value ?? ALL,
|
|
123
|
+
label: (
|
|
124
|
+
// The cell's test id and its chosen state, on the one node inside
|
|
125
|
+
// a cell this component owns — antd names the cells itself, and a
|
|
126
|
+
// label a radio is bound to is what a click has to land on.
|
|
127
|
+
_jsx("span", { "data-testid": `partition-chip-${value ?? "all"}`, "data-checked": value === props.value ? "true" : "false", children: label })),
|
|
128
|
+
})), onChange: (next) => {
|
|
129
|
+
props.onChange(next === ALL ? null : String(next));
|
|
130
|
+
} }));
|
|
131
|
+
}
|
|
132
|
+
return (_jsx("div", { style: ROW, "data-variant": "chips", ref: row, role: "radiogroup", "aria-label": name, "data-testid": "partition-chips", children: options.map(([value, label], index) => {
|
|
111
133
|
const selected = value === props.value;
|
|
112
|
-
return (_jsx(Button, { size: "small",
|
|
134
|
+
return (_jsx(Button, { size: "small", shape: "round", type: selected ? "primary" : "default", role: "radio", "aria-checked": selected,
|
|
113
135
|
// Roving tabindex: the row is ONE Tab stop and it lands on the
|
|
114
136
|
// chosen chip, not on the first of eight.
|
|
115
|
-
tabIndex: index === stop ? 0 : -1, style:
|
|
137
|
+
tabIndex: index === stop ? 0 : -1, style: CHIP, "data-testid": `partition-chip-${value ?? "all"}`, "data-analytics": "none", "data-analytics-reason": "choosing a section is a read, not a flow step", onKeyDown: onKeyDown(index), onClick: () => {
|
|
116
138
|
props.onChange(value);
|
|
117
|
-
}, children: label }, value ??
|
|
139
|
+
}, children: label }, value ?? ALL));
|
|
118
140
|
}) }));
|
|
119
141
|
}
|
|
120
142
|
//# sourceMappingURL=PartitionChips.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PartitionChips.js","sourceRoot":"","sources":["../../src/default/PartitionChips.tsx"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"PartitionChips.js","sourceRoot":"","sources":["../../src/default/PartitionChips.tsx"],"names":[],"mappings":"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"}
|
package/llms.txt
CHANGED
package/manifest.json
CHANGED
package/nav-manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stapel/search-react",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.8",
|
|
4
4
|
"description": "Headless React pair for stapel-search: a typed query client, TanStack Query hooks, and a URL-first state codec that makes a search shareable by construction (filters, ranges, geo, sort and the keyset cursor all live in the query string). Drill-down facets rendered with their remaining counts and with the server's own honesty flags — approximate, skipped, degraded — never swallowed; keyset pagination with the window refusal named; DSA Art. 26 `promoted` marking carried into every card slot and the P2B Art. 5 ranking disclosure exposed as data. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin, and /router binds the codec to react-router's useSearchParams.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
"limit": "14.5 KB"
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
|
-
"name": "default — the antd skin (query box + a typeahead that offers CATEGORY destinations with their live counts, filters incl. ranges/geo/category slots + the phone chip row with its leading category chip and the location summary row + the rail's evidence-ranked disclosure groups, panel search and sticky count/clear footer, results incl. the view switch, the card photo GALLERY as a SkinCarousel strip, the empty state's derived exits, ranking, the dictionary control for a vocabulary facet, the popular-values block, the partition row in both its chip and segmented variants, the select-style dictionary FIELD the desktop rail opens, the from/to pickers a bounded integer axis draws and the rail's own scrollbar sheet) must stay out of the main bundle. Raised 24.5 → 25.5 KB for those four controls, then 25.5 → 26.25 KB for the chip row's APPLIED mode — one chip per applied value and per numeric bound, each removing that one constraint beside a rail that is already on screen (a storefront was carrying its own copy of it), then 26.25 → 27 KB for the \"search in other categories\" LINE — which is a net deletion on the page that mounts it, replacing a full-width block of one row per section that arrived after the results and pushed them, then 27 → 27.75 KB for the two frames the page now tells apart: the dictionary FIELD reaching `<SearchPage>` at all (per-layout default, field in the rail and inline in the sheet) and the footer bar being static in a column and sticky in a sheet, where it used to be pinned over the last two groups everywhere, then 27.75 → 28.5 KB for `categoryHref` on the other-categories line — a real `<a href>` per row with a modifier-click left to the browser, instead of a `<button>` with no address a person could open in a new tab, then 28.5 → 29.25 KB for the phone's dictionary SHEET — a trigger row over the shared `SkinPickerSheet` with a recommended band, an alphabetical rest paged fifty at a time and a draft committed in one write, which is the control the composer's vocabulary picker already was while the buyer got a wall of checkboxes, then 29.25 → 30.5 KB for the panel that draws all of it: groups and ranges as one ordered sequence with one «Apply» for the panel rather than one per row, the empty-group heading rule, `categoryFilter={false}` and the `resultsLead` slot for a page reached by walking the catalogue, `PopularValues columns=\\\"responsive\\\"` on a container query, `SortSelect` annotating a blocked option at every width, a loading arm that covers the whole pane (0.34 CLS on a live host), and the chip row reserving its own box (a further 0.045). Measured with dependencies held constant, this package's src at the commit before that work and then at it: 29.05 -> 30.16 KB — 1.11 KB, and every part of it is a shift or a mislabelled control a host was living with, then 30.5 -> 31 KB for `<SearchResultsPane columns>` and its pass-through on `<SearchPage resultsColumns>`: a fixed track count or a per-breakpoint map, emitted as a hoisted container-query sheet (the results block is the window minus a 280px rail, so a media query would answer about a width the cards never have). Measured with dependencies held constant, this package's src before and after: 30.40 -> 30.65 KB — 250 B, and what it replaces is a host's `!important` rule against a grid declaration it could not read back",
|
|
60
|
+
"name": "default — the antd skin (query box + a typeahead that offers CATEGORY destinations with their live counts, filters incl. ranges/geo/category slots + the phone chip row with its leading category chip and the location summary row + the rail's evidence-ranked disclosure groups, panel search and sticky count/clear footer, results incl. the view switch, the card photo GALLERY as a SkinCarousel strip, the empty state's derived exits, ranking, the dictionary control for a vocabulary facet, the popular-values block, the partition row in both its chip and segmented variants, the select-style dictionary FIELD the desktop rail opens, the from/to pickers a bounded integer axis draws and the rail's own scrollbar sheet) must stay out of the main bundle. Raised 24.5 → 25.5 KB for those four controls, then 25.5 → 26.25 KB for the chip row's APPLIED mode — one chip per applied value and per numeric bound, each removing that one constraint beside a rail that is already on screen (a storefront was carrying its own copy of it), then 26.25 → 27 KB for the \"search in other categories\" LINE — which is a net deletion on the page that mounts it, replacing a full-width block of one row per section that arrived after the results and pushed them, then 27 → 27.75 KB for the two frames the page now tells apart: the dictionary FIELD reaching `<SearchPage>` at all (per-layout default, field in the rail and inline in the sheet) and the footer bar being static in a column and sticky in a sheet, where it used to be pinned over the last two groups everywhere, then 27.75 → 28.5 KB for `categoryHref` on the other-categories line — a real `<a href>` per row with a modifier-click left to the browser, instead of a `<button>` with no address a person could open in a new tab, then 28.5 → 29.25 KB for the phone's dictionary SHEET — a trigger row over the shared `SkinPickerSheet` with a recommended band, an alphabetical rest paged fifty at a time and a draft committed in one write, which is the control the composer's vocabulary picker already was while the buyer got a wall of checkboxes, then 29.25 → 30.5 KB for the panel that draws all of it: groups and ranges as one ordered sequence with one «Apply» for the panel rather than one per row, the empty-group heading rule, `categoryFilter={false}` and the `resultsLead` slot for a page reached by walking the catalogue, `PopularValues columns=\\\"responsive\\\"` on a container query, `SortSelect` annotating a blocked option at every width, a loading arm that covers the whole pane (0.34 CLS on a live host), and the chip row reserving its own box (a further 0.045). Measured with dependencies held constant, this package's src at the commit before that work and then at it: 29.05 -> 30.16 KB — 1.11 KB, and every part of it is a shift or a mislabelled control a host was living with, then 30.5 -> 31 KB for `<SearchResultsPane columns>` and its pass-through on `<SearchPage resultsColumns>`: a fixed track count or a per-breakpoint map, emitted as a hoisted container-query sheet (the results block is the window minus a 280px rail, so a media query would answer about a width the cards never have). Measured with dependencies held constant, this package's src before and after: 30.40 -> 30.65 KB — 250 B, and what it replaces is a host's `!important` rule against a grid declaration it could not read back, then 31 -> 31.25 KB for the segmented partition being antd's `Segmented` rather than a row of buttons wearing a border: the walker read `data-variant=\"segmented\"` with `role=\"radiogroup\"` and found `.ant-segmented` zero times and `input[type=radio]` zero times (D304), so the cells are the design system's now — real radios under one name, the browser's own arrow keys and Tab stop. Measured with dependencies held constant, this package's src before and after: 30.95 -> 31.05 KB — 100 B, against ~30 lines of hand-rolled joined-row geometry deleted",
|
|
61
61
|
"path": "dist/default/index.js",
|
|
62
|
-
"limit": "31 KB"
|
|
62
|
+
"limit": "31.25 KB"
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"name": "router — the react-router binding is opt-in; the main entry must never pull a router",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"size-limit": "^11.2.0",
|
|
116
116
|
"typescript": "^5.8.3",
|
|
117
117
|
"vitest": "^3.2.4",
|
|
118
|
-
"@stapel/attributes-react": "^0.16.
|
|
118
|
+
"@stapel/attributes-react": "^0.16.5",
|
|
119
119
|
"@stapel/core": "^0.25.4",
|
|
120
120
|
"@stapel/image": "^0.4.2",
|
|
121
121
|
"@stapel/showcase": "^0.3.0",
|
|
@@ -25,6 +25,28 @@
|
|
|
25
25
|
* with roving tabindex is the pattern for "one of these": Tab reaches the row
|
|
26
26
|
* once and lands on the chosen chip, the arrow keys move along it, and the
|
|
27
27
|
* group's own name says what is being chosen.
|
|
28
|
+
*
|
|
29
|
+
* ── The two variants are two CONTROLS, and each one is real ───────────────
|
|
30
|
+
*
|
|
31
|
+
* `chips` is this file's own row of `role="radio"` buttons — pills that wrap,
|
|
32
|
+
* the phone's shape.
|
|
33
|
+
*
|
|
34
|
+
* `segmented` is antd's `Segmented`, and it used to be this same row with a
|
|
35
|
+
* shared border drawn round it. That was a claim nothing backed: the walker
|
|
36
|
+
* read `data-variant="segmented"` with `role="radiogroup"` and found
|
|
37
|
+
* `.ant-segmented` zero times, `input[type=radio]` zero times, and plain
|
|
38
|
+
* `ant-btn` inside — on both axes of two categories (D304). A row that names
|
|
39
|
+
* a control it does not render sends the next reader looking for a bug in the
|
|
40
|
+
* wrong place, and hand-rolling the joined look was ~30 lines re-deciding
|
|
41
|
+
* geometry the design system already owns.
|
|
42
|
+
*
|
|
43
|
+
* So the segmented arm IS the design system's control now, and its radiogroup
|
|
44
|
+
* is the browser's rather than ours: real `input[type=radio]` sharing one
|
|
45
|
+
* `name`, so the selected state is the radio's own `checked` (`aria-checked`
|
|
46
|
+
* is how a BUTTON fakes what a radio has), the arrow keys are the control's,
|
|
47
|
+
* and one Tab stop is the platform's doing. The per-cell test ids stay where
|
|
48
|
+
* they were, plus `data-checked`, so a probe that reads a snapshot still has
|
|
49
|
+
* the chosen cell without asking the accessibility tree.
|
|
28
50
|
*/
|
|
29
51
|
import { useRef } from "react";
|
|
30
52
|
import type {
|
|
@@ -33,9 +55,9 @@ import type {
|
|
|
33
55
|
ReactElement,
|
|
34
56
|
ReactNode,
|
|
35
57
|
} from "react";
|
|
36
|
-
import { Button } from "antd";
|
|
58
|
+
import { Button, Segmented } from "antd";
|
|
37
59
|
import { useT } from "@stapel/core";
|
|
38
|
-
import {
|
|
60
|
+
import { radii, spacing } from "@stapel/tokens";
|
|
39
61
|
import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
|
|
40
62
|
|
|
41
63
|
/** One child of a partitioned category. `path` is the slash-joined id path
|
|
@@ -61,19 +83,26 @@ export interface PartitionChipsProps {
|
|
|
61
83
|
/**
|
|
62
84
|
* Which shape the row takes. `"chips"` (the default) is the phone's: a
|
|
63
85
|
* wrapping row of rounded pills above the feed. `"segmented"` is the
|
|
64
|
-
* desktop RAIL's — one joined control under the axis's
|
|
65
|
-
* how the reference classified draws the same choice (a
|
|
66
|
-
* all, used, new) and what a 280px column has room for.
|
|
86
|
+
* desktop RAIL's — antd's `Segmented`, one joined control under the axis's
|
|
87
|
+
* own label, which is how the reference classified draws the same choice (a
|
|
88
|
+
* car-type row: all, used, new) and what a 280px column has room for.
|
|
67
89
|
*
|
|
68
|
-
* The SEMANTICS do not vary with it
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* accessible half of this control for the visible half.
|
|
90
|
+
* The SEMANTICS do not vary with it: both are a `radiogroup` naming itself,
|
|
91
|
+
* with one Tab stop and the arrow keys moving the choice. What varies is
|
|
92
|
+
* who provides them — this file's `role="radio"` buttons in `chips`, the
|
|
93
|
+
* browser's own `input[type=radio]` in `segmented`.
|
|
73
94
|
*/
|
|
74
95
|
readonly variant?: "chips" | "segmented";
|
|
75
96
|
}
|
|
76
97
|
|
|
98
|
+
/**
|
|
99
|
+
* The `value` of the parent cell inside the segmented control. `null` is this
|
|
100
|
+
* component's word for "the parent, unnarrowed" and a radio's value is a
|
|
101
|
+
* string, so the sentinel exists only between here and antd — it never
|
|
102
|
+
* reaches {@link PartitionChipsProps.onChange}, which still reports `null`.
|
|
103
|
+
*/
|
|
104
|
+
const ALL = "__all__";
|
|
105
|
+
|
|
77
106
|
const CHIP: CSSProperties = { borderRadius: radii.full };
|
|
78
107
|
|
|
79
108
|
const ROW: CSSProperties = {
|
|
@@ -82,33 +111,6 @@ const ROW: CSSProperties = {
|
|
|
82
111
|
gap: spacing[2],
|
|
83
112
|
};
|
|
84
113
|
|
|
85
|
-
/**
|
|
86
|
-
* The segmented row: one joined control, no gaps, the group's own outline.
|
|
87
|
-
*
|
|
88
|
-
* `gap: 0` plus a shared border is what makes three buttons read as one
|
|
89
|
-
* control — the thing a rail needs, because a wrapping pill row in a 280px
|
|
90
|
-
* column is two ragged lines. `overflow: hidden` clips the cells' own corners
|
|
91
|
-
* to the group's radius so the ends are round and the joins are square.
|
|
92
|
-
*/
|
|
93
|
-
const SEGMENTED_ROW: CSSProperties = {
|
|
94
|
-
display: "flex",
|
|
95
|
-
gap: 0,
|
|
96
|
-
inlineSize: "100%",
|
|
97
|
-
border: `1px solid ${cssVar("border")}`,
|
|
98
|
-
borderRadius: cssVar("radius-md"),
|
|
99
|
-
overflow: "hidden",
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
/** One cell of the segmented row: an equal share of the width, square joins,
|
|
103
|
-
* no border of its own — the group draws the outline. */
|
|
104
|
-
const SEGMENTED_CELL: CSSProperties = {
|
|
105
|
-
flex: "1 1 0",
|
|
106
|
-
minInlineSize: 0,
|
|
107
|
-
borderRadius: 0,
|
|
108
|
-
borderInline: "none",
|
|
109
|
-
borderBlock: "none",
|
|
110
|
-
};
|
|
111
|
-
|
|
112
114
|
/** The row's cells, as `[value, label]` — the parent first, then the
|
|
113
115
|
* children in catalogue order. */
|
|
114
116
|
function cells(
|
|
@@ -163,30 +165,68 @@ export function PartitionChips(props: PartitionChipsProps): ReactElement {
|
|
|
163
165
|
const active = options.findIndex(([value]) => value === props.value);
|
|
164
166
|
const stop = active >= 0 ? active : 0;
|
|
165
167
|
|
|
166
|
-
const
|
|
168
|
+
const name = props.label ?? t(SEARCH_I18N_KEYS.partitionLabel);
|
|
169
|
+
|
|
170
|
+
if (props.variant === "segmented") {
|
|
171
|
+
// antd's own control: `.ant-segmented`, one `input[type=radio]` per cell
|
|
172
|
+
// under a shared `name`, the selected cell's `checked`, and the arrow keys
|
|
173
|
+
// — the radiogroup this variant used to only claim to be. `role` and
|
|
174
|
+
// `aria-label` reach the root because the component spreads what it is
|
|
175
|
+
// given over its own defaults (which are `radiogroup` and the string
|
|
176
|
+
// "segmented control").
|
|
177
|
+
return (
|
|
178
|
+
<Segmented
|
|
179
|
+
block
|
|
180
|
+
size="small"
|
|
181
|
+
role="radiogroup"
|
|
182
|
+
aria-label={name}
|
|
183
|
+
data-variant="segmented"
|
|
184
|
+
data-testid="partition-chips"
|
|
185
|
+
value={props.value ?? ALL}
|
|
186
|
+
options={options.map(([value, label]) => ({
|
|
187
|
+
value: value ?? ALL,
|
|
188
|
+
label: (
|
|
189
|
+
// The cell's test id and its chosen state, on the one node inside
|
|
190
|
+
// a cell this component owns — antd names the cells itself, and a
|
|
191
|
+
// label a radio is bound to is what a click has to land on.
|
|
192
|
+
<span
|
|
193
|
+
data-testid={`partition-chip-${value ?? "all"}`}
|
|
194
|
+
data-checked={value === props.value ? "true" : "false"}
|
|
195
|
+
>
|
|
196
|
+
{label}
|
|
197
|
+
</span>
|
|
198
|
+
),
|
|
199
|
+
}))}
|
|
200
|
+
onChange={(next) => {
|
|
201
|
+
props.onChange(next === ALL ? null : String(next));
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
167
207
|
return (
|
|
168
208
|
<div
|
|
169
|
-
style={
|
|
170
|
-
data-variant=
|
|
209
|
+
style={ROW}
|
|
210
|
+
data-variant="chips"
|
|
171
211
|
ref={row}
|
|
172
212
|
role="radiogroup"
|
|
173
|
-
aria-label={
|
|
213
|
+
aria-label={name}
|
|
174
214
|
data-testid="partition-chips"
|
|
175
215
|
>
|
|
176
216
|
{options.map(([value, label], index) => {
|
|
177
217
|
const selected = value === props.value;
|
|
178
218
|
return (
|
|
179
219
|
<Button
|
|
180
|
-
key={value ??
|
|
220
|
+
key={value ?? ALL}
|
|
181
221
|
size="small"
|
|
182
|
-
|
|
222
|
+
shape="round"
|
|
183
223
|
type={selected ? "primary" : "default"}
|
|
184
224
|
role="radio"
|
|
185
225
|
aria-checked={selected}
|
|
186
226
|
// Roving tabindex: the row is ONE Tab stop and it lands on the
|
|
187
227
|
// chosen chip, not on the first of eight.
|
|
188
228
|
tabIndex={index === stop ? 0 : -1}
|
|
189
|
-
style={
|
|
229
|
+
style={CHIP}
|
|
190
230
|
data-testid={`partition-chip-${value ?? "all"}`}
|
|
191
231
|
data-analytics="none"
|
|
192
232
|
data-analytics-reason="choosing a section is a read, not a flow step"
|