Skip to content
This repository has been archived by the owner on Jun 11, 2021. It is now read-only.

Commit

Permalink
feat(docsearch): add search suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
francoischalifour committed Apr 1, 2020
1 parent 53f634d commit d1fe8b2
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 8 deletions.
16 changes: 16 additions & 0 deletions packages/autocomplete-core/src/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {},
Expand Down Expand Up @@ -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,
Expand All @@ -60,6 +75,7 @@ function createAutocomplete<
getDropdownProps,
getMenuProps,
getItemProps,
refresh,
};
}

Expand Down
7 changes: 6 additions & 1 deletion packages/autocomplete-core/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ export interface AutocompleteApi<
TEvent,
TMouseEvent,
TKeyboardEvent
> {}
> {
/**
* Triggers a search to refresh the state.
*/
refresh(): Promise<void>;
}

export interface AutocompleteSuggestion<TItem> {
source: AutocompleteSource<TItem>;
Expand Down
15 changes: 14 additions & 1 deletion packages/docsearch-react/src/DocSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export function DocSearch({
getInputProps,
getMenuProps,
getItemProps,
setQuery,
refresh,
} = React.useMemo(
() =>
createAutocomplete<
Expand All @@ -66,7 +68,7 @@ export function DocSearch({
onStateChange({ state }) {
setState(state as any);
},
getSources({ query }) {
getSources({ query, state, setContext }) {
return getAlgoliaHits({
searchClient,
queries: [
Expand Down Expand Up @@ -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<DocSearchHit[]>(sources).map(items => {
return {
onSelect() {
Expand Down Expand Up @@ -220,9 +230,12 @@ export function DocSearch({

<div className="DocSearch-Dropdown" ref={dropdownRef}>
<Dropdown
inputRef={inputRef}
state={state}
getMenuProps={getMenuProps}
getItemProps={getItemProps}
setQuery={setQuery}
refresh={refresh}
/>
</div>

Expand Down
12 changes: 11 additions & 1 deletion packages/docsearch-react/src/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ interface DropdownProps {
state: AutocompleteState<InternalDocSearchHit>;
getMenuProps: GetMenuProps;
getItemProps: GetItemProps<InternalDocSearchHit, React.MouseEvent>;
setQuery(value: string): void;
refresh(): Promise<void>;
inputRef: React.MutableRefObject<HTMLInputElement>;
}

export function Dropdown(props: DropdownProps) {
Expand All @@ -25,7 +28,14 @@ export function Dropdown(props: DropdownProps) {
props.state.status === 'idle' &&
props.state.suggestions.every(source => source.items.length === 0)
) {
return <NoResults query={props.state.query} />;
return (
<NoResults
setQuery={props.setQuery}
refresh={props.refresh}
state={props.state}
inputRef={props.inputRef}
/>
);
}

return (
Expand Down
56 changes: 51 additions & 5 deletions packages/docsearch-react/src/NoResults/NoResults.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,58 @@
import React from 'react';
import { AutocompleteState } from '@francoischalifour/autocomplete-core';

interface NoResultsProps {
query: string;
state: AutocompleteState<any>;
setQuery(value: string): void;
refresh(): Promise<void>;
inputRef: React.MutableRefObject<HTMLInputElement>;
}

export function NoResults(props: NoResultsProps) {
return <div className="DocSearch-NoResults">
<div className="Docsearch-Hit-title">No results for “{props.query}“.</div>
<div className="DocSearch-Label">Try another search or if you believe this query should lead to actual results, please let us know with a <a href="">GitHub issue</a>.</div>
</div>;
return (
<div className="DocSearch-NoResults">
<p className="Docsearch-Hit-title">
No results for “{props.state.query}“.
</p>

<p>
Try searching for{' '}
{(props.state.context.searchSuggestions as string[])
.slice(0, 3)
.reduce<React.ReactNode[]>(
(acc, search) => [
...acc,
acc.length > 0 ? ', ' : '',
'“',
<button
className="DocSearch-Link"
key={search}
onClick={() => {
props.setQuery(search.toLowerCase() + ' ');
props.refresh();
props.inputRef.current.focus();
}}
>
{search}
</button>,
'“',
],
[]
)}
.
</p>

<p className="DocSearch-Label">
If you believe this query should return results, please{' '}
<a
href="https://github.com/algolia/docsearch-configs/issues/new?template=Missing_results.md"
target="_blank"
rel="noopener noreferrer"
>
let us know on GitHub
</a>
.
</p>
</div>
);
}
11 changes: 11 additions & 0 deletions packages/docsearch-react/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit d1fe8b2

Please sign in to comment.