@searchstax-inc/searchstudio-ux-react 1.0.49 → 4.1.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 +31 -0
- package/README.md +1307 -706
- package/README.mustache +590 -0
- package/dist/@searchstax-inc/components/SearchstaxAnswerWidget.d.ts +1 -1
- package/dist/@searchstax-inc/components/SearchstaxResultWidget.d.ts +1 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxAnswerWidgetTemplate.d.ts +3 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxExternalPromotionsWidgetTemplate.d.ts +3 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxFacetsWidgetTemplate.d.ts +8 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxInputWidgetTemplate.d.ts +4 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxLocationWidgetTemplate.d.ts +3 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxLocationWidgetTemplateConfig.d.ts +13 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxOverviewWidgetTemplate.d.ts +3 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxPaginationWidgetTemplate.d.ts +4 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxRelatedSearchesWidgetTemplate.d.ts +3 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxResultWidgetTemplate.d.ts +4 -0
- package/dist/@searchstax-inc/components/templates/SearchstaxSortingWidgetTemplate.d.ts +3 -0
- package/dist/@searchstax-inc/main.d.ts +1 -12
- package/dist/@searchstax-inc/searchstudio-ux-react.cjs +62 -70
- package/dist/@searchstax-inc/searchstudio-ux-react.d.cts +1 -12
- package/dist/@searchstax-inc/searchstudio-ux-react.iife.js +62 -70
- package/dist/@searchstax-inc/searchstudio-ux-react.mjs +2540 -2704
- package/dist/main.d.ts +12 -0
- package/package.json +12 -7
package/README.md
CHANGED
|
@@ -2,129 +2,85 @@
|
|
|
2
2
|
|
|
3
3
|
Library to build Site Search page
|
|
4
4
|
|
|
5
|
-
## changelog
|
|
6
|
-
changelog is documented at CHANGELOG.md
|
|
7
|
-
|
|
8
5
|
## Installation
|
|
9
6
|
npm install following package
|
|
10
7
|
`npm install --save @searchstax-inc/searchstudio-ux-react`
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
Add the following code to <head>:
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
<script type="text/javascript">
|
|
16
16
|
var _msq = _msq || []; //declare object
|
|
17
|
-
var analyticsBaseUrl = 'https://analytics-us-east.searchstax.co';
|
|
17
|
+
var analyticsBaseUrl = '<https://analytics-us-east.searchstax.co>';
|
|
18
18
|
(function () {
|
|
19
19
|
var ms = document.createElement('script');
|
|
20
20
|
ms.type = 'text/javascript';
|
|
21
|
-
ms.src = 'https://static.searchstax.co/studio-js/v3/js/studio-analytics.js';
|
|
21
|
+
ms.src = '<https://static.searchstax.co/studio-js/v3/js/studio-analytics.js>';
|
|
22
22
|
var s = document.getElementsByTagName('script')[0];
|
|
23
23
|
s.parentNode.insertBefore(ms, s);
|
|
24
24
|
})();
|
|
25
25
|
</script>
|
|
26
26
|
```
|
|
27
|
-
## Usage
|
|
28
|
-
|
|
29
|
-
After importing SearchstaxWrapper component needs to wrap all other components:
|
|
30
27
|
|
|
28
|
+
## Initialization
|
|
29
|
+
sampleConfig object needs to be of type: `ISearchstaxConfig`
|
|
31
30
|
```
|
|
31
|
+
const sampleConfig = {
|
|
32
|
+
language: 'en',
|
|
33
|
+
searchURL: '',
|
|
34
|
+
suggesterURL: '',
|
|
35
|
+
trackApiKey: '',
|
|
36
|
+
searchAuth: '',
|
|
37
|
+
authType: 'basic',
|
|
38
|
+
router: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
routeName: 'searchstax',
|
|
41
|
+
title: (result) => `Search results for: ${result.query}`,
|
|
42
|
+
ignoredKeys: []
|
|
43
|
+
},
|
|
44
|
+
hooks: {
|
|
45
|
+
beforeSearch: (props) => {
|
|
46
|
+
// modify props
|
|
47
|
+
return props;
|
|
48
|
+
},
|
|
49
|
+
afterSearch: (results) => {
|
|
50
|
+
// modify results
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
32
55
|
|
|
33
56
|
<SearchstaxWrapper
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// other components will go there
|
|
46
|
-
|
|
47
|
-
</SearchstaxWrapper>
|
|
48
|
-
```
|
|
57
|
+
language={sampleConfig.language}
|
|
58
|
+
searchURL={sampleConfig.searchURL}
|
|
59
|
+
suggesterURL={sampleConfig.suggesterURL}
|
|
60
|
+
trackApiKey={sampleConfig.trackApiKey}
|
|
61
|
+
searchAuth={sampleConfig.searchAuth}
|
|
62
|
+
authType={sampleConfig.authType}
|
|
63
|
+
router={sampleConfig.router}
|
|
64
|
+
beforeSearch={sampleConfig.hooks.beforeSearch}
|
|
65
|
+
afterSearch={sampleConfig.hooks.afterSearch}
|
|
66
|
+
>
|
|
67
|
+
</SearchstaxWrapper>
|
|
49
68
|
|
|
50
|
-
## Initialization
|
|
51
|
-
Initialization object needs to be of type: [ISearchstaxConfig](https://github.com/searchstax/searchstudio-ux-js/blob/main/src/interfaces/connector.interface.ts#L5)
|
|
52
69
|
|
|
53
|
-
Initialization example
|
|
54
|
-
```
|
|
55
|
-
sampleConfig = {
|
|
56
|
-
language: "en",
|
|
57
|
-
model:""
|
|
58
|
-
searchURL: "",
|
|
59
|
-
suggesterURL: "",
|
|
60
|
-
trackApiKey: "",
|
|
61
|
-
searchAuth: "",
|
|
62
|
-
authType: "basic",
|
|
63
|
-
router: {
|
|
64
|
-
enabled: true,
|
|
65
|
-
routeName: "searchstax",
|
|
66
|
-
title: (result: ISearchObject) => {
|
|
67
|
-
return "Search results for: " + result.query;
|
|
68
|
-
},
|
|
69
|
-
ignoredKeys: [],
|
|
70
|
-
},
|
|
71
|
-
hooks: {
|
|
72
|
-
beforeSearch: function (props: ISearchObject) {
|
|
73
|
-
const propsCopy = { ...props };
|
|
74
|
-
return propsCopy;
|
|
75
|
-
},
|
|
76
|
-
afterSearch: function (results: ISearchstaxParsedResult[]) {
|
|
77
|
-
const copy = [...results];
|
|
78
|
-
return copy;
|
|
79
|
-
},
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
70
|
```
|
|
83
71
|
|
|
84
72
|
## Initial layout
|
|
85
|
-
|
|
73
|
+
Add any other Site Search components as needed:
|
|
86
74
|
```
|
|
87
|
-
<SearchstaxWrapper
|
|
88
|
-
searchURL={sampleConfig.searchURL}
|
|
89
|
-
suggesterURL={sampleConfig.suggesterURL}
|
|
90
|
-
trackApiKey={sampleConfig.trackApiKey}
|
|
91
|
-
searchAuth={sampleConfig.searchAuth}
|
|
92
|
-
beforeSearch={sampleConfig.hooks.beforeSearch}
|
|
93
|
-
afterSearch={sampleConfig.hooks.afterSearch}
|
|
94
|
-
authType="basic"
|
|
95
|
-
language="en"
|
|
96
|
-
model=""
|
|
97
|
-
>
|
|
98
|
-
<div className="searchstax-page-layout-container">
|
|
99
|
-
<SearchstaxInputWidget
|
|
100
|
-
afterAutosuggest={afterAutosuggest}
|
|
101
|
-
beforeAutosuggest={beforeAutosuggest}
|
|
102
|
-
inputTemplate={InputTemplate}
|
|
103
|
-
></SearchstaxInputWidget>
|
|
75
|
+
<SearchstaxWrapper>
|
|
104
76
|
|
|
105
|
-
|
|
106
|
-
<div id="search-feedback-container"></div>
|
|
107
|
-
<div id="search-sorting-container"></div>
|
|
108
|
-
</div>
|
|
77
|
+
<SearchstaxInputWidget />
|
|
109
78
|
|
|
110
|
-
|
|
111
|
-
<div className="searchstax-page-layout-facet-container">
|
|
112
|
-
<div id="searchstax-facets-container"></div>
|
|
113
|
-
</div>
|
|
79
|
+
<SearchstaxResultsWidget />
|
|
114
80
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
afterLinkClick={afterLinkClick}
|
|
119
|
-
noResultTemplate={noResultTemplate}
|
|
120
|
-
resultsTemplate={resultsTemplate}
|
|
121
|
-
></SearchstaxResultWidget>
|
|
122
|
-
<div id="searchstax-related-searches-container"></div>
|
|
123
|
-
<div id="searchstax-pagination-container"></div>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
</SearchstaxWrapper>
|
|
81
|
+
{/* Other components */}
|
|
82
|
+
|
|
83
|
+
</SearchstaxWrapper>
|
|
128
84
|
```
|
|
129
85
|
|
|
130
86
|
### widgets
|
|
@@ -151,19 +107,52 @@ Following widgets are available:
|
|
|
151
107
|
[sorting Widget](#sorting-widget)
|
|
152
108
|
|
|
153
109
|
### Answer Widget ###
|
|
110
|
+
SearchStax Site Search solution offers React and Next.js widgets to assist in building your custom search page.
|
|
111
|
+
|
|
112
|
+
The SearchstaxAnswerWidget component provides a React and Next.js AI answer widget for your searches.
|
|
113
|
+
|
|
114
|
+
**Usage**
|
|
154
115
|
|
|
155
|
-
example of answer widget initialization with minimum options
|
|
156
116
|
```
|
|
157
117
|
<SearchstaxAnswerWidget
|
|
158
|
-
|
|
118
|
+
showMoreAfterWordCount={100}
|
|
119
|
+
feedbackwidget={feedbackConfig}
|
|
159
120
|
></SearchstaxAnswerWidget>
|
|
160
121
|
```
|
|
161
122
|
|
|
123
|
+
**Props**
|
|
124
|
+
|
|
125
|
+
- showMoreAfterWordCount - number(default 100) determining after how many symbols UI will show “Read More” view.
|
|
126
|
+
- feedbackWidget – an optional object that configures thumbs-up and thumbs-down feedback functionality.
|
|
127
|
+
- searchAnswerTemplate - template override for answers widget
|
|
162
128
|
|
|
163
|
-
|
|
129
|
+
Example of feedbackWidget config:
|
|
130
|
+
```
|
|
131
|
+
const feedbackConfig = {
|
|
132
|
+
renderFeedbackWidget: true,
|
|
133
|
+
emailOverride: searchstaxEmailOverride,
|
|
134
|
+
thumbsUpValue: 10,
|
|
135
|
+
thumbsDownValue: 0
|
|
136
|
+
}
|
|
164
137
|
```
|
|
165
138
|
|
|
166
|
-
|
|
139
|
+
|
|
140
|
+
**searchAnswerTemplate**
|
|
141
|
+
|
|
142
|
+
The templates prop allows customizing the answer UI.
|
|
143
|
+
|
|
144
|
+
It receives the following props:
|
|
145
|
+
|
|
146
|
+
- shouldShowAnswer – boolean
|
|
147
|
+
- answerErrorMessage – string
|
|
148
|
+
- fullAnswerFormatted – string
|
|
149
|
+
- showMoreButtonVisible – boolean
|
|
150
|
+
- answerLoading – boolean
|
|
151
|
+
|
|
152
|
+
**Example**
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
function answerTemplate(
|
|
167
156
|
answerData: null | ISearchstaxAnswerData,
|
|
168
157
|
showMore: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
|
|
169
158
|
) {
|
|
@@ -198,7 +187,7 @@ export function answerTemplate(
|
|
|
198
187
|
showMore(e);
|
|
199
188
|
}}
|
|
200
189
|
>
|
|
201
|
-
Read
|
|
190
|
+
Read more
|
|
202
191
|
</button>
|
|
203
192
|
</div>
|
|
204
193
|
)}
|
|
@@ -213,67 +202,205 @@ export function answerTemplate(
|
|
|
213
202
|
</>
|
|
214
203
|
);
|
|
215
204
|
}
|
|
216
|
-
|
|
217
|
-
const feedbackConfig = {
|
|
218
|
-
renderFeedbackWidget: true,
|
|
219
|
-
emailOverride: searchstaxEmailOverride,
|
|
220
|
-
thumbsUpValue: 10,
|
|
221
|
-
thumbsDownValue: 0,
|
|
222
|
-
lightweightTemplateOverride: `
|
|
223
|
-
<div class="searchstax-lightweight-widget-container">
|
|
224
|
-
<div class="searchstax-lightweight-widget-thumbs-up {{#thumbsUpActive}}active{{/thumbsUpActive}}">
|
|
225
|
-
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
226
|
-
<path d="M4.85079 7.59996L7.83141 1C8.4243 1 8.9929 1.23178 9.41213 1.64436C9.83136 2.05694 10.0669 2.61651 10.0669 3.19999V6.1333H14.2845C14.5005 6.13089 14.7145 6.17474 14.9116 6.26179C15.1087 6.34885 15.2842 6.47704 15.426 6.63747C15.5677 6.79791 15.6723 6.98676 15.7326 7.19094C15.7928 7.39513 15.8072 7.60975 15.7748 7.81996L14.7465 14.4199C14.6926 14.7696 14.5121 15.0884 14.2382 15.3175C13.9643 15.5466 13.6156 15.6706 13.2562 15.6666H4.85079M4.85079 7.59996V15.6666M4.85079 7.59996H2.61531C2.22006 7.59996 1.84099 7.75448 1.5615 8.02953C1.28201 8.30458 1.125 8.67763 1.125 9.06661V14.1999C1.125 14.5889 1.28201 14.9619 1.5615 15.237C1.84099 15.512 2.22006 15.6666 2.61531 15.6666H4.85079" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
227
|
-
</svg>
|
|
228
|
-
</div>
|
|
229
|
-
<div class="searchstax-lightweight-widget-separator"></div>
|
|
230
|
-
<div class="searchstax-lightweight-widget-thumbs-down {{#thumbsDownActive}}active{{/thumbsDownActive}}">
|
|
231
|
-
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
232
|
-
<path d="M12.1492 9.06801L9.16859 15.668C8.5757 15.668 8.0071 15.4362 7.58787 15.0236C7.16864 14.611 6.93311 14.0515 6.93311 13.468V10.5347H2.71552C2.4995 10.5371 2.28552 10.4932 2.08842 10.4062C1.89132 10.3191 1.71581 10.1909 1.57405 10.0305C1.43229 9.87006 1.32766 9.6812 1.26743 9.47702C1.2072 9.27284 1.19279 9.05822 1.22521 8.84801L2.25353 2.24806C2.30742 1.89833 2.48793 1.57955 2.76179 1.35046C3.03566 1.12136 3.38443 0.997398 3.74384 1.0014H12.1492M12.1492 9.06801V1.0014M12.1492 9.06801H14.3847C14.7799 9.06801 15.159 8.91349 15.4385 8.63844C15.718 8.36339 15.875 7.99034 15.875 7.60135V2.46805C15.875 2.07907 15.718 1.70602 15.4385 1.43097C15.159 1.15592 14.7799 1.0014 14.3847 1.0014H12.1492" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
233
|
-
</svg>
|
|
234
|
-
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
`,
|
|
238
|
-
}
|
|
239
|
-
|
|
240
205
|
<SearchstaxAnswerWidget
|
|
241
206
|
searchAnswerTemplate={answerTemplate}
|
|
242
|
-
|
|
243
|
-
feedbackwidget={feedbackConfig}
|
|
207
|
+
showMoreAfterWordCount={100}
|
|
244
208
|
></SearchstaxAnswerWidget>
|
|
245
209
|
```
|
|
246
210
|
|
|
247
|
-
|
|
248
|
-
```
|
|
211
|
+
### Input Widget ###
|
|
249
212
|
|
|
250
|
-
|
|
251
|
-
searchAnswerTemplate={answerTemplate}
|
|
252
|
-
showShowMoreAfterWordCount={100}
|
|
253
|
-
></SearchstaxAnswerWidget>
|
|
254
|
-
```
|
|
213
|
+
SearchStax Site Search solution provides React and Next.js widgets to help you build your custom search page.
|
|
255
214
|
|
|
256
|
-
|
|
215
|
+
The SearchstaxInputWidget component provides a search input with autosuggest/autocomplete functionality.
|
|
257
216
|
|
|
258
|
-
|
|
217
|
+
**Usage**
|
|
259
218
|
```
|
|
260
219
|
<SearchstaxInputWidget
|
|
220
|
+
suggestAfterMinChars={3}
|
|
261
221
|
afterAutosuggest={afterAutosuggest}
|
|
262
222
|
beforeAutosuggest={beforeAutosuggest}
|
|
263
223
|
></SearchstaxInputWidget>
|
|
264
224
|
```
|
|
265
225
|
|
|
226
|
+
**Props**
|
|
266
227
|
|
|
267
|
-
|
|
228
|
+
- suggestAfterMinChars - default 3. Number of characters needed for autosuggest to start triggering
|
|
229
|
+
- beforeAutosuggest - callback function that gets called before firing autosuggest. autosuggestProps are being passed as a property and can be modified, if passed along further search will execute with modified properties, if null is returned then event gets canceled and search never fires.
|
|
230
|
+
- afterAutosuggest - callback function that gets called after autosuggest has values but before rendering. It needs to return same type of data but it can be modified.
|
|
231
|
+
- inputTemplate - template override. look at examples below
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
**inputWidgetTemplate**
|
|
235
|
+
|
|
236
|
+
The inputTemplate prop allows customizing the input UI.
|
|
237
|
+
|
|
238
|
+
It receives the following props:
|
|
239
|
+
|
|
240
|
+
- locationEnabled – boolean
|
|
241
|
+
|
|
242
|
+
**example**
|
|
268
243
|
```
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
244
|
+
const config = {
|
|
245
|
+
appId: "APP_ID",
|
|
246
|
+
relatedSearchesAPIKey: "KEY"
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const locationWidgetConfig = {
|
|
250
|
+
locationDecode: (term: string): Promise<ISearchstaxLocation> => {
|
|
251
|
+
return new Promise((resolve) => {
|
|
252
|
+
// make a request to google geocoding API to retrieve lat, lon and address
|
|
253
|
+
|
|
254
|
+
const geocodingAPIKey = "GEOCODING_API_KEY";
|
|
255
|
+
const geocodingURL = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(
|
|
256
|
+
term
|
|
257
|
+
)}&key=${geocodingAPIKey}`;
|
|
258
|
+
fetch(geocodingURL)
|
|
259
|
+
.then((response) => response.json())
|
|
260
|
+
.then((data) => {
|
|
261
|
+
if (data.status === "OK" && data.results.length > 0) {
|
|
262
|
+
const result = data.results[0];
|
|
263
|
+
const location = {
|
|
264
|
+
lat: result.geometry.location.lat,
|
|
265
|
+
lon: result.geometry.location.lng,
|
|
266
|
+
address: result.formatted_address,
|
|
267
|
+
};
|
|
268
|
+
resolve(location);
|
|
269
|
+
} else {
|
|
270
|
+
resolve({
|
|
271
|
+
address: undefined,
|
|
272
|
+
lat: undefined,
|
|
273
|
+
lon: undefined,
|
|
274
|
+
error: true,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
.catch(() => {
|
|
279
|
+
resolve({
|
|
280
|
+
address: undefined,
|
|
281
|
+
lat: undefined,
|
|
282
|
+
lon: undefined,
|
|
283
|
+
error: true,
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
locationDecodeCoordinatesToAddress: (lat: string, lon: string): Promise<ISearchstaxLocation> => {
|
|
289
|
+
return new Promise((resolve) => {
|
|
290
|
+
fetch(
|
|
291
|
+
`https://geocoding-staging.searchstax.co/reverse?location=${lat},${lon}&components=country:US&app_id=${config.appId}`,
|
|
292
|
+
{
|
|
293
|
+
method: "GET",
|
|
294
|
+
headers: {
|
|
295
|
+
Authorization: `Token ${config.relatedSearchesAPIKey}`,
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
.then((response) => response.json())
|
|
300
|
+
.then((data) => {
|
|
301
|
+
if (data.status === "OK" && data.results.length > 0) {
|
|
302
|
+
const result = data.results[0];
|
|
303
|
+
resolve({
|
|
304
|
+
address: result.formatted_address,
|
|
305
|
+
lat: lat,
|
|
306
|
+
lon: lon,
|
|
307
|
+
error: false,
|
|
308
|
+
});
|
|
309
|
+
} else {
|
|
310
|
+
resolve({
|
|
311
|
+
address: undefined,
|
|
312
|
+
lat: lat,
|
|
313
|
+
lon: lon,
|
|
314
|
+
error: true,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
.catch(() => {
|
|
319
|
+
resolve({
|
|
320
|
+
address: undefined,
|
|
321
|
+
lat: lat,
|
|
322
|
+
lon: lon,
|
|
323
|
+
error: true,
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
locationSearchEnabled: false,
|
|
329
|
+
locationValuesOverride: {
|
|
330
|
+
locationDistanceEnabled: true,
|
|
331
|
+
filterValues: ["any", "1000"],
|
|
332
|
+
filterUnit: "miles",
|
|
333
|
+
locationFilterDefaultValue: "any"
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
function LocationTemplate(
|
|
337
|
+
locationData: null | ISearchstaxLocationRenderData,
|
|
338
|
+
locationChange: (value: string) => void,
|
|
339
|
+
locationBlur: (value: string) => void,
|
|
340
|
+
radiusChange: (value: string | number) => void,
|
|
341
|
+
getCurrentLocation: () => void,
|
|
342
|
+
inputValue?: string | null,
|
|
343
|
+
selectValue?: string | number | undefined,
|
|
344
|
+
locationError?: boolean
|
|
274
345
|
): React.ReactElement {
|
|
275
346
|
return (
|
|
276
|
-
|
|
347
|
+
<>
|
|
348
|
+
{locationData && (
|
|
349
|
+
<div
|
|
350
|
+
className="searchstax-location-input-container"
|
|
351
|
+
data-test-id="searchstax-location-input-container"
|
|
352
|
+
>
|
|
353
|
+
<div className="searchstax-location-input-wrapper">
|
|
354
|
+
<span className="searchstax-location-input-label">NEAR</span>
|
|
355
|
+
<div className="searchstax-location-input-wrapper-inner">
|
|
356
|
+
<input
|
|
357
|
+
type="text"
|
|
358
|
+
id="searchstax-location-input"
|
|
359
|
+
className={"searchstax-location-input" + (locationError ? " searchstax-input-location-error" : "")}
|
|
360
|
+
placeholder="Zip, Postal Code or City..."
|
|
361
|
+
aria-label="Search Location Input"
|
|
362
|
+
data-test-id="searchstax-location-input"
|
|
363
|
+
value={inputValue ?? ""}
|
|
364
|
+
onChange={(e) => locationChange(e.target.value)}
|
|
365
|
+
onBlur={(e) => locationBlur(e.target.value)}
|
|
366
|
+
/>
|
|
367
|
+
<button onClick={getCurrentLocation} className="searchstax-get-current-location-button">Use my current location</button>
|
|
368
|
+
</div>
|
|
369
|
+
{locationData.shouldShowLocationDistanceDropdown && (
|
|
370
|
+
<span className="searchstax-location-input-label">WITHIN</span>
|
|
371
|
+
)}
|
|
372
|
+
{locationData.shouldShowLocationDistanceDropdown && (
|
|
373
|
+
<select
|
|
374
|
+
id="searchstax-location-radius-select"
|
|
375
|
+
className="searchstax-location-radius-select"
|
|
376
|
+
aria-label="Search Location Radius Select"
|
|
377
|
+
data-test-id="searchstax-location-radius-select"
|
|
378
|
+
onChange={(e) => radiusChange(e.target.value)}
|
|
379
|
+
value={selectValue}
|
|
380
|
+
>
|
|
381
|
+
{locationData.locationSearchDistanceValues.map(
|
|
382
|
+
({ value, label }) => (
|
|
383
|
+
<option value={value} key={value}>
|
|
384
|
+
{label}
|
|
385
|
+
</option>
|
|
386
|
+
)
|
|
387
|
+
)}
|
|
388
|
+
</select>
|
|
389
|
+
)}
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
)}
|
|
393
|
+
</>)
|
|
394
|
+
}
|
|
395
|
+
function InputTemplate(
|
|
396
|
+
suggestions: ISearchstaxSuggestion[],
|
|
397
|
+
onMouseLeave: () => void,
|
|
398
|
+
onMouseOver: (suggestion: ISearchstaxSuggestion) => void,
|
|
399
|
+
onMouseClick: () => void
|
|
400
|
+
): React.ReactElement {
|
|
401
|
+
return (
|
|
402
|
+
<>
|
|
403
|
+
<div className="searchstax-search-input-wrapper">
|
|
277
404
|
<input
|
|
278
405
|
type="text"
|
|
279
406
|
id="searchstax-search-input"
|
|
@@ -295,6 +422,7 @@ example of input widget initialization with template override
|
|
|
295
422
|
>
|
|
296
423
|
<div
|
|
297
424
|
className="searchstax-autosuggest-item-term-container"
|
|
425
|
+
tabIndex={0}
|
|
298
426
|
dangerouslySetInnerHTML={{ __html: suggestion.term }}
|
|
299
427
|
onMouseOver={() => {
|
|
300
428
|
onMouseOver(suggestion);
|
|
@@ -307,16 +435,25 @@ example of input widget initialization with template override
|
|
|
307
435
|
);
|
|
308
436
|
})}
|
|
309
437
|
</div>
|
|
310
|
-
|
|
311
|
-
<button
|
|
312
|
-
className="searchstax-spinner-icon"
|
|
313
|
-
id="searchstax-search-input-action-button"
|
|
314
|
-
aria-label="search"
|
|
315
|
-
></button>
|
|
316
438
|
</div>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
439
|
+
<SearchstaxLocationWidget
|
|
440
|
+
searchLocationTemplate={LocationTemplate}
|
|
441
|
+
hooks={{
|
|
442
|
+
locationDecode: locationWidgetConfig.locationDecode,
|
|
443
|
+
locationDecodeCoordinatesToAddress:
|
|
444
|
+
locationWidgetConfig.locationDecodeCoordinatesToAddress,
|
|
445
|
+
}}
|
|
446
|
+
locationSearchEnabled={locationWidgetConfig.locationSearchEnabled}
|
|
447
|
+
locationValuesOverride={locationWidgetConfig.locationValuesOverride}
|
|
448
|
+
/>
|
|
449
|
+
<button
|
|
450
|
+
className="searchstax-spinner-icon"
|
|
451
|
+
id="searchstax-search-input-action-button"
|
|
452
|
+
aria-label="search"
|
|
453
|
+
></button>
|
|
454
|
+
</>
|
|
455
|
+
);
|
|
456
|
+
}
|
|
320
457
|
|
|
321
458
|
<SearchstaxInputWidget
|
|
322
459
|
afterAutosuggest={afterAutosuggest}
|
|
@@ -324,38 +461,221 @@ example of input widget initialization with template override
|
|
|
324
461
|
inputTemplate={InputTemplate}
|
|
325
462
|
></SearchstaxInputWidget>
|
|
326
463
|
```
|
|
327
|
-
|
|
328
464
|
### Location Widget ###
|
|
329
|
-
|
|
465
|
+
|
|
466
|
+
SearchStax Site Search solution offers a React search-location widget to assist with your custom search page.
|
|
467
|
+
|
|
468
|
+
The SearchstaxLocationWidget provides a location search input with location-based search functionality.
|
|
469
|
+
|
|
470
|
+
**Usage**
|
|
471
|
+
|
|
330
472
|
```
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
error: false
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
function locationDecodeCoordinatesToAddress(lat, lon): Promise<ISearchstaxLocation>{
|
|
473
|
+
const config = {
|
|
474
|
+
appId: "APP_ID",
|
|
475
|
+
relatedSearchesAPIKey: "KEY"
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const locationWidgetConfig = {
|
|
479
|
+
locationDecode: (term: string): Promise<ISearchstaxLocation> => {
|
|
343
480
|
return new Promise((resolve) => {
|
|
344
|
-
// make a request to geocoding API to
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
481
|
+
// make a request to google geocoding API to retrieve lat, lon and address
|
|
482
|
+
|
|
483
|
+
const geocodingAPIKey = "GEOCODING_API_KEY";
|
|
484
|
+
const geocodingURL = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(
|
|
485
|
+
term
|
|
486
|
+
)}&key=${geocodingAPIKey}`;
|
|
487
|
+
fetch(geocodingURL)
|
|
488
|
+
.then((response) => response.json())
|
|
489
|
+
.then((data) => {
|
|
490
|
+
if (data.status === "OK" && data.results.length > 0) {
|
|
491
|
+
const result = data.results[0];
|
|
492
|
+
const location = {
|
|
493
|
+
lat: result.geometry.location.lat,
|
|
494
|
+
lon: result.geometry.location.lng,
|
|
495
|
+
address: result.formatted_address,
|
|
496
|
+
};
|
|
497
|
+
resolve(location);
|
|
498
|
+
} else {
|
|
499
|
+
resolve({
|
|
500
|
+
address: undefined,
|
|
501
|
+
lat: undefined,
|
|
502
|
+
lon: undefined,
|
|
503
|
+
error: true,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
})
|
|
507
|
+
.catch(() => {
|
|
508
|
+
resolve({
|
|
509
|
+
address: undefined,
|
|
510
|
+
lat: undefined,
|
|
511
|
+
lon: undefined,
|
|
512
|
+
error: true,
|
|
513
|
+
});
|
|
514
|
+
});
|
|
351
515
|
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
516
|
+
},
|
|
517
|
+
locationDecodeCoordinatesToAddress: (lat: string, lon: string): Promise<ISearchstaxLocation> => {
|
|
518
|
+
return new Promise((resolve) => {
|
|
519
|
+
fetch(
|
|
520
|
+
`https://geocoding-staging.searchstax.co/reverse?location=${lat},${lon}&components=country:US&app_id=${config.appId}`,
|
|
521
|
+
{
|
|
522
|
+
method: "GET",
|
|
523
|
+
headers: {
|
|
524
|
+
Authorization: `Token ${config.relatedSearchesAPIKey}`,
|
|
525
|
+
},
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
.then((response) => response.json())
|
|
529
|
+
.then((data) => {
|
|
530
|
+
if (data.status === "OK" && data.results.length > 0) {
|
|
531
|
+
const result = data.results[0];
|
|
532
|
+
resolve({
|
|
533
|
+
address: result.formatted_address,
|
|
534
|
+
lat: lat,
|
|
535
|
+
lon: lon,
|
|
536
|
+
error: false,
|
|
537
|
+
});
|
|
538
|
+
} else {
|
|
539
|
+
resolve({
|
|
540
|
+
address: undefined,
|
|
541
|
+
lat: lat,
|
|
542
|
+
lon: lon,
|
|
543
|
+
error: true,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
})
|
|
547
|
+
.catch(() => {
|
|
548
|
+
resolve({
|
|
549
|
+
address: undefined,
|
|
550
|
+
lat: lat,
|
|
551
|
+
lon: lon,
|
|
552
|
+
error: true,
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
},
|
|
557
|
+
locationSearchEnabled: false,
|
|
558
|
+
locationValuesOverride: {
|
|
559
|
+
locationDistanceEnabled: true,
|
|
560
|
+
filterValues: ["any", "1000"],
|
|
561
|
+
filterUnit: "miles",
|
|
562
|
+
locationFilterDefaultValue: "any"
|
|
563
|
+
},
|
|
564
|
+
}
|
|
565
|
+
<SearchstaxLocationWidget hooks={ {locationDecode: locationDecodeFunction, locationDecodeCoordinatesToAddress: locationDecodeCoordinatesToAddress} } />
|
|
355
566
|
```
|
|
356
567
|
|
|
357
|
-
|
|
568
|
+
**Props**
|
|
569
|
+
|
|
570
|
+
- locationDecode - callback function to override location decoding
|
|
571
|
+
- locationDecodeCoordinatesToAddress - callback function to override location decoding
|
|
572
|
+
- searchLocationTemplate - template override for location widget
|
|
573
|
+
|
|
574
|
+
**Template Override**
|
|
575
|
+
|
|
576
|
+
The searchLocationTemplate prop allows customizing the location input UI.
|
|
577
|
+
|
|
578
|
+
It receives the following props:
|
|
579
|
+
|
|
580
|
+
- locationSearchDistanceValues – location distance values
|
|
581
|
+
- shouldShowLocationDistanceDropdown – boolean determining if distance dropdown should be shown
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
**Example**
|
|
585
|
+
|
|
358
586
|
```
|
|
587
|
+
const config = {
|
|
588
|
+
appId: "APP_ID",
|
|
589
|
+
relatedSearchesAPIKey: "KEY"
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const locationWidgetConfig = {
|
|
593
|
+
locationDecode: (term: string): Promise<ISearchstaxLocation> => {
|
|
594
|
+
return new Promise((resolve) => {
|
|
595
|
+
// make a request to google geocoding API to retrieve lat, lon and address
|
|
596
|
+
|
|
597
|
+
const geocodingAPIKey = "GEOCODING_API_KEY";
|
|
598
|
+
const geocodingURL = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(
|
|
599
|
+
term
|
|
600
|
+
)}&key=${geocodingAPIKey}`;
|
|
601
|
+
fetch(geocodingURL)
|
|
602
|
+
.then((response) => response.json())
|
|
603
|
+
.then((data) => {
|
|
604
|
+
if (data.status === "OK" && data.results.length > 0) {
|
|
605
|
+
const result = data.results[0];
|
|
606
|
+
const location = {
|
|
607
|
+
lat: result.geometry.location.lat,
|
|
608
|
+
lon: result.geometry.location.lng,
|
|
609
|
+
address: result.formatted_address,
|
|
610
|
+
};
|
|
611
|
+
resolve(location);
|
|
612
|
+
} else {
|
|
613
|
+
resolve({
|
|
614
|
+
address: undefined,
|
|
615
|
+
lat: undefined,
|
|
616
|
+
lon: undefined,
|
|
617
|
+
error: true,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
})
|
|
621
|
+
.catch(() => {
|
|
622
|
+
resolve({
|
|
623
|
+
address: undefined,
|
|
624
|
+
lat: undefined,
|
|
625
|
+
lon: undefined,
|
|
626
|
+
error: true,
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
},
|
|
631
|
+
locationDecodeCoordinatesToAddress: (lat: string, lon: string): Promise<ISearchstaxLocation> => {
|
|
632
|
+
return new Promise((resolve) => {
|
|
633
|
+
fetch(
|
|
634
|
+
`https://geocoding-staging.searchstax.co/reverse?location=${lat},${lon}&components=country:US&app_id=${config.appId}`,
|
|
635
|
+
{
|
|
636
|
+
method: "GET",
|
|
637
|
+
headers: {
|
|
638
|
+
Authorization: `Token ${config.relatedSearchesAPIKey}`,
|
|
639
|
+
},
|
|
640
|
+
}
|
|
641
|
+
)
|
|
642
|
+
.then((response) => response.json())
|
|
643
|
+
.then((data) => {
|
|
644
|
+
if (data.status === "OK" && data.results.length > 0) {
|
|
645
|
+
const result = data.results[0];
|
|
646
|
+
resolve({
|
|
647
|
+
address: result.formatted_address,
|
|
648
|
+
lat: lat,
|
|
649
|
+
lon: lon,
|
|
650
|
+
error: false,
|
|
651
|
+
});
|
|
652
|
+
} else {
|
|
653
|
+
resolve({
|
|
654
|
+
address: undefined,
|
|
655
|
+
lat: lat,
|
|
656
|
+
lon: lon,
|
|
657
|
+
error: true,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
})
|
|
661
|
+
.catch(() => {
|
|
662
|
+
resolve({
|
|
663
|
+
address: undefined,
|
|
664
|
+
lat: lat,
|
|
665
|
+
lon: lon,
|
|
666
|
+
error: true,
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
},
|
|
671
|
+
locationSearchEnabled: false,
|
|
672
|
+
locationValuesOverride: {
|
|
673
|
+
locationDistanceEnabled: true,
|
|
674
|
+
filterValues: ["any", "1000"],
|
|
675
|
+
filterUnit: "miles",
|
|
676
|
+
locationFilterDefaultValue: "any"
|
|
677
|
+
},
|
|
678
|
+
}
|
|
359
679
|
function LocationTemplate(
|
|
360
680
|
locationData: null | ISearchstaxLocationRenderData,
|
|
361
681
|
locationChange: (value: string) => void,
|
|
@@ -420,70 +740,145 @@ function LocationTemplate(
|
|
|
420
740
|
|
|
421
741
|
### Result Widget ###
|
|
422
742
|
|
|
423
|
-
|
|
743
|
+
The SearchStax Site Search solution provides a React and Next.js results widget to support your custom search page.
|
|
744
|
+
|
|
745
|
+
The SearchstaxResultsWidget displays the search results.
|
|
746
|
+
|
|
747
|
+
**Usage**
|
|
748
|
+
|
|
424
749
|
```
|
|
425
750
|
<SearchstaxResultWidget
|
|
426
751
|
afterLinkClick={afterLinkClick}
|
|
427
752
|
renderMethod={'pagination'}
|
|
753
|
+
resultsPerPage={10}
|
|
428
754
|
></SearchstaxResultWidget>
|
|
429
755
|
```
|
|
430
756
|
|
|
431
|
-
|
|
757
|
+
**Props**
|
|
758
|
+
|
|
759
|
+
- renderMethod – either “pagination” or “infiniteScroll”.
|
|
760
|
+
- resultsPerPage – number of results on a page.
|
|
761
|
+
- afterLinkClick – Callback function invoked when a result link is clicked. Allows modifying the result object.
|
|
762
|
+
- resultsTemplate - template override for result view
|
|
763
|
+
- noResultTemplate - template override for no results view
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
**Result Template Override**
|
|
767
|
+
|
|
768
|
+
The resultsTemplate prop allows customizing the result UI.
|
|
769
|
+
|
|
770
|
+
It receives no props:
|
|
771
|
+
- custom?: any - custom properties that may be added through aftersearchHook
|
|
772
|
+
- ribbon: string | null
|
|
773
|
+
- paths: string | null;
|
|
774
|
+
- url: string | null;
|
|
775
|
+
- title: string | null;
|
|
776
|
+
- titleTracking: string | null;
|
|
777
|
+
- promoted: boolean | null;
|
|
778
|
+
- thumbnail: string | null;
|
|
779
|
+
- date: string | null;
|
|
780
|
+
- snippet: string | null;
|
|
781
|
+
- description: string | null;
|
|
782
|
+
- uniqueId: string;
|
|
783
|
+
- position: number;
|
|
784
|
+
- distance: number | null;
|
|
785
|
+
- unit: string | null;
|
|
786
|
+
- unmappedFields: {
|
|
787
|
+
key: string;
|
|
788
|
+
value: string | string[] | boolean;
|
|
789
|
+
isImage?: boolean;
|
|
790
|
+
}[] - fields that were not mapped.
|
|
791
|
+
- allFields: { key: string; value: string | string[] | boolean }[] - all fields
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
**No Results Template Override**
|
|
795
|
+
|
|
796
|
+
The noResultTemplate prop allows customizing the result UI.
|
|
797
|
+
|
|
798
|
+
It receives following props:
|
|
799
|
+
|
|
800
|
+
- spellingSuggestion - suggestion of corrected spelling
|
|
801
|
+
- searchExecuted - boolean if search was executed
|
|
802
|
+
- searchTerm - term that was searched
|
|
803
|
+
|
|
804
|
+
**Example of default render method**
|
|
432
805
|
```
|
|
433
|
-
|
|
434
|
-
afterLinkClick={afterLinkClick}
|
|
435
|
-
renderMethod={'infiniteScroll'}
|
|
436
|
-
></SearchstaxResultWidget>
|
|
806
|
+
|
|
437
807
|
```
|
|
438
808
|
|
|
439
|
-
|
|
809
|
+
**Example of infinite scroll and pagination render methods**
|
|
440
810
|
```
|
|
441
|
-
function noResultTemplate(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
811
|
+
function noResultTemplate(
|
|
812
|
+
searchTerm: string,
|
|
813
|
+
metaData: ISearchstaxSearchMetadata | null,
|
|
814
|
+
executeSearch: (searchTerm: string) => void
|
|
815
|
+
): React.ReactElement {
|
|
816
|
+
return (
|
|
817
|
+
<div>
|
|
818
|
+
<div className="searchstax-no-results">
|
|
819
|
+
{" "}
|
|
820
|
+
Showing <strong>no results</strong> for <strong>"{searchTerm}"</strong>
|
|
821
|
+
<br />
|
|
822
|
+
{metaData?.spellingSuggestion && (
|
|
823
|
+
<span>
|
|
824
|
+
Did you mean{" "}
|
|
825
|
+
<a
|
|
826
|
+
href="#"
|
|
827
|
+
aria-label={`Did you mean ${metaData?.spellingSuggestion}`}
|
|
828
|
+
className="searchstax-suggestion-term"
|
|
829
|
+
onClick={(e) => {
|
|
830
|
+
e.preventDefault();
|
|
831
|
+
executeSearch(metaData?.spellingSuggestion);
|
|
832
|
+
}}
|
|
833
|
+
>
|
|
834
|
+
{metaData?.spellingSuggestion}
|
|
835
|
+
</a>
|
|
836
|
+
?
|
|
837
|
+
</span>
|
|
838
|
+
)}
|
|
469
839
|
</div>
|
|
470
|
-
|
|
471
|
-
|
|
840
|
+
<ul>
|
|
841
|
+
<li>
|
|
842
|
+
{" "}
|
|
843
|
+
Try searching for search related terms or topics. We offer a wide
|
|
844
|
+
variety of content to help you get the information you need.{" "}
|
|
845
|
+
</li>
|
|
846
|
+
<li>Lost? Click on the ‘X” in the Search Box to reset your search.</li>
|
|
847
|
+
</ul>
|
|
848
|
+
</div>
|
|
849
|
+
);
|
|
850
|
+
}
|
|
472
851
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
852
|
+
function resultsTemplate(
|
|
853
|
+
searchResults: ISearchstaxParsedResult[],
|
|
854
|
+
resultClicked: (
|
|
855
|
+
results: ISearchstaxParsedResult,
|
|
856
|
+
event: any
|
|
857
|
+
) => void
|
|
858
|
+
): React.ReactElement {
|
|
859
|
+
return (
|
|
860
|
+
<>
|
|
861
|
+
{searchResults && searchResults.length && (
|
|
862
|
+
<div className="searchstax-search-results" aria-live="polite">
|
|
863
|
+
{searchResults !== null &&
|
|
864
|
+
searchResults.map(function (searchResult) {
|
|
865
|
+
return (
|
|
866
|
+
<a
|
|
867
|
+
href={searchResult.url ?? ""}
|
|
868
|
+
onClick={(event) => {
|
|
485
869
|
resultClicked(searchResult, event);
|
|
486
|
-
}}
|
|
870
|
+
}}
|
|
871
|
+
onKeyDown={(e) => {
|
|
872
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
873
|
+
resultClicked(searchResult, e);
|
|
874
|
+
}
|
|
875
|
+
}}
|
|
876
|
+
data-searchstax-unique-result-id={searchResult.uniqueId}
|
|
877
|
+
key={searchResult.uniqueId}
|
|
878
|
+
aria-labelledby={`title-${searchResult.uniqueId}`}
|
|
879
|
+
className="searchstax-result-item-link searchstax-result-item-link-wrapping"
|
|
880
|
+
tabIndex={0}
|
|
881
|
+
>
|
|
487
882
|
<div
|
|
488
883
|
className={`searchstax-search-result ${
|
|
489
884
|
searchResult.thumbnail ? "has-thumbnail" : ""
|
|
@@ -494,36 +889,47 @@ function noResultTemplate(searchTerm: string, metaData: ISearchstaxSearchMetadat
|
|
|
494
889
|
<div className="searchstax-search-result-promoted"></div>
|
|
495
890
|
)}
|
|
496
891
|
|
|
497
|
-
|
|
498
892
|
{searchResult.ribbon && (
|
|
499
|
-
<div
|
|
500
|
-
|
|
501
|
-
|
|
893
|
+
<div
|
|
894
|
+
className="searchstax-search-result-ribbon"
|
|
895
|
+
dangerouslySetInnerHTML={{
|
|
896
|
+
__html: searchResult.ribbon,
|
|
897
|
+
}}
|
|
898
|
+
></div>
|
|
502
899
|
)}
|
|
503
900
|
|
|
504
901
|
{searchResult.thumbnail && (
|
|
505
902
|
<img
|
|
903
|
+
alt=""
|
|
506
904
|
src={searchResult.thumbnail}
|
|
507
905
|
className="searchstax-thumbnail"
|
|
508
906
|
/>
|
|
509
907
|
)}
|
|
510
908
|
|
|
511
909
|
<div className="searchstax-search-result-title-container">
|
|
512
|
-
<span
|
|
513
|
-
|
|
514
|
-
|
|
910
|
+
<span
|
|
911
|
+
className="searchstax-search-result-title"
|
|
912
|
+
id={`title-${searchResult.uniqueId}`}
|
|
913
|
+
dangerouslySetInnerHTML={{
|
|
914
|
+
__html: searchResult.title ?? "",
|
|
915
|
+
}}
|
|
916
|
+
></span>
|
|
515
917
|
</div>
|
|
516
918
|
|
|
517
919
|
{searchResult.paths && (
|
|
518
|
-
<p
|
|
519
|
-
|
|
520
|
-
|
|
920
|
+
<p
|
|
921
|
+
className="searchstax-search-result-common"
|
|
922
|
+
dangerouslySetInnerHTML={{ __html: searchResult.paths }}
|
|
923
|
+
></p>
|
|
521
924
|
)}
|
|
522
925
|
|
|
523
926
|
{searchResult.description && (
|
|
524
|
-
<p
|
|
525
|
-
|
|
526
|
-
|
|
927
|
+
<p
|
|
928
|
+
className="searchstax-search-result-description searchstax-search-result-common"
|
|
929
|
+
dangerouslySetInnerHTML={{
|
|
930
|
+
__html: searchResult.description,
|
|
931
|
+
}}
|
|
932
|
+
></p>
|
|
527
933
|
)}
|
|
528
934
|
|
|
529
935
|
{searchResult.unmappedFields.map(function (
|
|
@@ -535,6 +941,7 @@ function noResultTemplate(searchTerm: string, metaData: ISearchstaxSearchMetadat
|
|
|
535
941
|
typeof unmappedField.value === "string" && (
|
|
536
942
|
<div className="searchstax-search-result-image-container">
|
|
537
943
|
<img
|
|
944
|
+
alt=""
|
|
538
945
|
src={unmappedField.value}
|
|
539
946
|
className="searchstax-result-image"
|
|
540
947
|
/>
|
|
@@ -543,61 +950,105 @@ function noResultTemplate(searchTerm: string, metaData: ISearchstaxSearchMetadat
|
|
|
543
950
|
|
|
544
951
|
{!unmappedField.isImage && (
|
|
545
952
|
<div>
|
|
546
|
-
<p
|
|
547
|
-
|
|
548
|
-
|
|
953
|
+
<p
|
|
954
|
+
className="searchstax-search-result-common"
|
|
955
|
+
dangerouslySetInnerHTML={{
|
|
956
|
+
__html: unmappedField.value,
|
|
957
|
+
}}
|
|
958
|
+
></p>
|
|
549
959
|
</div>
|
|
550
960
|
)}
|
|
551
961
|
</div>
|
|
552
962
|
);
|
|
553
963
|
})}
|
|
554
964
|
</div>
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
965
|
+
</a>
|
|
966
|
+
);
|
|
967
|
+
})}
|
|
968
|
+
</div>
|
|
969
|
+
)}
|
|
970
|
+
</>
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
<SearchstaxResultWidget
|
|
974
|
+
afterLinkClick={afterLinkClick}
|
|
975
|
+
noResultTemplate={noResultTemplate}
|
|
976
|
+
resultsTemplate={resultsTemplate}
|
|
977
|
+
renderMethod={'pagination'}
|
|
978
|
+
></SearchstaxResultWidget>
|
|
979
|
+
// Infinite scroll
|
|
564
980
|
|
|
565
981
|
<SearchstaxResultWidget
|
|
566
982
|
afterLinkClick={afterLinkClick}
|
|
567
983
|
noResultTemplate={noResultTemplate}
|
|
568
984
|
resultsTemplate={resultsTemplate}
|
|
985
|
+
renderMethod={'infiniteScroll'}
|
|
569
986
|
></SearchstaxResultWidget>
|
|
987
|
+
|
|
570
988
|
```
|
|
571
989
|
|
|
572
990
|
### Pagination Widget ###
|
|
573
991
|
|
|
992
|
+
The SearchStax Site Search solution offers a pagination widget for React and Next.js search pages.
|
|
993
|
+
|
|
994
|
+
The SearchstaxPaginationWidget displays pagination controls for search results.
|
|
995
|
+
|
|
996
|
+
**Usage**
|
|
574
997
|
|
|
575
|
-
example of pagination widget initialization with minimum options
|
|
576
998
|
```
|
|
577
999
|
<SearchstaxPaginationWidget></SearchstaxPaginationWidget>
|
|
578
1000
|
```
|
|
579
1001
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
1002
|
+
**Props**
|
|
1003
|
+
- paginationTemplate - template override for pagination widget
|
|
1004
|
+
|
|
1005
|
+
**Main Template Override**
|
|
1006
|
+
|
|
1007
|
+
Main template for the pagination controls.
|
|
1008
|
+
|
|
1009
|
+
It receives following props:
|
|
1010
|
+
|
|
1011
|
+
- nextPageLink - link of next page;
|
|
1012
|
+
- previousPageLink - link of previous page;
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
**Infinite Scroll Template Override**
|
|
1016
|
+
|
|
1017
|
+
Main template for the pagination controls in infinite scroll mode.
|
|
1018
|
+
|
|
1019
|
+
It receives following props:
|
|
1020
|
+
- isLastPage - boolean, true if its last page
|
|
1021
|
+
- results - results.length can be used if there are results
|
|
1022
|
+
|
|
1023
|
+
**Example**
|
|
1024
|
+
|
|
1025
|
+
```
|
|
1026
|
+
function paginationTemplate(
|
|
1027
|
+
paginationData: IPaginationData | null,
|
|
584
1028
|
nextPage: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void,
|
|
585
|
-
previousPage: (
|
|
1029
|
+
previousPage: (
|
|
1030
|
+
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
|
|
1031
|
+
) => void
|
|
586
1032
|
) {
|
|
587
1033
|
return (
|
|
588
|
-
|
|
1034
|
+
<>
|
|
589
1035
|
{paginationData && paginationData?.totalResults !== 0 && (
|
|
590
1036
|
<div className="searchstax-pagination-container">
|
|
591
1037
|
<div className="searchstax-pagination-content">
|
|
592
1038
|
<a
|
|
593
|
-
className=
|
|
1039
|
+
className={`searchstax-pagination-previous ${paginationData.isFirstPage ? "disabled" : ""}`}
|
|
1040
|
+
tabIndex={0}
|
|
594
1041
|
style={
|
|
595
1042
|
paginationData?.isFirstPage ? { pointerEvents: "none" } : {}
|
|
596
1043
|
}
|
|
597
1044
|
onClick={(e) => {
|
|
598
1045
|
previousPage(e);
|
|
599
1046
|
}}
|
|
600
|
-
|
|
1047
|
+
onKeyDown={(e) => {
|
|
1048
|
+
if(e.key === 'Enter' || e.key === ' ') {
|
|
1049
|
+
previousPage(e as any);
|
|
1050
|
+
}
|
|
1051
|
+
}}
|
|
601
1052
|
id="searchstax-pagination-previous"
|
|
602
1053
|
>
|
|
603
1054
|
{" "}
|
|
@@ -610,14 +1061,19 @@ function paginationTemplate(
|
|
|
610
1061
|
{paginationData?.totalResults}
|
|
611
1062
|
</div>
|
|
612
1063
|
<a
|
|
613
|
-
className=
|
|
1064
|
+
className={`searchstax-pagination-next ${paginationData.isLastPage ? "disabled" : ""}`}
|
|
1065
|
+
tabIndex={0}
|
|
614
1066
|
style={
|
|
615
1067
|
paginationData?.isLastPage ? { pointerEvents: "none" } : {}
|
|
616
1068
|
}
|
|
617
1069
|
onClick={(e) => {
|
|
618
1070
|
nextPage(e);
|
|
619
1071
|
}}
|
|
620
|
-
|
|
1072
|
+
onKeyDown={(e) => {
|
|
1073
|
+
if(e.key === 'Enter' || e.key === ' ') {
|
|
1074
|
+
nextPage(e as any);
|
|
1075
|
+
}
|
|
1076
|
+
}}
|
|
621
1077
|
id="searchstax-pagination-next"
|
|
622
1078
|
>
|
|
623
1079
|
Next >
|
|
@@ -629,12 +1085,7 @@ function paginationTemplate(
|
|
|
629
1085
|
);
|
|
630
1086
|
}
|
|
631
1087
|
|
|
632
|
-
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
example of pagination widget for infinite scroll
|
|
636
|
-
```
|
|
637
|
-
function infiniteScrollTemplate(
|
|
1088
|
+
function infiniteScrollTemplate(
|
|
638
1089
|
paginationData: IPaginationData | null,
|
|
639
1090
|
nextPage: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void
|
|
640
1091
|
) {
|
|
@@ -647,6 +1098,7 @@ example of pagination widget for infinite scroll
|
|
|
647
1098
|
<div className="searchstax-pagination-container">
|
|
648
1099
|
<a
|
|
649
1100
|
className="searchstax-pagination-load-more"
|
|
1101
|
+
tabIndex={0}
|
|
650
1102
|
onClick={(e) => {
|
|
651
1103
|
nextPage(e);
|
|
652
1104
|
}}
|
|
@@ -663,13 +1115,21 @@ example of pagination widget for infinite scroll
|
|
|
663
1115
|
</>
|
|
664
1116
|
);
|
|
665
1117
|
}
|
|
666
|
-
|
|
667
|
-
|
|
1118
|
+
<SearchstaxPaginationWidget paginationTemplate={paginationTemplate}></SearchstaxPaginationWidget>
|
|
1119
|
+
// example of pagination widget for infinite scroll
|
|
1120
|
+
<SearchstaxPaginationWidget infiniteScrollTemplate={infiniteScrollTemplate}></SearchstaxPaginationWidget>
|
|
668
1121
|
```
|
|
669
1122
|
|
|
670
1123
|
### Facets Widget ###
|
|
671
1124
|
|
|
672
|
-
|
|
1125
|
+
The SearchStax Site Search solution offers a SearchstaxFacetsWidget component for React and Next.js. This widget displays the search facets.
|
|
1126
|
+
|
|
1127
|
+
**Facet Selection and Order**
|
|
1128
|
+
|
|
1129
|
+
Facet lists are configured and ordered on the Site Search [Faceting Tab](https://www.searchstax.com/docs/searchstudio/faceting-tab/).
|
|
1130
|
+
|
|
1131
|
+
**Usage**
|
|
1132
|
+
|
|
673
1133
|
```
|
|
674
1134
|
<SearchstaxFacetsWidget
|
|
675
1135
|
facetingType="and"
|
|
@@ -679,362 +1139,396 @@ example of facets widget initialization with minimum options
|
|
|
679
1139
|
></SearchstaxFacetsWidget>
|
|
680
1140
|
```
|
|
681
1141
|
|
|
1142
|
+
**Props**
|
|
1143
|
+
|
|
1144
|
+
- facetingType: "and" | "or" | "showUnavailable" | "tabs"; // type that determines how facets will behave
|
|
1145
|
+
- specificFacets?: string[]; // optional array of facet names that if provided will only render those facets
|
|
1146
|
+
- itemsPerPageDesktop: number; // default expanded facets for desktop
|
|
1147
|
+
- itemsPerPageMobile: number; // default expanded facets for mobile
|
|
1148
|
+
- facetsTemplateDesktop - template override for desktop view
|
|
1149
|
+
- facetsTemplateMobile - template override for mobile view
|
|
682
1150
|
|
|
683
|
-
|
|
1151
|
+
|
|
1152
|
+
**Main Template Desktop Override**
|
|
1153
|
+
|
|
1154
|
+
Main wrapper template for desktop facets display.
|
|
1155
|
+
|
|
1156
|
+
It receives following props from [IFacetsTemplateData](https://www.searchstax.com/docs/searchstudio/interfaces/#h-facets-interfaces)
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
**Main Template Mobile Override**
|
|
1160
|
+
|
|
1161
|
+
Main wrapper template for mobile facets display.
|
|
1162
|
+
|
|
1163
|
+
It receives following props from [IFacetsTemplateData](https://www.searchstax.com/docs/searchstudio/interfaces/#h-facets-interfaces)
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
**Example**
|
|
684
1167
|
```
|
|
685
|
-
function facetsTemplateDesktop(
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1168
|
+
function facetsTemplateDesktop(
|
|
1169
|
+
facetsTemplateDataDesktop: IFacetsTemplateData | null,
|
|
1170
|
+
facetContainers: {
|
|
1171
|
+
[key: string]: React.LegacyRef<HTMLDivElement> | undefined;
|
|
1172
|
+
},
|
|
1173
|
+
isNotDeactivated: (name: string) => boolean,
|
|
1174
|
+
toggleFacetGroup: (name: string) => void,
|
|
1175
|
+
selectFacet: (
|
|
1176
|
+
index: string,
|
|
1177
|
+
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
|
1178
|
+
data: IFacetValueData,
|
|
1179
|
+
isInput: boolean
|
|
1180
|
+
) => void,
|
|
1181
|
+
isChecked: (facetValue: IFacetValueData) => boolean | undefined,
|
|
1182
|
+
showMoreLessDesktop: (
|
|
1183
|
+
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
|
1184
|
+
data: IFacetData
|
|
1185
|
+
) => void
|
|
1186
|
+
) {
|
|
1187
|
+
return (
|
|
1188
|
+
<div className="searchstax-facets-container-desktop">
|
|
1189
|
+
{facetsTemplateDataDesktop?.facets.map((facet) => {
|
|
1190
|
+
return (
|
|
1191
|
+
<div
|
|
1192
|
+
className={`searchstax-facet-container ${
|
|
1193
|
+
isNotDeactivated(facet.name) ? "active" : ""
|
|
1194
|
+
}`}
|
|
1195
|
+
key={facet.name + "desktop"}
|
|
1196
|
+
>
|
|
1197
|
+
<div>
|
|
1198
|
+
<div
|
|
1199
|
+
className="searchstax-facet-title-container"
|
|
1200
|
+
onClick={() => {
|
|
1201
|
+
toggleFacetGroup(facet.name);
|
|
1202
|
+
}}
|
|
1203
|
+
>
|
|
1204
|
+
<div className="searchstax-facet-title">
|
|
1205
|
+
{" "}
|
|
1206
|
+
{facet.label}
|
|
723
1207
|
</div>
|
|
724
|
-
<div className="searchstax-facet-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1208
|
+
<div className="searchstax-facet-title-arrow active"></div>
|
|
1209
|
+
</div>
|
|
1210
|
+
<div className="searchstax-facet-values-container" aria-live="polite">
|
|
1211
|
+
{facet.values.map(
|
|
1212
|
+
//@ts-ignore
|
|
1213
|
+
(facetValue: IFacetValueData, key) => {
|
|
1214
|
+
facetContainers[key + facet.name] = createRef();
|
|
1215
|
+
return (
|
|
1216
|
+
<div
|
|
1217
|
+
key={facetValue.value + facetValue.parentName}
|
|
1218
|
+
className={`searchstax-facet-value-container ${
|
|
1219
|
+
facetValue.disabled
|
|
1220
|
+
? "searchstax-facet-value-disabled"
|
|
1221
|
+
: ""
|
|
1222
|
+
}`}
|
|
1223
|
+
ref={facetContainers[key + facet.name]}
|
|
1224
|
+
>
|
|
730
1225
|
<div
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}
|
|
737
|
-
ref={facetContainers[key + facet.name]}
|
|
1226
|
+
className={
|
|
1227
|
+
"searchstax-facet-input" +
|
|
1228
|
+
" desktop-" +
|
|
1229
|
+
key +
|
|
1230
|
+
facet.name
|
|
1231
|
+
}
|
|
738
1232
|
>
|
|
739
|
-
<
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
type="checkbox"
|
|
747
|
-
className="searchstax-facet-input-checkbox"
|
|
748
|
-
checked={isChecked(facetValue)}
|
|
749
|
-
readOnly={true}
|
|
750
|
-
aria-label={facetValue.value + ' ' + facetValue.count}
|
|
751
|
-
disabled={facetValue.disabled}
|
|
752
|
-
onClick={(e) => {
|
|
753
|
-
selectFacet(
|
|
754
|
-
key + facet.name,
|
|
755
|
-
e,
|
|
756
|
-
facetValue,
|
|
757
|
-
true
|
|
758
|
-
);
|
|
759
|
-
}}
|
|
760
|
-
/>
|
|
761
|
-
</div>
|
|
762
|
-
<div
|
|
763
|
-
className="searchstax-facet-value-label"
|
|
764
|
-
onClick={(e) => {
|
|
765
|
-
selectFacet(
|
|
766
|
-
key + facet.name,
|
|
767
|
-
e,
|
|
768
|
-
facetValue,
|
|
769
|
-
false
|
|
770
|
-
);
|
|
771
|
-
}}
|
|
772
|
-
>
|
|
773
|
-
{facetValue.value}
|
|
774
|
-
</div>
|
|
775
|
-
<div
|
|
776
|
-
className="searchstax-facet-value-count"
|
|
1233
|
+
<input
|
|
1234
|
+
type="checkbox"
|
|
1235
|
+
className="searchstax-facet-input-checkbox"
|
|
1236
|
+
checked={isChecked(facetValue)}
|
|
1237
|
+
readOnly={true}
|
|
1238
|
+
aria-label={facetValue.value + ' ' + facetValue.count}
|
|
1239
|
+
disabled={facetValue.disabled}
|
|
777
1240
|
onClick={(e) => {
|
|
778
1241
|
selectFacet(
|
|
779
1242
|
key + facet.name,
|
|
780
1243
|
e,
|
|
781
1244
|
facetValue,
|
|
782
|
-
|
|
1245
|
+
true
|
|
783
1246
|
);
|
|
784
1247
|
}}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1248
|
+
/>
|
|
1249
|
+
</div>
|
|
1250
|
+
<div
|
|
1251
|
+
className="searchstax-facet-value-label"
|
|
1252
|
+
onClick={(e) => {
|
|
1253
|
+
selectFacet(key + facet.name, e, facetValue, false);
|
|
1254
|
+
}}
|
|
1255
|
+
>
|
|
1256
|
+
{facetValue.value}
|
|
1257
|
+
</div>
|
|
1258
|
+
<div
|
|
1259
|
+
className="searchstax-facet-value-count"
|
|
1260
|
+
onClick={(e) => {
|
|
1261
|
+
selectFacet(key + facet.name, e, facetValue, false);
|
|
1262
|
+
}}
|
|
1263
|
+
>
|
|
1264
|
+
({facetValue.count})
|
|
788
1265
|
</div>
|
|
789
|
-
);
|
|
790
|
-
}
|
|
791
|
-
)}
|
|
792
|
-
|
|
793
|
-
{facet.hasMoreFacets && (
|
|
794
|
-
<div className="searchstax-facet-show-more-container">
|
|
795
|
-
<div
|
|
796
|
-
className="searchstax-facet-show-more-container"
|
|
797
|
-
onClick={(e) => {
|
|
798
|
-
showMoreLessDesktop(e, facet);
|
|
799
|
-
}}
|
|
800
|
-
>
|
|
801
|
-
{facet.showingAllFacets && (
|
|
802
|
-
<div className="searchstax-facet-show-less-button searchstax-facet-show-button">
|
|
803
|
-
less
|
|
804
|
-
</div>
|
|
805
|
-
)}
|
|
806
|
-
{!facet.showingAllFacets && (
|
|
807
|
-
<div className="searchstax-facet-show-more-button searchstax-facet-show-button">
|
|
808
|
-
more
|
|
809
|
-
</div>
|
|
810
|
-
)}
|
|
811
1266
|
</div>
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
)}
|
|
1270
|
+
|
|
1271
|
+
{facet.hasMoreFacets && (
|
|
1272
|
+
<div className="searchstax-facet-show-more-container">
|
|
1273
|
+
<div
|
|
1274
|
+
className="searchstax-facet-show-more-container"
|
|
1275
|
+
onClick={(e) => {
|
|
1276
|
+
showMoreLessDesktop(e, facet);
|
|
1277
|
+
}}
|
|
1278
|
+
onKeyDown={(e) => {
|
|
1279
|
+
if(e.key === 'Enter' || e.key === ' ') {
|
|
1280
|
+
showMoreLessDesktop(e as any, facet);
|
|
1281
|
+
}
|
|
1282
|
+
}}
|
|
1283
|
+
tabIndex={0}
|
|
1284
|
+
>
|
|
1285
|
+
{facet.showingAllFacets && (
|
|
1286
|
+
<div className="searchstax-facet-show-less-button searchstax-facet-show-button">
|
|
1287
|
+
less
|
|
1288
|
+
</div>
|
|
1289
|
+
)}
|
|
1290
|
+
{!facet.showingAllFacets && (
|
|
1291
|
+
<div className="searchstax-facet-show-more-button searchstax-facet-show-button">
|
|
1292
|
+
more
|
|
1293
|
+
</div>
|
|
1294
|
+
)}
|
|
812
1295
|
</div>
|
|
813
|
-
|
|
814
|
-
|
|
1296
|
+
</div>
|
|
1297
|
+
)}
|
|
815
1298
|
</div>
|
|
816
1299
|
</div>
|
|
817
|
-
);
|
|
818
|
-
})}
|
|
819
|
-
</div>
|
|
820
|
-
);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
function facetsTemplateMobile(
|
|
824
|
-
facetsTemplateDataMobile: IFacetsTemplateData | null,
|
|
825
|
-
selectedFacetsCheckboxes: IFacetValue[],
|
|
826
|
-
facetContainers: {
|
|
827
|
-
[key: string]: React.LegacyRef<HTMLDivElement> | undefined;
|
|
828
|
-
},
|
|
829
|
-
isNotDeactivated: (name: string) => boolean,
|
|
830
|
-
toggleFacetGroup: (name: string) => void,
|
|
831
|
-
selectFacet: (
|
|
832
|
-
index: string,
|
|
833
|
-
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
|
834
|
-
data: IFacetValueData,
|
|
835
|
-
isInput: boolean
|
|
836
|
-
) => void,
|
|
837
|
-
isChecked: (facetValue: IFacetValueData) => boolean | undefined,
|
|
838
|
-
unselectFacet: (facet: IFacetValue) => void,
|
|
839
|
-
showMoreLessMobile: (
|
|
840
|
-
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
|
841
|
-
data: IFacetData
|
|
842
|
-
) => void,
|
|
843
|
-
openOverlay: () => void,
|
|
844
|
-
closeOverlay: () => void,
|
|
845
|
-
unselectAll: () => void
|
|
846
|
-
) {
|
|
847
|
-
return facetsTemplateDataMobile ? (
|
|
848
|
-
<div className="searchstax-facets-container-mobile">
|
|
849
|
-
<div className="searchstax-facets-pills-container">
|
|
850
|
-
<div
|
|
851
|
-
className="searchstax-facets-pill searchstax-facets-pill-filter-by"
|
|
852
|
-
onClick={() => {
|
|
853
|
-
openOverlay();
|
|
854
|
-
}}
|
|
855
|
-
>
|
|
856
|
-
<div className="searchstax-facets-pill-label">Filter By</div>
|
|
857
1300
|
</div>
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1301
|
+
);
|
|
1302
|
+
})}
|
|
1303
|
+
</div>
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function facetsTemplateMobile(
|
|
1308
|
+
facetsTemplateDataMobile: IFacetsTemplateData | null,
|
|
1309
|
+
selectedFacetsCheckboxes: IFacetValue[],
|
|
1310
|
+
facetContainers: {
|
|
1311
|
+
[key: string]: React.LegacyRef<HTMLDivElement> | undefined;
|
|
1312
|
+
},
|
|
1313
|
+
isNotDeactivated: (name: string) => boolean,
|
|
1314
|
+
toggleFacetGroup: (name: string) => void,
|
|
1315
|
+
selectFacet: (
|
|
1316
|
+
index: string,
|
|
1317
|
+
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
|
1318
|
+
data: IFacetValueData,
|
|
1319
|
+
isInput: boolean,
|
|
1320
|
+
isMobile?: boolean
|
|
1321
|
+
) => void,
|
|
1322
|
+
isChecked: (facetValue: IFacetValueData) => boolean | undefined,
|
|
1323
|
+
unselectFacet: (facet: IFacetValue) => void,
|
|
1324
|
+
showMoreLessMobile: (
|
|
1325
|
+
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
|
1326
|
+
data: IFacetData
|
|
1327
|
+
) => void,
|
|
1328
|
+
openOverlay: () => void,
|
|
1329
|
+
closeOverlay: () => void,
|
|
1330
|
+
unselectAll: () => void
|
|
1331
|
+
) {
|
|
1332
|
+
return facetsTemplateDataMobile ? (
|
|
1333
|
+
<div className="searchstax-facets-container-mobile">
|
|
1334
|
+
<div className="searchstax-facets-pills-container">
|
|
1335
|
+
<div
|
|
1336
|
+
className="searchstax-facets-pill searchstax-facets-pill-filter-by"
|
|
1337
|
+
onClick={() => {
|
|
1338
|
+
openOverlay();
|
|
1339
|
+
}}
|
|
1340
|
+
>
|
|
1341
|
+
<div className="searchstax-facets-pill-label">Filter By</div>
|
|
1342
|
+
</div>
|
|
1343
|
+
<div className="searchstax-facets-pills-selected">
|
|
1344
|
+
{selectedFacetsCheckboxes.map((facet) => {
|
|
1345
|
+
return (
|
|
876
1346
|
<div
|
|
877
|
-
className="searchstax-facets-pill searchstax-
|
|
1347
|
+
className="searchstax-facets-pill searchstax-facets-pill-facets"
|
|
1348
|
+
key={facet.value}
|
|
878
1349
|
onClick={() => {
|
|
879
|
-
|
|
1350
|
+
unselectFacet(facet);
|
|
880
1351
|
}}
|
|
881
1352
|
>
|
|
882
1353
|
<div className="searchstax-facets-pill-label">
|
|
883
|
-
|
|
1354
|
+
{facet.value} ({facet.count})
|
|
884
1355
|
</div>
|
|
1356
|
+
<div className="searchstax-facets-pill-icon-close"></div>
|
|
885
1357
|
</div>
|
|
886
|
-
)
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
<div className="searchstax-facets-
|
|
896
|
-
Filter By
|
|
897
|
-
</div>
|
|
898
|
-
<div
|
|
899
|
-
className="searchstax-search-close"
|
|
900
|
-
onClick={() => {
|
|
901
|
-
closeOverlay();
|
|
902
|
-
}}
|
|
903
|
-
></div>
|
|
1358
|
+
);
|
|
1359
|
+
})}
|
|
1360
|
+
{selectedFacetsCheckboxes.length !== 0 && (
|
|
1361
|
+
<div
|
|
1362
|
+
className="searchstax-facets-pill searchstax-clear-filters searchstax-facets-pill-clear-all"
|
|
1363
|
+
onClick={() => {
|
|
1364
|
+
unselectAll();
|
|
1365
|
+
}}
|
|
1366
|
+
>
|
|
1367
|
+
<div className="searchstax-facets-pill-label">Clear Filters</div>
|
|
904
1368
|
</div>
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1369
|
+
)}
|
|
1370
|
+
</div>
|
|
1371
|
+
<div
|
|
1372
|
+
className={`searchstax-facets-mobile-overlay ${
|
|
1373
|
+
//@ts-ignore
|
|
1374
|
+
facetsTemplateDataMobile.overlayOpened ? "searchstax-show" : ""
|
|
1375
|
+
}`}
|
|
1376
|
+
>
|
|
1377
|
+
<div className="searchstax-facets-mobile-overlay-header">
|
|
1378
|
+
<div className="searchstax-facets-mobile-overlay-header-title">
|
|
1379
|
+
Filter By
|
|
1380
|
+
</div>
|
|
1381
|
+
<div
|
|
1382
|
+
className="searchstax-search-close"
|
|
1383
|
+
onClick={() => {
|
|
1384
|
+
closeOverlay();
|
|
1385
|
+
}}
|
|
1386
|
+
></div>
|
|
1387
|
+
</div>
|
|
1388
|
+
<div className="searchstax-facets-container-mobile">
|
|
1389
|
+
{facetsTemplateDataMobile?.facets.map((facet) => {
|
|
1390
|
+
return (
|
|
1391
|
+
<div
|
|
1392
|
+
key={facet.name + "mobile"}
|
|
1393
|
+
className={`searchstax-facet-container ${
|
|
1394
|
+
isNotDeactivated(facet.name) ? `active` : ``
|
|
1395
|
+
}`}
|
|
1396
|
+
>
|
|
1397
|
+
<div>
|
|
1398
|
+
<div
|
|
1399
|
+
className="searchstax-facet-title-container"
|
|
1400
|
+
onClick={() => {
|
|
1401
|
+
toggleFacetGroup(facet.name);
|
|
1402
|
+
}}
|
|
1403
|
+
>
|
|
1404
|
+
<div className="searchstax-facet-title">
|
|
1405
|
+
{" "}
|
|
1406
|
+
{facet.label}{" "}
|
|
926
1407
|
</div>
|
|
927
|
-
<div className="searchstax-facet-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1408
|
+
<div className="searchstax-facet-title-arrow active"></div>
|
|
1409
|
+
</div>
|
|
1410
|
+
<div className="searchstax-facet-values-container" aria-live="polite">
|
|
1411
|
+
{facet.values.map(
|
|
1412
|
+
//@ts-ignore
|
|
1413
|
+
(facetValue: IFacetValueData, key) => {
|
|
1414
|
+
facetContainers[key + facet.name] = createRef();
|
|
1415
|
+
|
|
1416
|
+
return (
|
|
1417
|
+
<div
|
|
1418
|
+
key={facetValue.value + facetValue.parentName}
|
|
1419
|
+
className={`searchstax-facet-value-container ${
|
|
1420
|
+
facetValue.disabled
|
|
1421
|
+
? `searchstax-facet-value-disabled`
|
|
1422
|
+
: ``
|
|
1423
|
+
}`}
|
|
1424
|
+
ref={facetContainers[key + facet.name]}
|
|
1425
|
+
>
|
|
934
1426
|
<div
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
}
|
|
941
|
-
ref={facetContainers[key + facet.name]}
|
|
1427
|
+
className={
|
|
1428
|
+
"searchstax-facet-input" +
|
|
1429
|
+
" mobile-" +
|
|
1430
|
+
key +
|
|
1431
|
+
facet.name
|
|
1432
|
+
}
|
|
942
1433
|
>
|
|
943
|
-
<
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
type="checkbox"
|
|
951
|
-
className="searchstax-facet-input-checkbox"
|
|
952
|
-
checked={isChecked(facetValue)}
|
|
953
|
-
readOnly={true}
|
|
954
|
-
aria-label={facetValue.value + ' ' + facetValue.count}
|
|
955
|
-
disabled={facetValue.disabled}
|
|
956
|
-
onClick={(e) => {
|
|
957
|
-
selectFacet(
|
|
958
|
-
key + facet.name,
|
|
959
|
-
e,
|
|
960
|
-
facetValue,
|
|
961
|
-
true
|
|
962
|
-
);
|
|
963
|
-
}}
|
|
964
|
-
/>
|
|
965
|
-
</div>
|
|
966
|
-
<div
|
|
967
|
-
className="searchstax-facet-value-label"
|
|
1434
|
+
<input
|
|
1435
|
+
type="checkbox"
|
|
1436
|
+
className="searchstax-facet-input-checkbox"
|
|
1437
|
+
checked={isChecked(facetValue)}
|
|
1438
|
+
readOnly={true}
|
|
1439
|
+
aria-label={facetValue.value + ' ' + facetValue.count}
|
|
1440
|
+
disabled={facetValue.disabled}
|
|
968
1441
|
onClick={(e) => {
|
|
969
1442
|
selectFacet(
|
|
970
1443
|
key + facet.name,
|
|
971
1444
|
e,
|
|
972
1445
|
facetValue,
|
|
973
|
-
|
|
1446
|
+
true,
|
|
1447
|
+
true
|
|
974
1448
|
);
|
|
975
1449
|
}}
|
|
976
|
-
|
|
977
|
-
{facetValue.value}
|
|
978
|
-
</div>
|
|
979
|
-
<div
|
|
980
|
-
className="searchstax-facet-value-count"
|
|
981
|
-
onClick={(e) => {
|
|
982
|
-
selectFacet(
|
|
983
|
-
key + facet.name,
|
|
984
|
-
e,
|
|
985
|
-
facetValue,
|
|
986
|
-
false
|
|
987
|
-
);
|
|
988
|
-
}}
|
|
989
|
-
>
|
|
990
|
-
({facetValue.count})
|
|
991
|
-
</div>
|
|
1450
|
+
/>
|
|
992
1451
|
</div>
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1452
|
+
<div
|
|
1453
|
+
className="searchstax-facet-value-label"
|
|
1454
|
+
onClick={(e) => {
|
|
1455
|
+
selectFacet(
|
|
1456
|
+
key + facet.name,
|
|
1457
|
+
e,
|
|
1458
|
+
facetValue,
|
|
1459
|
+
false
|
|
1460
|
+
);
|
|
1461
|
+
}}
|
|
1462
|
+
>
|
|
1463
|
+
{facetValue.value}
|
|
1464
|
+
</div>
|
|
1465
|
+
<div
|
|
1466
|
+
className="searchstax-facet-value-count"
|
|
1467
|
+
onClick={(e) => {
|
|
1468
|
+
selectFacet(
|
|
1469
|
+
key + facet.name,
|
|
1470
|
+
e,
|
|
1471
|
+
facetValue,
|
|
1472
|
+
false
|
|
1473
|
+
);
|
|
1474
|
+
}}
|
|
1475
|
+
>
|
|
1476
|
+
({facetValue.count})
|
|
1477
|
+
</div>
|
|
1478
|
+
</div>
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
)}
|
|
996
1482
|
|
|
1483
|
+
<div
|
|
1484
|
+
className="searchstax-facet-show-more-container"
|
|
1485
|
+
v-if="facet.hasMoreFacets"
|
|
1486
|
+
>
|
|
997
1487
|
<div
|
|
998
1488
|
className="searchstax-facet-show-more-container"
|
|
999
|
-
|
|
1489
|
+
onClick={(e) => {
|
|
1490
|
+
showMoreLessMobile(e, facet);
|
|
1491
|
+
}}
|
|
1492
|
+
onKeyDown={(e) => {
|
|
1493
|
+
if(e.key === 'Enter' || e.key === ' ') {
|
|
1494
|
+
showMoreLessMobile(e as any, facet);
|
|
1495
|
+
}
|
|
1496
|
+
}}
|
|
1497
|
+
tabIndex={0}
|
|
1000
1498
|
>
|
|
1001
|
-
|
|
1002
|
-
className="searchstax-facet-show-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
)}
|
|
1012
|
-
{!facet.showingAllFacets && (
|
|
1013
|
-
<div className="searchstax-facet-show-more-button searchstax-facet-show-button">
|
|
1014
|
-
more
|
|
1015
|
-
</div>
|
|
1016
|
-
)}
|
|
1017
|
-
</div>
|
|
1499
|
+
{facet.showingAllFacets && (
|
|
1500
|
+
<div className="searchstax-facet-show-less-button searchstax-facet-show-button">
|
|
1501
|
+
less
|
|
1502
|
+
</div>
|
|
1503
|
+
)}
|
|
1504
|
+
{!facet.showingAllFacets && (
|
|
1505
|
+
<div className="searchstax-facet-show-more-button searchstax-facet-show-button">
|
|
1506
|
+
more
|
|
1507
|
+
</div>
|
|
1508
|
+
)}
|
|
1018
1509
|
</div>
|
|
1019
1510
|
</div>
|
|
1020
1511
|
</div>
|
|
1021
1512
|
</div>
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
<button
|
|
1026
|
-
className="searchstax-facets-mobile-overlay-done"
|
|
1027
|
-
onClick={() => {
|
|
1028
|
-
closeOverlay();
|
|
1029
|
-
}}
|
|
1030
|
-
>
|
|
1031
|
-
Done
|
|
1032
|
-
</button>
|
|
1513
|
+
</div>
|
|
1514
|
+
);
|
|
1515
|
+
})}
|
|
1033
1516
|
</div>
|
|
1517
|
+
<button
|
|
1518
|
+
className="searchstax-facets-mobile-overlay-done"
|
|
1519
|
+
onClick={() => {
|
|
1520
|
+
closeOverlay();
|
|
1521
|
+
}}
|
|
1522
|
+
>
|
|
1523
|
+
Done
|
|
1524
|
+
</button>
|
|
1034
1525
|
</div>
|
|
1035
1526
|
</div>
|
|
1036
|
-
|
|
1037
|
-
|
|
1527
|
+
</div>
|
|
1528
|
+
) : (
|
|
1529
|
+
<></>
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1038
1532
|
<SearchstaxFacetsWidget
|
|
1039
1533
|
facetingType="and"
|
|
1040
1534
|
itemsPerPageDesktop={2}
|
|
@@ -1047,102 +1541,161 @@ function facetsTemplateDesktop(
|
|
|
1047
1541
|
|
|
1048
1542
|
### SearchFeedback Widget ###
|
|
1049
1543
|
|
|
1050
|
-
|
|
1544
|
+
The SearchStax Site Search solution provides a search feedback widget to support your React and Next.js search pages.
|
|
1545
|
+
|
|
1546
|
+
The SearchstaxFeedbackWidget displays search feedback and stats.
|
|
1547
|
+
|
|
1548
|
+
**Usage**
|
|
1549
|
+
|
|
1051
1550
|
```
|
|
1052
1551
|
<SearchstaxOverviewWidget></SearchstaxOverviewWidget>
|
|
1053
1552
|
```
|
|
1054
1553
|
|
|
1554
|
+
**Props**
|
|
1555
|
+
|
|
1556
|
+
- searchOverviewTemplate - template override for search overview
|
|
1557
|
+
|
|
1558
|
+
**Main Template Override**
|
|
1559
|
+
|
|
1560
|
+
Main template for the search feedback message.
|
|
1055
1561
|
|
|
1056
|
-
|
|
1562
|
+
It receives following props:
|
|
1563
|
+
- searchExecuted - boolean, true if search was executed
|
|
1564
|
+
- hasResults - boolean, true if search has results
|
|
1565
|
+
- startResultIndex - number, start of page results
|
|
1566
|
+
- endResultIndex - number, end of page results
|
|
1567
|
+
- totalResults - number, total results
|
|
1568
|
+
- searchTerm - term that was searched
|
|
1569
|
+
- autoCorrectedQuery - query that search was autocorrected to
|
|
1570
|
+
- originalQuery - original query
|
|
1571
|
+
|
|
1572
|
+
**Example**
|
|
1057
1573
|
```
|
|
1058
1574
|
function searchOverviewTemplate(
|
|
1059
1575
|
searchFeedbackData: null | ISearchstaxSearchFeedbackData,
|
|
1060
|
-
onOriginalQueryClick: (
|
|
1576
|
+
onOriginalQueryClick: (
|
|
1577
|
+
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
|
|
1578
|
+
) => void
|
|
1061
1579
|
) {
|
|
1062
1580
|
return (
|
|
1063
1581
|
<>
|
|
1064
|
-
{searchFeedbackData &&
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
{
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
<div>
|
|
1080
|
-
{" "}
|
|
1081
|
-
Search instead for{" "}
|
|
1082
|
-
<a
|
|
1083
|
-
href="#"
|
|
1084
|
-
onClick={(e) => {
|
|
1085
|
-
onOriginalQueryClick(e);
|
|
1086
|
-
}}
|
|
1087
|
-
className="searchstax-feedback-original-query"
|
|
1088
|
-
>
|
|
1089
|
-
{searchFeedbackData.originalQuery}
|
|
1090
|
-
</a>
|
|
1091
|
-
</div>
|
|
1582
|
+
{searchFeedbackData &&
|
|
1583
|
+
searchFeedbackData?.searchExecuted &&
|
|
1584
|
+
searchFeedbackData?.totalResults > 0 && (
|
|
1585
|
+
<>
|
|
1586
|
+
Showing{" "}
|
|
1587
|
+
<b>
|
|
1588
|
+
{searchFeedbackData.startResultIndex} -{" "}
|
|
1589
|
+
{searchFeedbackData.endResultIndex}
|
|
1590
|
+
</b>{" "}
|
|
1591
|
+
of
|
|
1592
|
+
<b> {searchFeedbackData.totalResults}</b> results
|
|
1593
|
+
{searchFeedbackData.searchTerm && (
|
|
1594
|
+
<span>
|
|
1595
|
+
for "<b>{searchFeedbackData.searchTerm}</b>"{" "}
|
|
1596
|
+
</span>
|
|
1092
1597
|
)}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1598
|
+
<div className="searchstax-feedback-container-suggested">
|
|
1599
|
+
{searchFeedbackData.autoCorrectedQuery && (
|
|
1600
|
+
<div>
|
|
1601
|
+
{" "}
|
|
1602
|
+
Search instead for{" "}
|
|
1603
|
+
<a
|
|
1604
|
+
href="#"
|
|
1605
|
+
onClick={(e) => {
|
|
1606
|
+
onOriginalQueryClick(e);
|
|
1607
|
+
}}
|
|
1608
|
+
className="searchstax-feedback-original-query"
|
|
1609
|
+
aria-label={`Search instead for ${searchFeedbackData.originalQuery}`}
|
|
1610
|
+
>
|
|
1611
|
+
{searchFeedbackData.originalQuery}
|
|
1612
|
+
</a>
|
|
1613
|
+
</div>
|
|
1614
|
+
)}
|
|
1615
|
+
</div>
|
|
1616
|
+
</>
|
|
1617
|
+
)}
|
|
1096
1618
|
</>
|
|
1097
1619
|
);
|
|
1098
1620
|
}
|
|
1099
|
-
|
|
1621
|
+
<SearchstaxOverviewWidget searchOverviewTemplate={searchOverviewTemplate}></SearchstaxOverviewWidget>
|
|
1100
1622
|
```
|
|
1101
1623
|
|
|
1102
|
-
|
|
1624
|
+
### RelatedSearches widget ###
|
|
1103
1625
|
|
|
1626
|
+
The SearchStax Site Search solution offers a related-searches widget for React and Next.js search pages.
|
|
1104
1627
|
|
|
1628
|
+
The SearchstaxRelatedSearchesWidget displays related searches.
|
|
1105
1629
|
|
|
1106
|
-
|
|
1630
|
+
**Usage**
|
|
1107
1631
|
```
|
|
1108
|
-
<SearchstaxRelatedSearchesWidget
|
|
1632
|
+
<SearchstaxRelatedSearchesWidget
|
|
1109
1633
|
relatedSearchesURL={config.relatedSearchesURL}
|
|
1110
1634
|
relatedSearchesAPIKey={config.relatedSearchesAPIKey}
|
|
1111
1635
|
></SearchstaxRelatedSearchesWidget>
|
|
1112
1636
|
```
|
|
1113
1637
|
|
|
1638
|
+
**Props**
|
|
1639
|
+
|
|
1640
|
+
- relatedSearchesURL: // : An endpoint from the Discovery tab of the App Settings > All APls screen.
|
|
1641
|
+
- relatedSearchesAPIKey?: The Discovery API key from the Discovery tab of the App Settings > All
|
|
1642
|
+
- searchRelatedSearchesTemplate - template override for related searches
|
|
1643
|
+
|
|
1644
|
+
**Main Template Override**
|
|
1645
|
+
|
|
1646
|
+
Main template for related searches.
|
|
1647
|
+
|
|
1648
|
+
It receives following props:
|
|
1649
|
+
|
|
1650
|
+
- hasRelatedSearches - boolean, true if has related searches
|
|
1651
|
+
- relatedSearches - array of related searches
|
|
1652
|
+
- related_search - string, related searc
|
|
1653
|
+
- last - boolean, true if last related search
|
|
1654
|
+
|
|
1655
|
+
**Example**
|
|
1114
1656
|
|
|
1115
|
-
example of search feedback widget initialization with template overrides
|
|
1116
1657
|
```
|
|
1117
|
-
|
|
1658
|
+
function searchRelatedSearchesTemplate(
|
|
1118
1659
|
relatedData: null | ISearchstaxRelatedSearchesData,
|
|
1119
1660
|
executeSearch: (result: ISearchstaxRelatedSearchResult) => void
|
|
1120
1661
|
) {
|
|
1121
1662
|
return (
|
|
1122
1663
|
<>
|
|
1123
|
-
{relatedData &&
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1664
|
+
{relatedData &&
|
|
1665
|
+
relatedData?.searchExecuted &&
|
|
1666
|
+
relatedData?.hasRelatedSearches && (
|
|
1667
|
+
<div
|
|
1668
|
+
className="searchstax-related-searches-container"
|
|
1669
|
+
id="searchstax-related-searches-container"
|
|
1670
|
+
>
|
|
1671
|
+
{" "}
|
|
1672
|
+
Related searches: <span id="searchstax-related-searches"></span>
|
|
1673
|
+
{relatedData.relatedSearches && (
|
|
1674
|
+
<span className="searchstax-related-search">
|
|
1675
|
+
{relatedData.relatedSearches.map((related) => (
|
|
1676
|
+
<span
|
|
1677
|
+
key={related.related_search}
|
|
1678
|
+
onClick={() => {
|
|
1679
|
+
executeSearch(related);
|
|
1680
|
+
}}
|
|
1681
|
+
onKeyDown={(e) => {
|
|
1682
|
+
if(e.key === 'Enter' || e.key === ' ') {
|
|
1683
|
+
executeSearch(related);
|
|
1684
|
+
}
|
|
1685
|
+
}}
|
|
1686
|
+
tabIndex={0}
|
|
1687
|
+
className="searchstax-related-search searchstax-related-search-item"
|
|
1688
|
+
aria-label={'Related search: ' + related.related_search}
|
|
1689
|
+
>
|
|
1690
|
+
{" "}
|
|
1691
|
+
{related.related_search}
|
|
1692
|
+
{!related.last && <span>,</span>}
|
|
1693
|
+
</span>
|
|
1694
|
+
))}
|
|
1695
|
+
</span>
|
|
1696
|
+
)}
|
|
1697
|
+
</div>
|
|
1698
|
+
)}
|
|
1146
1699
|
</>
|
|
1147
1700
|
);
|
|
1148
1701
|
}
|
|
@@ -1151,60 +1704,87 @@ example of search feedback widget initialization with template overrides
|
|
|
1151
1704
|
relatedSearchesAPIKey={config.relatedSearchesAPIKey}
|
|
1152
1705
|
searchRelatedSearchesTemplate={searchRelatedSearchesTemplate}
|
|
1153
1706
|
></SearchstaxRelatedSearchesWidget>
|
|
1154
|
-
|
|
1155
1707
|
```
|
|
1156
1708
|
|
|
1157
1709
|
### ExternalPromotions widget ###
|
|
1158
1710
|
|
|
1159
|
-
|
|
1711
|
+
The SearchStax Site Search solution offers an external-promotions widget to assist with your React and Next.js custom search pages.
|
|
1712
|
+
|
|
1713
|
+
The SearchstaxExternalPromotionsWidget displays external promotions fetched from the API.
|
|
1714
|
+
|
|
1715
|
+
**Usage**
|
|
1716
|
+
|
|
1160
1717
|
```
|
|
1161
1718
|
<SearchstaxExternalPromotionsWidget></SearchstaxExternalPromotionsWidget>
|
|
1162
1719
|
```
|
|
1163
1720
|
|
|
1721
|
+
**Props**
|
|
1722
|
+
|
|
1723
|
+
- searchExternalPromotionsTemplate - template override for external promotions widget
|
|
1724
|
+
|
|
1725
|
+
**Main Template Override**
|
|
1726
|
+
|
|
1727
|
+
Main template for external promotions.
|
|
1164
1728
|
|
|
1165
|
-
|
|
1729
|
+
It receives following props:
|
|
1730
|
+
|
|
1731
|
+
- hasExternalPromotions - boolean, true if has external promotions
|
|
1732
|
+
- url - url of external promotion
|
|
1733
|
+
- uniqueId - unique id
|
|
1734
|
+
- name - name of promotion
|
|
1735
|
+
- description - description
|
|
1736
|
+
|
|
1737
|
+
**Example**
|
|
1166
1738
|
```
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
<p className="searchstax-search-result-description searchstax-search-result-common">
|
|
1199
|
-
{" "}
|
|
1200
|
-
{externalPromotion.url}{" "}
|
|
1201
|
-
</p>
|
|
1202
|
-
)}
|
|
1739
|
+
function searchExternalPromotionsTemplate(
|
|
1740
|
+
externalPromotionsData: null | ISearchstaxExternalPromotionsData,
|
|
1741
|
+
trackClick: (
|
|
1742
|
+
externalPromotion: IExternalPromotion,
|
|
1743
|
+
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
|
|
1744
|
+
) => void
|
|
1745
|
+
) {
|
|
1746
|
+
return (
|
|
1747
|
+
<>
|
|
1748
|
+
{externalPromotionsData &&
|
|
1749
|
+
externalPromotionsData?.searchExecuted &&
|
|
1750
|
+
externalPromotionsData?.hasExternalPromotions &&
|
|
1751
|
+
externalPromotionsData.externalPromotions.map((externalPromotion) => (
|
|
1752
|
+
<div
|
|
1753
|
+
className="searchstax-external-promotion searchstax-search-result"
|
|
1754
|
+
key={externalPromotion.id}
|
|
1755
|
+
>
|
|
1756
|
+
<div className="icon-elevated"></div>
|
|
1757
|
+
{externalPromotion.url && (
|
|
1758
|
+
<a
|
|
1759
|
+
href={externalPromotion.url}
|
|
1760
|
+
onClick={(event) => {
|
|
1761
|
+
trackClick(externalPromotion, event);
|
|
1762
|
+
}}
|
|
1763
|
+
className="searchstax-result-item-link"
|
|
1764
|
+
></a>
|
|
1765
|
+
)}
|
|
1766
|
+
<div className="searchstax-search-result-title-container">
|
|
1767
|
+
<span className="searchstax-search-result-title">
|
|
1768
|
+
{externalPromotion.name}
|
|
1769
|
+
</span>
|
|
1203
1770
|
</div>
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1771
|
+
{externalPromotion.description && (
|
|
1772
|
+
<p className="searchstax-search-result-description searchstax-search-result-common">
|
|
1773
|
+
{" "}
|
|
1774
|
+
{externalPromotion.description}{" "}
|
|
1775
|
+
</p>
|
|
1776
|
+
)}
|
|
1777
|
+
{externalPromotion.url && (
|
|
1778
|
+
<p className="searchstax-search-result-description searchstax-search-result-common">
|
|
1779
|
+
{" "}
|
|
1780
|
+
{externalPromotion.url}{" "}
|
|
1781
|
+
</p>
|
|
1782
|
+
)}
|
|
1783
|
+
</div>
|
|
1784
|
+
))}
|
|
1785
|
+
</>
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1208
1788
|
<SearchstaxExternalPromotionsWidget
|
|
1209
1789
|
searchExternalPromotionsTemplate={searchExternalPromotionsTemplate}
|
|
1210
1790
|
></SearchstaxExternalPromotionsWidget>
|
|
@@ -1213,44 +1793,65 @@ example of search feedback widget initialization with template overrides
|
|
|
1213
1793
|
|
|
1214
1794
|
### Sorting Widget ###
|
|
1215
1795
|
|
|
1216
|
-
|
|
1796
|
+
The SearchStax Site Search solution offers a sorting widget for your React or Next.js custom search page.
|
|
1797
|
+
|
|
1798
|
+
The SearchstaxSortingWidget displays sorting options for search results.
|
|
1799
|
+
|
|
1800
|
+
**Usage**
|
|
1801
|
+
|
|
1217
1802
|
```
|
|
1218
1803
|
<SearchstaxSortingWidget></SearchstaxSortingWidget>
|
|
1219
1804
|
```
|
|
1220
1805
|
|
|
1806
|
+
**Props**
|
|
1807
|
+
|
|
1808
|
+
- searchSortingTemplate - template override for sorting widget
|
|
1809
|
+
|
|
1810
|
+
**Main Template Override**
|
|
1811
|
+
|
|
1812
|
+
Main template for sorting widget.
|
|
1813
|
+
|
|
1814
|
+
It receives following props:
|
|
1815
|
+
|
|
1816
|
+
- searchExecuted - boolean, true if search was executed
|
|
1817
|
+
- hasResultsOrExternalPromotions - boolean, true if there are results or external promotions
|
|
1818
|
+
- sortOptions - array of sort options, has key/value
|
|
1819
|
+
|
|
1820
|
+
**Example**
|
|
1221
1821
|
|
|
1222
|
-
example of sorting widget initialization with template override
|
|
1223
1822
|
```
|
|
1224
|
-
|
|
1823
|
+
function searchSortingTemplate(
|
|
1225
1824
|
sortingData: null | ISearchstaxSearchSortingData,
|
|
1226
1825
|
orderChange: (value: string) => void,
|
|
1227
1826
|
selectedSorting: string
|
|
1228
1827
|
) {
|
|
1229
1828
|
return (
|
|
1230
1829
|
<>
|
|
1231
|
-
{sortingData &&
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1830
|
+
{sortingData &&
|
|
1831
|
+
sortingData?.searchExecuted &&
|
|
1832
|
+
sortingData?.hasResultsOrExternalPromotions && (
|
|
1833
|
+
<div className="searchstax-sorting-container">
|
|
1834
|
+
<label className="searchstax-sorting-label" htmlFor="searchstax-search-order-select">
|
|
1835
|
+
Sort By
|
|
1836
|
+
</label>
|
|
1837
|
+
<select
|
|
1838
|
+
id="searchstax-search-order-select"
|
|
1839
|
+
className="searchstax-search-order-select"
|
|
1840
|
+
value={selectedSorting}
|
|
1841
|
+
onChange={(e) => {
|
|
1842
|
+
orderChange(e.target.value);
|
|
1843
|
+
}}
|
|
1844
|
+
>
|
|
1845
|
+
{sortingData.sortOptions.map(function (sortOption) {
|
|
1245
1846
|
return (
|
|
1246
1847
|
<option key={sortOption.key} value={sortOption.key}>
|
|
1247
1848
|
{sortOption.value}
|
|
1248
1849
|
</option>
|
|
1249
1850
|
);
|
|
1250
1851
|
})}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1852
|
+
</select>
|
|
1853
|
+
</div>
|
|
1854
|
+
)}
|
|
1254
1855
|
</>
|
|
1255
1856
|
);
|
|
1256
1857
|
}
|
|
@@ -1259,7 +1860,7 @@ example of sorting widget initialization with template override
|
|
|
1259
1860
|
|
|
1260
1861
|
|
|
1261
1862
|
## Template overrides
|
|
1262
|
-
Templates use
|
|
1863
|
+
Templates use relatedsearches templating.
|
|
1263
1864
|
|
|
1264
1865
|
## STYLING
|
|
1265
1866
|
|
|
@@ -1271,4 +1872,4 @@ css can be taken from
|
|
|
1271
1872
|
|
|
1272
1873
|
```
|
|
1273
1874
|
./../node_modules/@searchstax-inc/searchstudio-ux-js/dist/styles/mainTheme.css
|
|
1274
|
-
```
|
|
1875
|
+
```
|