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