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

feat(docsearch): add recent searches #40

Merged
merged 65 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
dc2f694
chore: put some base work
eunjae-lee Mar 5, 2020
d6a8568
chore: add basic styles
eunjae-lee Mar 5, 2020
b85bbc4
chore: close on clicking bg
eunjae-lee Mar 5, 2020
cedf054
fix: setup React and DocSearch environment
francoischalifour Mar 6, 2020
a56dd8c
feat(docsearch): init
francoischalifour Mar 9, 2020
fbad419
fix: remove empty `onMouseLeave` which is handled in another prop getter
francoischalifour Mar 10, 2020
edb373e
feat(docsearch): use Highlight component
francoischalifour Mar 10, 2020
3bf4855
feat(docsearch): design search button
Shipow Mar 10, 2020
45ef5e5
feat(docsearch): design footer
Shipow Mar 10, 2020
3e2e63b
feat(docsearch): suggestion template + css
Shipow Mar 11, 2020
9d59991
feat(docsearch): design
Shipow Mar 11, 2020
dd8a2a5
feat(docsearch): design
Shipow Mar 11, 2020
3d12237
feat(docsearch): design more modal looking
Shipow Mar 12, 2020
d0225b8
feat(docsearch): group records by level
francoischalifour Mar 23, 2020
12cacb1
css WIP
Shipow Mar 24, 2020
03ea766
css
Shipow Mar 24, 2020
bf0d6a0
css
Shipow Mar 24, 2020
e9ade13
css
Shipow Mar 24, 2020
425a0af
css
Shipow Mar 24, 2020
bcb81a3
css + icons
Shipow Mar 24, 2020
3cc8a3d
search button
Shipow Mar 25, 2020
3d122fa
skin search button
Shipow Mar 25, 2020
9d3a4bd
feat: introduce tree design
francoischalifour Mar 25, 2020
0dbff3f
feat: create source for each lvl0
francoischalifour Mar 25, 2020
c3d5ea9
refactor: create component architecture
francoischalifour Mar 25, 2020
2458744
feat: support touch events
francoischalifour Mar 25, 2020
d49fe79
feat: use snippeted attributes
francoischalifour Mar 25, 2020
141b72e
fix: align content source icon
francoischalifour Mar 25, 2020
d28a704
feat: change logo color in dark mode
francoischalifour Mar 25, 2020
ea70dae
feat: prepare screens for error and no results
francoischalifour Mar 25, 2020
b89658f
fix: don't highlight if no items
francoischalifour Mar 25, 2020
fc0f34e
fix(mobile): don't blur if target is input
francoischalifour Mar 25, 2020
cf0efd7
fix: hit source spacing + tree icon positioning
Shipow Mar 26, 2020
102cd65
feat: responsive WIP
Shipow Mar 26, 2020
e443a9e
fix: retrieve necessary attributes
francoischalifour Mar 26, 2020
d20a2ab
feat(docsearch): display Command key on Mac-like machines only
francoischalifour Mar 26, 2020
51b41bc
fix: typescript error
francoischalifour Mar 26, 2020
b5f221e
feat(docsearch): prevent body scroll when opened
francoischalifour Mar 26, 2020
500465f
fix: css mobile + tree icon color
Shipow Mar 26, 2020
d3445a7
fix: responsive
Shipow Mar 26, 2020
b599957
fix: try hotfix vh browser ios
Shipow Mar 26, 2020
a3020c0
fix(docsearch): add Docusaurus hamburger menu
francoischalifour Mar 26, 2020
448bc53
feat(docsearch): add Cancel button on mobile
francoischalifour Mar 26, 2020
ca77e48
fix: css
Shipow Mar 26, 2020
b29ee7d
feat(docsearch): include SearchButton in library
francoischalifour Mar 26, 2020
c945b56
fix(docsearch): title case Ctrl key
francoischalifour Mar 26, 2020
7d3f864
feat(docsearch): decrease snippet length on mobile
francoischalifour Mar 26, 2020
1acb585
fic: css
Shipow Mar 26, 2020
f8afe92
fix: mobile searchbar cancel
Shipow Mar 26, 2020
7526967
fix: wrong calc for dropdown height
Shipow Mar 26, 2020
2b81258
feat(docsearch): scroll to top of modal on query change
francoischalifour Mar 26, 2020
86120b6
fix(docsearch): fix display for all levels
francoischalifour Mar 26, 2020
db000c7
fix(docsearch): don't escape snippeted hits
francoischalifour Mar 26, 2020
9fb131f
feat(docsearch): attach Algolia user agents to search client
francoischalifour Mar 26, 2020
c4af4cb
feat: apparently there is a new position in CSS3 ?!!
Shipow Mar 26, 2020
45cd692
fix: cancel button mobile flex
Shipow Mar 26, 2020
8c3c8f7
fix: cancel button mobile flex bis
Shipow Mar 26, 2020
b31f34b
fix: design mobile cancel button + modal footer
Shipow Mar 27, 2020
9f9c61e
In progress...
francoischalifour Mar 31, 2020
2c37a97
Merge branch 'next' into feat/recent-searches
francoischalifour Apr 1, 2020
4766570
tmp
francoischalifour Apr 1, 2020
5d16212
tmp
francoischalifour Apr 2, 2020
ed2fac8
Merge branch 'next' into feat/recent-searches
francoischalifour Apr 2, 2020
275b05e
feat(docsearch): add recent searches
francoischalifour Apr 2, 2020
2a54b18
fix: types
francoischalifour Apr 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 43 additions & 23 deletions packages/docsearch-react/src/DocSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import {
} from '@francoischalifour/autocomplete-core';
import { getAlgoliaHits } from '@francoischalifour/autocomplete-preset-algolia';

