@stapel/search-react 0.32.8 → 0.33.1
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 +101 -0
- package/README.md +76 -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/SearchPage.d.ts +59 -2
- package/dist/default/SearchPage.d.ts.map +1 -1
- package/dist/default/SearchPage.js +24 -4
- package/dist/default/SearchPage.js.map +1 -1
- package/dist/default/SearchResultsPane.d.ts +55 -0
- package/dist/default/SearchResultsPane.d.ts.map +1 -1
- package/dist/default/SearchResultsPane.js +133 -2
- package/dist/default/SearchResultsPane.js.map +1 -1
- package/dist/default/index.d.ts +3 -3
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +2 -2
- package/dist/default/index.js.map +1 -1
- package/llms.txt +3 -3
- package/manifest.json +3 -1
- package/nav-manifest.json +1 -1
- package/package.json +3 -3
- package/src/analytics/generated/events.json +1 -1
- package/src/default/PartitionChips.tsx +86 -1
- package/src/default/SearchPage.tsx +73 -1
- package/src/default/SearchResultsPane.tsx +209 -13
- package/src/default/index.ts +3 -0
|
@@ -94,6 +94,7 @@ import { SearchResultsPane } from "./SearchResultsPane.js";
|
|
|
94
94
|
import type {
|
|
95
95
|
ResultsColumns,
|
|
96
96
|
SearchResultsWrapper,
|
|
97
|
+
SearchToolbarPin,
|
|
97
98
|
} from "./SearchResultsPane.js";
|
|
98
99
|
import { SortSelect } from "./SortSelect.js";
|
|
99
100
|
import { SEARCH_BUILTIN_VIEWS, ViewSwitch, resolveView } from "./ViewSwitch.js";
|
|
@@ -283,6 +284,24 @@ const RAIL: CSSProperties = {
|
|
|
283
284
|
paddingBlockEnd: spacing[2],
|
|
284
285
|
};
|
|
285
286
|
|
|
287
|
+
/**
|
|
288
|
+
* The rail with a host's own offset under it.
|
|
289
|
+
*
|
|
290
|
+
* `top` and the height cap move TOGETHER, and that is the whole reason this is
|
|
291
|
+
* a function rather than one property: a rail pushed 64px down the window whose
|
|
292
|
+
* cap is still `100dvh` ends 64px past the foot of the screen, so its last
|
|
293
|
+
* control is unreachable — the internal scroll has scrolled past the window.
|
|
294
|
+
*
|
|
295
|
+
* A number is pixels; a string is taken as written, so
|
|
296
|
+
* `railTop="var(--stapel-header-height)"` reads the height `<PublicShell>`
|
|
297
|
+
* publishes instead of restating it.
|
|
298
|
+
*/
|
|
299
|
+
export function railStyle(top: number | string | undefined): CSSProperties {
|
|
300
|
+
if (top === undefined) return RAIL;
|
|
301
|
+
const offset = typeof top === "number" ? `${String(top)}px` : top;
|
|
302
|
+
return { ...RAIL, top, maxHeight: `calc(100dvh - ${offset})` };
|
|
303
|
+
}
|
|
304
|
+
|
|
286
305
|
/** The results take what is left. `minWidth: 0` so a long word inside a card
|
|
287
306
|
* cannot push the grid wider than its column. */
|
|
288
307
|
const RESULTS_COLUMN: CSSProperties = { flex: "1 1 auto", minWidth: 0 };
|
|
@@ -522,6 +541,50 @@ export interface SearchPageProps extends ThemeModeProp, ParseSearchStateOptions
|
|
|
522
541
|
* rail beside a 110px result column.
|
|
523
542
|
*/
|
|
524
543
|
readonly filtersLayout?: SearchFiltersLayout;
|
|
544
|
+
/**
|
|
545
|
+
* WHERE the filter rail's sticky top edge is — the offset of whatever chrome
|
|
546
|
+
* is pinned above this page.
|
|
547
|
+
*
|
|
548
|
+
* ```tsx
|
|
549
|
+
* // the height <PublicShell> publishes, read rather than restated
|
|
550
|
+
* <SearchPage railTop="var(--stapel-header-height)" />
|
|
551
|
+
* ```
|
|
552
|
+
*
|
|
553
|
+
* The rail is `position: sticky; top: 0` written INLINE, and an inline
|
|
554
|
+
* declaration is beaten by nothing but `!important` — so a host with a fixed
|
|
555
|
+
* header had exactly one way to say "start below it", and the fleet's
|
|
556
|
+
* storefront carried that `!important` as the only one in the repo aimed at a
|
|
557
|
+
* pair's own geometry. Every deployment with a pinned header has this
|
|
558
|
+
* problem; none of them could state it.
|
|
559
|
+
*
|
|
560
|
+
* A number is pixels; a string is taken as written (a `var()`, a `calc()`,
|
|
561
|
+
* `"4rem"`). The internal scroll cap moves with it — see {@link railStyle} —
|
|
562
|
+
* so the rail still ends at the foot of the window rather than that far past
|
|
563
|
+
* it. Default `0`, which is where the rail has always started.
|
|
564
|
+
*
|
|
565
|
+
* The phone SHEET is unaffected: it is a dialog, not a column, and it has no
|
|
566
|
+
* header to clear.
|
|
567
|
+
*/
|
|
568
|
+
readonly railTop?: number | string;
|
|
569
|
+
/**
|
|
570
|
+
* PIN the results toolbar under whatever chrome is above this page — the
|
|
571
|
+
* other column's half of {@link railTop}.
|
|
572
|
+
*
|
|
573
|
+
* ```tsx
|
|
574
|
+
* <SearchPage
|
|
575
|
+
* railTop="var(--stapel-header-height)"
|
|
576
|
+
* stickyToolbar={{ top: "var(--stapel-header-height)" }}
|
|
577
|
+
* />
|
|
578
|
+
* ```
|
|
579
|
+
*
|
|
580
|
+
* Handed straight to `<SearchResultsPane stickyToolbar>`, which pins the row
|
|
581
|
+
* it actually drew — the whole header block in the wide shape, the toolbar
|
|
582
|
+
* alone in the compact one. There is exactly one sort control on a results
|
|
583
|
+
* page and this pins it where it stands; a host that draws a bar of its own
|
|
584
|
+
* over the page ends up with two, and the one a person presses is whichever
|
|
585
|
+
* the layout put under their thumb.
|
|
586
|
+
*/
|
|
587
|
+
readonly stickyToolbar?: SearchToolbarPin;
|
|
525
588
|
/**
|
|
526
589
|
* The host's own exits from an empty result — sibling sections with their
|
|
527
590
|
* counts. A SLOT for the same reason `breadcrumb` is one: walking the tree
|
|
@@ -639,6 +702,8 @@ interface SearchPageBodyProps {
|
|
|
639
702
|
readonly degradationNotice?: DegradationNoticeVariant;
|
|
640
703
|
readonly railFrom?: SearchRailFrom;
|
|
641
704
|
readonly filtersLayout?: SearchFiltersLayout;
|
|
705
|
+
readonly railTop?: number | string;
|
|
706
|
+
readonly stickyToolbar?: SearchToolbarPin;
|
|
642
707
|
readonly defaultFiltersOpen?: boolean;
|
|
643
708
|
readonly pageSize?: boolean;
|
|
644
709
|
readonly breadcrumb?: ReactNode;
|
|
@@ -838,6 +903,9 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
|
|
|
838
903
|
toolbar={toolbar}
|
|
839
904
|
{...(props.resultsLead !== undefined ? { lead: props.resultsLead } : {})}
|
|
840
905
|
{...(phoneToolbar ? { header: "compact" as const } : {})}
|
|
906
|
+
{...(props.stickyToolbar !== undefined
|
|
907
|
+
? { stickyToolbar: props.stickyToolbar }
|
|
908
|
+
: {})}
|
|
841
909
|
headingLevel={props.resultsHeadingLevel ?? 1}
|
|
842
910
|
{...(view.render !== undefined ? { renderResults: view.render } : {})}
|
|
843
911
|
{...(props.wrapResults !== undefined ? { wrapResults: props.wrapResults } : {})}
|
|
@@ -998,7 +1066,7 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
|
|
|
998
1066
|
</>
|
|
999
1067
|
) : showFilters ? (
|
|
1000
1068
|
<Flex align="flex-start" gap={spacing[5]} data-testid="search-page-columns">
|
|
1001
|
-
<div className={RAIL_CLASS} style={
|
|
1069
|
+
<div className={RAIL_CLASS} style={railStyle(props.railTop)}>
|
|
1002
1070
|
{/* The rail's scrollbar, in the gutter and in the token palette —
|
|
1003
1071
|
see `railScrollbarCss`. Hoisted, deduped by `href`. */}
|
|
1004
1072
|
<style href={RAIL_STYLE_HREF} precedence="default">
|
|
@@ -1050,6 +1118,8 @@ export function SearchPage(props: SearchPageProps): ReactElement {
|
|
|
1050
1118
|
degradationNotice,
|
|
1051
1119
|
railFrom,
|
|
1052
1120
|
filtersLayout,
|
|
1121
|
+
railTop,
|
|
1122
|
+
stickyToolbar,
|
|
1053
1123
|
defaultFiltersOpen,
|
|
1054
1124
|
pageSize,
|
|
1055
1125
|
breadcrumb,
|
|
@@ -1102,6 +1172,8 @@ export function SearchPage(props: SearchPageProps): ReactElement {
|
|
|
1102
1172
|
{...(degradationNotice !== undefined ? { degradationNotice } : {})}
|
|
1103
1173
|
{...(railFrom !== undefined ? { railFrom } : {})}
|
|
1104
1174
|
{...(filtersLayout !== undefined ? { filtersLayout } : {})}
|
|
1175
|
+
{...(railTop !== undefined ? { railTop } : {})}
|
|
1176
|
+
{...(stickyToolbar !== undefined ? { stickyToolbar } : {})}
|
|
1105
1177
|
{...(defaultFiltersOpen !== undefined ? { defaultFiltersOpen } : {})}
|
|
1106
1178
|
{...(pageSize !== undefined ? { pageSize } : {})}
|
|
1107
1179
|
{...(breadcrumb !== undefined ? { breadcrumb } : {})}
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
SkinTheme,
|
|
36
36
|
visuallyHidden,
|
|
37
37
|
} from "@stapel/tokens-antd/skin";
|
|
38
|
-
import { breakpoints, spacing } from "@stapel/tokens";
|
|
38
|
+
import { breakpoints, cssVar, 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";
|
|
@@ -109,6 +109,144 @@ export type SearchResultsWrapper = (
|
|
|
109
109
|
*/
|
|
110
110
|
export const RESULTS_MAX_WIDTH = 1400;
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* The class on the row that carries the results toolbar — the sort control, the
|
|
114
|
+
* view switch, the page size, the count and the surface's own action.
|
|
115
|
+
*
|
|
116
|
+
* The row had no element of its own: no class, no `data-testid`, and in the
|
|
117
|
+
* compact header it was one child of a 112px vertical stack. A host that wanted
|
|
118
|
+
* it pinned under a fixed header therefore had to reach it with a `:has()` on
|
|
119
|
+
* the heading beside it, a `display: contents` to drop the stack's box, and the
|
|
120
|
+
* pane's own breakpoint restated in a media query — three rules aimed at a
|
|
121
|
+
* shape the pane could change under them at any release.
|
|
122
|
+
*
|
|
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
|
+
*/
|
|
129
|
+
export const RESULTS_TOOLBAR_CLASS = "stapel-search-results-toolbar";
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Where {@link SearchResultsPaneProps.stickyToolbar} pins the row.
|
|
133
|
+
*
|
|
134
|
+
* `top` is a CSS length — a number is pixels, a string is taken as written, so
|
|
135
|
+
* `top: "var(--stapel-header-height)"` reads the height `<PublicShell>`
|
|
136
|
+
* publishes rather than restating it. Default `0`.
|
|
137
|
+
*/
|
|
138
|
+
export interface SearchToolbarPin {
|
|
139
|
+
readonly top?: number | string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The controls' own line: it may not become two.
|
|
144
|
+
*
|
|
145
|
+
* A wrapping row changes height when the count lands — the count arrives with
|
|
146
|
+
* the answer, one render after the toolbar was already on screen — and a bar
|
|
147
|
+
* that grows while it is pinned pushes the first cards of the feed down under
|
|
148
|
+
* the reader's eye. `nowrap` makes the height a constant; `min-inline-size: 0`
|
|
149
|
+
* plus `overflow-x: auto` is what a row that cannot wrap does with the overflow
|
|
150
|
+
* instead, which on a phone is the scroll strip every sort row already is.
|
|
151
|
+
*/
|
|
152
|
+
const TOOLBAR_ROW: CSSProperties = {
|
|
153
|
+
flexWrap: "nowrap",
|
|
154
|
+
minInlineSize: 0,
|
|
155
|
+
overflowX: "auto",
|
|
156
|
+
// Thin, and only when there is something to scroll to — the same reason the
|
|
157
|
+
// filter rail states its own: an invisible scroll port is indistinguishable
|
|
158
|
+
// from a row that ends where it was cut.
|
|
159
|
+
scrollbarWidth: "thin",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The compact header's stack, WITHOUT its box.
|
|
164
|
+
*
|
|
165
|
+
* `position: sticky` travels inside its parent, so a toolbar nested in a 112px
|
|
166
|
+
* stack can move 112px and no further — the parent has to be the results
|
|
167
|
+
* column, which is as tall as the feed. `display: contents` drops the stack's
|
|
168
|
+
* box and hands its three rows straight to that column, which is the same
|
|
169
|
+
* device the fleet's storefront was writing in its own sheet.
|
|
170
|
+
*
|
|
171
|
+
* The one visible consequence: the gap between heading, toolbar and count
|
|
172
|
+
* becomes the column's rather than the stack's.
|
|
173
|
+
*/
|
|
174
|
+
const COMPACT_STACK: CSSProperties = { display: "contents" };
|
|
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
|
+
|
|
219
|
+
/** The compact shape's toolbar box — the row the pin acts on. */
|
|
220
|
+
const COMPACT_TOOLBAR: CSSProperties = {
|
|
221
|
+
display: "flex",
|
|
222
|
+
alignItems: "center",
|
|
223
|
+
...TOOLBAR_ROW,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The pin itself, and it is NET-ZERO in flow: `position` and a layer and a
|
|
228
|
+
* background, and not one pixel of padding.
|
|
229
|
+
*
|
|
230
|
+
* A pinned bar that grows padding to breathe over the cards passing under it
|
|
231
|
+
* has to take the same padding back as negative margin or the column pays for
|
|
232
|
+
* it, and the pair does not know which of those two a host's spacing wants. A
|
|
233
|
+
* row that occupies exactly the box it already occupied cannot shift anything.
|
|
234
|
+
*/
|
|
235
|
+
function toolbarPinStyle(pin: SearchToolbarPin | undefined): CSSProperties | undefined {
|
|
236
|
+
if (pin === undefined) return undefined;
|
|
237
|
+
return {
|
|
238
|
+
position: "sticky",
|
|
239
|
+
top: pin.top ?? 0,
|
|
240
|
+
// Over the cards, under the page's own chrome — and under antd's popups,
|
|
241
|
+
// so the sort select still opens over its own bar.
|
|
242
|
+
zIndex: 1,
|
|
243
|
+
// Opaque, or the cards scroll THROUGH the bar. The theme's own surface
|
|
244
|
+
// role, resolved at paint time, so it follows the brand and the dark side
|
|
245
|
+
// without a second colour being written here.
|
|
246
|
+
background: cssVar("surface"),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
112
250
|
/**
|
|
113
251
|
* The results grid. `auto-fill` + `minmax(260px, 1fr)`: as many columns as fit,
|
|
114
252
|
* each at least a readable card and never wider than its share — and the same
|
|
@@ -396,6 +534,33 @@ export interface SearchResultsPaneProps extends ThemeModeProp {
|
|
|
396
534
|
* viewport is not spent saying "Results" over a list of results.
|
|
397
535
|
*/
|
|
398
536
|
readonly header?: "banner" | "compact";
|
|
537
|
+
/**
|
|
538
|
+
* PIN the toolbar row under whatever chrome is above this pane.
|
|
539
|
+
*
|
|
540
|
+
* ```tsx
|
|
541
|
+
* // the height <PublicShell> publishes, read rather than restated
|
|
542
|
+
* <SearchResultsPane stickyToolbar={{ top: "var(--stapel-header-height)" }} />
|
|
543
|
+
* ```
|
|
544
|
+
*
|
|
545
|
+
* A catalogue page is thirty cards long and the control that reorders them is
|
|
546
|
+
* at the top of it, so by the fourth row the sort is a screenful above the
|
|
547
|
+
* list it sorts — the same argument that made the filter rail sticky, applied
|
|
548
|
+
* to the other column.
|
|
549
|
+
*
|
|
550
|
+
* It pins the row the pane ACTUALLY DREW, which is why this is a prop and not
|
|
551
|
+
* a host's stylesheet: the pane knows which of its two header shapes it is in
|
|
552
|
+
* and a sheet has to guess. In `"banner"` the whole header block pins (it is
|
|
553
|
+
* one line: heading, count, toolbar). In `"compact"` the toolbar alone pins —
|
|
554
|
+
* one row, the height of one phone control — because the stack it sits in is
|
|
555
|
+
* ~112px and that is not a bar anybody can pin under a 56px header.
|
|
556
|
+
*
|
|
557
|
+
* Two things it does NOT do, both deliberate: it adds no padding (see
|
|
558
|
+
* {@link toolbarPinStyle} — a pinned bar that grows to breathe has to give
|
|
559
|
+
* the room back, and which spacing that is belongs to the host), and it draws
|
|
560
|
+
* no second sort control. There is exactly one on a results page, and this
|
|
561
|
+
* pins it where it stands.
|
|
562
|
+
*/
|
|
563
|
+
readonly stickyToolbar?: SearchToolbarPin;
|
|
399
564
|
/**
|
|
400
565
|
* The category's feature schema, used ONLY to name an applied filter in the
|
|
401
566
|
* empty state's exits ("Without Brand" rather than "Without vendor").
|
|
@@ -503,6 +668,7 @@ export function SearchResultsPane(props: SearchResultsPaneProps): ReactElement {
|
|
|
503
668
|
// The compact arm's caption: the host's word is drawn, the pair's own is
|
|
504
669
|
// not, and `headingVisible` overrides either way — see that prop.
|
|
505
670
|
const compactHeadingSeen = props.headingVisible ?? props.heading !== undefined;
|
|
671
|
+
const toolbarPin = toolbarPinStyle(props.stickyToolbar);
|
|
506
672
|
const columnRules =
|
|
507
673
|
props.columns === undefined || props.layout === "list"
|
|
508
674
|
? null
|
|
@@ -542,7 +708,15 @@ export function SearchResultsPane(props: SearchResultsPaneProps): ReactElement {
|
|
|
542
708
|
<div data-testid="search-results-lead">{props.lead}</div>
|
|
543
709
|
)}
|
|
544
710
|
{props.header === "compact" ? (
|
|
545
|
-
|
|
711
|
+
/* No box of its own — see `COMPACT_STACK`. The three rows are
|
|
712
|
+
items of the results COLUMN, so the toolbar between them has a
|
|
713
|
+
parent as tall as the feed to travel in. */
|
|
714
|
+
<Flex
|
|
715
|
+
vertical
|
|
716
|
+
gap={spacing[2]}
|
|
717
|
+
style={COMPACT_STACK}
|
|
718
|
+
data-testid="search-results-header-compact"
|
|
719
|
+
>
|
|
546
720
|
<Typography.Title
|
|
547
721
|
level={props.headingLevel ?? 4}
|
|
548
722
|
style={compactHeadingSeen ? { margin: 0 } : visuallyHidden}
|
|
@@ -551,23 +725,45 @@ export function SearchResultsPane(props: SearchResultsPaneProps): ReactElement {
|
|
|
551
725
|
>
|
|
552
726
|
{props.heading ?? t(SEARCH_I18N_KEYS.resultsTitle)}
|
|
553
727
|
</Typography.Title>
|
|
554
|
-
|
|
728
|
+
<div
|
|
729
|
+
className={RESULTS_TOOLBAR_CLASS}
|
|
730
|
+
data-testid="search-results-toolbar"
|
|
731
|
+
style={{ ...COMPACT_TOOLBAR, ...toolbarPin }}
|
|
732
|
+
>
|
|
733
|
+
{props.toolbar}
|
|
734
|
+
</div>
|
|
555
735
|
<Count bag={bag} />
|
|
556
736
|
</Flex>
|
|
557
737
|
) : (
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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 }}
|
|
563
760
|
>
|
|
564
|
-
{props.heading ?? t(SEARCH_I18N_KEYS.resultsTitle)}
|
|
565
|
-
</Typography.Title>
|
|
566
|
-
<Flex align="center" wrap gap={spacing[3]}>
|
|
567
761
|
<Count bag={bag} />
|
|
568
|
-
{
|
|
762
|
+
<Flex align="center" gap={spacing[3]} style={TOOLBAR_END}>
|
|
763
|
+
{props.toolbar}
|
|
764
|
+
</Flex>
|
|
569
765
|
</Flex>
|
|
570
|
-
|
|
766
|
+
</>
|
|
571
767
|
)}
|
|
572
768
|
|
|
573
769
|
<DegradationNotice
|
package/src/default/index.ts
CHANGED
|
@@ -45,6 +45,7 @@ export {
|
|
|
45
45
|
RAIL_CLASS,
|
|
46
46
|
RAIL_STYLE_HREF,
|
|
47
47
|
railScrollbarCss,
|
|
48
|
+
railStyle,
|
|
48
49
|
} from "./SearchPage.js";
|
|
49
50
|
export type {
|
|
50
51
|
SearchPageProps,
|
|
@@ -73,6 +74,7 @@ export {
|
|
|
73
74
|
RESULTS_AUTO_FILL_COLUMNS,
|
|
74
75
|
RESULTS_COLUMNS_CLASS,
|
|
75
76
|
RESULTS_COLUMNS_STYLE_HREF,
|
|
77
|
+
RESULTS_TOOLBAR_CLASS,
|
|
76
78
|
resultsColumnsCss,
|
|
77
79
|
} from "./SearchResultsPane.js";
|
|
78
80
|
export type {
|
|
@@ -80,6 +82,7 @@ export type {
|
|
|
80
82
|
SearchResultsPaneProps,
|
|
81
83
|
SearchResultsRenderer,
|
|
82
84
|
SearchResultsWrapper,
|
|
85
|
+
SearchToolbarPin,
|
|
83
86
|
} from "./SearchResultsPane.js";
|
|
84
87
|
|
|
85
88
|
export {
|