Skip to content

Commit

Permalink
feat(plugins): add categories to Query Suggestions and Recent Searches
Browse files Browse the repository at this point in the history
  • Loading branch information
francoischalifour committed Feb 12, 2021
1 parent 154d4b2 commit 54ef36c
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 46 deletions.
2 changes: 1 addition & 1 deletion bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
{
"path": "packages/autocomplete-plugin-query-suggestions/dist/umd/index.production.js",
"maxSize": "3.25 kB"
"maxSize": "4 kB"
}
]
}
1 change: 1 addition & 0 deletions examples/js/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const querySuggestionsPlugin = createQuerySuggestionsPlugin({
hitsPerPage: state.query ? 5 : 10,
});
},
categoryAttribute: 'categories',
});

autocomplete({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { SearchOptions } from '@algolia/client-search';
import { SearchClient } from 'algoliasearch/lite';

import { getTemplates } from './getTemplates';
import { QuerySuggestionsHit } from './types';
import { AutocompleteQuerySuggestionsHit, QuerySuggestionsHit } from './types';

export type CreateQuerySuggestionsPluginParams<
TItem extends QuerySuggestionsHit
Expand All @@ -19,15 +19,32 @@ export type CreateQuerySuggestionsPluginParams<
source: AutocompleteSource<TItem>;
onTapAhead(item: TItem): void;
}): AutocompleteSource<TItem>;
/**
* The attribute to display categories.
*/
categoryAttribute?: string;
/**
* The number of items to display categories for.
* @default 1
*/
categoriesLimit?: number;
/**
* The number of categories to display per item.
* @default 1
*/
categoriesPerItem?: number;
};

export function createQuerySuggestionsPlugin<
TItem extends QuerySuggestionsHit
TItem extends AutocompleteQuerySuggestionsHit
>({
searchClient,
indexName,
getSearchParams = () => ({}),
transformSource = ({ source }) => source,
categoryAttribute,
categoriesPerItem = 1,
categoriesLimit = 1,
}: CreateQuerySuggestionsPluginParams<TItem>): AutocompletePlugin<
TItem,
undefined
Expand All @@ -39,8 +56,6 @@ export function createQuerySuggestionsPlugin<
refresh();
}

const templates = getTemplates({ onTapAhead });

