Skip to content

Commit b8602af

Browse files
sarahdayandhayab
authored andcommitted
feat(recommend): introduce relatedProducts widget (#6154)
1 parent 82d3001 commit b8602af

File tree

18 files changed

+1011
-8
lines changed

18 files changed

+1011
-8
lines changed

bundlesize.config.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
},
1111
{
1212
"path": "./packages/instantsearch.js/dist/instantsearch.production.min.js",
13-
"maxSize": "79.25 kB"
13+
"maxSize": "80 kB"
1414
},
1515
{
1616
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
17-
"maxSize": "174 kB"
17+
"maxSize": "174.75 kB"
1818
},
1919
{
2020
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",

examples/js/getting-started/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"version": "1.3.0",
44
"private": true,
55
"scripts": {
6-
"start": "BABEL_ENV=parcel parcel index.html --port 3000",
7-
"build": "BABEL_ENV=parcel parcel build index.html",
6+
"start": "BABEL_ENV=parcel parcel index.html products.html --port 3000",
7+
"build": "BABEL_ENV=parcel parcel build index.html products.html",
88
"lint": "eslint .",
99
"lint:fix": "npm run lint -- --fix"
1010
},
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1, shrink-to-fit=no"
8+
/>
9+
<meta name="theme-color" content="#000000" />
10+
11+
<link rel="shortcut icon" href="./favicon.png" />
12+
13+
<link
14+
rel="stylesheet"
15+
href="https://cdn.jsdelivr.net/npm/instantsearch.css@7/themes/satellite-min.css"
16+
/>
17+
<link rel="stylesheet" href="./src/index.css" />
18+
<link rel="stylesheet" href="./src/app.css" />
19+
20+
<title>InstantSearch.js — Getting started</title>
21+
</head>
22+
23+
<body>
24+
<header class="header">
25+
<h1 class="header-title">
26+
<a href="/">Getting started</a>
27+
</h1>
28+
<p class="header-subtitle">
29+
using
30+
<a href="https://github.com/algolia/instantsearch">
31+
InstantSearch.js
32+
</a>
33+
</p>
34+
</header>
35+
36+
<div class="container">
37+
<a href="/">← Back to search</a>
38+
<div id="hits"></div>
39+
<div id="related-products"></div>
40+
</div>
41+
42+
<script type="module" src="./src/products.js"></script>
43+
</body>
44+
</html>

examples/js/getting-started/src/app.css

+40
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,43 @@
5858
margin: 2rem auto;
5959
text-align: center;
6060
}
61+
62+
#related-products,
63+
.ais-Hits--single {
64+
margin-top: 1rem;
65+
}
66+
67+
.ais-Hits--single article {
68+
display: flex;
69+
gap: 1rem;
70+
}
71+
72+
.ais-Hits--single img {
73+
width: 150px;
74+
height: 150px;
75+
object-fit: contain;
76+
flex-shrink: 0;
77+
}
78+
79+
.ais-RelatedProducts-list {
80+
display: grid;
81+
grid-template-columns: repeat(4, 1fr);
82+
gap: 1rem;
83+
}
84+
85+
.ais-RelatedProducts-item {
86+
align-items: start;
87+
}
88+
89+
.ais-RelatedProducts-item img {
90+
width: 100%;
91+
height: 100px;
92+
object-fit: contain;
93+
}
94+
95+
.ais-RelatedProducts-item article {
96+
display: flex;
97+
flex-direction: column;
98+
height: 100%;
99+
justify-content: space-between;
100+
}

