From d1fe8b2be3d30f067892ac9f04f6f802b6b40826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Wed, 1 Apr 2020 15:34:14 +0200 Subject: [PATCH] feat(docsearch): add search suggestions --- .../autocomplete-core/src/autocomplete.ts | 16 ++++++ packages/autocomplete-core/src/types/api.ts | 7 ++- packages/docsearch-react/src/DocSearch.tsx | 15 ++++- .../docsearch-react/src/Dropdown/Dropdown.tsx | 12 +++- .../src/NoResults/NoResults.tsx | 56 +++++++++++++++++-- packages/docsearch-react/src/style.css | 11 ++++ 6 files changed, 109 insertions(+), 8 deletions(-) diff --git a/packages/autocomplete-core/src/autocomplete.ts b/packages/autocomplete-core/src/autocomplete.ts index 70507f8c5..a13fe6bf3 100644 --- a/packages/autocomplete-core/src/autocomplete.ts +++ b/packages/autocomplete-core/src/autocomplete.ts @@ -5,6 +5,7 @@ import { getPropGetters } from './propGetters'; import { getAutocompleteSetters } from './setters'; import { PublicAutocompleteOptions, AutocompleteApi } from './types'; +import { onInput } from './onInput'; function createAutocomplete< TItem extends {}, @@ -45,6 +46,20 @@ function createAutocomplete< setContext, }); + function refresh() { + return onInput({ + query: store.getState().query, + store, + props, + setHighlightedIndex, + setQuery, + setSuggestions, + setIsOpen, + setStatus, + setContext, + }); + } + return { setHighlightedIndex, setQuery, @@ -60,6 +75,7 @@ function createAutocomplete< getDropdownProps, getMenuProps, getItemProps, + refresh, }; } diff --git a/packages/autocomplete-core/src/types/api.ts b/packages/autocomplete-core/src/types/api.ts index 7ce4618a2..aede74f34 100644 --- a/packages/autocomplete-core/src/types/api.ts +++ b/packages/autocomplete-core/src/types/api.ts @@ -14,7 +14,12 @@ export interface AutocompleteApi< TEvent, TMouseEvent, TKeyboardEvent - > {} + > { + /** + * Triggers a search to refresh the state. + */ + refresh(): Promise; +} export interface AutocompleteSuggestion { source: AutocompleteSource; diff --git a/packages/docsearch-react/src/DocSearch.tsx b/packages/docsearch-react/src/DocSearch.tsx index c98831227..1e3ce73ec 100644 --- a/packages/docsearch-react/src/DocSearch.tsx +++ b/packages/docsearch-react/src/DocSearch.tsx @@ -51,6 +51,8 @@ export function DocSearch({ getInputProps, getMenuProps, getItemProps, + setQuery, + refresh, } = React.useMemo( () => createAutocomplete< @@ -66,7 +68,7 @@ export function DocSearch({ onStateChange({ state }) { setState(state as any); }, - getSources({ query }) { + getSources({ query, state, setContext }) { return getAlgoliaHits({ searchClient, queries: [ @@ -117,6 +119,14 @@ export function DocSearch({ }); const sources = groupBy(formattedHits, hit => hit.hierarchy.lvl0); + // We store the `lvl0`s to display them as search suggestions + // in the “no results“ screen. + if (state.context.searchSuggestions === undefined) { + setContext({ + searchSuggestions: Object.keys(sources), + }); + } + return Object.values(sources).map(items => { return { onSelect() { @@ -220,9 +230,12 @@ export function DocSearch({
diff --git a/packages/docsearch-react/src/Dropdown/Dropdown.tsx b/packages/docsearch-react/src/Dropdown/Dropdown.tsx index 9aefaa8a7..889acf403 100644 --- a/packages/docsearch-react/src/Dropdown/Dropdown.tsx +++ b/packages/docsearch-react/src/Dropdown/Dropdown.tsx @@ -14,6 +14,9 @@ interface DropdownProps { state: AutocompleteState; getMenuProps: GetMenuProps; getItemProps: GetItemProps; + setQuery(value: string): void; + refresh(): Promise; + inputRef: React.MutableRefObject; } export function Dropdown(props: DropdownProps) { @@ -25,7 +28,14 @@ export function Dropdown(props: DropdownProps) { props.state.status === 'idle' && props.state.suggestions.every(source => source.items.length === 0) ) { - return ; + return ( + + ); } return ( diff --git a/packages/docsearch-react/src/NoResults/NoResults.tsx b/packages/docsearch-react/src/NoResults/NoResults.tsx index 3c2793e36..a296ea1f7 100644 --- a/packages/docsearch-react/src/NoResults/NoResults.tsx +++ b/packages/docsearch-react/src/NoResults/NoResults.tsx @@ -1,12 +1,58 @@ import React from 'react'; +import { AutocompleteState } from '@francoischalifour/autocomplete-core'; interface NoResultsProps { - query: string; + state: AutocompleteState; + setQuery(value: string): void; + refresh(): Promise; + inputRef: React.MutableRefObject; } export function NoResults(props: NoResultsProps) { - return
-
No results for “{props.query}“.
-
Try another search or if you believe this query should lead to actual results, please let us know with a GitHub issue.
-
; + return ( +
+

+ No results for “{props.state.query}“. +

+ +

+ Try searching for{' '} + {(props.state.context.searchSuggestions as string[]) + .slice(0, 3) + .reduce( + (acc, search) => [ + ...acc, + acc.length > 0 ? ', ' : '', + '“', + , + '“', + ], + [] + )} + . +

+ +

+ If you believe this query should return results, please{' '} + + let us know on GitHub + + . +

+
+ ); } diff --git a/packages/docsearch-react/src/style.css b/packages/docsearch-react/src/style.css index 8ae3e3e89..fc01c325e 100644 --- a/packages/docsearch-react/src/style.css +++ b/packages/docsearch-react/src/style.css @@ -169,6 +169,17 @@ html[data-theme='dark'] { text-decoration: none; } +.DocSearch-Link { + appearance: none; + background: none; + border: none; + color: var(--docsearch-highlight-color); + cursor: pointer; + font: inherit; + margin: 0; + padding: 0; +} + .DocSearch-Modal { position: relative; flex-direction: column;