@stapel/search-react 0.4.0 → 0.8.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 +191 -0
- package/README.md +1 -1
- package/dist/default/DegradationNotice.d.ts +7 -0
- package/dist/default/DegradationNotice.d.ts.map +1 -1
- package/dist/default/DegradationNotice.js +17 -6
- package/dist/default/DegradationNotice.js.map +1 -1
- package/dist/default/FacetGroupControl.d.ts +40 -0
- package/dist/default/FacetGroupControl.d.ts.map +1 -0
- package/dist/default/FacetGroupControl.js +176 -0
- package/dist/default/FacetGroupControl.js.map +1 -0
- package/dist/default/FacetPanelPane.d.ts +54 -3
- package/dist/default/FacetPanelPane.d.ts.map +1 -1
- package/dist/default/FacetPanelPane.js +116 -22
- package/dist/default/FacetPanelPane.js.map +1 -1
- package/dist/default/FilterChips.d.ts +28 -0
- package/dist/default/FilterChips.d.ts.map +1 -0
- package/dist/default/FilterChips.js +204 -0
- package/dist/default/FilterChips.js.map +1 -0
- package/dist/default/LanguageSelect.d.ts +9 -0
- package/dist/default/LanguageSelect.d.ts.map +1 -0
- package/dist/default/LanguageSelect.js +72 -0
- package/dist/default/LanguageSelect.js.map +1 -0
- package/dist/default/PageSizeSelect.d.ts +29 -0
- package/dist/default/PageSizeSelect.d.ts.map +1 -0
- package/dist/default/PageSizeSelect.js +25 -0
- package/dist/default/PageSizeSelect.js.map +1 -0
- package/dist/default/RangeFilterRow.d.ts +12 -0
- package/dist/default/RangeFilterRow.d.ts.map +1 -0
- package/dist/default/RangeFilterRow.js +75 -0
- package/dist/default/RangeFilterRow.js.map +1 -0
- package/dist/default/RankingDisclosurePane.d.ts +5 -0
- package/dist/default/RankingDisclosurePane.d.ts.map +1 -1
- package/dist/default/RankingDisclosurePane.js +15 -15
- package/dist/default/RankingDisclosurePane.js.map +1 -1
- package/dist/default/SearchBox.d.ts +13 -0
- package/dist/default/SearchBox.d.ts.map +1 -0
- package/dist/default/SearchBox.js +67 -0
- package/dist/default/SearchBox.js.map +1 -0
- package/dist/default/SearchPage.d.ts +94 -16
- package/dist/default/SearchPage.d.ts.map +1 -1
- package/dist/default/SearchPage.js +191 -8
- package/dist/default/SearchPage.js.map +1 -1
- package/dist/default/SearchResultCard.d.ts +19 -18
- package/dist/default/SearchResultCard.d.ts.map +1 -1
- package/dist/default/SearchResultCard.js +178 -5
- package/dist/default/SearchResultCard.js.map +1 -1
- package/dist/default/SearchResultsPane.d.ts +61 -3
- package/dist/default/SearchResultsPane.d.ts.map +1 -1
- package/dist/default/SearchResultsPane.js +51 -21
- package/dist/default/SearchResultsPane.js.map +1 -1
- package/dist/default/SortSelect.d.ts +10 -2
- package/dist/default/SortSelect.d.ts.map +1 -1
- package/dist/default/SortSelect.js +27 -24
- package/dist/default/SortSelect.js.map +1 -1
- package/dist/default/UrlIssueNotice.d.ts.map +1 -1
- package/dist/default/UrlIssueNotice.js +19 -1
- package/dist/default/UrlIssueNotice.js.map +1 -1
- package/dist/default/ViewSwitch.d.ts +66 -0
- package/dist/default/ViewSwitch.d.ts.map +1 -0
- package/dist/default/ViewSwitch.js +49 -0
- package/dist/default/ViewSwitch.js.map +1 -0
- package/dist/default/index.d.ts +38 -15
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +29 -12
- package/dist/default/index.js.map +1 -1
- package/dist/default/sortLabels.d.ts +5 -0
- package/dist/default/sortLabels.d.ts.map +1 -0
- package/dist/default/sortLabels.js +32 -0
- package/dist/default/sortLabels.js.map +1 -0
- package/dist/headless/FacetPanel.d.ts +17 -0
- package/dist/headless/FacetPanel.d.ts.map +1 -1
- package/dist/headless/FacetPanel.js +23 -2
- package/dist/headless/FacetPanel.js.map +1 -1
- package/dist/headless/useAppliedCount.d.ts +8 -0
- package/dist/headless/useAppliedCount.d.ts.map +1 -0
- package/dist/headless/useAppliedCount.js +30 -0
- package/dist/headless/useAppliedCount.js.map +1 -0
- package/dist/headless/useScorerNames.d.ts +4 -0
- package/dist/headless/useScorerNames.d.ts.map +1 -0
- package/dist/headless/useScorerNames.js +34 -0
- package/dist/headless/useScorerNames.js.map +1 -0
- package/dist/headless/useSearchBox.d.ts +44 -0
- package/dist/headless/useSearchBox.d.ts.map +1 -0
- package/dist/headless/useSearchBox.js +116 -0
- package/dist/headless/useSearchBox.js.map +1 -0
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +38 -4
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/keys.d.ts +55 -0
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +101 -4
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +42 -4
- package/dist/i18n/ru.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/model/queries.d.ts +26 -2
- package/dist/model/queries.d.ts.map +1 -1
- package/dist/model/queries.js +38 -1
- package/dist/model/queries.js.map +1 -1
- package/dist/state/limits.d.ts +44 -0
- package/dist/state/limits.d.ts.map +1 -0
- package/dist/state/limits.js +44 -0
- package/dist/state/limits.js.map +1 -0
- package/dist/state/ranges.d.ts +52 -0
- package/dist/state/ranges.d.ts.map +1 -0
- package/dist/state/ranges.js +108 -0
- package/dist/state/ranges.js.map +1 -0
- package/llms.txt +16 -4
- package/manifest.json +268 -22
- package/nav-manifest.json +1 -1
- package/package.json +15 -10
- package/src/analytics/generated/events.json +1 -1
- package/src/default/DegradationNotice.tsx +34 -12
- package/src/default/FacetGroupControl.tsx +312 -0
- package/src/default/FacetPanelPane.tsx +320 -87
- package/src/default/FilterChips.tsx +415 -0
- package/src/default/LanguageSelect.tsx +93 -0
- package/src/default/PageSizeSelect.tsx +80 -0
- package/src/default/RangeFilterRow.tsx +148 -0
- package/src/default/RankingDisclosurePane.tsx +67 -35
- package/src/default/SearchBox.tsx +130 -0
- package/src/default/SearchPage.tsx +454 -44
- package/src/default/SearchResultCard.tsx +231 -24
- package/src/default/SearchResultsPane.tsx +158 -71
- package/src/default/SortSelect.tsx +50 -37
- package/src/default/UrlIssueNotice.tsx +21 -3
- package/src/default/ViewSwitch.tsx +147 -0
- package/src/default/index.ts +55 -15
- package/src/default/sortLabels.ts +34 -0
- package/src/headless/FacetPanel.tsx +30 -2
- package/src/headless/useAppliedCount.ts +41 -0
- package/src/headless/useScorerNames.ts +36 -0
- package/src/headless/useSearchBox.ts +189 -0
- package/src/i18n/es.ts +46 -4
- package/src/i18n/keys.ts +114 -4
- package/src/i18n/ru.ts +50 -4
- package/src/index.ts +22 -2
- package/src/model/queries.ts +51 -1
- package/src/state/limits.ts +50 -0
- package/src/state/ranges.ts +144 -0
- package/dist/default/ErrorAlert.d.ts +0 -9
- package/dist/default/ErrorAlert.d.ts.map +0 -1
- package/dist/default/ErrorAlert.js +0 -27
- package/dist/default/ErrorAlert.js.map +0 -1
- package/dist/default/theme.d.ts +0 -24
- package/dist/default/theme.d.ts.map +0 -1
- package/dist/default/theme.js +0 -39
- package/dist/default/theme.js.map +0 -1
- package/src/default/ErrorAlert.tsx +0 -46
- package/src/default/theme.tsx +0 -66
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { SearchRange } from "../api/types.js";
|
|
3
|
+
import type { RangeGroup } from "../state/ranges.js";
|
|
4
|
+
/** Floor width of one bound field, so two of them fit a 390px phone row. */
|
|
5
|
+
export declare const RANGE_FIELD_MIN_WIDTH = 96;
|
|
6
|
+
export interface RangeFilterRowProps {
|
|
7
|
+
readonly group: RangeGroup;
|
|
8
|
+
/** `null` clears the slug's range entirely. */
|
|
9
|
+
readonly onApply: (slug: string, range: SearchRange | null) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function RangeFilterRow(props: RangeFilterRowProps): ReactElement;
|
|
12
|
+
//# sourceMappingURL=RangeFilterRow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RangeFilterRow.d.ts","sourceRoot":"","sources":["../../src/default/RangeFilterRow.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAM1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIrD,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;CACrE;AAMD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,YAAY,CAyGvE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* One numeric range row — "Price: from ___ to ___".
|
|
4
|
+
*
|
|
5
|
+
* The pair could encode `r.price=100..500`, round-trip it through the URL and
|
|
6
|
+
* send it, and had no control that produced one. A marketplace without a price
|
|
7
|
+
* range filter is not a marketplace (audit S-2), and this is the row that fixes
|
|
8
|
+
* it; `state/ranges.ts` decides which rows exist.
|
|
9
|
+
*
|
|
10
|
+
* ── Why it is applied by a button and not by typing ───────────────────────
|
|
11
|
+
*
|
|
12
|
+
* A facet checkbox commits on click because a click is a finished thought. A
|
|
13
|
+
* range is TWO fields, and committing each keystroke would run a search for
|
|
14
|
+
* `1`, `10`, `100` on the way to `1000` — three wrong result pages, three
|
|
15
|
+
* history entries' worth of churn, and a facet panel that reshuffles under the
|
|
16
|
+
* hand still typing. So the row holds a draft and commits on Apply (or Enter),
|
|
17
|
+
* which is also what makes "from > to" refusable instead of merely empty.
|
|
18
|
+
*/
|
|
19
|
+
import { useRef, useState } from "react";
|
|
20
|
+
import { Button, Flex, InputNumber, Typography } from "antd";
|
|
21
|
+
import { actionAvailable, actionBlocked, useT } from "@stapel/core";
|
|
22
|
+
import { GatedButton } from "@stapel/tokens-antd/skin";
|
|
23
|
+
import { spacing } from "@stapel/tokens";
|
|
24
|
+
import { isRangeUsable } from "../state/ranges.js";
|
|
25
|
+
import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
|
|
26
|
+
/** Floor width of one bound field, so two of them fit a 390px phone row. */
|
|
27
|
+
export const RANGE_FIELD_MIN_WIDTH = 96;
|
|
28
|
+
function toDraft(value) {
|
|
29
|
+
return value ?? "";
|
|
30
|
+
}
|
|
31
|
+
export function RangeFilterRow(props) {
|
|
32
|
+
const t = useT();
|
|
33
|
+
const { group } = props;
|
|
34
|
+
const [from, setFrom] = useState(toDraft(group.from));
|
|
35
|
+
const [to, setTo] = useState(toDraft(group.to));
|
|
36
|
+
// The URL is still the state: when it moves on its own — Back, a shared
|
|
37
|
+
// link, "clear all" — the draft follows it rather than keeping a number the
|
|
38
|
+
// results are no longer about.
|
|
39
|
+
const applied = useRef(`${toDraft(group.from)}..${toDraft(group.to)}`);
|
|
40
|
+
const current = `${toDraft(group.from)}..${toDraft(group.to)}`;
|
|
41
|
+
if (applied.current !== current) {
|
|
42
|
+
applied.current = current;
|
|
43
|
+
if (from !== toDraft(group.from))
|
|
44
|
+
setFrom(toDraft(group.from));
|
|
45
|
+
if (to !== toDraft(group.to))
|
|
46
|
+
setTo(toDraft(group.to));
|
|
47
|
+
}
|
|
48
|
+
const draft = {
|
|
49
|
+
...(from !== "" ? { from } : {}),
|
|
50
|
+
...(to !== "" ? { to } : {}),
|
|
51
|
+
};
|
|
52
|
+
const usable = isRangeUsable(draft);
|
|
53
|
+
const empty = from === "" && to === "";
|
|
54
|
+
const apply = usable
|
|
55
|
+
? actionAvailable()
|
|
56
|
+
: actionBlocked(SEARCH_I18N_KEYS.facetsRangeInvalid);
|
|
57
|
+
const commit = () => {
|
|
58
|
+
if (!usable)
|
|
59
|
+
return;
|
|
60
|
+
props.onApply(group.slug, empty ? null : draft);
|
|
61
|
+
};
|
|
62
|
+
const unit = group.unit === undefined ? "" : ` ${group.unit}`;
|
|
63
|
+
return (_jsxs(Flex, { vertical: true, gap: spacing[1], "data-testid": `facet-range-${group.slug}`, "data-active": group.active ? "true" : "false", children: [_jsx(Typography.Text, { strong: true, children: group.label }), _jsxs(Flex, { gap: spacing[2], align: "center", wrap: true, children: [_jsx(InputNumber, { value: from === "" ? null : Number(from), placeholder: t(SEARCH_I18N_KEYS.facetsRangeFrom), "aria-label": `${t(SEARCH_I18N_KEYS.facetsRangeFromAria, {
|
|
64
|
+
feature: group.label,
|
|
65
|
+
})}${unit}`, "data-testid": `facet-range-${group.slug}-from`, style: { minWidth: RANGE_FIELD_MIN_WIDTH }, ...(group.min !== undefined ? { min: group.min } : {}), ...(group.max !== undefined ? { max: group.max } : {}), ...(group.step !== undefined ? { step: group.step } : {}), onChange: (value) => {
|
|
66
|
+
setFrom(value === null || value === undefined ? "" : String(value));
|
|
67
|
+
}, onPressEnter: commit }), _jsx(InputNumber, { value: to === "" ? null : Number(to), placeholder: t(SEARCH_I18N_KEYS.facetsRangeTo), "aria-label": `${t(SEARCH_I18N_KEYS.facetsRangeToAria, {
|
|
68
|
+
feature: group.label,
|
|
69
|
+
})}${unit}`, "data-testid": `facet-range-${group.slug}-to`, style: { minWidth: RANGE_FIELD_MIN_WIDTH }, ...(group.min !== undefined ? { min: group.min } : {}), ...(group.max !== undefined ? { max: group.max } : {}), ...(group.step !== undefined ? { step: group.step } : {}), onChange: (value) => {
|
|
70
|
+
setTo(value === null || value === undefined ? "" : String(value));
|
|
71
|
+
}, onPressEnter: commit }), _jsx(GatedButton, { gate: apply, type: usable && !empty ? "primary" : "default", testId: `facet-range-${group.slug}-apply`, "data-analytics": "none", "data-analytics-reason": "a filter is a read, not a flow step", onClick: commit, children: t(SEARCH_I18N_KEYS.facetsRangeApply) }), group.active && (_jsx(Button, { "data-testid": `facet-range-${group.slug}-clear`, "data-analytics": "none", "data-analytics-reason": "a filter is a read, not a flow step", onClick: () => {
|
|
72
|
+
props.onApply(group.slug, null);
|
|
73
|
+
}, children: t(SEARCH_I18N_KEYS.facetsRangeClear) }))] })] }));
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=RangeFilterRow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RangeFilterRow.js","sourceRoot":"","sources":["../../src/default/RangeFilterRow.tsx"],"names":[],"mappings":"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"}
|
|
@@ -13,5 +13,10 @@ export interface RankingDisclosurePaneProps extends ThemeModeProp {
|
|
|
13
13
|
/** Doc type to disclose; omitted asks for the deployment's default. */
|
|
14
14
|
readonly type?: string;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* The widest a disclosure column may grow. A statutory text is a READING
|
|
18
|
+
* surface: past this it stops being a paragraph and becomes a banner.
|
|
19
|
+
*/
|
|
20
|
+
export declare const RANKING_MAX_WIDTH = 720;
|
|
16
21
|
export declare function RankingDisclosurePane(props: RankingDisclosurePaneProps): ReactElement;
|
|
17
22
|
//# sourceMappingURL=RankingDisclosurePane.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RankingDisclosurePane.d.ts","sourceRoot":"","sources":["../../src/default/RankingDisclosurePane.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"RankingDisclosurePane.d.ts","sourceRoot":"","sources":["../../src/default/RankingDisclosurePane.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAc1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,0BAA2B,SAAQ,aAAa;IAC/D,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,0BAA0B,GAChC,YAAY,CA6Gd"}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Flex, List, Tag, Typography } from "antd";
|
|
3
|
+
import { useFormat, useT } from "@stapel/core";
|
|
4
|
+
import { EmptyState, ErrorAlert, LoadList, SkinTheme, } from "@stapel/tokens-antd/skin";
|
|
5
|
+
import { fontSize, spacing } from "@stapel/tokens";
|
|
4
6
|
import { RankingDisclosure } from "../headless/RankingDisclosure.js";
|
|
5
7
|
import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
+
import { sortLabel } from "./sortLabels.js";
|
|
9
|
+
/**
|
|
10
|
+
* The widest a disclosure column may grow. A statutory text is a READING
|
|
11
|
+
* surface: past this it stops being a paragraph and becomes a banner.
|
|
12
|
+
*/
|
|
13
|
+
export const RANKING_MAX_WIDTH = 720;
|
|
8
14
|
export function RankingDisclosurePane(props) {
|
|
9
15
|
const t = useT();
|
|
10
|
-
const
|
|
11
|
-
return (_jsx(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
...describe(toFlowError(error)),
|
|
15
|
-
message: t(SEARCH_I18N_KEYS.rankingLoadFailed),
|
|
16
|
-
} })),
|
|
17
|
-
empty: () => (_jsx(Empty, { "data-testid": "ranking-empty", description: t(SEARCH_I18N_KEYS.rankingEmpty) })),
|
|
18
|
-
ready: (scorers) => (_jsx(List, { "data-testid": "ranking-list", dataSource: [...scorers], renderItem: (scorer) => (_jsx(List.Item, { "data-scorer": scorer.slug, children: _jsxs(Flex, { vertical: true, gap: 2, style: { width: "100%" }, children: [_jsxs(Flex, { justify: "space-between", align: "center", gap: 8, children: [_jsx(Typography.Text, { strong: true, children: t(scorer.description_key) }), _jsx(Tag, { children: scorer.weight })] }), _jsx(Typography.Text, { type: "secondary", children: scorer.description }), _jsxs(Typography.Text, { type: "secondary", style: { fontSize: 12 }, children: [t(SEARCH_I18N_KEYS.rankingAppliesTo), ": ", scorer.applies_to_sorts.join(", ")] }), !scorer.active && (_jsx(Typography.Text, { type: "warning", "data-testid": "ranking-inactive", children: t(SEARCH_I18N_KEYS.rankingInactive, {
|
|
16
|
+
const format = useFormat();
|
|
17
|
+
return (_jsx(SkinTheme, { surface: "base", ...(props.mode !== undefined ? { mode: props.mode } : {}), style: { width: "100%", maxWidth: RANKING_MAX_WIDTH }, children: _jsx(RankingDisclosure, { ...(props.type !== undefined ? { type: props.type } : {}), children: (bag) => (_jsxs(Flex, { vertical: true, gap: spacing[3], "data-testid": "search-ranking", children: [_jsx(Typography.Title, { level: 4, style: { margin: 0 }, children: t(SEARCH_I18N_KEYS.rankingTitle) }), _jsx(Typography.Paragraph, { type: "secondary", style: { margin: 0 }, children: t(SEARCH_I18N_KEYS.rankingIntro) }), _jsx(LoadList, { state: bag.state, testId: "ranking", skeletonRows: 4, onRetry: bag.refetch, empty: _jsx(EmptyState, { compact: true, title: t(SEARCH_I18N_KEYS.rankingEmpty), testId: "ranking-empty" }), failed: (error) => (_jsx(ErrorAlert, { testId: "ranking-failed", thrown: error, message: t(SEARCH_I18N_KEYS.rankingLoadFailed), onRetry: bag.refetch })), children: (scorers) => (_jsx(List, { "data-testid": "ranking-list", dataSource: [...scorers], renderItem: (scorer) => (_jsx(List.Item, { "data-scorer": scorer.slug, children: _jsxs(Flex, { vertical: true, gap: spacing[1], style: { width: "100%" }, children: [_jsxs(Flex, { justify: "space-between", align: "center", gap: spacing[2], children: [_jsx(Typography.Text, { strong: true, children: t(scorer.description_key) }), _jsxs(Tag, { children: [t(SEARCH_I18N_KEYS.rankingWeight), " ", format.number(scorer.weight) ?? scorer.weight] })] }), _jsx(Typography.Text, { type: "secondary", children: scorer.description }), _jsxs(Typography.Text, { type: "secondary", style: { fontSize: fontSize.xs.fontSize }, children: [t(SEARCH_I18N_KEYS.rankingAppliesTo), ": ", scorer.applies_to_sorts
|
|
18
|
+
.map((sort) => sortLabel(t, sort))
|
|
19
|
+
.join(", ")] }), !scorer.active && (_jsx(Typography.Text, { type: "warning", "data-testid": "ranking-inactive", children: t(SEARCH_I18N_KEYS.rankingInactive, {
|
|
19
20
|
reason: scorer.inactive_reason,
|
|
20
|
-
}) }))] }) }, scorer.slug)) })),
|
|
21
|
-
}), bag.notes.length > 0 && (_jsxs(Flex, { vertical: true, gap: 2, children: [_jsx(Typography.Text, { strong: true, children: t(SEARCH_I18N_KEYS.rankingNotes) }), bag.notes.map((note) => (_jsx(Typography.Text, { type: "secondary", children: note }, note)))] }))] })) }) }));
|
|
21
|
+
}) }))] }) }, scorer.slug)) })) }), bag.notes.length > 0 && (_jsxs(Flex, { vertical: true, gap: spacing[1], children: [_jsx(Typography.Text, { strong: true, children: t(SEARCH_I18N_KEYS.rankingNotes) }), bag.notes.map((note) => (_jsx(Typography.Text, { type: "secondary", children: note }, note)))] }))] })) }) }));
|
|
22
22
|
}
|
|
23
23
|
//# sourceMappingURL=RankingDisclosurePane.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RankingDisclosurePane.js","sourceRoot":"","sources":["../../src/default/RankingDisclosurePane.tsx"],"names":[],"mappings":";AAUA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"RankingDisclosurePane.js","sourceRoot":"","sources":["../../src/default/RankingDisclosurePane.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { UseSearchBoxOptions } from "../headless/useSearchBox.js";
|
|
3
|
+
import type { ThemeModeProp } from "./types.js";
|
|
4
|
+
export interface SearchBoxProps extends ThemeModeProp, UseSearchBoxOptions {
|
|
5
|
+
/** Override the placeholder — a category page says what it searches. */
|
|
6
|
+
readonly placeholder?: string;
|
|
7
|
+
/** Render the submit button beside the field. Default `true`: on a phone
|
|
8
|
+
* keyboard "search" is not always offered, and a visible button is. */
|
|
9
|
+
readonly submitButton?: boolean;
|
|
10
|
+
readonly autoFocus?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function SearchBox(props: SearchBoxProps): ReactElement;
|
|
13
|
+
//# sourceMappingURL=SearchBox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SearchBox.d.ts","sourceRoot":"","sources":["../../src/default/SearchBox.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAM1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,cAAe,SAAQ,aAAa,EAAE,mBAAmB;IACxE,wEAAwE;IACxE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;2EACuE;IACvE,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,CAgF7D"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `<SearchBox>` — the control this pair shipped a search page without.
|
|
4
|
+
*
|
|
5
|
+
* `setText` existed from the first release and had ZERO callers in the whole
|
|
6
|
+
* repository: the URL codec could carry `q`, the state machine could set it,
|
|
7
|
+
* the request sent it — and nothing on any screen could type one. A results
|
|
8
|
+
* page reachable only by editing the address bar is not a search feature, so
|
|
9
|
+
* this is the blocker of the pair's audit (S-1) and the reason the page is now
|
|
10
|
+
* a screen rather than a viewer.
|
|
11
|
+
*
|
|
12
|
+
* ── What it does, and what it refuses to do ───────────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* - **Types into a draft, searches after a pause.** `useSearchBox` owns the
|
|
15
|
+
* debounce and the URL round-trip; this file is the antd shape of it.
|
|
16
|
+
* - **Suggests from the INDEX.** `GET /suggest` returns title prefixes out of
|
|
17
|
+
* the catalogue — never a query log, because stapel-search keeps none — so
|
|
18
|
+
* every suggestion is a search that has results. The endpoint was typed and
|
|
19
|
+
* unreachable for three releases (S-8).
|
|
20
|
+
* - **Never grows a "no results" dropdown.** With nothing to suggest the menu
|
|
21
|
+
* stays shut: an empty popover under a half-typed word says "there is
|
|
22
|
+
* nothing" about a search that has not run.
|
|
23
|
+
* - **Caps the input at the server's own limit** (200 chars), so
|
|
24
|
+
* `error.400.search_query_too_long` is a refusal this control cannot cause.
|
|
25
|
+
*
|
|
26
|
+
* Exported from `/default` so a container can mount it in the header —
|
|
27
|
+
* `nav/manifest.ts` calls `/s` "a navigation TARGET reached from the header's
|
|
28
|
+
* search box", and this is that box. It needs the same `<SearchStateProvider>`
|
|
29
|
+
* as everything else; a header outside one renders `<SearchPage>`'s copy.
|
|
30
|
+
*/
|
|
31
|
+
import { useState } from "react";
|
|
32
|
+
import { AutoComplete, Button, Flex, Input } from "antd";
|
|
33
|
+
import { useT } from "@stapel/core";
|
|
34
|
+
import { SkinTheme } from "@stapel/tokens-antd/skin";
|
|
35
|
+
import { spacing } from "@stapel/tokens";
|
|
36
|
+
import { useSearchBox } from "../headless/useSearchBox.js";
|
|
37
|
+
import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
|
|
38
|
+
export function SearchBox(props) {
|
|
39
|
+
const t = useT();
|
|
40
|
+
const { mode, placeholder, submitButton, autoFocus, ...boxOptions } = props;
|
|
41
|
+
const box = useSearchBox(boxOptions);
|
|
42
|
+
const [open, setOpen] = useState(false);
|
|
43
|
+
const options = box.suggestions.map((value) => ({ value }));
|
|
44
|
+
return (_jsx(SkinTheme, { surface: "bare", ...(mode !== undefined ? { mode } : {}), "data-testid": "search-box", children: _jsxs(Flex, { gap: spacing[2], align: "center", wrap: false, children: [_jsx(AutoComplete, { value: box.draft, options: options,
|
|
45
|
+
// Shut whenever there is nothing to say — see the header. The
|
|
46
|
+
// open state is the skin's, not antd's: the prop that reports the
|
|
47
|
+
// menu opening was renamed between antd 5 and 6 and this package
|
|
48
|
+
// supports both, so nothing here asks antd when to open.
|
|
49
|
+
open: open && options.length > 0, style: { flex: 1, minWidth: 0 }, onSelect: (value) => {
|
|
50
|
+
setOpen(false);
|
|
51
|
+
box.submit(value);
|
|
52
|
+
}, onChange: (value) => {
|
|
53
|
+
setOpen(true);
|
|
54
|
+
box.setDraft(value);
|
|
55
|
+
}, children: _jsx(Input, { allowClear: {
|
|
56
|
+
clearIcon: (_jsx("span", { "aria-label": t(SEARCH_I18N_KEYS.boxClear), role: "img", children: "×" })),
|
|
57
|
+
}, autoFocus: autoFocus === true, maxLength: box.maxLength, "aria-label": t(SEARCH_I18N_KEYS.boxLabel), placeholder: placeholder ?? t(SEARCH_I18N_KEYS.boxPlaceholder), "data-testid": "search-box-input", "data-analytics": "none", "data-analytics-reason": "typing a query is a read, not a flow step", onPressEnter: () => {
|
|
58
|
+
setOpen(false);
|
|
59
|
+
box.submit();
|
|
60
|
+
}, onBlur: () => {
|
|
61
|
+
setOpen(false);
|
|
62
|
+
} }) }), submitButton !== false && (_jsx(Button, { type: "primary", "data-testid": "search-box-submit", "data-analytics": "none", "data-analytics-reason": "running a search is a read, not a flow step", onClick: () => {
|
|
63
|
+
setOpen(false);
|
|
64
|
+
box.submit();
|
|
65
|
+
}, children: t(SEARCH_I18N_KEYS.boxSubmit) }))] }) }));
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=SearchBox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SearchBox.js","sourceRoot":"","sources":["../../src/default/SearchBox.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `<SearchPage>` — the composed screen the nav manifest points at: filters on
|
|
3
|
-
* the left, sort and results on the right, the ranking link in the footer.
|
|
4
|
-
*
|
|
5
|
-
* It composes the two panes and owns NO state of its own. The URL is the
|
|
6
|
-
* state, and it arrives through the `adapter` prop — which is where a host
|
|
7
|
-
* hands in `useRouterSearchParams()` from `@stapel/search-react/router`, or
|
|
8
|
-
* its own binding. The page does not reach for a router, so the same
|
|
9
|
-
* component renders under react-router, under a Next.js app router, and in a
|
|
10
|
-
* test with a plain `URLSearchParams`.
|
|
11
|
-
*
|
|
12
|
-
* The three seams a storefront fills (spec §6.2 items 1–3): `renderCard`
|
|
13
|
-
* (a `<ListingCard>`), `categoryFeatures` (from categories-react, for facet
|
|
14
|
-
* labels), and `footer` (the container's own chrome). None of them is
|
|
15
|
-
* optional behaviour in disguise — every one has a working default.
|
|
16
|
-
*/
|
|
17
1
|
import type { ReactElement, ReactNode } from "react";
|
|
18
2
|
import type { FeatureDef } from "@stapel/attributes-react";
|
|
19
3
|
import type { SearchParamsAdapter } from "../headless/SearchStateProvider.js";
|
|
20
4
|
import type { ParseSearchStateOptions } from "../state/urlState.js";
|
|
5
|
+
import type { CategoryFilterSlotProps, GeoFilterSlotProps } from "./FacetPanelPane.js";
|
|
6
|
+
import type { SearchView } from "./ViewSwitch.js";
|
|
21
7
|
import type { DegradationNoticeVariant } from "./DegradationNotice.js";
|
|
22
8
|
import type { SearchCardRenderer } from "./SearchResultCard.js";
|
|
23
9
|
import type { ThemeModeProp } from "./types.js";
|
|
10
|
+
/** Where the filters live: beside the results, or behind a button in a sheet. */
|
|
11
|
+
export type SearchFiltersLayout = "column" | "sheet";
|
|
12
|
+
/**
|
|
13
|
+
* The desktop filter rail's width.
|
|
14
|
+
*
|
|
15
|
+
* A rail, not a half-page. `Col md={7}` gave the panel a SHARE of the page, so
|
|
16
|
+
* on a wide screen the filters grew with it — the visual pass measured 570 of
|
|
17
|
+
* 1280px (45%) spent on the controls that narrow a list, next to the list they
|
|
18
|
+
* narrow. Filters are a fixed instrument: a checkbox column needs the width of
|
|
19
|
+
* its longest label and nothing more, and every pixel past that comes out of
|
|
20
|
+
* the results. 280px is that width at the default type step, and it is the
|
|
21
|
+
* same panel the phone sheet draws — one component, two frames.
|
|
22
|
+
*/
|
|
23
|
+
export declare const FILTERS_RAIL_WIDTH = 280;
|
|
24
24
|
export interface SearchPageProps extends ThemeModeProp, ParseSearchStateOptions {
|
|
25
25
|
/** The URL binding. `useRouterSearchParams()` from `./router` is the
|
|
26
26
|
* react-router one. */
|
|
@@ -28,11 +28,89 @@ export interface SearchPageProps extends ThemeModeProp, ParseSearchStateOptions
|
|
|
28
28
|
readonly renderCard?: SearchCardRenderer;
|
|
29
29
|
readonly categoryFeatures?: readonly FeatureDef[];
|
|
30
30
|
readonly locale?: string;
|
|
31
|
+
/** Render the query box at the top. `false` for a container whose HEADER
|
|
32
|
+
* already mounts `<SearchBox>` — one box per screen, not two. */
|
|
33
|
+
readonly searchBox?: boolean;
|
|
34
|
+
/** BCP-47 tags this deployment indexes, for the language filter. */
|
|
35
|
+
readonly languages?: readonly string[];
|
|
36
|
+
/** The catalogue picker slot — see {@link FacetPanelPaneProps}. */
|
|
37
|
+
readonly renderCategoryFilter?: (slot: CategoryFilterSlotProps) => ReactNode;
|
|
38
|
+
/** The location control slot (`geo-react`). */
|
|
39
|
+
readonly renderGeoFilter?: (slot: GeoFilterSlotProps) => ReactNode;
|
|
31
40
|
/** Container chrome under the results — e.g. the ranking-disclosure link. */
|
|
32
41
|
readonly footer?: ReactNode;
|
|
42
|
+
/**
|
|
43
|
+
* Rendered at the TOP of the filter column, above everything else.
|
|
44
|
+
*
|
|
45
|
+
* The slot exists for a filter this pair cannot ship and has no named seam
|
|
46
|
+
* for. Whatever a host renders here reads and writes the same URL state as
|
|
47
|
+
* the facets beside it (`useSearchState()`), so it is a filter in every
|
|
48
|
+
* sense that matters and not a decoration bolted on top.
|
|
49
|
+
*/
|
|
50
|
+
readonly filtersHeader?: ReactNode;
|
|
51
|
+
/** What this surface calls its result list. See
|
|
52
|
+
* {@link SearchResultsPaneProps.heading}. */
|
|
53
|
+
readonly resultsHeading?: ReactNode;
|
|
33
54
|
/** How the results surface says what the engine could not do (default
|
|
34
55
|
* `"banner"`). Handed straight to `<SearchResultsPane>`. */
|
|
35
56
|
readonly degradationNotice?: DegradationNoticeVariant;
|
|
57
|
+
/**
|
|
58
|
+
* Force the filter surface instead of reading the viewport. For tests and
|
|
59
|
+
* for a host that renders the page inside a phone-width container that is
|
|
60
|
+
* not the viewport — not an escape hatch for "I prefer a column on phones".
|
|
61
|
+
*/
|
|
62
|
+
readonly filtersLayout?: SearchFiltersLayout;
|
|
63
|
+
/**
|
|
64
|
+
* Open the phone filter sheet on mount.
|
|
65
|
+
*
|
|
66
|
+
* For a container that deep-links INTO the filters ("Refine this search"
|
|
67
|
+
* from a category page), and for the story that photographs the sheet —
|
|
68
|
+
* a state reached only by a tap is a state nothing outside a browser has
|
|
69
|
+
* ever seen. The person still closes it; this is the initial value, not a
|
|
70
|
+
* controlled one.
|
|
71
|
+
*/
|
|
72
|
+
readonly defaultFiltersOpen?: boolean;
|
|
73
|
+
/** Offer a page-size control beside the sort. Default `true`. */
|
|
74
|
+
readonly pageSize?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* The trail above the heading — "Home / Cars / Sedans".
|
|
77
|
+
*
|
|
78
|
+
* A SLOT, because a breadcrumb is a walk up the CATEGORY tree and the tree
|
|
79
|
+
* belongs to `categories-react`; a search package knows the `category`
|
|
80
|
+
* parameter's value and nothing about its ancestors. Rendered above the
|
|
81
|
+
* results heading, inside the results column, so it sits over the list it
|
|
82
|
+
* describes rather than over the whole two-column page.
|
|
83
|
+
*/
|
|
84
|
+
readonly breadcrumb?: ReactNode;
|
|
85
|
+
/**
|
|
86
|
+
* The arrangements the view switch offers. Default: the pair's own list and
|
|
87
|
+
* grid. A deployment adds its own — `{ id: "map", labelKey, icon, render }`
|
|
88
|
+
* — and the switch treats it like the two that ship; see {@link SearchView}.
|
|
89
|
+
*
|
|
90
|
+
* A single view draws no switch at all.
|
|
91
|
+
*/
|
|
92
|
+
readonly views?: readonly SearchView[];
|
|
93
|
+
/** Which arrangement the page opens in. Default: the first of `views`. */
|
|
94
|
+
readonly defaultView?: string;
|
|
95
|
+
/** Told when the arrangement changes, for a host that remembers it. The
|
|
96
|
+
* view is NOT url state — see `<ViewSwitch>` for why. */
|
|
97
|
+
readonly onViewChange?: (id: string) => void;
|
|
98
|
+
/**
|
|
99
|
+
* The action at the trailing end of the results toolbar — conventionally
|
|
100
|
+
* "notify me about new ones".
|
|
101
|
+
*
|
|
102
|
+
* A SLOT, and it cannot be anything else: saving a search and mailing its
|
|
103
|
+
* new hits is a subscription with an owner, a schedule and a consent record,
|
|
104
|
+
* none of which this pair has. What the pair CAN state is where such a
|
|
105
|
+
* control belongs and that the page keeps room for it.
|
|
106
|
+
*/
|
|
107
|
+
readonly resultsAction?: ReactNode;
|
|
108
|
+
/**
|
|
109
|
+
* Heading level for the results caption. Default `1` — on a results SCREEN
|
|
110
|
+
* the list's name is the page's heading. A container that already prints its
|
|
111
|
+
* own `<h1>` above this page passes a lower level.
|
|
112
|
+
*/
|
|
113
|
+
readonly resultsHeadingLevel?: 1 | 2 | 3 | 4 | 5;
|
|
36
114
|
}
|
|
37
115
|
export declare function SearchPage(props: SearchPageProps): ReactElement;
|
|
38
116
|
//# sourceMappingURL=SearchPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchPage.d.ts","sourceRoot":"","sources":["../../src/default/SearchPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SearchPage.d.ts","sourceRoot":"","sources":["../../src/default/SearchPage.tsx"],"names":[],"mappings":"AAkDA,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAG9E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAIpE,OAAO,KAAK,EACV,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAO7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,iFAAiF;AACjF,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAiCtC,MAAM,WAAW,eAAgB,SAAQ,aAAa,EAAE,uBAAuB;IAC7E;2BACuB;IACvB,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IACzC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;qEACiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,mEAAmE;IACnE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,SAAS,CAAC;IAC7E,+CAA+C;IAC/C,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,SAAS,CAAC;IACnE,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IACnC;iDAC6C;IAC7C,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC;IACpC;gEAC4D;IAC5D,QAAQ,CAAC,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IACtD;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IAC7C;;;;;;;;OAQG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;6DACyD;IACzD,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C;;;;;;;;OAQG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAClD;AAmOD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CAuD/D"}
|