import { DocSearchHit, InternalDocSearchHit } from './types';
import {
DocSearchHit,
InternalDocSearchHit,
RecentDocSearchHit,
} from './types';
import { createSearchClient, groupBy, noop } from './utils';
import { SearchBox } from './SearchBox';
import { Dropdown } from './Dropdown';
import { Footer } from './Footer';

import { createRecentSearches } from './recent-searches';

interface DocSearchProps {
appId?: string;
apiKey: string;
Expand Down Expand Up @@ -42,18 +48,9 @@ export function DocSearch({
appId,
apiKey,
]);
const recentSearches = useRef(createRecentSearches<RecentDocSearchHit>());

const {
getEnvironmentProps,
getRootProps,
getFormProps,
getLabelProps,
getInputProps,
getMenuProps,
getItemProps,
setQuery,
refresh,
} = React.useMemo(
const autocomplete = React.useMemo(
() =>
createAutocomplete<
InternalDocSearchHit,
Expand Down Expand Up @@ -127,9 +124,27 @@ export function DocSearch({
});
}

if (!query) {
return [
{
onSelect({ suggestion }) {
recentSearches.current.saveSearch(suggestion);
onClose();
},
getSuggestionUrl({ suggestion }) {
return suggestion.url;
},
getSuggestions() {
return recentSearches.current.getSearches();
},
},
];
}

return Object.values<DocSearchHit[]>(sources).map(items => {
return {
onSelect() {
onSelect({ suggestion }) {
recentSearches.current.saveSearch(suggestion);
onClose();
},
getSuggestionUrl({ suggestion }) {
Expand Down Expand Up @@ -165,6 +180,8 @@ export function DocSearch({
[indexName, searchParameters, searchClient, onClose]
);

const { getEnvironmentProps, getRootProps } = autocomplete;

useEffect(() => {
const isMobileMediaQuery = window.matchMedia('(max-width: 750px)');

Expand Down Expand Up @@ -219,23 +236,26 @@ export function DocSearch({
<div className="DocSearch-Modal">
<header className="DocSearch-SearchBar" ref={searchBoxRef}>
<SearchBox
inputRef={inputRef}
query={state.query}
getFormProps={getFormProps}
getLabelProps={getLabelProps}
getInputProps={getInputProps}
{...autocomplete}
state={state}
onClose={onClose}
inputRef={inputRef}
/>
</header>

<div className="DocSearch-Dropdown" ref={dropdownRef}>
<Dropdown
inputRef={inputRef}
{...autocomplete}
state={state}
getMenuProps={getMenuProps}
getItemProps={getItemProps}
setQuery={setQuery}
refresh={refresh}
inputRef={inputRef}
onItemClick={item => {
recentSearches.current.saveSearch(item);
onClose();
}}
onAction={item => {
recentSearches.current.deleteSearch(item);
autocomplete.refresh();
}}
/>
</div>

Expand Down
59 changes: 30 additions & 29 deletions packages/docsearch-react/src/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,49 @@
import React from 'react';
import {
AutocompleteApi,
AutocompleteState,
GetMenuProps,
GetItemProps,
} from '@francoischalifour/autocomplete-core';

import { InternalDocSearchHit } from '../types';
import { Error } from '../Error';
import { NoResults } from '../NoResults';
import { InternalDocSearchHit, RecentDocSearchHit } from '../types';
import { EmptyScreen } from '../EmptyScreen';
import { Results } from '../Results';
import { NoResults } from '../NoResults';
import { Error } from '../Error';

interface DropdownProps {
state: AutocompleteState<InternalDocSearchHit>;
getMenuProps: GetMenuProps;
getItemProps: GetItemProps<InternalDocSearchHit, React.MouseEvent>;
setQuery(value: string): void;
refresh(): Promise<void>;
inputRef: React.MutableRefObject<HTMLInputElement>;
interface DropdownProps<TItem>
extends AutocompleteApi<
TItem,
React.FormEvent,
React.MouseEvent,
React.KeyboardEvent
> {
state: AutocompleteState<TItem>;
onItemClick(search: RecentDocSearchHit): void;
onAction(search: RecentDocSearchHit): void;
inputRef: React.MutableRefObject<null | HTMLInputElement>;
}

export function Dropdown(props: DropdownProps) {
export function Dropdown(props: DropdownProps<InternalDocSearchHit>) {
if (props.state.status === 'error') {
return <Error />;
}

if (
props.state.status === 'idle' &&
props.state.suggestions.every(source => source.items.length === 0)
) {
const hasSuggestions = props.state.suggestions.some(
source => source.items.length > 0
);

if (!props.state.query) {
return (
<NoResults
setQuery={props.setQuery}
refresh={props.refresh}
state={props.state}
inputRef={props.inputRef}
<EmptyScreen
{...(props as DropdownProps<any>)}
hasSuggestions={hasSuggestions}
/>
);
}

return (
<Results
suggestions={props.state.suggestions}
getMenuProps={props.getMenuProps}
getItemProps={props.getItemProps}
/>
);
if (props.state.status === 'idle' && hasSuggestions === false) {
return <NoResults {...props} />;
}

return <Results {...props} />;
}
147 changes: 147 additions & 0 deletions packages/docsearch-react/src/EmptyScreen/EmptyScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React from 'react';
import {
AutocompleteApi,
AutocompleteState,
} from '@francoischalifour/autocomplete-core';

import { RecentDocSearchHit } from '../types';

interface EmptyScreenProps
extends AutocompleteApi<
RecentDocSearchHit,
React.FormEvent,
React.MouseEvent,
React.KeyboardEvent
> {
state: AutocompleteState<RecentDocSearchHit>;
hasSuggestions: boolean;
onItemClick(item: RecentDocSearchHit): void;
onAction(search: RecentDocSearchHit): void;
}

export function EmptyScreen(props: EmptyScreenProps) {
if (props.state.status === 'idle' && props.hasSuggestions === false) {
return (
<div>
<p>Select results and your history will appear here.</p>
</div>
);
}

return (
<div className="DocSearch-Dropdown-Container">
{props.state.suggestions.map(({ source, items }, index) => {
return (
<section key={['recent', index].join(':')} className="DocSearch-Hits">
<div className="DocSearch-Hit-source">Recent</div>

<ul {...props.getMenuProps()}>
{items.map(item => {
return (
<li
key={['recent', item.objectID].join(':')}
className="DocSearch-Hit"
{...props.getItemProps({
item,
source,
onClick() {
props.onItemClick(item);
},
})}
>
<a href={item.url}>
<div className="DocSearch-Hit-Container">
<div className="DocSearch-Hit-icon">
<svg width="20" height="20">
<g
stroke="currentColor"
strokeWidth="2"
fill="none"
fillRule="evenodd"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M3.18 6.6a8.23 8.23 0 1112.93 9.94h0a8.23 8.23 0 01-11.63 0" />
<path d="M6.44 7.25H2.55V3.36M10.45 6v5.6M10.45 11.6L13 13" />
</g>
</svg>
</div>

{item.hierarchy[item.type] && item.type === 'lvl1' && (
<div className="DocSearch-Hit-content-wrapper">
<span className="DocSearch-Hit-title">
{item.hierarchy.lvl1}
</span>
{item.content && (
<span className="DocSearch-Hit-path">
{item.content}
</span>
)}
</div>
)}

{item.hierarchy[item.type] &&
(item.type === 'lvl2' ||
item.type === 'lvl3' ||
item.type === 'lvl4' ||
item.type === 'lvl5' ||
item.type === 'lvl6') && (
<div className="DocSearch-Hit-content-wrapper">
<span className="DocSearch-Hit-title">
{item.hierarchy[item.type]}
</span>
<span className="DocSearch-Hit-path">
{item.hierarchy.lvl1}
</span>
</div>
)}

{item.type === 'content' && (
<div className="DocSearch-Hit-content-wrapper">
<span className="DocSearch-Hit-title">
{item.content}
</span>
<span className="DocSearch-Hit-path">
{item.hierarchy.lvl1}
</span>
</div>
)}

<div className="DocSearch-Hit-action">
<button
className="DocSearch-Hit-action-button"
title="Delete this search"
onClick={event => {
event.preventDefault();
event.stopPropagation();

props.onAction(item);
}}
>
<svg width="20" height="20">
<g
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<path
d="M10,10 L15.0853291,4.91467086 L10,10 L15.0853291,15.0853291 L10,10 Z M10,10 L4.91467086,4.91467086 L10,10 L4.91467086,15.0853291 L10,10 Z"
transform="translate(10.000000, 10.000000) rotate(-360.000000) translate(-10.000000, -10.000000) "
></path>
</g>
</svg>
</button>
</div>
</div>
</a>
</li>
);
})}
</ul>
</section>
);
})}
</div>
);
}
1 change: 1 addition & 0 deletions packages/docsearch-react/src/EmptyScreen/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './EmptyScreen';
23 changes: 16 additions & 7 deletions packages/docsearch-react/src/NoResults/NoResults.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import React from 'react';
import { AutocompleteState } from '@francoischalifour/autocomplete-core';
import {
AutocompleteApi,
AutocompleteState,
} from '@francoischalifour/autocomplete-core';

interface NoResultsProps {
state: AutocompleteState<any>;
setQuery(value: string): void;
refresh(): Promise<void>;
inputRef: React.MutableRefObject<HTMLInputElement>;
import { InternalDocSearchHit } from '../types';

interface NoResultsProps
extends AutocompleteApi<
InternalDocSearchHit,
React.FormEvent,
React.MouseEvent,
React.KeyboardEvent
> {
state: AutocompleteState<InternalDocSearchHit>;
inputRef: React.MutableRefObject<null | HTMLInputElement>;
}

export function NoResults(props: NoResultsProps) {
Expand All @@ -30,7 +39,7 @@ export function NoResults(props: NoResultsProps) {
onClick={() => {
props.setQuery(search.toLowerCase() + ' ');
props.refresh();
props.inputRef.current.focus();
props.inputRef.current!.focus();
}}
>
{search}
Expand Down
Loading