@stapel/search-react 0.35.0 → 0.36.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 +73 -0
- package/README.md +10 -1
- package/dist/default/FacetPanelPane.d.ts.map +1 -1
- package/dist/default/FacetPanelPane.js +1 -1
- package/dist/default/FacetPanelPane.js.map +1 -1
- package/dist/default/PartitionChips.d.ts +16 -0
- package/dist/default/PartitionChips.d.ts.map +1 -1
- package/dist/default/PartitionChips.js +14 -3
- package/dist/default/PartitionChips.js.map +1 -1
- package/dist/default/SearchPage.d.ts +101 -3
- package/dist/default/SearchPage.d.ts.map +1 -1
- package/dist/default/SearchPage.js +37 -13
- package/dist/default/SearchPage.js.map +1 -1
- package/dist/default/SearchResultsPane.d.ts.map +1 -1
- package/dist/default/SearchResultsPane.js +18 -1
- package/dist/default/SearchResultsPane.js.map +1 -1
- package/dist/default/index.d.ts +1 -1
- package/dist/default/index.d.ts.map +1 -1
- 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 +4 -4
- package/src/analytics/generated/events.json +1 -1
- package/src/default/FacetPanelPane.tsx +12 -1
- package/src/default/PartitionChips.tsx +41 -2
- package/src/default/SearchPage.tsx +166 -14
- package/src/default/SearchResultsPane.tsx +25 -2
- package/src/default/index.ts +3 -0
|
@@ -80,6 +80,7 @@ import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
|
|
|
80
80
|
import { FacetPanelPane } from "./FacetPanelPane.js";
|
|
81
81
|
import type {
|
|
82
82
|
CategoryFilterSlotProps,
|
|
83
|
+
FacetPanelPaneProps,
|
|
83
84
|
GeoFilterSlotProps,
|
|
84
85
|
} from "./FacetPanelPane.js";
|
|
85
86
|
import { FilterChips } from "./FilterChips.js";
|
|
@@ -107,6 +108,54 @@ import type { ThemeModeProp } from "./types.js";
|
|
|
107
108
|
/** Where the filters live: beside the results, or behind a button in a sheet. */
|
|
108
109
|
export type SearchFiltersLayout = "column" | "sheet";
|
|
109
110
|
|
|
111
|
+
/**
|
|
112
|
+
* WHY the filter sheet's open state moved — handed to `onFiltersOpenChange`
|
|
113
|
+
* beside the new value.
|
|
114
|
+
*
|
|
115
|
+
* A host that only mirrors the boolean can ignore it. A host that ACTS on the
|
|
116
|
+
* close cannot: "the person pressed Show results" and "the person swiped the
|
|
117
|
+
* sheet away" are the same `false` and not the same event, and a container
|
|
118
|
+
* that logs one as the other reports an intent nobody had.
|
|
119
|
+
*
|
|
120
|
+
* - `open` — the sheet was asked for (the all-filters chip, the location
|
|
121
|
+
* row's door).
|
|
122
|
+
* - `apply` — the footer's "Show N results" committed and closed it.
|
|
123
|
+
* - `dismiss` — the dialog itself closed: the X, the scrim, Escape.
|
|
124
|
+
* - `consumer` — the host closed it through `filtersHeader`'s `closeFilters`.
|
|
125
|
+
*/
|
|
126
|
+
export type SearchFiltersOpenReason = "open" | "apply" | "dismiss" | "consumer";
|
|
127
|
+
|
|
128
|
+
/** What `filtersHeader` is handed when a host passes a function. */
|
|
129
|
+
export interface SearchFiltersHeaderSlotProps {
|
|
130
|
+
/**
|
|
131
|
+
* Shut the sheet, with the reason `"consumer"`.
|
|
132
|
+
*
|
|
133
|
+
* This is the whole point of the callback form: a control in this slot that
|
|
134
|
+
* NAVIGATES on a press (a partition or axis chip that changes the route)
|
|
135
|
+
* has to close the sheet on that same press, or the next page opens under a
|
|
136
|
+
* drawer that is still up. Without it a host had to lift the open state out
|
|
137
|
+
* of the page — which it could not, because the page only offered
|
|
138
|
+
* `defaultFiltersOpen`.
|
|
139
|
+
*
|
|
140
|
+
* Harmless in the column layout, where there is no sheet to shut: the page
|
|
141
|
+
* still reports the change, and nothing moves on screen.
|
|
142
|
+
*/
|
|
143
|
+
readonly closeFilters: () => void;
|
|
144
|
+
/** Is the sheet open around this header right now. */
|
|
145
|
+
readonly open: boolean;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The `filtersHeader` slot: a node, or a function told the sheet's state.
|
|
150
|
+
*
|
|
151
|
+
* The node form is what it has always been. The function form exists so a
|
|
152
|
+
* header can close the sheet it is inside without the host owning the open
|
|
153
|
+
* state — see {@link SearchFiltersHeaderSlotProps.closeFilters}.
|
|
154
|
+
*/
|
|
155
|
+
export type SearchFiltersHeader =
|
|
156
|
+
| ReactNode
|
|
157
|
+
| ((slot: SearchFiltersHeaderSlotProps) => ReactNode);
|
|
158
|
+
|
|
110
159
|
/**
|
|
111
160
|
* WHERE the filter rail earns its 280px, when the token `tablet` edge is the
|
|
112
161
|
* wrong place to draw it.
|
|
@@ -413,6 +462,22 @@ export interface SearchPageProps extends ThemeModeProp, ParseSearchStateOptions
|
|
|
413
462
|
* saves less than folding a column's does. Set it to override either.
|
|
414
463
|
*/
|
|
415
464
|
readonly visibleGroups?: number | null;
|
|
465
|
+
/**
|
|
466
|
+
* The filter panel's footer — what the filters DID (the live count) and the
|
|
467
|
+
* way out of them — see {@link FacetPanelPaneProps.footerBar}.
|
|
468
|
+
*
|
|
469
|
+
* Defaulted PER LAYOUT: `"static"` in the desktop COLUMN (the rail scrolls
|
|
470
|
+
* with the page, and a bar pinned to the port's floor sat on top of the last
|
|
471
|
+
* groups), and none in the phone SHEET, whose own "Show N results" footer is
|
|
472
|
+
* already the count AND the exit.
|
|
473
|
+
*
|
|
474
|
+
* It is a PROP because it could not be reached any other way: the bar writes
|
|
475
|
+
* its own `display` inline, so a host stylesheet cannot suppress it without
|
|
476
|
+
* `!important`, and this page hard-coded the column's value. A surface that
|
|
477
|
+
* draws its own count under the rail passes `false`; one that wants the bar
|
|
478
|
+
* inside the sheet as well passes `"sticky"`.
|
|
479
|
+
*/
|
|
480
|
+
readonly footerBar?: FacetPanelPaneProps["footerBar"];
|
|
416
481
|
/** Print the engine's list of uncounted facet slugs in the filter panel.
|
|
417
482
|
* Default `false` — see {@link FacetPanelPaneProps.skippedNotice}. */
|
|
418
483
|
readonly skippedNotice?: boolean;
|
|
@@ -437,8 +502,12 @@ export interface SearchPageProps extends ThemeModeProp, ParseSearchStateOptions
|
|
|
437
502
|
* for. Whatever a host renders here reads and writes the same URL state as
|
|
438
503
|
* the facets beside it (`useSearchState()`), so it is a filter in every
|
|
439
504
|
* sense that matters and not a decoration bolted on top.
|
|
505
|
+
*
|
|
506
|
+
* A FUNCTION is handed `{ closeFilters, open }` — for a header whose own
|
|
507
|
+
* control navigates away, which on a phone has to take the sheet down with
|
|
508
|
+
* it. See {@link SearchFiltersHeaderSlotProps}.
|
|
440
509
|
*/
|
|
441
|
-
readonly filtersHeader?:
|
|
510
|
+
readonly filtersHeader?: SearchFiltersHeader;
|
|
442
511
|
/**
|
|
443
512
|
* The block-size that slot will END UP at, declared before it has anything
|
|
444
513
|
* in it — a number in CSS pixels or any length (`"96px"`, `"6rem"`).
|
|
@@ -624,9 +693,47 @@ export interface SearchPageProps extends ThemeModeProp, ParseSearchStateOptions
|
|
|
624
693
|
* from a category page), and for the story that photographs the sheet —
|
|
625
694
|
* a state reached only by a tap is a state nothing outside a browser has
|
|
626
695
|
* ever seen. The person still closes it; this is the initial value, not a
|
|
627
|
-
* controlled one.
|
|
696
|
+
* controlled one — pass `filtersOpen` for that.
|
|
628
697
|
*/
|
|
629
698
|
readonly defaultFiltersOpen?: boolean;
|
|
699
|
+
/**
|
|
700
|
+
* The sheet's open state, OWNED BY THE HOST.
|
|
701
|
+
*
|
|
702
|
+
* React's usual contract: pass this and the page stops keeping its own copy
|
|
703
|
+
* — every open and every close is a call to `onFiltersOpenChange` and
|
|
704
|
+
* nothing moves until the value comes back. Leave it out and the page is
|
|
705
|
+
* uncontrolled exactly as before, `defaultFiltersOpen` its initial value.
|
|
706
|
+
*
|
|
707
|
+
* It exists because a control the host renders INSIDE the sheet can end the
|
|
708
|
+
* search that sheet belongs to. A partition chip in `filtersHeader` that
|
|
709
|
+
* navigates leaves the drawer standing over the page it opened, and the
|
|
710
|
+
* host had no handle on the state to shut it — the page exposed the initial
|
|
711
|
+
* value and nothing else. A host that only needs the close and not the
|
|
712
|
+
* state can take `filtersHeader`'s `closeFilters` instead and keep the page
|
|
713
|
+
* uncontrolled.
|
|
714
|
+
*
|
|
715
|
+
* ```tsx
|
|
716
|
+
* const [filtersOpen, setFiltersOpen] = useState(false);
|
|
717
|
+
* <SearchPage
|
|
718
|
+
* filtersOpen={filtersOpen}
|
|
719
|
+
* onFiltersOpenChange={(open) => { setFiltersOpen(open); }}
|
|
720
|
+
* filtersHeader={<PartitionChips onPick={(href) => { setFiltersOpen(false); navigate(href); }} />}
|
|
721
|
+
* />
|
|
722
|
+
* ```
|
|
723
|
+
*/
|
|
724
|
+
readonly filtersOpen?: boolean;
|
|
725
|
+
/**
|
|
726
|
+
* Told that the sheet wants to open or close, and WHY — see
|
|
727
|
+
* {@link SearchFiltersOpenReason}.
|
|
728
|
+
*
|
|
729
|
+
* Called in both modes, controlled and not: a host that wants to watch the
|
|
730
|
+
* sheet (an analytics event on the dismiss, a scroll lock of its own) does
|
|
731
|
+
* not have to take ownership of the state to hear about it.
|
|
732
|
+
*/
|
|
733
|
+
readonly onFiltersOpenChange?: (
|
|
734
|
+
open: boolean,
|
|
735
|
+
reason: SearchFiltersOpenReason
|
|
736
|
+
) => void;
|
|
630
737
|
/** Offer a page-size control beside the sort. Default `true`. */
|
|
631
738
|
readonly pageSize?: boolean;
|
|
632
739
|
/**
|
|
@@ -702,6 +809,7 @@ interface SearchPageBodyProps {
|
|
|
702
809
|
readonly resultsLead?: ReactNode;
|
|
703
810
|
readonly dictionaryMode?: "field" | "inline" | "sheet";
|
|
704
811
|
readonly visibleGroups?: number | null;
|
|
812
|
+
readonly footerBar?: FacetPanelPaneProps["footerBar"];
|
|
705
813
|
readonly categoryFeatures?: readonly FeatureDef[];
|
|
706
814
|
readonly categoryFeaturesPending?: boolean;
|
|
707
815
|
readonly filtersHeaderReserve?: number | string;
|
|
@@ -717,7 +825,7 @@ interface SearchPageBodyProps {
|
|
|
717
825
|
readonly geoLabel?: ReactNode;
|
|
718
826
|
readonly skippedNotice?: boolean;
|
|
719
827
|
readonly footer?: ReactNode;
|
|
720
|
-
readonly filtersHeader?:
|
|
828
|
+
readonly filtersHeader?: SearchFiltersHeader;
|
|
721
829
|
readonly resultsHeader?: ReactNode;
|
|
722
830
|
readonly appliedChips?: boolean | "desktop";
|
|
723
831
|
readonly otherCategories?: boolean;
|
|
@@ -731,6 +839,11 @@ interface SearchPageBodyProps {
|
|
|
731
839
|
readonly railTop?: number | string;
|
|
732
840
|
readonly stickyToolbar?: SearchToolbarPin;
|
|
733
841
|
readonly defaultFiltersOpen?: boolean;
|
|
842
|
+
readonly filtersOpen?: boolean;
|
|
843
|
+
readonly onFiltersOpenChange?: (
|
|
844
|
+
open: boolean,
|
|
845
|
+
reason: SearchFiltersOpenReason
|
|
846
|
+
) => void;
|
|
734
847
|
readonly pageSize?: boolean;
|
|
735
848
|
readonly breadcrumb?: ReactNode;
|
|
736
849
|
readonly wrapResults?: SearchResultsWrapper;
|
|
@@ -767,7 +880,36 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
|
|
|
767
880
|
: surface === "sheet"
|
|
768
881
|
? "sheet"
|
|
769
882
|
: "column");
|
|
770
|
-
|
|
883
|
+
/**
|
|
884
|
+
* The rail's footer bar, defaulted PER LAYOUT and overridable — see
|
|
885
|
+
* {@link SearchPageProps.footerBar}.
|
|
886
|
+
*
|
|
887
|
+
* STATIC, not sticky, in the column: the rail scrolls with the page, and a
|
|
888
|
+
* bar pinned to the port's floor sat on top of the last groups. None in the
|
|
889
|
+
* sheet, whose own "Show N results" footer is already the count and the exit.
|
|
890
|
+
*/
|
|
891
|
+
const footerBar: FacetPanelPaneProps["footerBar"] =
|
|
892
|
+
props.footerBar ?? (layout === "sheet" ? undefined : "static");
|
|
893
|
+
// Controlled or not, decided by the PRESENCE of `filtersOpen` and read once
|
|
894
|
+
// per render — the state the page keeps is only ever the uncontrolled half,
|
|
895
|
+
// and a controlled host's value is never copied into it (copying it is how
|
|
896
|
+
// a controlled component starts disagreeing with its owner one frame after
|
|
897
|
+
// the owner refuses a change).
|
|
898
|
+
const controlledFiltersOpen = props.filtersOpen;
|
|
899
|
+
const [ownFiltersOpen, setOwnFiltersOpen] = useState(
|
|
900
|
+
props.defaultFiltersOpen === true
|
|
901
|
+
);
|
|
902
|
+
const sheetOpen = controlledFiltersOpen ?? ownFiltersOpen;
|
|
903
|
+
const { onFiltersOpenChange } = props;
|
|
904
|
+
const setSheetOpen = (next: boolean, reason: SearchFiltersOpenReason): void => {
|
|
905
|
+
if (controlledFiltersOpen === undefined) {
|
|
906
|
+
setOwnFiltersOpen(next);
|
|
907
|
+
}
|
|
908
|
+
onFiltersOpenChange?.(next, reason);
|
|
909
|
+
};
|
|
910
|
+
const closeFilters = (): void => {
|
|
911
|
+
setSheetOpen(false, "consumer");
|
|
912
|
+
};
|
|
771
913
|
|
|
772
914
|
// How the results are ARRANGED. Component state, not URL state: it changes
|
|
773
915
|
// how the same answer is drawn, never what the answer is, so it must not
|
|
@@ -857,7 +999,14 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
|
|
|
857
999
|
? { style: { minBlockSize: props.filtersHeaderReserve } }
|
|
858
1000
|
: {})}
|
|
859
1001
|
>
|
|
860
|
-
{
|
|
1002
|
+
{/* The function form is called HERE, inside the slot it fills, so a
|
|
1003
|
+
header that reads `open` re-renders with the sheet. Presence is
|
|
1004
|
+
still decided by the PROP above and never by what the function
|
|
1005
|
+
returned: a header that renders nothing this frame must not
|
|
1006
|
+
collapse the box it reserved. */}
|
|
1007
|
+
{typeof filtersHeader === "function"
|
|
1008
|
+
? filtersHeader({ closeFilters, open: sheetOpen })
|
|
1009
|
+
: filtersHeader}
|
|
861
1010
|
</div>
|
|
862
1011
|
)}
|
|
863
1012
|
{/* The facet panel is skipped entirely when the only thing it would draw
|
|
@@ -873,11 +1022,8 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
|
|
|
873
1022
|
result count scrolled out of sight above the fold. */}
|
|
874
1023
|
{filtersEmpty ? null : (
|
|
875
1024
|
<FacetPanelPane
|
|
876
|
-
{...(layout === "sheet"
|
|
877
|
-
|
|
878
|
-
: // STATIC, not sticky: the rail scrolls with the page, and a bar
|
|
879
|
-
// pinned to the port's floor sat on top of the last groups.
|
|
880
|
-
{ footerBar: "static" as const })}
|
|
1025
|
+
{...(layout === "sheet" ? { heading: null } : {})}
|
|
1026
|
+
{...(footerBar !== undefined ? { footerBar } : {})}
|
|
881
1027
|
dictionaryMode={props.dictionaryMode ?? (layout === "sheet" ? "sheet" : "field")}
|
|
882
1028
|
// `??` would treat an explicit `null` ("never fold") the same as
|
|
883
1029
|
// "not set": `visibleGroups` uses `null` as a real value, unlike
|
|
@@ -1036,7 +1182,7 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
|
|
|
1036
1182
|
// has the whole panel on screen beside this row.
|
|
1037
1183
|
filtersDoor={layout === "sheet"}
|
|
1038
1184
|
onOpenAll={() => {
|
|
1039
|
-
setSheetOpen(true);
|
|
1185
|
+
setSheetOpen(true, "open");
|
|
1040
1186
|
}}
|
|
1041
1187
|
/>
|
|
1042
1188
|
)}
|
|
@@ -1073,7 +1219,7 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
|
|
|
1073
1219
|
still the whole panel, for the person who wants all of it. */}
|
|
1074
1220
|
<FilterChips
|
|
1075
1221
|
onOpenAll={() => {
|
|
1076
|
-
setSheetOpen(true);
|
|
1222
|
+
setSheetOpen(true, "open");
|
|
1077
1223
|
}}
|
|
1078
1224
|
{...(categoryFeatures !== undefined ? { categoryFeatures } : {})}
|
|
1079
1225
|
{...(locale !== undefined ? { locale } : {})}
|
|
@@ -1092,7 +1238,7 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
|
|
|
1092
1238
|
<SkinDialog
|
|
1093
1239
|
open={sheetOpen}
|
|
1094
1240
|
onClose={() => {
|
|
1095
|
-
setSheetOpen(false);
|
|
1241
|
+
setSheetOpen(false, "dismiss");
|
|
1096
1242
|
}}
|
|
1097
1243
|
title={t(SEARCH_I18N_KEYS.facetsTitle)}
|
|
1098
1244
|
dismissLabel={t(SEARCH_I18N_KEYS.filtersDismiss)}
|
|
@@ -1105,7 +1251,7 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
|
|
|
1105
1251
|
data-analytics="none"
|
|
1106
1252
|
data-analytics-reason="the filters are already applied; this closes the sheet"
|
|
1107
1253
|
onClick={() => {
|
|
1108
|
-
setSheetOpen(false);
|
|
1254
|
+
setSheetOpen(false, "apply");
|
|
1109
1255
|
}}
|
|
1110
1256
|
>
|
|
1111
1257
|
{applyLabel}
|
|
@@ -1175,6 +1321,8 @@ export function SearchPage(props: SearchPageProps): ReactElement {
|
|
|
1175
1321
|
railTop,
|
|
1176
1322
|
stickyToolbar,
|
|
1177
1323
|
defaultFiltersOpen,
|
|
1324
|
+
filtersOpen,
|
|
1325
|
+
onFiltersOpenChange,
|
|
1178
1326
|
pageSize,
|
|
1179
1327
|
breadcrumb,
|
|
1180
1328
|
wrapResults,
|
|
@@ -1187,6 +1335,7 @@ export function SearchPage(props: SearchPageProps): ReactElement {
|
|
|
1187
1335
|
resultsHeadingVisible,
|
|
1188
1336
|
dictionaryMode,
|
|
1189
1337
|
visibleGroups,
|
|
1338
|
+
footerBar,
|
|
1190
1339
|
pinnedFacets,
|
|
1191
1340
|
mode,
|
|
1192
1341
|
...parseOptions
|
|
@@ -1201,6 +1350,7 @@ export function SearchPage(props: SearchPageProps): ReactElement {
|
|
|
1201
1350
|
{...(resultsHeadingVisible !== undefined ? { resultsHeadingVisible } : {})}
|
|
1202
1351
|
{...(dictionaryMode !== undefined ? { dictionaryMode } : {})}
|
|
1203
1352
|
{...(visibleGroups !== undefined ? { visibleGroups } : {})}
|
|
1353
|
+
{...(footerBar !== undefined ? { footerBar } : {})}
|
|
1204
1354
|
{...(pinnedFacets !== undefined ? { pinnedFacets } : {})}
|
|
1205
1355
|
{...(categoryFeatures !== undefined ? { categoryFeatures } : {})}
|
|
1206
1356
|
{...(categoryFeaturesPending !== undefined
|
|
@@ -1233,6 +1383,8 @@ export function SearchPage(props: SearchPageProps): ReactElement {
|
|
|
1233
1383
|
{...(railTop !== undefined ? { railTop } : {})}
|
|
1234
1384
|
{...(stickyToolbar !== undefined ? { stickyToolbar } : {})}
|
|
1235
1385
|
{...(defaultFiltersOpen !== undefined ? { defaultFiltersOpen } : {})}
|
|
1386
|
+
{...(filtersOpen !== undefined ? { filtersOpen } : {})}
|
|
1387
|
+
{...(onFiltersOpenChange !== undefined ? { onFiltersOpenChange } : {})}
|
|
1236
1388
|
{...(pageSize !== undefined ? { pageSize } : {})}
|
|
1237
1389
|
{...(breadcrumb !== undefined ? { breadcrumb } : {})}
|
|
1238
1390
|
{...(wrapResults !== undefined ? { wrapResults } : {})}
|
|
@@ -216,6 +216,24 @@ const WIDE_HEADING: CSSProperties = { margin: 0, minInlineSize: 0 };
|
|
|
216
216
|
*/
|
|
217
217
|
const TOOLBAR_END: CSSProperties = { flex: "0 0 auto" };
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* The wide row's LEADING half, and it is always in the row (D466).
|
|
221
|
+
*
|
|
222
|
+
* The count arrives with the answer, one render after the toolbar is already
|
|
223
|
+
* on screen, and `<Count>` renders nothing at all until it does — so a row
|
|
224
|
+
* spaced by `justify: space-between` had ONE item in the first frame and TWO
|
|
225
|
+
* in the second, which moved the sort/view control from the leading edge to
|
|
226
|
+
* the trailing one as the number landed: x 328→459 at 1280, 564→863 at 1920,
|
|
227
|
+
* 312→796 at 1100, and the same jump on a seller's page.
|
|
228
|
+
*
|
|
229
|
+
* A leading box that is always there, and grows, is what fixes it: the
|
|
230
|
+
* trailing group's x is then `row right − its own width` in every frame,
|
|
231
|
+
* whatever the count says or whether it says anything. `min-inline-size: 0`
|
|
232
|
+
* lets a long count shrink into the space that is left rather than push the
|
|
233
|
+
* controls, which the row's own `overflow-x` then scrolls.
|
|
234
|
+
*/
|
|
235
|
+
const TOOLBAR_LEAD: CSSProperties = { flex: "1 1 auto", minInlineSize: 0 };
|
|
236
|
+
|
|
219
237
|
/** The compact shape's toolbar box — the row the pin acts on. */
|
|
220
238
|
const COMPACT_TOOLBAR: CSSProperties = {
|
|
221
239
|
display: "flex",
|
|
@@ -751,14 +769,19 @@ export function SearchResultsPane(props: SearchResultsPaneProps): ReactElement {
|
|
|
751
769
|
</Typography.Title>
|
|
752
770
|
</div>
|
|
753
771
|
<Flex
|
|
754
|
-
justify="space-between"
|
|
755
772
|
align="center"
|
|
756
773
|
gap={spacing[3]}
|
|
757
774
|
className={RESULTS_TOOLBAR_CLASS}
|
|
758
775
|
data-testid="search-results-toolbar"
|
|
759
776
|
style={{ ...TOOLBAR_ROW, ...toolbarPin }}
|
|
760
777
|
>
|
|
761
|
-
|
|
778
|
+
{/* The elastic half, present whether or not there is a
|
|
779
|
+
count in it — see TOOLBAR_LEAD. It is what holds the
|
|
780
|
+
controls against the trailing edge in the frame before
|
|
781
|
+
the number arrives, so nothing travels when it does. */}
|
|
782
|
+
<div style={TOOLBAR_LEAD} data-testid="search-results-toolbar-lead">
|
|
783
|
+
<Count bag={bag} />
|
|
784
|
+
</div>
|
|
762
785
|
<Flex align="center" gap={spacing[3]} style={TOOLBAR_END}>
|
|
763
786
|
{props.toolbar}
|
|
764
787
|
</Flex>
|