@headband/react 0.1.1 → 0.2.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/LICENSE +21 -0
- package/README.md +307 -160
- package/dist/index.d.mts +271 -5
- package/dist/index.d.ts +271 -5
- package/dist/index.js +485 -132
- package/dist/index.mjs +469 -139
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,13 +1,152 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode, ComponentType } from 'react';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Runtime helpers for Headband-managed retrieval experiments.
|
|
6
|
+
*
|
|
7
|
+
* Headband stores experiment rules, but applications own runtime assignment.
|
|
8
|
+
* These helpers fetch the saved rules and deterministically assign a subject
|
|
9
|
+
* to a variant without adding another dependency.
|
|
10
|
+
*/
|
|
11
|
+
type ExperimentConfigStatus = "draft" | "active" | "paused" | "archived";
|
|
12
|
+
interface ExperimentVariantConfig {
|
|
13
|
+
key: string;
|
|
14
|
+
indexUid: string;
|
|
15
|
+
weight: number;
|
|
16
|
+
}
|
|
17
|
+
interface ExperimentConfig {
|
|
18
|
+
id?: string;
|
|
19
|
+
experimentKey: string;
|
|
20
|
+
name: string;
|
|
21
|
+
status: ExperimentConfigStatus;
|
|
22
|
+
trafficPercent: number;
|
|
23
|
+
killSwitch: boolean;
|
|
24
|
+
primaryMetric: string;
|
|
25
|
+
guardrailMetric: string | null;
|
|
26
|
+
assignmentSalt: string;
|
|
27
|
+
variants: ExperimentVariantConfig[];
|
|
28
|
+
updatedAt?: string;
|
|
29
|
+
}
|
|
30
|
+
interface ExperimentSearchMetadata {
|
|
31
|
+
experimentKey: string;
|
|
32
|
+
variant: string;
|
|
33
|
+
sessionId?: string;
|
|
34
|
+
}
|
|
35
|
+
type ExperimentEventType = "experiment_view" | "search_result_click" | "conversion" | "custom";
|
|
36
|
+
interface ExperimentEventInput extends ExperimentSearchMetadata {
|
|
37
|
+
eventType: ExperimentEventType;
|
|
38
|
+
indexUid: string;
|
|
39
|
+
query?: string;
|
|
40
|
+
filters?: string;
|
|
41
|
+
conversionValue?: number;
|
|
42
|
+
metadata?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
interface ExperimentEventResponse {
|
|
45
|
+
accepted: boolean;
|
|
46
|
+
eventType: ExperimentEventType;
|
|
47
|
+
experimentKey: string;
|
|
48
|
+
variant: string;
|
|
49
|
+
indexUid: string;
|
|
50
|
+
}
|
|
51
|
+
interface ExperimentAssignment extends ExperimentSearchMetadata {
|
|
52
|
+
indexUid: string;
|
|
53
|
+
config: ExperimentConfig;
|
|
54
|
+
}
|
|
55
|
+
interface HostedExperimentAssignmentQuery {
|
|
56
|
+
projectId?: string;
|
|
57
|
+
indexUid?: string;
|
|
58
|
+
experimentKey: string;
|
|
59
|
+
subjectId: string;
|
|
60
|
+
sessionId?: string;
|
|
61
|
+
}
|
|
62
|
+
interface HostedExperimentAssignmentResponse {
|
|
63
|
+
assigned: boolean;
|
|
64
|
+
reason?: "not_runnable" | "outside_rollout" | "no_weight";
|
|
65
|
+
experimentKey: string;
|
|
66
|
+
variant?: string;
|
|
67
|
+
indexUid?: string;
|
|
68
|
+
sessionId: string;
|
|
69
|
+
config: {
|
|
70
|
+
status: ExperimentConfigStatus;
|
|
71
|
+
trafficPercent: number;
|
|
72
|
+
killSwitch: boolean;
|
|
73
|
+
updatedAt?: string;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
interface ExperimentConfigQuery {
|
|
77
|
+
projectId?: string;
|
|
78
|
+
indexUid?: string;
|
|
79
|
+
experimentKey?: string;
|
|
80
|
+
}
|
|
81
|
+
interface ExperimentConfigResponse {
|
|
82
|
+
data?: {
|
|
83
|
+
experiments?: ExperimentConfig[];
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
declare function buildExperimentConfigPath(query?: ExperimentConfigQuery): string;
|
|
87
|
+
declare function isRunnableExperiment(config: ExperimentConfig): boolean;
|
|
88
|
+
declare function chooseExperimentVariant(config: ExperimentConfig, subjectId: string): ExperimentVariantConfig | null;
|
|
89
|
+
declare function assignExperiment(config: ExperimentConfig, subjectId: string, options?: {
|
|
90
|
+
sessionId?: string;
|
|
91
|
+
}): ExperimentAssignment | null;
|
|
92
|
+
declare function experimentSearchMetadata(assignment: ExperimentAssignment | null | undefined): ExperimentSearchMetadata | undefined;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Search events for click feedback: `search_result_click` and `conversion`.
|
|
96
|
+
*
|
|
97
|
+
* A search response carries a `queryId` when click feedback is on for your organization. Send it
|
|
98
|
+
* with a click so Headband knows which search showed the result. Clicks and conversions are
|
|
99
|
+
* counted per query; when an index has "Use click feedback in ranking" on, results people pick
|
|
100
|
+
* for a query move up for that query. No experiment is needed.
|
|
101
|
+
*/
|
|
102
|
+
type SearchEventType = "search_result_click" | "conversion";
|
|
103
|
+
interface SearchEvent {
|
|
104
|
+
type: SearchEventType;
|
|
105
|
+
/** The index the result came from. */
|
|
106
|
+
index: string;
|
|
107
|
+
/** The clicked or converted document's id (its primary key). */
|
|
108
|
+
docId: string | number;
|
|
109
|
+
/** The `queryId` of the search that showed the result. */
|
|
110
|
+
queryId?: string;
|
|
111
|
+
/** 1-based position of the result in the whole result list, not just the page. */
|
|
112
|
+
position?: number;
|
|
113
|
+
/** A stable id for the visitor's session. Strongly recommended: events without one count half. */
|
|
114
|
+
sessionId?: string;
|
|
115
|
+
/** Optional, non-negative: an order value for a conversion. */
|
|
116
|
+
value?: number;
|
|
117
|
+
}
|
|
118
|
+
type SearchEventRejectReason = "invalid_event" | "invalid_type" | "invalid_index" | "invalid_doc_id" | "invalid_query_id" | "invalid_position" | "invalid_session_id" | "invalid_value" | "index_not_found" | "bot";
|
|
119
|
+
interface SendEventsResponse {
|
|
120
|
+
accepted: number;
|
|
121
|
+
/** `index` is the event's position in the array you sent. */
|
|
122
|
+
rejected: Array<{
|
|
123
|
+
index: number;
|
|
124
|
+
reason: SearchEventRejectReason;
|
|
125
|
+
}>;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A session id for this browser tab, kept in sessionStorage when it is available and in memory
|
|
129
|
+
* otherwise. It identifies nothing about the visitor; Headband stores it only as a keyed hash.
|
|
130
|
+
*/
|
|
131
|
+
declare function browserSessionId(): string;
|
|
132
|
+
|
|
4
133
|
/**
|
|
5
134
|
* Headband search client.
|
|
6
135
|
*
|
|
7
|
-
* Makes requests to the Headband `/v1/search
|
|
136
|
+
* Makes requests to the Headband `/v1/search`, `/v1/multi-search`, `/v1/warm` and
|
|
137
|
+
* `/v1/relevance/events` endpoints.
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Visor's ranking mode: `instant` (the default, typo-tolerant, as-you-type) or `retrieval` (BM25F,
|
|
142
|
+
* for longer queries), on the same index. Indexes on the other engines ignore it.
|
|
8
143
|
*/
|
|
144
|
+
type SearchMode = "instant" | "retrieval";
|
|
9
145
|
interface SearchParams {
|
|
10
|
-
|
|
146
|
+
experimentKey?: string;
|
|
147
|
+
variant?: string;
|
|
148
|
+
sessionId?: string;
|
|
149
|
+
filter?: string | string[];
|
|
11
150
|
facets?: string[];
|
|
12
151
|
sort?: string[];
|
|
13
152
|
limit?: number;
|
|
@@ -15,12 +154,21 @@ interface SearchParams {
|
|
|
15
154
|
attributesToHighlight?: string[];
|
|
16
155
|
attributesToRetrieve?: string[];
|
|
17
156
|
attributesToCrop?: string[];
|
|
157
|
+
/** Visor crops to 20 words and refuses any other value with 400 feature_not_supported. */
|
|
18
158
|
cropLength?: number;
|
|
159
|
+
/** Visor returns no match positions and refuses `true` with 400 feature_not_supported. */
|
|
19
160
|
showMatchesPosition?: boolean;
|
|
161
|
+
/** Visor matches as `last` does and refuses `all` and `frequency` with 400 feature_not_supported. */
|
|
20
162
|
matchingStrategy?: "all" | "last" | "frequency";
|
|
163
|
+
mode?: SearchMode;
|
|
21
164
|
}
|
|
22
165
|
interface SearchResponse {
|
|
23
166
|
hits: Record<string, unknown>[];
|
|
167
|
+
/**
|
|
168
|
+
* The id of this search, `q_` and 16 characters, when click feedback is on for your
|
|
169
|
+
* organization. Send it with clicks (`trackClick`) so Headband knows which search showed them.
|
|
170
|
+
*/
|
|
171
|
+
queryId?: string;
|
|
24
172
|
estimatedTotalHits?: number;
|
|
25
173
|
totalHits?: number;
|
|
26
174
|
processingTimeMs: number;
|
|
@@ -34,22 +182,60 @@ interface SearchResponse {
|
|
|
34
182
|
interface MultiSearchQuery {
|
|
35
183
|
indexUid: string;
|
|
36
184
|
q: string;
|
|
37
|
-
filter?: string;
|
|
185
|
+
filter?: string | string[];
|
|
38
186
|
facets?: string[];
|
|
39
187
|
sort?: string[];
|
|
40
188
|
limit?: number;
|
|
41
189
|
offset?: number;
|
|
42
190
|
attributesToHighlight?: string[];
|
|
43
191
|
attributesToRetrieve?: string[];
|
|
192
|
+
mode?: SearchMode;
|
|
44
193
|
}
|
|
45
194
|
interface MultiSearchResponse {
|
|
46
195
|
results: Array<SearchResponse & {
|
|
47
196
|
indexUid: string;
|
|
48
197
|
}>;
|
|
49
198
|
}
|
|
199
|
+
interface ExperimentConfigsResponse {
|
|
200
|
+
experiments: ExperimentConfig[];
|
|
201
|
+
}
|
|
202
|
+
/** `auto` starts an engine used in the last 24 hours and stages an older one. */
|
|
203
|
+
type WarmLevel = "auto" | "stage" | "start";
|
|
204
|
+
/** Indexes by name, or every index of a project. */
|
|
205
|
+
type WarmInput = {
|
|
206
|
+
indexes: string[];
|
|
207
|
+
} | {
|
|
208
|
+
project: string;
|
|
209
|
+
branch?: string;
|
|
210
|
+
};
|
|
211
|
+
interface WarmResponse {
|
|
212
|
+
level: WarmLevel;
|
|
213
|
+
indexes: Array<{
|
|
214
|
+
index: string;
|
|
215
|
+
engine: string;
|
|
216
|
+
state: "awake" | "staging" | "staged" | "starting" | "refused";
|
|
217
|
+
reason?: string;
|
|
218
|
+
}>;
|
|
219
|
+
}
|
|
50
220
|
interface HeadbandClient {
|
|
51
221
|
search(index: string, query: string, params?: SearchParams): Promise<SearchResponse>;
|
|
52
222
|
multiSearch(queries: MultiSearchQuery[]): Promise<MultiSearchResponse>;
|
|
223
|
+
getExperimentConfigs(query?: ExperimentConfigQuery): Promise<ExperimentConfigsResponse>;
|
|
224
|
+
assignExperiment(query: HostedExperimentAssignmentQuery): Promise<HostedExperimentAssignmentResponse>;
|
|
225
|
+
recordExperimentEvent(event: ExperimentEventInput): Promise<ExperimentEventResponse>;
|
|
226
|
+
/** Pre-wakes indexes, or a project's, so the next search does not wait for a sleeping engine. Free. */
|
|
227
|
+
warm(target: WarmInput, opts?: {
|
|
228
|
+
level?: WarmLevel;
|
|
229
|
+
}): Promise<WarmResponse>;
|
|
230
|
+
/** Sends up to 100 click and conversion events (POST /v1/relevance/events). */
|
|
231
|
+
sendEvents(events: SearchEvent[]): Promise<SendEventsResponse>;
|
|
232
|
+
/**
|
|
233
|
+
* Records a click on a search result. Pass the search's `queryId`; the client remembers it for
|
|
234
|
+
* the document so a later `trackConversion` names the same search.
|
|
235
|
+
*/
|
|
236
|
+
trackClick(event: Omit<SearchEvent, "type">): Promise<SendEventsResponse>;
|
|
237
|
+
/** Records a conversion (a purchase, a sign-up) of a document found by search. */
|
|
238
|
+
trackConversion(event: Omit<SearchEvent, "type" | "position">): Promise<SendEventsResponse>;
|
|
53
239
|
}
|
|
54
240
|
declare class HeadbandError extends Error {
|
|
55
241
|
readonly status: number;
|
|
@@ -58,6 +244,25 @@ declare class HeadbandError extends Error {
|
|
|
58
244
|
}
|
|
59
245
|
declare function headband(host: string, apiKey: string): HeadbandClient;
|
|
60
246
|
|
|
247
|
+
type FilterValue = string | number | boolean;
|
|
248
|
+
type FilterInput = string | string[] | null | undefined;
|
|
249
|
+
declare function quoteFilterValue(value: FilterValue): string;
|
|
250
|
+
declare function filterEq(attribute: string, value: FilterValue): string;
|
|
251
|
+
declare function filterNotEq(attribute: string, value: FilterValue): string;
|
|
252
|
+
declare function filterIn(attribute: string, values: FilterValue[]): string;
|
|
253
|
+
declare function filterExists(attribute: string): string;
|
|
254
|
+
declare function filterRange(attribute: string, range: {
|
|
255
|
+
gt?: number;
|
|
256
|
+
gte?: number;
|
|
257
|
+
lt?: number;
|
|
258
|
+
lte?: number;
|
|
259
|
+
min?: number;
|
|
260
|
+
max?: number;
|
|
261
|
+
}): string | undefined;
|
|
262
|
+
declare function andFilters(...filters: FilterInput[]): string | undefined;
|
|
263
|
+
declare function orFilters(...filters: FilterInput[]): string | undefined;
|
|
264
|
+
declare function combineFilters(baseFilter: FilterInput, generatedFilter: FilterInput): string | string[] | undefined;
|
|
265
|
+
|
|
61
266
|
/** Map of attribute -> selected values for facet refinements. */
|
|
62
267
|
type RefinementMap = Map<string, Set<string>>;
|
|
63
268
|
/** Numeric range filter: attribute -> { min?, max? }. */
|
|
@@ -92,20 +297,56 @@ interface SearchActions {
|
|
|
92
297
|
unregisterFacet: (attribute: string) => void;
|
|
93
298
|
refresh: () => void;
|
|
94
299
|
}
|
|
300
|
+
/** How useClickTracking finds a hit's id and the visitor's session. */
|
|
301
|
+
interface ClickTrackingConfig {
|
|
302
|
+
/** The field hits carry the document id in. Default `id`. */
|
|
303
|
+
idField: string;
|
|
304
|
+
/** The session id events carry; a per-tab id when absent. */
|
|
305
|
+
sessionId?: string;
|
|
306
|
+
}
|
|
95
307
|
interface HeadbandContextValue {
|
|
96
308
|
client: HeadbandClient;
|
|
97
309
|
index: string;
|
|
98
310
|
state: SearchState;
|
|
99
311
|
actions: SearchActions;
|
|
312
|
+
tracking: ClickTrackingConfig;
|
|
100
313
|
}
|
|
101
314
|
declare function useHeadbandContext(): HeadbandContextValue;
|
|
102
315
|
interface HeadbandProviderProps {
|
|
103
316
|
client: HeadbandClient;
|
|
104
317
|
index: string;
|
|
105
318
|
initialQuery?: string;
|
|
319
|
+
experiment?: ExperimentSearchMetadata | null;
|
|
320
|
+
/**
|
|
321
|
+
* Optional app-owned filter expression(s) ANDed with UI refinements/ranges.
|
|
322
|
+
* Use this for tenant, permission, availability, or custom OR/AND groups that
|
|
323
|
+
* should apply to every search request.
|
|
324
|
+
*/
|
|
325
|
+
baseFilter?: FilterInput;
|
|
326
|
+
/**
|
|
327
|
+
* Visor's ranking mode for every search: `instant` (the engine's default) or `retrieval` (BM25F,
|
|
328
|
+
* for longer queries). Indexes on the other engines ignore it.
|
|
329
|
+
*/
|
|
330
|
+
mode?: SearchMode;
|
|
331
|
+
/**
|
|
332
|
+
* Store-style facet counts (default true). With a value ticked in a facet list, that list still
|
|
333
|
+
* counts its other values, so a shopper can tick a second one. Each facet with a ticked value
|
|
334
|
+
* costs one more search per request. Set false for one search per request, and lists that narrow
|
|
335
|
+
* to the ticked values.
|
|
336
|
+
*/
|
|
337
|
+
disjunctiveFacets?: boolean;
|
|
338
|
+
/**
|
|
339
|
+
* The field your documents' ids are in, for click feedback (`useClickTracking`). Default `id`.
|
|
340
|
+
*/
|
|
341
|
+
idField?: string;
|
|
342
|
+
/**
|
|
343
|
+
* A stable id for the visitor's session, sent with click and conversion events. Without it the
|
|
344
|
+
* experiment's session id is used, else an id kept for this browser tab.
|
|
345
|
+
*/
|
|
346
|
+
sessionId?: string;
|
|
106
347
|
children: ReactNode;
|
|
107
348
|
}
|
|
108
|
-
declare function HeadbandProvider({ client, index, initialQuery, children, }: HeadbandProviderProps): react_jsx_runtime.JSX.Element;
|
|
349
|
+
declare function HeadbandProvider({ client, index, initialQuery, experiment, baseFilter, mode, disjunctiveFacets, idField, sessionId, children, }: HeadbandProviderProps): react_jsx_runtime.JSX.Element;
|
|
109
350
|
|
|
110
351
|
/**
|
|
111
352
|
* Core hook that exposes the full search state and actions.
|
|
@@ -273,6 +514,31 @@ interface UseHighlightReturn {
|
|
|
273
514
|
*/
|
|
274
515
|
declare function useHighlight({ hit, attribute, }: UseHighlightProps): UseHighlightReturn;
|
|
275
516
|
|
|
517
|
+
interface UseClickTrackingReturn {
|
|
518
|
+
/** The current search's id, when click feedback is on for your organization. */
|
|
519
|
+
queryId: string | undefined;
|
|
520
|
+
/**
|
|
521
|
+
* Records a click on a hit. `positionOnPage` is the hit's 0-based place in the current page;
|
|
522
|
+
* the absolute position is worked out from the page. Never throws: tracking must not break a
|
|
523
|
+
* results page, so a failed send resolves to null.
|
|
524
|
+
*/
|
|
525
|
+
trackClick: (hit: Record<string, unknown>, positionOnPage?: number) => Promise<SendEventsResponse | null>;
|
|
526
|
+
/**
|
|
527
|
+
* Records a conversion of a document. It names the search the document was last clicked from
|
|
528
|
+
* in this tab, unless you pass a `queryId`. Never throws.
|
|
529
|
+
*/
|
|
530
|
+
trackConversion: (docId: string | number, opts?: {
|
|
531
|
+
value?: number;
|
|
532
|
+
queryId?: string;
|
|
533
|
+
}) => Promise<SendEventsResponse | null>;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Click feedback events for the provider's index: `search_result_click` and `conversion`. When the
|
|
537
|
+
* index has "Use click feedback in ranking" on, results people pick for a query move up for that
|
|
538
|
+
* query.
|
|
539
|
+
*/
|
|
540
|
+
declare function useClickTracking(): UseClickTrackingReturn;
|
|
541
|
+
|
|
276
542
|
interface SearchBoxProps {
|
|
277
543
|
/** Placeholder text. */
|
|
278
544
|
placeholder?: string;
|
|
@@ -413,4 +679,4 @@ interface PoweredByProps {
|
|
|
413
679
|
*/
|
|
414
680
|
declare function PoweredBy({ className, styled, href, }: PoweredByProps): react_jsx_runtime.JSX.Element;
|
|
415
681
|
|
|
416
|
-
export { type HeadbandClient, type HeadbandContextValue, HeadbandError, HeadbandProvider, type HeadbandProviderProps, Highlight, type HighlightProps, type Hit, Hits, type HitsProps, type MultiSearchQuery, type MultiSearchResponse, Pagination, type PaginationProps, PoweredBy, type PoweredByProps, RangeInput, type RangeInputProps, type RangeMap, RefinementList, type RefinementListItem, type RefinementListProps, type RefinementMap, type SearchActions, SearchBox, type SearchBoxProps, type SearchParams, type SearchResponse, type SearchState, SortBy, type SortByItem, type SortByProps, Stats, type StatsProps, type UseHighlightProps, type UseHighlightReturn, type UseHitsReturn, type UsePaginationReturn, type UseRangeProps, type UseRangeReturn, type UseRefinementListProps, type UseRefinementListReturn, type UseSearchBoxReturn, type UseSortByProps, type UseSortByReturn, type UseStatsReturn, headband, useHeadbandContext, useHighlight, useHits, usePagination, useRange, useRefinementList, useSearch, useSearchBox, useSortBy, useStats };
|
|
682
|
+
export { type ClickTrackingConfig, type ExperimentAssignment, type ExperimentConfig, type ExperimentConfigQuery, type ExperimentConfigResponse, type ExperimentConfigStatus, type ExperimentConfigsResponse, type ExperimentEventInput, type ExperimentEventResponse, type ExperimentEventType, type ExperimentSearchMetadata, type ExperimentVariantConfig, type FilterInput, type FilterValue, type HeadbandClient, type HeadbandContextValue, HeadbandError, HeadbandProvider, type HeadbandProviderProps, Highlight, type HighlightProps, type Hit, Hits, type HitsProps, type HostedExperimentAssignmentQuery, type HostedExperimentAssignmentResponse, type MultiSearchQuery, type MultiSearchResponse, Pagination, type PaginationProps, PoweredBy, type PoweredByProps, RangeInput, type RangeInputProps, type RangeMap, RefinementList, type RefinementListItem, type RefinementListProps, type RefinementMap, type SearchActions, SearchBox, type SearchBoxProps, type SearchEvent, type SearchEventRejectReason, type SearchEventType, type SearchMode, type SearchParams, type SearchResponse, type SearchState, type SendEventsResponse, SortBy, type SortByItem, type SortByProps, Stats, type StatsProps, type UseClickTrackingReturn, type UseHighlightProps, type UseHighlightReturn, type UseHitsReturn, type UsePaginationReturn, type UseRangeProps, type UseRangeReturn, type UseRefinementListProps, type UseRefinementListReturn, type UseSearchBoxReturn, type UseSortByProps, type UseSortByReturn, type UseStatsReturn, type WarmInput, type WarmLevel, type WarmResponse, andFilters, assignExperiment, browserSessionId, buildExperimentConfigPath, chooseExperimentVariant, combineFilters, experimentSearchMetadata, filterEq, filterExists, filterIn, filterNotEq, filterRange, headband, isRunnableExperiment, orFilters, quoteFilterValue, useClickTracking, useHeadbandContext, useHighlight, useHits, usePagination, useRange, useRefinementList, useSearch, useSearchBox, useSortBy, useStats };
|