return [
transformSource({
source: {
Expand All @@ -49,7 +64,7 @@ export function createQuerySuggestionsPlugin<
return item.query;
},
getItems() {
return getAlgoliaHits<TItem>({
return getAlgoliaHits<QuerySuggestionsHit<typeof indexName>>({
searchClient,
queries: [
{
Expand All @@ -58,9 +73,40 @@ export function createQuerySuggestionsPlugin<
params: getSearchParams({ state }),
},
],
}).then(([hits]) => {
if (!query || !categoryAttribute) {
return hits as any;
}

return hits.reduce<
Array<AutocompleteQuerySuggestionsHit<typeof indexName>>
>((acc, current, i) => {
const items: Array<
AutocompleteQuerySuggestionsHit<typeof indexName>
> = [current];

if (i <= categoriesPerItem - 1) {
const categories = current.instant_search.facets.exact_matches[
categoryAttribute
]
.map((x) => x.value)
.slice(0, categoriesLimit);

for (const category of categories) {
items.push({
__autocomplete_qsCategory: category,
...current,
} as any);
}
}

acc.push(...items);

return acc;
}, []);
});
},
templates,
templates: getTemplates({ onTapAhead }),
},
onTapAhead,
}),
Expand Down
68 changes: 44 additions & 24 deletions packages/autocomplete-plugin-query-suggestions/src/getTemplates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,58 @@ export function getTemplates<TItem extends QuerySuggestionsHit>({
return (
<Fragment>
<div className="aa-ItemIcon aa-ItemIcon--no-border">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M16.041 15.856c-0.034 0.026-0.067 0.055-0.099 0.087s-0.060 0.064-0.087 0.099c-1.258 1.213-2.969 1.958-4.855 1.958-1.933 0-3.682-0.782-4.95-2.050s-2.050-3.017-2.050-4.95 0.782-3.682 2.050-4.95 3.017-2.050 4.95-2.050 3.682 0.782 4.95 2.050 2.050 3.017 2.050 4.95c0 1.886-0.745 3.597-1.959 4.856zM21.707 20.293l-3.675-3.675c1.231-1.54 1.968-3.493 1.968-5.618 0-2.485-1.008-4.736-2.636-6.364s-3.879-2.636-6.364-2.636-4.736 1.008-6.364 2.636-2.636 3.879-2.636 6.364 1.008 4.736 2.636 6.364 3.879 2.636 6.364 2.636c2.125 0 4.078-0.737 5.618-1.968l3.675 3.675c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414z" />
</svg>
</div>
<div className="aa-ItemContent">
<div className="aa-ItemContentTitle">
{reverseHighlightHit<QuerySuggestionsHit>({
hit: item,
attribute: 'query',
createElement,
})}
</div>
</div>
<div className="aa-ItemActions">
<button
className="aa-ItemActionButton"
title={`Fill query with "${item.query}"`}
onClick={(event) => {
event.stopPropagation();
onTapAhead(item);
}}
>
{!item.__autocomplete_qsCategory && (
<svg
viewBox="0 0 24 24"
width="18"
height="18"
fill="currentColor"
>
<path d="M8 17v-7.586l8.293 8.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-8.293-8.293h7.586c0.552 0 1-0.448 1-1s-0.448-1-1-1h-10c-0.552 0-1 0.448-1 1v10c0 0.552 0.448 1 1 1s1-0.448 1-1z" />
<path d="M16.041 15.856c-0.034 0.026-0.067 0.055-0.099 0.087s-0.060 0.064-0.087 0.099c-1.258 1.213-2.969 1.958-4.855 1.958-1.933 0-3.682-0.782-4.95-2.050s-2.050-3.017-2.050-4.95 0.782-3.682 2.050-4.95 3.017-2.050 4.95-2.050 3.682 0.782 4.95 2.050 2.050 3.017 2.050 4.95c0 1.886-0.745 3.597-1.959 4.856zM21.707 20.293l-3.675-3.675c1.231-1.54 1.968-3.493 1.968-5.618 0-2.485-1.008-4.736-2.636-6.364s-3.879-2.636-6.364-2.636-4.736 1.008-6.364 2.636-2.636 3.879-2.636 6.364 1.008 4.736 2.636 6.364 3.879 2.636 6.364 2.636c2.125 0 4.078-0.737 5.618-1.968l3.675 3.675c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414z" />
</svg>
</button>
)}
</div>

<div className="aa-ItemContent">
<div className="aa-ItemContentTitle">
{item.__autocomplete_qsCategory ? (
<div style={{ fontSize: '0.92rem' }}>
in{' '}
<span style={{ color: 'var(--aa-content-text-color)' }}>
{item.__autocomplete_qsCategory}
</span>
</div>
) : (
reverseHighlightHit<QuerySuggestionsHit>({
hit: item,
attribute: 'query',
createElement,
})
)}
</div>
</div>

{!item.__autocomplete_qsCategory && (
<div className="aa-ItemActions">
<button
className="aa-ItemActionButton"
title={`Fill query with "${item.query}"`}
onClick={(event) => {
event.stopPropagation();
onTapAhead(item);
}}
>
<svg
viewBox="0 0 24 24"
width="18"
height="18"
fill="currentColor"
>
<path d="M8 17v-7.586l8.293 8.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-8.293-8.293h7.586c0.552 0 1-0.448 1-1s-0.448-1-1-1h-10c-0.552 0-1 0.448-1 1v10c0 0.552 0.448 1 1 1s1-0.448 1-1z" />
</svg>
</button>
</div>
)}
</Fragment>
);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
import { Hit } from '@algolia/client-search';

export type QuerySuggestionsHit = Hit<{
type QuerySuggestionsFacetValue = { value: string; count: number };

type QuerySuggestionsIndexMatch<TKey extends string> = Record<
TKey,
{
exact_nb_hits: number;
facets: {
exact_matches: QuerySuggestionsFacetValue[];
};
analytics: QuerySuggestionsFacetValue[];
}
>;

export type QuerySuggestionsHit<TIndexKey extends string = any> = Hit<{
query: string;
popularity: number;
nb_words: number;
}>;
}> &
QuerySuggestionsIndexMatch<TIndexKey>;

export type AutocompleteQuerySuggestionsHit<
TIndexKey extends string = any
> = QuerySuggestionsHit<TIndexKey> & {
__autocomplete_qsCategory?: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ export function createRecentSearchesPlugin<TItem extends RecentSearchesItem>({
onSelect(({ item, state, source }) => {
const inputValue = source.getItemInputValue({ item, state });

if (inputValue) {
if (source.sourceId === 'querySuggestionsPlugin' && inputValue) {
store.add({
id: inputValue,
objectID: inputValue,
query: inputValue,
category: (item as any).__autocomplete_qsCategory,
} as any);
}
});
Expand All @@ -49,7 +50,7 @@ export function createRecentSearchesPlugin<TItem extends RecentSearchesItem>({

if (query) {
store.add({
id: query,
objectID: query,
query,
} as any);
}
Expand All @@ -62,8 +63,6 @@ export function createRecentSearchesPlugin<TItem extends RecentSearchesItem>({
refresh();
}

const templates = getTemplates<any>({ onRemove });

return Promise.resolve(lastItemsRef.current).then((items) => {
if (items.length === 0) {
return [];
Expand All @@ -79,7 +78,7 @@ export function createRecentSearchesPlugin<TItem extends RecentSearchesItem>({
getItems() {
return items;
},
templates,
templates: getTemplates({ onRemove }),
},
onRemove,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function createStore<TItem extends RecentSearchesItem>(
): RecentSearchesStore<TItem> {
return {
add(item) {
storage.onRemove(item.id);
storage.onRemove(item.objectID);
storage.onAdd(item);
},
remove(id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,24 @@ export function getTemplates<TItem extends RecentSearchesItem>({
</div>
<div className="aa-ItemContent">
<div className="aa-ItemContentTitle">
{reverseHighlightHit<any>({
{reverseHighlightHit<RecentSearchesItem>({
hit: item,
attribute: 'query',
createElement,
})}
</div>

{item.category && (
<div className="aa-ItemContentSubtitle">– in {item.category}</div>
)}
</div>
<div className="aa-ItemActions">
<button
className="aa-ItemActionButton"
title="Remove this search"
onClick={(event) => {
event.stopPropagation();
onRemove(item.id);
onRemove(item.objectID);
}}
>
<svg
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type RecentSearchesItem = {
id: string;
objectID: string;
query: string;
category?: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function createLocalStorage<TItem extends RecentSearchesItem>({
storage.setItem([item, ...storage.getItem()]);
},
onRemove(id) {
storage.setItem(storage.getItem().filter((x) => x.id !== id));
storage.setItem(storage.getItem().filter((x) => x.objectID !== id));
},
};
}
8 changes: 6 additions & 2 deletions packages/autocomplete-theme-classic/src/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -370,14 +370,18 @@ body {
white-space: nowrap;
}
.aa-ItemContentSubtitle {
color: var(--aa-muted-color);
color: var(--aa-content-text-color);
display: inline-block;
font-size: 0.75em;
font-size: 0.92em;
margin-top: -2px;
max-width: 100%;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&::before {
content: ' ';
white-space: pre;
}
&:empty {
display: none;
}
Expand Down

0 comments on commit 54ef36c

Please sign in to comment.