@stapel/search-react 0.32.3 → 0.32.5
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 +26 -0
- package/dist/default/FilterChips.d.ts +17 -0
- package/dist/default/FilterChips.d.ts.map +1 -1
- package/dist/default/FilterChips.js +42 -3
- package/dist/default/FilterChips.js.map +1 -1
- package/dist/default/OtherCategoriesLine.d.ts +35 -6
- package/dist/default/OtherCategoriesLine.d.ts.map +1 -1
- package/dist/default/OtherCategoriesLine.js +54 -11
- package/dist/default/OtherCategoriesLine.js.map +1 -1
- package/dist/default/SearchPage.d.ts +12 -1
- package/dist/default/SearchPage.d.ts.map +1 -1
- package/dist/default/SearchPage.js +5 -3
- package/dist/default/SearchPage.js.map +1 -1
- package/dist/default/SearchResultsPane.d.ts +47 -0
- package/dist/default/SearchResultsPane.d.ts.map +1 -1
- package/dist/default/SearchResultsPane.js +57 -2
- package/dist/default/SearchResultsPane.js.map +1 -1
- package/dist/default/index.d.ts +4 -4
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +3 -3
- package/dist/default/index.js.map +1 -1
- package/llms.txt +1 -1
- package/manifest.json +1 -1
- package/nav-manifest.json +1 -1
- package/package.json +5 -5
- package/src/analytics/generated/events.json +1 -1
- package/src/default/FilterChips.tsx +50 -2
- package/src/default/OtherCategoriesLine.tsx +65 -12
- package/src/default/SearchPage.tsx +21 -1
- package/src/default/SearchResultsPane.tsx +107 -1
- package/src/default/index.ts +10 -1
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
SkinTheme,
|
|
36
36
|
visuallyHidden,
|
|
37
37
|
} from "@stapel/tokens-antd/skin";
|
|
38
|
-
import { spacing } from "@stapel/tokens";
|
|
38
|
+
import { breakpoints, spacing } from "@stapel/tokens";
|
|
39
39
|
import type { FeatureDef } from "@stapel/attributes-react";
|
|
40
40
|
import type { SearchItem } from "../api/types.js";
|
|
41
41
|
import { SearchResults } from "../headless/SearchResults.js";
|
|
@@ -143,6 +143,64 @@ const RESULTS_LIST: CSSProperties = {
|
|
|
143
143
|
gridTemplateColumns: "1fr",
|
|
144
144
|
};
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* HOW MANY CARD COLUMNS, when the host has an opinion.
|
|
148
|
+
*
|
|
149
|
+
* A number is that many columns at every width. A map is one number per token
|
|
150
|
+
* breakpoint, and the two named rungs are the only ones there are: `phone` is
|
|
151
|
+
* the base, `tablet` and `desktop` take over as the block gets wider. A key
|
|
152
|
+
* left out inherits the rung below it.
|
|
153
|
+
*/
|
|
154
|
+
export type ResultsColumns =
|
|
155
|
+
| number
|
|
156
|
+
| Readonly<Partial<Record<"phone" | "tablet" | "desktop", number>>>;
|
|
157
|
+
|
|
158
|
+
/** The class the responsive arm's rules are hung on. */
|
|
159
|
+
export const RESULTS_COLUMNS_CLASS = "stapel-search-results-columns";
|
|
160
|
+
/** The `href` the hoisted column sheet is deduplicated by. */
|
|
161
|
+
export const RESULTS_COLUMNS_STYLE_HREF = "stapel-search-results-columns";
|
|
162
|
+
|
|
163
|
+
/** N equal tracks, each allowed to shrink below its content — `minmax(0,
|
|
164
|
+
* 1fr)` rather than `1fr`, or one long unbroken title widens the whole row. */
|
|
165
|
+
function fixedColumns(count: number): string {
|
|
166
|
+
return `repeat(${String(Math.max(1, Math.floor(count)))}, minmax(0, 1fr))`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The map, as CSS — and the query measures the BLOCK, not the window.
|
|
171
|
+
*
|
|
172
|
+
* It has to: this grid sits in the results column, which on a 1440px desktop
|
|
173
|
+
* is the window minus a 280px filter rail minus the gap. A media query would
|
|
174
|
+
* hand it a desktop column count at a width it never has, and the same three
|
|
175
|
+
* rungs are what `<PopularValues columns="responsive">` already climbs one
|
|
176
|
+
* pane over. The keys NAME the token breakpoints because those are the
|
|
177
|
+
* numbers a host thinks in; what they are compared against is this block's
|
|
178
|
+
* own inline size.
|
|
179
|
+
*
|
|
180
|
+
* Ascending, so the widest matching rung wins by ordinary cascade order.
|
|
181
|
+
*/
|
|
182
|
+
export function resultsColumnsCss(columns: ResultsColumns): string {
|
|
183
|
+
const block = `.${RESULTS_COLUMNS_CLASS}`;
|
|
184
|
+
if (typeof columns === "number") {
|
|
185
|
+
return `${block}{grid-template-columns:${fixedColumns(columns)}}`;
|
|
186
|
+
}
|
|
187
|
+
const rules = [`${block}{container-type:inline-size}`];
|
|
188
|
+
if (columns.phone !== undefined) {
|
|
189
|
+
rules.push(`${block}{grid-template-columns:${fixedColumns(columns.phone)}}`);
|
|
190
|
+
}
|
|
191
|
+
for (const rung of [
|
|
192
|
+
{ at: breakpoints.tablet, count: columns.tablet },
|
|
193
|
+
{ at: breakpoints.desktop, count: columns.desktop },
|
|
194
|
+
]) {
|
|
195
|
+
if (rung.count === undefined) continue;
|
|
196
|
+
rules.push(
|
|
197
|
+
`@container (min-width: ${String(rung.at)}px)` +
|
|
198
|
+
`{${block}{grid-template-columns:${fixedColumns(rung.count)}}}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return rules.join("\n");
|
|
202
|
+
}
|
|
203
|
+
|
|
146
204
|
export interface SearchResultsPaneProps extends ThemeModeProp {
|
|
147
205
|
/**
|
|
148
206
|
* The card slot (spec §6.2 item 1). A storefront passes
|
|
@@ -230,6 +288,26 @@ export interface SearchResultsPaneProps extends ThemeModeProp {
|
|
|
230
288
|
* Ignored when `renderResults` replaces the arrangement entirely.
|
|
231
289
|
*/
|
|
232
290
|
readonly layout?: SearchResultsLayout;
|
|
291
|
+
/**
|
|
292
|
+
* HOW MANY COLUMNS the `"grid"` arrangement draws.
|
|
293
|
+
*
|
|
294
|
+
* Default: none of them — `repeat(auto-fill, minmax(260px, 1fr))` decides,
|
|
295
|
+
* which is the right answer for a storefront that wants as many readable
|
|
296
|
+
* cards as fit and no breakpoint table to maintain. It is the wrong answer
|
|
297
|
+
* for a deployment whose cards are taller or wider than the default's, and
|
|
298
|
+
* such a host had exactly one way to say so: an `!important` rule on this
|
|
299
|
+
* pane's grid from outside, against a declaration it could not read back.
|
|
300
|
+
*
|
|
301
|
+
* A number is that many columns at every width. A map ({@link
|
|
302
|
+
* ResultsColumns}) is one number per token breakpoint — `{ phone: 1, tablet:
|
|
303
|
+
* 2 }` is the two-cards-per-row tablet SERP this exists for — compared
|
|
304
|
+
* against the width of this BLOCK rather than of the window, because the
|
|
305
|
+
* filter rail takes 280px of that window and the cards never see it.
|
|
306
|
+
*
|
|
307
|
+
* Ignored by `layout="list"` (one row per result IS one column) and by
|
|
308
|
+
* `renderResults`, which replaces the arrangement entirely.
|
|
309
|
+
*/
|
|
310
|
+
readonly columns?: ResultsColumns;
|
|
233
311
|
/**
|
|
234
312
|
* Heading level for the results caption. Default `4`, which is what every
|
|
235
313
|
* surface that EMBEDS this pane under its own title needs.
|
|
@@ -364,6 +442,12 @@ export function SearchResultsPane(props: SearchResultsPaneProps): ReactElement {
|
|
|
364
442
|
const { renderCard, renderResults, wrapResults } = props;
|
|
365
443
|
const scorerName = useScorerNames();
|
|
366
444
|
const maxWidth = props.maxWidth === undefined ? RESULTS_MAX_WIDTH : props.maxWidth;
|
|
445
|
+
// A column count is the host's opinion about the GRID; the list arrangement
|
|
446
|
+
// is one column by definition and has nothing to say to it.
|
|
447
|
+
const columnRules =
|
|
448
|
+
props.columns === undefined || props.layout === "list"
|
|
449
|
+
? null
|
|
450
|
+
: resultsColumnsCss(props.columns);
|
|
367
451
|
|
|
368
452
|
return (
|
|
369
453
|
<SkinTheme
|
|
@@ -482,9 +566,31 @@ export function SearchResultsPane(props: SearchResultsPaneProps): ReactElement {
|
|
|
482
566
|
) : (
|
|
483
567
|
<div
|
|
484
568
|
style={props.layout === "list" ? RESULTS_LIST : RESULTS_GRID}
|
|
569
|
+
// The class carries the host's column count; without one
|
|
570
|
+
// it is absent and the `auto-fill` default is the only
|
|
571
|
+
// rule in play.
|
|
572
|
+
{...(columnRules !== null
|
|
573
|
+
? { className: RESULTS_COLUMNS_CLASS }
|
|
574
|
+
: {})}
|
|
485
575
|
data-testid="search-results-grid"
|
|
486
576
|
data-layout={props.layout ?? "grid"}
|
|
577
|
+
{...(columnRules !== null
|
|
578
|
+
? {
|
|
579
|
+
"data-columns":
|
|
580
|
+
typeof props.columns === "number"
|
|
581
|
+
? String(props.columns)
|
|
582
|
+
: "responsive",
|
|
583
|
+
}
|
|
584
|
+
: {})}
|
|
487
585
|
>
|
|
586
|
+
{columnRules !== null ? (
|
|
587
|
+
<style
|
|
588
|
+
href={RESULTS_COLUMNS_STYLE_HREF}
|
|
589
|
+
precedence="default"
|
|
590
|
+
>
|
|
591
|
+
{columnRules}
|
|
592
|
+
</style>
|
|
593
|
+
) : null}
|
|
488
594
|
{items.map((item: SearchItem) => (
|
|
489
595
|
<div key={item.key}>
|
|
490
596
|
{renderCard !== undefined ? (
|
package/src/default/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ export {
|
|
|
55
55
|
OTHER_CATEGORIES_SLOT_MIN_HEIGHT,
|
|
56
56
|
OTHER_CATEGORIES_STYLE_HREF,
|
|
57
57
|
otherCategoriesCss,
|
|
58
|
+
otherCategoriesSlotHeight,
|
|
58
59
|
} from "./OtherCategoriesLine.js";
|
|
59
60
|
export type {
|
|
60
61
|
OtherCategoriesLineProps,
|
|
@@ -62,8 +63,15 @@ export type {
|
|
|
62
63
|
OtherCategoryNamer,
|
|
63
64
|
} from "./OtherCategoriesLine.js";
|
|
64
65
|
|
|
65
|
-
export {
|
|
66
|
+
export {
|
|
67
|
+
SearchResultsPane,
|
|
68
|
+
RESULTS_MAX_WIDTH,
|
|
69
|
+
RESULTS_COLUMNS_CLASS,
|
|
70
|
+
RESULTS_COLUMNS_STYLE_HREF,
|
|
71
|
+
resultsColumnsCss,
|
|
72
|
+
} from "./SearchResultsPane.js";
|
|
66
73
|
export type {
|
|
74
|
+
ResultsColumns,
|
|
67
75
|
SearchResultsPaneProps,
|
|
68
76
|
SearchResultsRenderer,
|
|
69
77
|
SearchResultsWrapper,
|
|
@@ -77,6 +85,7 @@ export {
|
|
|
77
85
|
CHIP_ROW_MIN_HEIGHT,
|
|
78
86
|
CHIP_ROW_STYLE_HREF,
|
|
79
87
|
appliedChipTestId,
|
|
88
|
+
appliedRowMinHeight,
|
|
80
89
|
buildAppliedChips,
|
|
81
90
|
capChipRow,
|
|
82
91
|
categoryLeaf,
|