@stapel/search-react 0.33.0 → 0.34.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/CHANGELOG.md +84 -0
- package/README.md +19 -2
- package/dist/default/FacetGroupControl.d.ts +13 -0
- package/dist/default/FacetGroupControl.d.ts.map +1 -1
- package/dist/default/FacetGroupControl.js +33 -2
- package/dist/default/FacetGroupControl.js.map +1 -1
- package/dist/default/FacetPanelPane.d.ts.map +1 -1
- package/dist/default/FacetPanelPane.js +6 -2
- package/dist/default/FacetPanelPane.js.map +1 -1
- package/dist/default/PartitionChips.d.ts.map +1 -1
- package/dist/default/PartitionChips.js +83 -2
- package/dist/default/PartitionChips.js.map +1 -1
- package/dist/default/SearchResultsPane.d.ts +5 -5
- package/dist/default/SearchResultsPane.d.ts.map +1 -1
- package/dist/default/SearchResultsPane.js +51 -9
- package/dist/default/SearchResultsPane.js.map +1 -1
- package/dist/headless/FacetPanel.d.ts +11 -0
- package/dist/headless/FacetPanel.d.ts.map +1 -1
- package/dist/headless/FacetPanel.js +20 -1
- package/dist/headless/FacetPanel.js.map +1 -1
- package/llms.txt +1 -1
- package/manifest.json +1 -1
- package/nav-manifest.json +1 -1
- package/package.json +7 -7
- package/src/analytics/generated/events.json +1 -1
- package/src/default/FacetGroupControl.tsx +52 -1
- package/src/default/FacetPanelPane.tsx +18 -1
- package/src/default/PartitionChips.tsx +86 -1
- package/src/default/SearchResultsPane.tsx +74 -25
- package/src/headless/FacetPanel.tsx +31 -1
|
@@ -120,11 +120,11 @@ export const RESULTS_MAX_WIDTH = 1400;
|
|
|
120
120
|
* pane's own breakpoint restated in a media query — three rules aimed at a
|
|
121
121
|
* shape the pane could change under them at any release.
|
|
122
122
|
*
|
|
123
|
-
* It is the same class in BOTH header shapes, and in both it names
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
123
|
+
* It is the same class in BOTH header shapes, and in both it names THE SAME
|
|
124
|
+
* THING: the controls' own line, and never the heading beside it (D452). The
|
|
125
|
+
* compact shape always drew it that way; the wide shape did not, and pinning a
|
|
126
|
+
* block that carried the `<h1>` pinned 100px of chrome at 1440 and 150px at
|
|
127
|
+
* 1280 — see `WIDE_HEADING_ROW`.
|
|
128
128
|
*/
|
|
129
129
|
export const RESULTS_TOOLBAR_CLASS = "stapel-search-results-toolbar";
|
|
130
130
|
|
|
@@ -173,6 +173,49 @@ const TOOLBAR_ROW: CSSProperties = {
|
|
|
173
173
|
*/
|
|
174
174
|
const COMPACT_STACK: CSSProperties = { display: "contents" };
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* The WIDE shape's heading row — a row of its own, at the results column's
|
|
178
|
+
* full measure (D452).
|
|
179
|
+
*
|
|
180
|
+
* The heading used to be the leading flex item of the block that pins, with
|
|
181
|
+
* the controls as the trailing one. Two things followed, and both were
|
|
182
|
+
* measured on the stand:
|
|
183
|
+
*
|
|
184
|
+
* - the controls row is `nowrap` and holds a count, a sort select, a page
|
|
185
|
+
* size and a view switch, so it took whatever width it needed and left the
|
|
186
|
+
* heading **415px** of a 1200px column. An `<h1>` at 42px (50 at 1280) then
|
|
187
|
+
* wrapped to TWO LINES;
|
|
188
|
+
* - those two lines were inside the pinned box, so what stood under the
|
|
189
|
+
* header while the feed scrolled was **100px** of chrome at 1440 and
|
|
190
|
+
* **150px** at 1280 — a third of the fold on a laptop.
|
|
191
|
+
*
|
|
192
|
+
* Nothing about the heading belongs in a pinned bar: it names the page once,
|
|
193
|
+
* it is read once, and it is the one element on the page whose length the pair
|
|
194
|
+
* does not control (`resultsHeading` is the host's sentence — "Buy a
|
|
195
|
+
* Samsung Galaxy S23 in Kazan"). So it takes its own line at the full width,
|
|
196
|
+
* where it has room not to wrap, and the pin acts on the controls alone —
|
|
197
|
+
* which is what `header="compact"` already did.
|
|
198
|
+
*
|
|
199
|
+
* `100%` is DECLARED rather than left to the column's `align-items: stretch`:
|
|
200
|
+
* the row is what a host reads to know the heading is not in the pinned box,
|
|
201
|
+
* and a width that only happens to be full is not a contract.
|
|
202
|
+
*/
|
|
203
|
+
const WIDE_HEADING_ROW: CSSProperties = { inlineSize: "100%", minInlineSize: 0 };
|
|
204
|
+
|
|
205
|
+
/** The wide heading itself: no margin of its own — the column's gap is the
|
|
206
|
+
* rhythm — and free to shrink inside its row rather than forcing it wider. */
|
|
207
|
+
const WIDE_HEADING: CSSProperties = { margin: 0, minInlineSize: 0 };
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The wide row's trailing group — the surface's controls.
|
|
211
|
+
*
|
|
212
|
+
* `flex: 0 0 auto`: the count on the leading end is the elastic half (it is
|
|
213
|
+
* one short string and it arrives late), and the controls keep every pixel
|
|
214
|
+
* they need. Both ends shrinking is how a sort select ends up narrower than
|
|
215
|
+
* its own longest option.
|
|
216
|
+
*/
|
|
217
|
+
const TOOLBAR_END: CSSProperties = { flex: "0 0 auto" };
|
|
218
|
+
|
|
176
219
|
/** The compact shape's toolbar box — the row the pin acts on. */
|
|
177
220
|
const COMPACT_TOOLBAR: CSSProperties = {
|
|
178
221
|
display: "flex",
|
|
@@ -692,29 +735,35 @@ export function SearchResultsPane(props: SearchResultsPaneProps): ReactElement {
|
|
|
692
735
|
<Count bag={bag} />
|
|
693
736
|
</Flex>
|
|
694
737
|
) : (
|
|
695
|
-
/*
|
|
696
|
-
|
|
697
|
-
direct
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
738
|
+
/* D452 — the heading is its OWN row, at the column's full width,
|
|
739
|
+
and the row that pins is the controls. See `WIDE_HEADING_ROW`.
|
|
740
|
+
Both are direct children of the results column: the toolbar
|
|
741
|
+
has a parent as tall as the feed to travel in, and the heading
|
|
742
|
+
has the whole measure to set its line in. */
|
|
743
|
+
<>
|
|
744
|
+
<div style={WIDE_HEADING_ROW} data-testid="search-results-heading-row">
|
|
745
|
+
<Typography.Title
|
|
746
|
+
level={props.headingLevel ?? 4}
|
|
747
|
+
style={WIDE_HEADING}
|
|
748
|
+
data-testid="search-results-heading"
|
|
749
|
+
>
|
|
750
|
+
{props.heading ?? t(SEARCH_I18N_KEYS.resultsTitle)}
|
|
751
|
+
</Typography.Title>
|
|
752
|
+
</div>
|
|
753
|
+
<Flex
|
|
754
|
+
justify="space-between"
|
|
755
|
+
align="center"
|
|
756
|
+
gap={spacing[3]}
|
|
757
|
+
className={RESULTS_TOOLBAR_CLASS}
|
|
758
|
+
data-testid="search-results-toolbar"
|
|
759
|
+
style={{ ...TOOLBAR_ROW, ...toolbarPin }}
|
|
710
760
|
>
|
|
711
|
-
{props.heading ?? t(SEARCH_I18N_KEYS.resultsTitle)}
|
|
712
|
-
</Typography.Title>
|
|
713
|
-
<Flex align="center" gap={spacing[3]} style={TOOLBAR_ROW}>
|
|
714
761
|
<Count bag={bag} />
|
|
715
|
-
{
|
|
762
|
+
<Flex align="center" gap={spacing[3]} style={TOOLBAR_END}>
|
|
763
|
+
{props.toolbar}
|
|
764
|
+
</Flex>
|
|
716
765
|
</Flex>
|
|
717
|
-
|
|
766
|
+
</>
|
|
718
767
|
)}
|
|
719
768
|
|
|
720
769
|
<DegradationNotice
|
|
@@ -29,6 +29,17 @@ export interface FacetPanelBag {
|
|
|
29
29
|
* category) — it is NOT what a failed query looks like.
|
|
30
30
|
*/
|
|
31
31
|
readonly state: LoadState<readonly FacetGroup[]>;
|
|
32
|
+
/**
|
|
33
|
+
* The groups in hand belong to the PREVIOUS answer, and a newer one is in
|
|
34
|
+
* flight — `LoadReady.refreshing`, lifted out of the state so a skin can
|
|
35
|
+
* read it without narrowing the union first.
|
|
36
|
+
*
|
|
37
|
+
* A skin holds its geometry still while this is `true`: the axes are about
|
|
38
|
+
* to change, and a rail that resizes group by group as they arrive is the
|
|
39
|
+
* 0.0586 CLS a partition press measured (p43). It is never `true` on a
|
|
40
|
+
* first load — there is nothing to hold still then.
|
|
41
|
+
*/
|
|
42
|
+
readonly refreshing: boolean;
|
|
32
43
|
/**
|
|
33
44
|
* `true` when the counts came from a SAMPLE because the candidate set
|
|
34
45
|
* exceeded the backend's cap. The panel must say so — the spec makes this
|
|
@@ -228,7 +239,23 @@ export function useFacetPanel(props: {
|
|
|
228
239
|
props.enabled !== undefined ? { enabled: props.enabled } : undefined
|
|
229
240
|
);
|
|
230
241
|
|
|
231
|
-
|
|
242
|
+
/*
|
|
243
|
+
* KEEP THE PREVIOUS ANSWER, AND SAY SO (p43, CLS 0.0586 on a partition press).
|
|
244
|
+
*
|
|
245
|
+
* `useSearchQuery` already runs with `placeholderData: keepPreviousData`, so
|
|
246
|
+
* the data in hand during a key change is the previous answer's — the panel
|
|
247
|
+
* has been drawing it for three releases. What it could not do is TELL a skin
|
|
248
|
+
* that this is what it was doing, so the rail resized group by group as the
|
|
249
|
+
* new axis's facets landed: `facet-group-make` and `facet-group-model` moved
|
|
250
|
+
* their neighbours as their option counts changed under them.
|
|
251
|
+
*
|
|
252
|
+
* `keepPrevious` reads TanStack's own `isPlaceholderData` into
|
|
253
|
+
* `LoadReady.refreshing`, which every projection below carries across
|
|
254
|
+
* (`mapLoad`, and `useHostFacetLabels` through it). It is set on EVERY ready
|
|
255
|
+
* answer once asked for, `false` included, so a skin keying its DOM off it
|
|
256
|
+
* does not grow and drop a wrapper as the flag comes and goes.
|
|
257
|
+
*/
|
|
258
|
+
const envelope = loadStateFromQuery(query, { keepPrevious: true });
|
|
232
259
|
const meta = envelope.status === "ready" ? envelope.data.facet_meta : EMPTY_META;
|
|
233
260
|
|
|
234
261
|
const groups = mapLoad(envelope, (data) =>
|
|
@@ -271,6 +298,9 @@ export function useFacetPanel(props: {
|
|
|
271
298
|
|
|
272
299
|
return {
|
|
273
300
|
state: labelled,
|
|
301
|
+
// Read off the ANSWER's own state rather than off the projection, so a
|
|
302
|
+
// label pass that returns the groups untouched cannot lose it.
|
|
303
|
+
refreshing: envelope.status === "ready" && envelope.refreshing === true,
|
|
274
304
|
approximate: meta.approximate,
|
|
275
305
|
skipped: meta.skipped,
|
|
276
306
|
counted: meta.counted,
|