@stapel/search-react 0.38.0 → 0.40.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 +161 -0
- package/README.md +85 -6
- package/dist/default/FacetGroupControl.d.ts.map +1 -1
- package/dist/default/FacetGroupControl.js +31 -4
- package/dist/default/FacetGroupControl.js.map +1 -1
- package/dist/default/FacetPanelPane.d.ts +24 -0
- package/dist/default/FacetPanelPane.d.ts.map +1 -1
- package/dist/default/FacetPanelPane.js +27 -6
- package/dist/default/FacetPanelPane.js.map +1 -1
- package/dist/default/PartitionChips.d.ts +55 -0
- package/dist/default/PartitionChips.d.ts.map +1 -1
- package/dist/default/PartitionChips.js +59 -4
- package/dist/default/PartitionChips.js.map +1 -1
- package/dist/default/SearchPage.d.ts +33 -1
- package/dist/default/SearchPage.d.ts.map +1 -1
- package/dist/default/SearchPage.js +32 -5
- package/dist/default/SearchPage.js.map +1 -1
- package/dist/default/SearchResultsPane.d.ts +76 -0
- package/dist/default/SearchResultsPane.d.ts.map +1 -1
- package/dist/default/SearchResultsPane.js +132 -45
- package/dist/default/SearchResultsPane.js.map +1 -1
- package/dist/default/index.d.ts +5 -3
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +2 -1
- package/dist/default/index.js.map +1 -1
- package/dist/default/swatches.d.ts +44 -0
- package/dist/default/swatches.d.ts.map +1 -0
- package/dist/default/swatches.js +200 -0
- package/dist/default/swatches.js.map +1 -0
- package/llms.txt +3 -3
- package/manifest.json +4 -1
- package/nav-manifest.json +1 -1
- package/package.json +4 -4
- package/src/analytics/generated/events.json +1 -1
- package/src/default/FacetGroupControl.tsx +39 -0
- package/src/default/FacetPanelPane.tsx +61 -5
- package/src/default/PartitionChips.tsx +164 -4
- package/src/default/SearchPage.tsx +72 -1
- package/src/default/SearchResultsPane.tsx +134 -5
- package/src/default/index.ts +14 -1
- package/src/default/swatches.ts +214 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A COLOUR FACET SHOWS THE COLOUR.
|
|
3
|
+
*
|
|
4
|
+
* The reference's colour group draws a filled dot beside every value; ours
|
|
5
|
+
* drew the word and made the buyer read it (deep/elektronika-telefony.md §3,
|
|
6
|
+
* the one rail regression that pass found). A colour is the one attribute
|
|
7
|
+
* whose label is strictly worse than the thing itself — "silver" versus
|
|
8
|
+
* "gold" in a catalogue's own transliteration is a paragraph of prose for a difference a 10px dot settles.
|
|
9
|
+
*
|
|
10
|
+
* Two questions, and each is answered conservatively, because both failures
|
|
11
|
+
* are visible on a shopper's screen:
|
|
12
|
+
*
|
|
13
|
+
* 1. **is this axis a colour?** — {@link isColorAxis}, from the slug the
|
|
14
|
+
* catalogue mapped the axis to (and the address key, and `axis_role` if a
|
|
15
|
+
* schema ever carries one). A guess on the slug, exactly like
|
|
16
|
+
* `looksLikeExclusiveAxisSlug` next door: nothing on the wire marks an
|
|
17
|
+
* axis "colour", and inspecting the VALUES for colour-ish names would put
|
|
18
|
+
* dots on a paint-brand axis whose makes are called `Bordeaux`;
|
|
19
|
+
* 2. **which colour is this value?** — {@link swatchColor}, and the honest
|
|
20
|
+
* answer is usually "nobody said". A value code is a catalogue's own term
|
|
21
|
+
* (`chernyy`, `dark-slate-2`) and neither the answer, the feature schema
|
|
22
|
+
* nor the vocabulary endpoint carries a hue for it. So the pair paints a
|
|
23
|
+
* dot only where the code IS a colour by a name it can resolve — the
|
|
24
|
+
* design system's own colour roles first (§68: one neutral vocabulary of
|
|
25
|
+
* ROLES, and it deliberately ships no hue ramp), then CSS's own colour
|
|
26
|
+
* keywords, then a code that spells the hue out in hex — and draws
|
|
27
|
+
* NOTHING otherwise. An invented mapping from a transliterated Russian
|
|
28
|
+
* word to a hex value is data this pair does not have.
|
|
29
|
+
*/
|
|
30
|
+
import { cssVar } from "@stapel/tokens";
|
|
31
|
+
import type { FeatureDef } from "@stapel/attributes-react";
|
|
32
|
+
|
|
33
|
+
/** What {@link isColorAxis} needs of a group — the shape `FacetGroup` has. */
|
|
34
|
+
export interface ColorAxisLike {
|
|
35
|
+
readonly slug: string;
|
|
36
|
+
readonly urlKey?: string;
|
|
37
|
+
readonly feature?: FeatureDef | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The control-type tails a catalogue hangs on an axis slug when one feature
|
|
42
|
+
* type is not enough to tell two mappings apart (`color_ref_select` is the
|
|
43
|
+
* live phones leaf's own colour axis; `color_select` and `color_multi` are
|
|
44
|
+
* the same axis under a different editor).
|
|
45
|
+
*
|
|
46
|
+
* Stripped before the head is read, so the control a value is PICKED with
|
|
47
|
+
* cannot change what the value IS.
|
|
48
|
+
*/
|
|
49
|
+
const CONTROL_TAILS: readonly string[] = [
|
|
50
|
+
"ref_hierarchical_select",
|
|
51
|
+
"hierarchical_select",
|
|
52
|
+
"ref_select",
|
|
53
|
+
"multiselect",
|
|
54
|
+
"multi_select",
|
|
55
|
+
"select",
|
|
56
|
+
"multi",
|
|
57
|
+
"picker",
|
|
58
|
+
"field",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
function normalizeSlug(slug: string): string {
|
|
62
|
+
let normalized = slug.toLowerCase().replace(/-/g, "_");
|
|
63
|
+
for (const tail of CONTROL_TAILS) {
|
|
64
|
+
if (normalized.endsWith(`_${tail}`)) {
|
|
65
|
+
normalized = normalized.slice(0, -(tail.length + 1));
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return normalized;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The two spellings, and only as the slug's HEAD segment: `color_fridge` is
|
|
73
|
+
* the colour of a fridge and `colorado_region` is a place. */
|
|
74
|
+
const COLOR_HEADS: ReadonlySet<string> = new Set(["color", "colour"]);
|
|
75
|
+
|
|
76
|
+
function headIsColor(slug: string | undefined): boolean {
|
|
77
|
+
if (slug === undefined || slug === "") return false;
|
|
78
|
+
const normalized = normalizeSlug(slug);
|
|
79
|
+
const head = normalized.split("_")[0] ?? "";
|
|
80
|
+
return COLOR_HEADS.has(head);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Is this axis a colour vocabulary?
|
|
85
|
+
*
|
|
86
|
+
* Three sources, in the order of how much authority they carry:
|
|
87
|
+
*
|
|
88
|
+
* - `axis_role` — the schema SAYING what an axis is, which is the only
|
|
89
|
+
* non-guess available. The canon's role vocabulary is closed and has no
|
|
90
|
+
* colour in it yet (`make`/`model`/`generation`/`year`/`mileage`), so this
|
|
91
|
+
* arm reads the field as text and is dead until the canon grows one. It is
|
|
92
|
+
* written now so that the day it does, nothing here has to change;
|
|
93
|
+
* - the axis slug, and the ADDRESS key beside it — the live phones leaf maps
|
|
94
|
+
* its colour to `color_ref_select` and publishes it as `color`, so either
|
|
95
|
+
* spelling alone would miss half the deployments.
|
|
96
|
+
*/
|
|
97
|
+
export function isColorAxis(group: ColorAxisLike): boolean {
|
|
98
|
+
const role = group.feature?.["axis_role"];
|
|
99
|
+
if (typeof role === "string" && COLOR_HEADS.has(role.toLowerCase())) return true;
|
|
100
|
+
return headIsColor(group.slug) || headIsColor(group.urlKey);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The design system's colour ROLES, for a value code that names one.
|
|
105
|
+
*
|
|
106
|
+
* The fleet's token vocabulary is neutral and role-shaped on purpose (§68):
|
|
107
|
+
* there is no `red`, no `blue`, and no ramp — so a colour axis whose values
|
|
108
|
+
* are hues matches nothing here, which is the correct answer rather than a
|
|
109
|
+
* gap. What does match is a catalogue that codes STATES as colours (a
|
|
110
|
+
* `status` axis mapped under a colour slug), and those take the brand's own
|
|
111
|
+
* value in both themes rather than a frozen hex.
|
|
112
|
+
*/
|
|
113
|
+
const TOKEN_ROLE_SWATCHES: Readonly<Record<string, string>> = {
|
|
114
|
+
brand: cssVar("brand"),
|
|
115
|
+
error: cssVar("error"),
|
|
116
|
+
info: cssVar("info"),
|
|
117
|
+
link: cssVar("link"),
|
|
118
|
+
success: cssVar("success"),
|
|
119
|
+
surface: cssVar("surface"),
|
|
120
|
+
text: cssVar("text"),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* CSS's own colour keywords — a NAME that matches, in the vocabulary every
|
|
125
|
+
* browser already agrees on.
|
|
126
|
+
*
|
|
127
|
+
* The basic sixteen plus the extended keywords a product catalogue actually
|
|
128
|
+
* uses. Deliberately not the full 148: every entry here is a promise that a
|
|
129
|
+
* value code spelled that way means that colour, and `rebeccapurple` in a
|
|
130
|
+
* phone catalogue is far likelier to be somebody's model name.
|
|
131
|
+
*/
|
|
132
|
+
const CSS_COLOR_KEYWORDS: readonly string[] = [
|
|
133
|
+
"aqua",
|
|
134
|
+
"beige",
|
|
135
|
+
"black",
|
|
136
|
+
"blue",
|
|
137
|
+
"brown",
|
|
138
|
+
"chocolate",
|
|
139
|
+
"coral",
|
|
140
|
+
"crimson",
|
|
141
|
+
"cyan",
|
|
142
|
+
"fuchsia",
|
|
143
|
+
"gold",
|
|
144
|
+
"gray",
|
|
145
|
+
"green",
|
|
146
|
+
"grey",
|
|
147
|
+
"indigo",
|
|
148
|
+
"ivory",
|
|
149
|
+
"khaki",
|
|
150
|
+
"lavender",
|
|
151
|
+
"lime",
|
|
152
|
+
"magenta",
|
|
153
|
+
"maroon",
|
|
154
|
+
"navy",
|
|
155
|
+
"olive",
|
|
156
|
+
"orange",
|
|
157
|
+
"orchid",
|
|
158
|
+
"pink",
|
|
159
|
+
"plum",
|
|
160
|
+
"purple",
|
|
161
|
+
"red",
|
|
162
|
+
"salmon",
|
|
163
|
+
"sand",
|
|
164
|
+
"sienna",
|
|
165
|
+
"silver",
|
|
166
|
+
"skyblue",
|
|
167
|
+
"tan",
|
|
168
|
+
"teal",
|
|
169
|
+
"tomato",
|
|
170
|
+
"turquoise",
|
|
171
|
+
"violet",
|
|
172
|
+
"wheat",
|
|
173
|
+
"white",
|
|
174
|
+
"yellow",
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const CSS_COLOR_SET: ReadonlySet<string> = new Set(CSS_COLOR_KEYWORDS);
|
|
178
|
+
|
|
179
|
+
/** `#abc`, `#aabbcc`, `#aabbccdd` — a catalogue that codes the hue itself. */
|
|
180
|
+
const HEX = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The CSS colour one value code names, or `null` for "nobody said".
|
|
184
|
+
*
|
|
185
|
+
* `null` is the ordinary answer and the row then draws no dot at all: a grey
|
|
186
|
+
* placeholder beside eleven values would say "these are all the same colour",
|
|
187
|
+
* which is worse than the word on its own.
|
|
188
|
+
*
|
|
189
|
+
* The code is read as written apart from case and separators — `dark_blue`
|
|
190
|
+
* and `dark-blue` are one code and neither is a CSS keyword, so both get
|
|
191
|
+
* nothing. Only a code that IS a name resolves.
|
|
192
|
+
*/
|
|
193
|
+
export function swatchColor(code: string): string | null {
|
|
194
|
+
const raw = code.trim();
|
|
195
|
+
if (raw === "") return null;
|
|
196
|
+
if (HEX.test(raw)) return raw;
|
|
197
|
+
const normalized = raw.toLowerCase().replace(/[-_\s]/g, "");
|
|
198
|
+
const role = TOKEN_ROLE_SWATCHES[normalized];
|
|
199
|
+
if (role !== undefined) return role;
|
|
200
|
+
return CSS_COLOR_SET.has(normalized) ? normalized : null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* The dot a value gets when this axis is a colour AND the value names one.
|
|
205
|
+
* `null` everywhere else, which is most of the time — see {@link swatchColor}.
|
|
206
|
+
*/
|
|
207
|
+
export function facetSwatch(group: ColorAxisLike, code: string): string | null {
|
|
208
|
+
return isColorAxis(group) ? swatchColor(code) : null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** The swatch's own size, in CSS pixels: a dot beside a line of text, sized
|
|
212
|
+
* to the x-height rather than to the control, so it reads as part of the
|
|
213
|
+
* label and not as a second checkbox. */
|
|
214
|
+
export const SWATCH_SIZE = 12;
|