Skip to content

Commit 610dba3

Browse files
docs(sandbox): add Query Suggestions with Rich Hits (#486)
Co-authored-by: François Chalifour <[email protected]>
1 parent 1429c40 commit 610dba3

File tree

8 files changed

+270
-0
lines changed

8 files changed

+270
-0
lines changed

.codesandbox/ci.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"sandboxes": [
66
"/examples/js",
77
"/examples/react-renderer",
8+
"/examples/query-suggestions-with-rich-hits",
89
"/examples/query-suggestions",
910
"/examples/query-suggestions-with-recent-searches",
1011
"/examples/query-suggestions-with-inline-categories",

examples/query-suggestions-with-rich-hits/README.md

Whitespace-only changes.
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/** @jsx h */
2+
import {
3+
autocomplete,
4+
getAlgoliaHits,
5+
snippetHit,
6+
} from '@algolia/autocomplete-js';
7+
import {
8+
AutocompleteInsightsApi,
9+
createAlgoliaInsightsPlugin,
10+
} from '@algolia/autocomplete-plugin-algolia-insights';
11+
import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions';
12+
import { Hit } from '@algolia/client-search';
13+
import algoliasearch from 'algoliasearch';
14+
import { h, Fragment } from 'preact';
15+
import insightsClient from 'search-insights';
16+
17+
import '@algolia/autocomplete-theme-classic';
18+
19+
type Product = {
20+
brand: string;
21+
categories: string[];
22+
description: string;
23+
image: string;
24+
name: string;
25+
price: number;
26+
rating: number;
27+
__autocomplete_indexName: string;
28+
__autocomplete_queryID: string;
29+
};
30+
type ProductHit = Hit<Product>;
31+
32+
const appId = 'latency';
33+
const apiKey = '6be0576ff61c053d5f9a3225e2a90f76';
34+
const searchClient = algoliasearch(appId, apiKey);
35+
insightsClient('init', { appId, apiKey });
36+
37+
const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient });
38+
39+
const querySuggestionsPlugin = createQuerySuggestionsPlugin({
40+
searchClient,
41+
indexName: 'instant_search_demo_query_suggestions',
42+
getSearchParams() {
43+
return {
44+
hitsPerPage: 5,
45+
};
46+
},
47+
});
48+
49+
autocomplete({
50+
container: '#autocomplete',
51+
placeholder: 'Search',
52+
debug: true,
53+
openOnFocus: true,
54+
plugins: [algoliaInsightsPlugin, querySuggestionsPlugin],
55+
getSources({ query, state }) {
56+
if (!query) {
57+
return [];
58+
}
59+
60+
return [
61+
{
62+
sourceId: 'products',
63+
getItems() {
64+
return getAlgoliaHits<Product>({
65+
searchClient,
66+
queries: [
67+
{
68+
indexName: 'instant_search',
69+
query,
70+
params: {
71+
clickAnalytics: true,
72+
attributesToSnippet: ['name:10'],
73+
snippetEllipsisText: '…',
74+
},
75+
},
76+
],
77+
});
78+
},
79+
templates: {
80+
header() {
81+
return (
82+
<Fragment>
83+
<span className="aa-SourceHeaderTitle">Products</span>
84+
<div className="aa-SourceHeaderLine" />
85+
</Fragment>
86+
);
87+
},
88+
item({ item }) {
89+
return (
90+
<ProductItem
91+
hit={item}
92+
insights={state.context.algoliaInsightsPlugin.insights}
93+
/>
94+
);
95+
},
96+
noResults() {
97+
return (
98+
<div className="aa-ItemContent">No products for this query.</div>
99+
);
100+
},
101+
},
102+
},
103+
];
104+
},
105+
});
106+
107+
type ProductItemProps = {
108+
hit: ProductHit;
109+
insights: AutocompleteInsightsApi;
110+
};
111+
112+
function ProductItem({ hit, insights }: ProductItemProps) {
113+
return (
114+
<Fragment>
115+
<div className="aa-ItemIcon aa-ItemIcon--align-top">
116+
<img src={hit.image} alt={hit.name} width="40" height="40" />
117+
</div>
118+
<div className="aa-ItemContent">
119+
<div className="aa-ItemContentTitle">
120+
{snippetHit<ProductHit>({ hit, attribute: 'name' })}
121+
</div>
122+
<div className="aa-ItemContentDescription">
123+
From <strong>{hit.brand}</strong> in{' '}
124+
<strong>{hit.categories[0]}</strong>
125+
</div>
126+
{hit.rating > 0 && (
127+
<div className="aa-ItemContentDescription">
128+
<div style={{ display: 'flex', gap: 1, color: '#ffc107' }}>
129+
{Array.from({ length: 5 }, (_value, index) => {
130+
const isFilled = hit.rating >= index + 1;
131+
132+
return (
133+
<svg
134+
key={index}
135+
width="16"
136+
height="16"
137+
viewBox="0 0 24 24"
138+
fill={isFilled ? 'currentColor' : 'none'}
139+
stroke="currentColor"
140+
strokeWidth="3"
141+
strokeLinecap="round"
142+
strokeLinejoin="round"
143+
>
144+
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
145+
</svg>
146+
);
147+
})}
148+
</div>
149+
</div>
150+
)}
151+
<div className="aa-ItemContentDescription" style={{ color: '#000' }}>
152+
<strong>${hit.price.toLocaleString()}</strong>
153+
</div>
154+
</div>
155+
<div className="aa-ItemActions">
156+
<button
157+
className="aa-ItemActionButton aa-TouchOnly aa-ActiveOnly"
158+
type="button"
159+
title="Select"
160+
>
161+
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
162+
<path d="M18.984 6.984h2.016v6h-15.188l3.609 3.609-1.406 1.406-6-6 6-6 1.406 1.406-3.609 3.609h13.172v-4.031z" />
163+
</svg>
164+
</button>
165+
<button
166+
className="aa-ItemActionButton"
167+
type="button"
168+
title="Add to cart"
169+
onClick={(event) => {
170+
event.preventDefault();
171+
event.stopPropagation();
172+
173+
insights.convertedObjectIDsAfterSearch({
174+
eventName: 'Added to cart',
175+
index: hit.__autocomplete_indexName,
176+
objectIDs: [hit.objectID],
177+
queryID: hit.__autocomplete_queryID,
178+
});
179+
}}
180+
>
181+
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
182+
<path d="M19 5h-14l1.5-2h11zM21.794 5.392l-2.994-3.992c-0.196-0.261-0.494-0.399-0.8-0.4h-12c-0.326 0-0.616 0.156-0.8 0.4l-2.994 3.992c-0.043 0.056-0.081 0.117-0.111 0.182-0.065 0.137-0.096 0.283-0.095 0.426v14c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h14c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-14c0-0.219-0.071-0.422-0.189-0.585-0.004-0.005-0.007-0.010-0.011-0.015zM4 7h16v13c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-14c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707zM15 10c0 0.829-0.335 1.577-0.879 2.121s-1.292 0.879-2.121 0.879-1.577-0.335-2.121-0.879-0.879-1.292-0.879-2.121c0-0.552-0.448-1-1-1s-1 0.448-1 1c0 1.38 0.561 2.632 1.464 3.536s2.156 1.464 3.536 1.464 2.632-0.561 3.536-1.464 1.464-2.156 1.464-3.536c0-0.552-0.448-1-1-1s-1 0.448-1 1z" />
183+
</svg>
184+
</button>
185+
</div>
186+
</Fragment>
187+
);
188+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { h } from 'preact';
2+
3+
// Parcel picks the `source` field of the monorepo packages and thus doesn't
4+
// apply the Babel config to replace our `__DEV__` global expression.
5+
// We therefore need to manually override it in the example app.
6+
// See https://twitter.com/devongovett/status/1134231234605830144
7+
(global as any).__DEV__ = process.env.NODE_ENV !== 'production';
8+
(global as any).__TEST__ = false;
9+
(global as any).h = h;
228 KB
Loading
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<html lang="en">
2+
<head>
3+
<meta charset="UTF-8" />
4+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
5+
6+
<link rel="shortcut icon" href="favicon.png" type="image/x-icon" />
7+
<link rel="stylesheet" href="style.css" />
8+
9+
<title>Query Suggestions with Rich Hits Sandbox</title>
10+
</head>
11+
12+
<body>
13+
<div class="container">
14+
<div id="autocomplete"></div>
15+
</div>
16+
17+
<script src="env.ts"></script>
18+
<script src="app.tsx"></script>
19+
</body>
20+
</html>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@algolia/query-suggestions-with-rich-hits-example",
3+
"description": "Autocomplete Query Suggestions With Rich Hits Sandbox",
4+
"version": "1.0.0-alpha.44",
5+
"private": true,
6+
"license": "MIT",
7+
"main": "index.html",
8+
"scripts": {
9+
"build": "parcel build index.html",
10+
"start": "parcel index.html"
11+
},
12+
"dependencies": {
13+
"@algolia/autocomplete-js": "1.0.0-alpha.44",
14+
"@algolia/autocomplete-plugin-algolia-insights": "^1.0.0-alpha.44",
15+
"@algolia/autocomplete-plugin-query-suggestions": "1.0.0-alpha.44",
16+
"@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.44",
17+
"@algolia/autocomplete-preset-algolia": "1.0.0-alpha.44",
18+
"@algolia/autocomplete-theme-classic": "1.0.0-alpha.44",
19+
"@algolia/client-search": "4.8.3",
20+
"algoliasearch": "4.8.3",
21+
"preact": "10.5.7",
22+
"search-insights": "1.6.3"
23+
},
24+
"devDependencies": {
25+
"parcel-bundler": "1.12.4"
26+
},
27+
"keywords": [
28+
"algolia",
29+
"autocomplete",
30+
"javascript"
31+
]
32+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
* {
2+
box-sizing: border-box;
3+
}
4+
5+
body {
6+
background-color: rgb(244, 244, 249);
7+
color: rgb(65, 65, 65);
8+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
9+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
10+
sans-serif;
11+
-webkit-font-smoothing: antialiased;
12+
-moz-osx-font-smoothing: grayscale;
13+
padding: 1rem;
14+
}
15+
16+
.container {
17+
margin: 0 auto;
18+
max-width: 640px;
19+
width: 100%;
20+
}

0 commit comments

Comments
 (0)