examples/js/getting-started/src/app.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ search.addWidgets([
2929
templates: {
3030
item: (hit, { html, components }) => html`
3131
<article>
32-
<h1>${components.Highlight({ hit, attribute: 'name' })}</h1>
32+
<h1>
33+
<a href="/products.html?pid=${hit.objectID}"
34+
>${components.Highlight({ hit, attribute: 'name' })}</a
35+
>
36+
</h1>
3337
<p>${components.Highlight({ hit, attribute: 'description' })}</p>
38+
<a href="/products.html?pid=${hit.objectID}">See product</a>
3439
</article>
3540
`,
3641
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import algoliasearch from 'algoliasearch/lite';
2+
import instantsearch from 'instantsearch.js';
3+
import { configure, hits, relatedProducts } from 'instantsearch.js/es/widgets';
4+
5+
const searchParams = new URLSearchParams(document.location.search);
6+
7+
const pid = searchParams.get('pid');
8+
9+
const searchClient = algoliasearch(
10+
'latency',
11+
'6be0576ff61c053d5f9a3225e2a90f76'
12+
);
13+
14+
const search = instantsearch({
15+
indexName: 'instant_search',
16+
searchClient,
17+
insights: true,
18+
});
19+
20+
search.addWidgets([
21+
hits({
22+
container: '#hits',
23+
templates: {
24+
item: (hit, { html, components }) => html`
25+
<article>
26+
<img src="${hit.image}" />
27+
<div>
28+
<h1>${components.Highlight({ hit, attribute: 'name' })}</h1>
29+
<p>${components.Highlight({ hit, attribute: 'description' })}</p>
30+
</div>
31+
</article>
32+
`,
33+
},
34+
cssClasses: { root: 'ais-Hits--single' },
35+
}),
36+
relatedProducts({
37+
container: '#related-products',
38+
objectIDs: [pid],
39+
maxRecommendations: 4,
40+
templates: {
41+
item: (hit, { html }) => html`
42+
<article>
43+
<div>
44+
<img src="${hit.image}" />
45+
<h2>${hit.name}</h2>
46+
</div>
47+
<a href="/products.html?pid=${hit.objectID}">See product</a>
48+
</article>
49+
`,
50+
},
51+
}),
52+
configure({
53+
hitsPerPage: 1,
54+
filters: `objectID:${pid}`,
55+
}),
56+
]);
57+
58+
search.start();

packages/instantsearch.css/src/themes/algolia.scss

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ a[class^='ais-'] {
4343
.ais-RangeSlider,
4444
.ais-RatingMenu,
4545
.ais-RefinementList,
46+
.ais-RelatedProducts,
4647
.ais-SearchBox,
4748
.ais-RelevantSort,
4849
.ais-SortBy,

packages/instantsearch.css/src/themes/satellite.scss

+1-2
Original file line numberDiff line numberDiff line change
@@ -605,8 +605,7 @@ $break-medium: 767px;
605605
}
606606

607607
/**
608-
* Hits and InfiniteHits
609-
* FrequentlyBoughtTogether and RelatedProducts
608+
* Hits, InfiniteHits, FrequentlyBoughtTogether and RelatedProducts
610609
*/
611610

612611
.ais-Hits-item,

packages/instantsearch.js/src/__tests__/common-widgets.test.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
stats,
2626
ratingMenu,
2727
numericMenu,
28+
relatedProducts,
2829
frequentlyBoughtTogether,
2930
} from '../widgets';
3031

@@ -480,6 +481,22 @@ const testSetups: TestSetupsMap<TestSuites> = {
480481
})
481482
.start();
482483
},
484+
createRelatedProductsWidgetTests({ instantSearchOptions, widgetParams }) {
485+
instantsearch(instantSearchOptions)
486+
.addWidgets([
487+
relatedProducts({
488+
container: document.body.appendChild(document.createElement('div')),
489+
...widgetParams,
490+
}),
491+
])
492+
.on('error', () => {
493+
/*
494+
* prevent rethrowing InstantSearch errors, so tests can be asserted.
495+
* IRL this isn't needed, as the error doesn't stop execution.
496+
*/
497+
})
498+
.start();
499+
},
483500
createFrequentlyBoughtTogetherTests({ instantSearchOptions, widgetParams }) {
484501
instantsearch(instantSearchOptions)
485502
.addWidgets([
@@ -489,7 +506,7 @@ const testSetups: TestSetupsMap<TestSuites> = {
489506
}),
490507
])
491508
.on('error', () => {
492-
/**
509+
/*
493510
* prevent rethrowing InstantSearch errors, so tests can be asserted.
494511
* IRL this isn't needed, as the error doesn't stop execution.
495512
*/
@@ -521,6 +538,7 @@ const testOptions: TestOptionsMap<TestSuites> = {
521538
createSortByWidgetTests: undefined,
522539
createStatsWidgetTests: undefined,
523540
createNumericMenuWidgetTests: undefined,
541+
createRelatedProductsWidgetTests: undefined,
524542
createFrequentlyBoughtTogetherTests: undefined,
525543
};
526544

packages/instantsearch.js/src/widgets/__tests__/index.test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ function initiateAllWidgets(): Array<[WidgetNames, Widget | IndexWidget]> {
155155
objectIDs: ['objectID'],
156156
});
157157
}
158+
case 'relatedProducts': {
159+
const relatedProducts = widget as Widgets['relatedProducts'];
160+
return relatedProducts({
161+
container,
162+
objectIDs: ['objectID'],
163+
});
164+
}
158165
default: {
159166
const defaultWidget = widget as UnknownWidgetFactory;
160167
return defaultWidget({ container, attribute: 'attr' });

packages/instantsearch.js/src/widgets/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export { default as places } from './places/places';
3737
export { default as poweredBy } from './powered-by/powered-by';
3838
export { default as queryRuleContext } from './query-rule-context/query-rule-context';
3939
export { default as queryRuleCustomData } from './query-rule-custom-data/query-rule-custom-data';
40+
export { default as relatedProducts } from './related-products/related-products';
4041
export { default as rangeInput } from './range-input/range-input';
4142
export { default as rangeSlider } from './range-slider/range-slider';
4243
export { default as ratingMenu } from './rating-menu/rating-menu';

0 commit comments

Comments
 (0)