From fa42ab2bd9d8ae405c724b25b7c97050ddf7106b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 31 Mar 2020 17:08:28 +0200 Subject: [PATCH 001/151] feat(docsearch): add DocSearch for Docusaurus (#39) Co-authored-by: eunjae-lee Co-authored-by: Shipow --- babel.config.js | 27 ++ package.json | 44 ++ rollup.config.js | 20 + src/DocSearch.tsx | 235 ++++++++++ src/Dropdown/Dropdown.tsx | 38 ++ src/Dropdown/index.ts | 1 + src/Error/Error.tsx | 10 + src/Error/index.ts | 1 + src/Footer/AlgoliaLogo.tsx | 20 + src/Footer/Footer.tsx | 64 +++ src/Footer/index.ts | 1 + src/Footer/index.tsx | 1 + src/NoResults/NoResults.tsx | 12 + src/NoResults/index.ts | 1 + src/Results/ActionIcon.tsx | 31 ++ src/Results/Results.tsx | 147 +++++++ src/Results/SourceIcon.tsx | 58 +++ src/Results/index.ts | 1 + src/SearchBox/LoadingIcon.tsx | 23 + src/SearchBox/ResetIcon.tsx | 13 + src/SearchBox/SearchBox.tsx | 74 ++++ src/SearchBox/SearchIcon.tsx | 17 + src/SearchBox/index.tsx | 1 + src/SearchButton/SearchButton.tsx | 57 +++ src/SearchButton/index.ts | 1 + src/index.ts | 2 + src/style.css | 543 ++++++++++++++++++++++++ src/svg/icon_action_external.svg | 11 + src/svg/icon_action_goto.svg | 14 + src/svg/icon_source_anchor.svg | 14 + src/svg/icon_source_content.svg | 13 + src/svg/icon_source_page.svg | 11 + src/svg/icon_source_recent.svg | 16 + src/svg/icon_tree_child.svg | 12 + src/svg/icon_tree_last-child.svg | 12 + src/svg/light_footer_key_arrow-down.svg | 12 + src/svg/light_footer_key_arrow-up.svg | 12 + src/svg/light_footer_key_enter.svg | 14 + src/svg/light_footer_key_esc.svg | 11 + src/svg/logo-alogia.svg | 11 + src/types/DocSearchHit.ts | 81 ++++ src/types/InternalDocSearchHit.tsx | 5 + src/types/index.ts | 2 + src/utils/createSearchClient.ts | 13 + src/utils/groupBy.ts | 16 + src/utils/index.ts | 3 + src/utils/noop.ts | 1 + src/version.ts | 1 + tsconfig.declaration.json | 8 + tsconfig.json | 3 + 50 files changed, 1739 insertions(+) create mode 100644 babel.config.js create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 src/DocSearch.tsx create mode 100644 src/Dropdown/Dropdown.tsx create mode 100644 src/Dropdown/index.ts create mode 100644 src/Error/Error.tsx create mode 100644 src/Error/index.ts create mode 100644 src/Footer/AlgoliaLogo.tsx create mode 100644 src/Footer/Footer.tsx create mode 100644 src/Footer/index.ts create mode 100644 src/Footer/index.tsx create mode 100644 src/NoResults/NoResults.tsx create mode 100644 src/NoResults/index.ts create mode 100644 src/Results/ActionIcon.tsx create mode 100644 src/Results/Results.tsx create mode 100644 src/Results/SourceIcon.tsx create mode 100644 src/Results/index.ts create mode 100644 src/SearchBox/LoadingIcon.tsx create mode 100644 src/SearchBox/ResetIcon.tsx create mode 100644 src/SearchBox/SearchBox.tsx create mode 100644 src/SearchBox/SearchIcon.tsx create mode 100644 src/SearchBox/index.tsx create mode 100644 src/SearchButton/SearchButton.tsx create mode 100644 src/SearchButton/index.ts create mode 100644 src/index.ts create mode 100644 src/style.css create mode 100644 src/svg/icon_action_external.svg create mode 100644 src/svg/icon_action_goto.svg create mode 100644 src/svg/icon_source_anchor.svg create mode 100644 src/svg/icon_source_content.svg create mode 100644 src/svg/icon_source_page.svg create mode 100644 src/svg/icon_source_recent.svg create mode 100644 src/svg/icon_tree_child.svg create mode 100644 src/svg/icon_tree_last-child.svg create mode 100644 src/svg/light_footer_key_arrow-down.svg create mode 100644 src/svg/light_footer_key_arrow-up.svg create mode 100644 src/svg/light_footer_key_enter.svg create mode 100644 src/svg/light_footer_key_esc.svg create mode 100644 src/svg/logo-alogia.svg create mode 100644 src/types/DocSearchHit.ts create mode 100644 src/types/InternalDocSearchHit.tsx create mode 100644 src/types/index.ts create mode 100644 src/utils/createSearchClient.ts create mode 100644 src/utils/groupBy.ts create mode 100644 src/utils/index.ts create mode 100644 src/utils/noop.ts create mode 100644 src/version.ts create mode 100644 tsconfig.declaration.json create mode 100644 tsconfig.json diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 00000000..7d68e20c --- /dev/null +++ b/babel.config.js @@ -0,0 +1,27 @@ +/* eslint-disable import/no-commonjs */ + +module.exports = api => { + const isTest = api.env('test'); + const modules = isTest ? 'commonjs' : false; + const targets = {}; + + if (isTest) { + targets.node = true; + } else { + targets.browsers = ['last 2 versions', 'ie >= 9']; + } + + return { + presets: [ + '@babel/preset-typescript', + [ + '@babel/preset-env', + { + modules, + targets, + }, + ], + ], + plugins: [['@babel/plugin-transform-react-jsx']], + }; +}; diff --git a/package.json b/package.json new file mode 100644 index 00000000..d9e6bdc6 --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "docsearch-react", + "version": "1.0.0-alpha.10", + "license": "MIT", + "homepage": "https://github.com/algolia/autocomplete.js", + "repository": "algolia/autocomplete.js", + "author": { + "name": "Algolia, Inc.", + "url": "https://www.algolia.com" + }, + "sideEffects": false, + "files": [ + "dist/" + ], + "source": "src/index.ts", + "types": "dist/esm/index.d.ts", + "module": "dist/esm/index.js", + "main": "dist/umd/index.js", + "umd:main": "dist/umd/index.js", + "unpkg": "dist/umd/index.js", + "jsdelivr": "dist/umd/index.js", + "scripts": { + "build": "yarn build:clean && yarn build:umd && yarn build:esm && yarn build:css", + "": "// TODO: have a proper build:css, probably including autoprefixer?", + "build:css": "cp src/style.css dist/esm/ && cp src/style.css dist/umd/", + "build:css:watch": "chokidar src/style.css --command \"yarn build:css\"", + "build:esm": "babel src --root-mode upward --extensions '.ts,.tsx' --out-dir dist/esm", + "build:esm:watch": "yarn build:esm --watch", + "build:umd": "rollup --config", + "build:types": "tsc -p ./tsconfig.declaration.json --outDir ./dist/esm", + "build:types:watch": "chokidar \"**/*.ts\" \"**/*.tsx\" --command \"yarn build:types\" --ignore \"dist\"", + "build:clean": "rm -rf ./dist", + "watch": "concurrently \"yarn build:esm:watch\" \"yarn build:types:watch\" \"yarn build:css:watch\"" + }, + "dependencies": { + "@francoischalifour/autocomplete-core": "^1.0.0-alpha.10", + "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.10", + "algoliasearch": "^4.0.0" + }, + "peerDependencies": { + "react": "^16.8.0", + "react-dom": "^16.8.0" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 00000000..a64b3b2b --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,20 @@ +import json from '@rollup/plugin-json'; + +import { name } from './package.json'; +import { sharedPlugins } from '../autocomplete-core/rollup.config'; + +export default { + input: 'src/index.ts', + external: ['react', 'react-dom'], + output: { + file: 'dist/umd/index.js', + format: 'umd', + sourcemap: true, + name, + globals: { + react: 'React', + 'react-dom': 'ReactDOM', + }, + }, + plugins: [json(), ...sharedPlugins], +}; diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx new file mode 100644 index 00000000..c9883122 --- /dev/null +++ b/src/DocSearch.tsx @@ -0,0 +1,235 @@ +import React, { useRef, useEffect } from 'react'; +import { + createAutocomplete, + AutocompleteState, +} from '@francoischalifour/autocomplete-core'; +import { getAlgoliaHits } from '@francoischalifour/autocomplete-preset-algolia'; + +import { DocSearchHit, InternalDocSearchHit } from './types'; +import { createSearchClient, groupBy, noop } from './utils'; +import { SearchBox } from './SearchBox'; +import { Dropdown } from './Dropdown'; +import { Footer } from './Footer'; + +interface DocSearchProps { + appId?: string; + apiKey: string; + indexName: string; + searchParameters: any; + onClose(): void; +} + +export function DocSearch({ + appId = 'BH4D9OD16A', + apiKey, + indexName, + searchParameters, + onClose = noop, +}: DocSearchProps) { + const [state, setState] = React.useState< + AutocompleteState + >({ + query: '', + suggestions: [], + } as any); + + const searchBoxRef = useRef(null); + const dropdownRef = useRef(null); + const inputRef = useRef(null); + const snipetLength = useRef(10); + + const searchClient = React.useMemo(() => createSearchClient(appId, apiKey), [ + appId, + apiKey, + ]); + + const { + getEnvironmentProps, + getRootProps, + getFormProps, + getLabelProps, + getInputProps, + getMenuProps, + getItemProps, + } = React.useMemo( + () => + createAutocomplete< + InternalDocSearchHit, + React.FormEvent, + React.MouseEvent, + React.KeyboardEvent + >({ + defaultHighlightedIndex: 0, + autoFocus: true, + placeholder: 'Search docs...', + openOnFocus: true, + onStateChange({ state }) { + setState(state as any); + }, + getSources({ query }) { + return getAlgoliaHits({ + searchClient, + queries: [ + { + indexName, + query, + params: { + attributesToRetrieve: [ + 'hierarchy.lvl0', + 'hierarchy.lvl1', + 'hierarchy.lvl2', + 'hierarchy.lvl3', + 'hierarchy.lvl4', + 'hierarchy.lvl5', + 'hierarchy.lvl6', + 'content', + 'type', + 'url', + ], + attributesToSnippet: [ + `hierarchy.lvl1:${snipetLength.current}`, + `hierarchy.lvl2:${snipetLength.current}`, + `hierarchy.lvl3:${snipetLength.current}`, + `hierarchy.lvl4:${snipetLength.current}`, + `hierarchy.lvl5:${snipetLength.current}`, + `hierarchy.lvl6:${snipetLength.current}`, + `content:${snipetLength.current}`, + ], + snippetEllipsisText: '…', + highlightPreTag: '', + highlightPostTag: '', + hitsPerPage: 20, + distinct: 4, + ...searchParameters, + }, + }, + ], + }).then((hits: DocSearchHit[]) => { + const formattedHits = hits.map(hit => { + const url = new URL(hit.url); + return { + ...hit, + url: hit.url + // @TODO: temporary convenience for development. + .replace(url.origin, '') + .replace('#__docusaurus', ''), + }; + }); + const sources = groupBy(formattedHits, hit => hit.hierarchy.lvl0); + + return Object.values(sources).map(items => { + return { + onSelect() { + onClose(); + }, + getSuggestionUrl({ suggestion }) { + return suggestion.url; + }, + getSuggestions() { + return Object.values( + groupBy(items, item => item.hierarchy.lvl1) + ) + .map(hits => + hits.map(item => { + return { + ...item, + // eslint-disable-next-line @typescript-eslint/camelcase + __docsearch_parent: + item.type !== 'lvl1' && + hits.find( + siblingItem => + siblingItem.type === 'lvl1' && + siblingItem.hierarchy.lvl1 === + item.hierarchy.lvl1 + ), + }; + }) + ) + .flat(); + }, + }; + }); + }); + }, + }), + [indexName, searchParameters, searchClient, onClose] + ); + + useEffect(() => { + const isMobileMediaQuery = window.matchMedia('(max-width: 750px)'); + + if (isMobileMediaQuery.matches) { + snipetLength.current = 5; + } + }, []); + + useEffect(() => { + if (dropdownRef.current) { + dropdownRef.current.scrollTop = 0; + } + }, [state.query]); + + useEffect(() => { + if (!(searchBoxRef.current && dropdownRef.current && inputRef.current)) { + return undefined; + } + + const { onTouchStart, onTouchMove } = getEnvironmentProps({ + searchBoxElement: searchBoxRef.current, + dropdownElement: dropdownRef.current, + inputElement: inputRef.current, + }); + + window.addEventListener('touchstart', onTouchStart); + window.addEventListener('touchmove', onTouchMove); + + return () => { + window.removeEventListener('touchstart', onTouchStart); + window.removeEventListener('touchmove', onTouchMove); + }; + }, [getEnvironmentProps, searchBoxRef, dropdownRef, inputRef]); + + return ( +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ ); +} diff --git a/src/Dropdown/Dropdown.tsx b/src/Dropdown/Dropdown.tsx new file mode 100644 index 00000000..9aefaa8a --- /dev/null +++ b/src/Dropdown/Dropdown.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { + AutocompleteState, + GetMenuProps, + GetItemProps, +} from '@francoischalifour/autocomplete-core'; + +import { InternalDocSearchHit } from '../types'; +import { Error } from '../Error'; +import { NoResults } from '../NoResults'; +import { Results } from '../Results'; + +interface DropdownProps { + state: AutocompleteState; + getMenuProps: GetMenuProps; + getItemProps: GetItemProps; +} + +export function Dropdown(props: DropdownProps) { + if (props.state.status === 'error') { + return ; + } + + if ( + props.state.status === 'idle' && + props.state.suggestions.every(source => source.items.length === 0) + ) { + return ; + } + + return ( + + ); +} diff --git a/src/Dropdown/index.ts b/src/Dropdown/index.ts new file mode 100644 index 00000000..2f29bad4 --- /dev/null +++ b/src/Dropdown/index.ts @@ -0,0 +1 @@ +export * from './Dropdown'; diff --git a/src/Error/Error.tsx b/src/Error/Error.tsx new file mode 100644 index 00000000..12d983b2 --- /dev/null +++ b/src/Error/Error.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +export function Error() { + return ( +

+ We‘re unable to fetch results. You might want to check your network + connection. +

+ ); +} diff --git a/src/Error/index.ts b/src/Error/index.ts new file mode 100644 index 00000000..ae6e95d0 --- /dev/null +++ b/src/Error/index.ts @@ -0,0 +1 @@ +export * from './Error'; diff --git a/src/Footer/AlgoliaLogo.tsx b/src/Footer/AlgoliaLogo.tsx new file mode 100644 index 00000000..7690b5f7 --- /dev/null +++ b/src/Footer/AlgoliaLogo.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +export function AlgoliaLogo() { + return ( + + Search by + + + + + ); +} diff --git a/src/Footer/Footer.tsx b/src/Footer/Footer.tsx new file mode 100644 index 00000000..7e098632 --- /dev/null +++ b/src/Footer/Footer.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +import { AlgoliaLogo } from './AlgoliaLogo'; + +export function Footer() { + return ( + <> +
+ +
+
    +
  • + + + + + + to select +
  • +
  • + + + + + + + + + + + to navigate +
  • +
  • + + + + + + to close +
  • +
+ + ); +} + +interface CommandIconProps { + children: React.ReactNode; +} + +function CommandIcon(props: CommandIconProps) { + return ( + + + {props.children} + + + ); +} diff --git a/src/Footer/index.ts b/src/Footer/index.ts new file mode 100644 index 00000000..ddcc5a9c --- /dev/null +++ b/src/Footer/index.ts @@ -0,0 +1 @@ +export * from './Footer'; diff --git a/src/Footer/index.tsx b/src/Footer/index.tsx new file mode 100644 index 00000000..ddcc5a9c --- /dev/null +++ b/src/Footer/index.tsx @@ -0,0 +1 @@ +export * from './Footer'; diff --git a/src/NoResults/NoResults.tsx b/src/NoResults/NoResults.tsx new file mode 100644 index 00000000..3c2793e3 --- /dev/null +++ b/src/NoResults/NoResults.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +interface NoResultsProps { + query: string; +} + +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.
+
; +} diff --git a/src/NoResults/index.ts b/src/NoResults/index.ts new file mode 100644 index 00000000..20eae4db --- /dev/null +++ b/src/NoResults/index.ts @@ -0,0 +1 @@ +export * from './NoResults'; diff --git a/src/Results/ActionIcon.tsx b/src/Results/ActionIcon.tsx new file mode 100644 index 00000000..15e955e0 --- /dev/null +++ b/src/Results/ActionIcon.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +export function SelectIcon() { + return ( + + + + + + + ); +} + +export function GoToExternal() { + return ( + + + + ); +} diff --git a/src/Results/Results.tsx b/src/Results/Results.tsx new file mode 100644 index 00000000..def174b4 --- /dev/null +++ b/src/Results/Results.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import { + GetMenuProps, + GetItemProps, + AutocompleteSuggestion, +} from '@francoischalifour/autocomplete-core'; + +import { SourceIcon } from './SourceIcon'; +import { SelectIcon } from './ActionIcon'; +import { InternalDocSearchHit } from '../types'; + +interface ResultsProps { + suggestions: Array>; + getMenuProps: GetMenuProps; + getItemProps: GetItemProps; +} + +export function Results(props: ResultsProps) { + return ( +
+ {props.suggestions.map(({ source, items }) => { + const title = items[0].hierarchy.lvl0; + + return ( +
+
{title}
+ + +
+ ); + })} +
+ ); +} diff --git a/src/Results/SourceIcon.tsx b/src/Results/SourceIcon.tsx new file mode 100644 index 00000000..864826d3 --- /dev/null +++ b/src/Results/SourceIcon.tsx @@ -0,0 +1,58 @@ +import React from 'react'; + +export function SourceIcon(props: { type: string }) { + switch (props.type) { + case 'lvl1': + return ; + case 'content': + return ; + default: + return ; + } +} + +function LvlIcon() { + return ( + + + + ); +} + +function AnchorIcon() { + return ( + + + + ); +} + +function ContentIcon() { + return ( + + + + ); +} diff --git a/src/Results/index.ts b/src/Results/index.ts new file mode 100644 index 00000000..9646dd49 --- /dev/null +++ b/src/Results/index.ts @@ -0,0 +1 @@ +export * from './Results'; diff --git a/src/SearchBox/LoadingIcon.tsx b/src/SearchBox/LoadingIcon.tsx new file mode 100644 index 00000000..6963ca43 --- /dev/null +++ b/src/SearchBox/LoadingIcon.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +export function LoadingIcon() { + return ( + + + + + + + + + + + ); +} diff --git a/src/SearchBox/ResetIcon.tsx b/src/SearchBox/ResetIcon.tsx new file mode 100644 index 00000000..202c2cac --- /dev/null +++ b/src/SearchBox/ResetIcon.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export function ResetIcon() { + return ( + + + + ); +} diff --git a/src/SearchBox/SearchBox.tsx b/src/SearchBox/SearchBox.tsx new file mode 100644 index 00000000..8952d4ab --- /dev/null +++ b/src/SearchBox/SearchBox.tsx @@ -0,0 +1,74 @@ +import React, { MutableRefObject } from 'react'; +import { + GetFormProps, + GetLabelProps, + GetInputProps, +} from '@francoischalifour/autocomplete-core'; + +import { SearchIcon } from './SearchIcon'; +import { ResetIcon } from './ResetIcon'; +import { LoadingIcon } from './LoadingIcon'; + +interface SearchBoxProps { + inputRef: MutableRefObject; + query: string; + getFormProps: GetFormProps; + getLabelProps: GetLabelProps; + getInputProps: GetInputProps< + React.ChangeEvent, + React.MouseEvent, + React.KeyboardEvent + >; + onClose(): void; +} + +export function SearchBox(props: SearchBoxProps) { + const { onSubmit, onReset } = props.getFormProps({ + inputElement: props.inputRef.current, + }); + + return ( + <> +
+ + +
+ +
+ + + + +
+ + + + ); +} diff --git a/src/SearchBox/SearchIcon.tsx b/src/SearchBox/SearchIcon.tsx new file mode 100644 index 00000000..fab22ae7 --- /dev/null +++ b/src/SearchBox/SearchIcon.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +export function SearchIcon() { + return ( + + + + ); +} diff --git a/src/SearchBox/index.tsx b/src/SearchBox/index.tsx new file mode 100644 index 00000000..ffc46c99 --- /dev/null +++ b/src/SearchBox/index.tsx @@ -0,0 +1 @@ +export * from './SearchBox'; diff --git a/src/SearchButton/SearchButton.tsx b/src/SearchButton/SearchButton.tsx new file mode 100644 index 00000000..ddc8adf8 --- /dev/null +++ b/src/SearchButton/SearchButton.tsx @@ -0,0 +1,57 @@ +import React, { useState, useEffect } from 'react'; + +interface SearchButtonProps { + onClick(): void; +} + +const ACTION_KEY_DEFAULT = 'Ctrl'; +const ACTION_KEY_APPLE = '⌘'; + +function isAppleDevice() { + if (typeof navigator === 'undefined') { + return ACTION_KEY_DEFAULT; + } + + return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform); +} + +export function SearchButton(props: SearchButtonProps) { + const [key, setKey] = useState(() => + isAppleDevice() ? ACTION_KEY_APPLE : ACTION_KEY_DEFAULT + ); + + useEffect(() => { + if (isAppleDevice()) { + setKey(ACTION_KEY_APPLE); + } + }, []); + + return ( + + ); +} diff --git a/src/SearchButton/index.ts b/src/SearchButton/index.ts new file mode 100644 index 00000000..6f4ca098 --- /dev/null +++ b/src/SearchButton/index.ts @@ -0,0 +1 @@ +export * from './SearchButton'; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..c305abfc --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export * from './DocSearch'; +export * from './SearchButton'; diff --git a/src/style.css b/src/style.css new file mode 100644 index 00000000..8ae3e3e8 --- /dev/null +++ b/src/style.css @@ -0,0 +1,543 @@ +/* Variables */ + +/* primary +secondary +bg +bg light +bg dark +text default +text muted +spacing +font size */ + +:root { + --docsearch-input-color: var(--ifm-color-emphasis-800); + --docsearch-highlight-color: var(--ifm-color-primary); + --docsearch-placeholder-color: rgb(150, 155, 175); + --docsearch-container-background: rgb(60, 65, 85, 0.8); + --docsearch-modal-background: var(--ifm-color-emphasis-100); + --docsearch-modal-shadow: inset 1px 1px 0px 0px rgba(255, 255, 255, 0.5), + 0px 3px 8px 0px rgba(85, 90, 100, 1); + --docsearch-searchbox-background: rgb(210, 215, 225); + --docsearch-searchbox-active-background: rgb(195, 200, 220); + --docsearch-searchbox-shadow: inset 1px 2px 6px 0px rgb(190, 195, 215), + inset 0px 1px 1px 0px rgb(134, 146, 221), 1px 1px 0px 0px white; + --docsearch-hit-color: var(--ifm-color-emphasis-800); + --docsearch-hit-active-color: white; + --docsearch-hit-background: white; + --docsearch-hit-shadow: 0px 2px 6px 0px rgb(212, 217, 225); + --docsearch-key-gradient: linear-gradient( + -225deg, + rgb(213, 219, 228) 0%, + rgb(248, 248, 248) 100% + ); + --docsearch-key-shadow: inset 0px -2px 0px 0px rgb(205, 205, 230), + inset 0px 0px 1px 1px white, 0px 1px 2px 1px rgba(30, 35, 90, 0.4); + --docsearch-footer-background: white; + --docsearch-footer-shadow: 0px -5px 7px 0px rgba(69, 98, 155, 0.12); + --docsearch-logo-color: #5468ff; + --docsearch-muted-color: rgb(190, 195, 215); + --docsearch-modal-width: 560px; + --docsearch-modal-height: 600px; + --docsearch-searchbox-height: 56px; + --docsearch-hit-height: 56px; + --docsearch-footer-height: 44px; + --docsearch-spacing: 12px; +} + +/* Darkmode */ + +html[data-theme='dark'] { + --docsearch-container-background: rgba(9, 10, 17, 0.8); + --docsearch-modal-background: rgb(21, 23, 42); + --docsearch-modal-shadow: inset 1px 1px 0px 0px rgb(44, 46, 64), + 0px 3px 8px 0px rgb(0, 3, 9); + --docsearch-searchbox-background: rgb(9, 10, 17); + --docsearch-searchbox-shadow: inset 0px 0px 0 2px var(--ifm-color-primary); + --docsearch-searchbox-active-background: rgb(9, 10, 17); + --docsearch-hit-color: var(--ifm-color-emphasis-500); + --docsearch-hit-shadow: none; + --docsearch-hit-background: rgb(9, 10, 17); + --docsearch-key-gradient: linear-gradient( + -26.56505117707799deg, + rgb(86, 88, 114) 0%, + rgb(49, 53, 91) 100% + ); + --docsearch-key-shadow: inset 0px -2px 0px 0px rgb(40, 45, 85), + inset 0px 0px 1px 1px rgb(81, 87, 125), 0px 2px 2px 0px rgb(3, 4, 9); + --docsearch-footer-background: rgb(30, 33, 54); + --docsearch-footer-shadow: inset 0px 1px 0px 0px rgba(73, 76, 106, 0.52), + 0px -4px 8px 0px rgba(0, 0, 0, 0.34); + --docsearch-logo-color: #fff; + --docsearch-muted-color: rgb(100, 105, 125); +} + +.DocSearch--active .main-wrapper { + filter: blur(2px); +} + +/* Search Button */ + +.DocSearch-SearchButton { + display: flex; + height: 40px; + margin: 0 0 0 20px; + padding: 0 12px; + margin-left: 2em; + border: none; + border-radius: 20px; + cursor: pointer; + align-items: center; + background: var(--docsearch-searchbox-background); + color: var(--ifm-color-emphasis-900); + transition: background-color 0.4s ease-out, box-shadow 0.4s ease-out; +} + +.DocSearch-SearchButton:hover, +.DocSearch-SearchButton:active, +.DocSearch-SearchButton:focus { + outline: none; + box-shadow: var(--docsearch-searchbox-shadow); + background: var(--docsearch-searchbox-active-background); +} + +.DocSearch-SearchButton-Icon { + color: var(--docsearch-highlight-color); +} + +.DocSearch-SearchButton-Placeholder { + margin: 0 8px; + font-size: 1rem; +} + +.DocSearch-SearchButton-Key { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 18px; + margin-right: 0.4em; + padding-bottom: 2px; + border-radius: 3px; + background: var(--docsearch-key-gradient); + box-shadow: var(--docsearch-key-shadow); + color: var(--ifm-color-emphasis-600); +} + +/* Body modifier */ + +.DocSearch--active { + /* + * We need to mark it as important because some websites override the + * `style` attribute (e.g. Docusaurus). + */ + overflow: hidden !important; +} + +/* Hide Docusaurus hamburger button when modal is opened */ +.DocSearch--active .menu__button { + display: none !important; +} + +/* Container & Modal */ + +.DocSearch-Container, +.DocSearch-Container * { + box-sizing: border-box; +} + +.DocSearch-Container { + height: 100vh; + width: 100vw; + position: fixed; + left: 0; + top: 0; + background-color: var(--docsearch-container-background); + animation: container 0.1s ease-in forwards; +} + +@keyframes container { + 0% { + opacity: 0.5; + } + 100% { + opacity: 1; + } +} + +.DocSearch-Container a { + text-decoration: none; +} + +.DocSearch-Modal { + position: relative; + flex-direction: column; + max-width: var(--docsearch-modal-width); + margin: 60px auto auto auto; + border-radius: 6px; + background: var(--docsearch-modal-background); + box-shadow: var(--docsearch-modal-shadow); +} + +/* Modal Searchbox */ + +.DocSearch-SearchBar { + display: flex; + padding: var(--docsearch-spacing) var(--docsearch-spacing) 0; +} + +.DocSearch-Form { + display: flex; + width: 100%; + position: relative; + height: var(--docsearch-searchbox-height); + padding: 0 var(--docsearch-spacing); + align-items: center; + border-radius: 4px; + background: var(--docsearch-searchbox-background); + box-shadow: var(--docsearch-searchbox-shadow); +} + +.DocSearch-Input { + height: 100%; + flex: 1; + width: 80%; + padding-left: 8px; + font: inherit; + font-size: 1.2em; + appearance: none; + border: none; + outline: none; + background: transparent; + color: var(--docsearch-input-color); +} + +.DocSearch-Input::placeholder { + color: var(--docsearch-placeholder-color); +} + +.DocSearch-Input::-webkit-search-cancel-button, +.DocSearch-Input::-webkit-search-decoration, +.DocSearch-Input::-webkit-search-results-button, +.DocSearch-Input::-webkit-search-results-decoration { + display: none; +} + +.DocSearch-LoadingIndicator, +.DocSearch-MagnifierLabel, +.DocSearch-Reset { + height: 20px; + width: 20px; + margin: 0; + padding: 0; +} + +.DocSearch-Container--Stalled .DocSearch-LoadingIndicator, +.DocSearch-MagnifierLabel, +.DocSearch-Reset { + display: flex; + align-items: center; + justify-content: center; +} + +.DocSearch-Container--Stalled .DocSearch-MagnifierLabel, +.DocSearch-LoadingIndicator { + display: none; +} + +.DocSearch-Reset { + appearance: none; + right: 0; + border: none; + border-radius: 50%; + background: none; + cursor: pointer; +} + +.DocSearch-Reset[hidden] { + display: none; +} + +.DocSearch-Reset:focus { + outline: none; +} + +.DocSearch-Reset svg { + height: 10px; + width: 10px; +} + +.DocSearch-LoadingIndicator svg, +.DocSearch-MagnifierLabel svg { + height: 24px; + width: 24px; +} + +.DocSearch-MagnifierLabel { + color: var(--docsearch-highlight-color); +} + +.DocSearch-Cancel { + display: none; +} + +/* Modal Dropdown */ + +.DocSearch-Dropdown { + height: calc( + var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - + var(--docsearch-spacing) * 2 - var(--docsearch-footer-height) + ); + padding: 0 var(--docsearch-spacing) var(--docsearch-hit-height); + margin-bottom: var(--docsearch-footer-height); + overflow-y: scroll; + letter-spacing: 0.02em; +} + +.DocSearch-Dropdown ul { + margin: 0; + padding: 0; + list-style: none; +} + +.DocSearch-Dropdown-Container { + position: relative; +} + +/* Modal Footer */ + +.DocSearch-Footer { + position: absolute; + bottom: 0; + display: flex; + width: 100%; + height: var(--docsearch-footer-height); + padding: 0 var(--docsearch-spacing); + flex-direction: row-reverse; + flex-shrink: 0; + justify-content: space-between; + align-items: center; + border-radius: 0 0 8px 8px; + background: var(--docsearch-footer-background); + box-shadow: var(--docsearch-footer-shadow); + z-index: var(--ifm-z-index-fixed); +} + +.DocSearch-Commands { + display: flex; + margin: 0; + padding: 0; + list-style: none; +} + +.DocSearch-Commands li:not(:last-of-type) { + margin-right: 0.8em; +} + +.DocSearch-Commands { + display: flex; + color: var(--ifm-color-emphasis-600); +} + +.DocSearch-Commands li { + display: flex; + align-items: center; +} + +.DocSearch-Commands-Key { + display: flex; + width: 20px; + height: 18px; + margin-right: 0.4em; + padding-bottom: 2px; + align-items: center; + justify-content: center; + border-radius: 2px; + background: var(--docsearch-key-gradient); + box-shadow: var(--docsearch-key-shadow); +} + +.DocSearch-Logo a { + display: flex; +} + +.DocSearch-Logo svg { + margin-left: 8px; + color: var(--docsearch-logo-color); +} + +.DocSearch-Label { + font-size: 0.75em; + line-height: 1.8em; + color: var(--docsearch-muted-color); +} + +/* Hit */ + +.DocSearch-Hits mark { + background: none; + color: var(--docsearch-highlight-color); +} + +.DocSearch-Hit { + display: flex; + position: relative; + border-radius: 4px; + background: var(--docsearch-hit-background); + box-shadow: var(--docsearch-hit-shadow); + padding-left: var(--docsearch-spacing); + margin-bottom: 4px; +} + +.DocSearch-Hit-source { + margin: 0 calc(var(--docsearch-spacing) * -1); + padding: 8px var(--docsearch-spacing) 0; + line-height: 32px; + font-size: 0.85em; + font-weight: 600; + color: var(--docsearch-highlight-color); + letter-spacing: 0; + position: sticky; + top: 0; + z-index: var(--ifm-z-index-fixed); + background: var(--docsearch-modal-background); +} + +.DocSearch-Hit-Tree { + width: 24px; + height: var(--docsearch-hit-height); + margin-right: 4px; + color: var(--docsearch-muted-color); + opacity: 0.5; +} + +.DocSearch-Hit[aria-selected='true'] { + /* transition: background-color ease 0.1s; */ + background-color: var(--docsearch-highlight-color); +} + +.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-title, +.DocSearch-Hit[aria-selected='true'] mark, +.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-text, +.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-path, +.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-icon, +.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-action { + color: var(--docsearch-hit-active-color) !important; +} + +.DocSearch-Hit a { + display: block; + border-radius: 4px; + width: 100%; +} + +.DocSearch-Hit[aria-selected='true'] mark { + text-decoration: underline; +} + +.DocSearch-Hit-Container { + display: flex; + height: var(--docsearch-hit-height); + padding: 0 var(--docsearch-spacing) 0 0; + flex-direction: row; + align-items: center; + color: var(--docsearch-hit-color); +} + +.DocSearch-Hit-icon, +.DocSearch-Hit-action { + height: 20px; + width: 20px; + color: var(--docsearch-muted-color); +} + +.DocSearch-Hit-content-wrapper { + position: relative; + display: flex; + width: 80%; + margin: 0 8px; + line-height: 1.1em; + flex: 1 0 auto; + font-weight: 500; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + justify-content: center; + flex-direction: column; +} + +.DocSearch-Hit-title { + font-size: 0.9em; +} + +.DocSearch-Hit-path { + font-size: 0.7em; + color: var(--docsearch-muted-color); +} + +/* No Results */ + +.DocSearch-NoResults { + width: 80%; + margin: 0 auto; + text-align: center; + padding: var(--docsearch-spacing) 0; + margin-top: var(--docsearch-spacing); +} + +/* Responsive */ + +@media (max-width: 750px) { + :root { + /* quickfix https://stackoverflow.com/questions/37112218/css3-100vh-not-constant-in-mobile-browser */ + --docsearch-modal-height: calc(100vh - 110px); + --docsearch-spacing: 10px; + --docsearch-footer-height: 40px; + } + .DocSearch-SearchButton-KeySeparator, + .DocSearch-SearchButton-Key { + display: none; + } + .DocSearch-Modal { + border-radius: 0; + box-shadow: none; + margin: 0 auto; + } + .DocSearch-Cancel { + display: inline-block; + width: 0; + white-space: nowrap; + overflow: hidden; + margin-left: var(--docsearch-spacing); + padding: 0; + appearance: none; + border: 0; + cursor: pointer; + background: none; + flex: none; + font: inherit; + font-size: 1em; + font-weight: 500; + letter-spacing: 0.02em; + color: var(--docsearch-highlight-color); + animation: cancel-button ease-out 0.2s forwards; + } + + @keyframes cancel-button { + 100% { + width: 4em; + } + } + + .DocSearch-Commands { + display: none; + } + .DocSearch-Hit { + letter-spacing: 0; + } +} + +/* todo: hide keyboard shortcut on smartphones, touchscreens device - cross browser limitation with media q, maybe js is better https://codepen.io/Ferie/pen/vQOMmO?editors=1010 */ +@media (hover: none) and (pointer: coarse) { + /* ... */ +} diff --git a/src/svg/icon_action_external.svg b/src/svg/icon_action_external.svg new file mode 100644 index 00000000..24adf7f8 --- /dev/null +++ b/src/svg/icon_action_external.svg @@ -0,0 +1,11 @@ + + + + 1c947b1e-eb2b-4ad3-ac34-75998ca8847a@1.00x + Created with sketchtool. + + + + + + \ No newline at end of file diff --git a/src/svg/icon_action_goto.svg b/src/svg/icon_action_goto.svg new file mode 100644 index 00000000..5bcb44e5 --- /dev/null +++ b/src/svg/icon_action_goto.svg @@ -0,0 +1,14 @@ + + + + 1c20c2f5-d737-495d-a54b-64fd79363595@1.00x + Created with sketchtool. + + + + + + + + + \ No newline at end of file diff --git a/src/svg/icon_source_anchor.svg b/src/svg/icon_source_anchor.svg new file mode 100644 index 00000000..cf0ba862 --- /dev/null +++ b/src/svg/icon_source_anchor.svg @@ -0,0 +1,14 @@ + + + + 78adcbdb-966a-4cd6-9309-109b3e7780e1@1.00x + Created with sketchtool. + + + + + + + + + \ No newline at end of file diff --git a/src/svg/icon_source_content.svg b/src/svg/icon_source_content.svg new file mode 100644 index 00000000..42747459 --- /dev/null +++ b/src/svg/icon_source_content.svg @@ -0,0 +1,13 @@ + + + + 90955cc9-f7b3-4479-898c-19510cc4b6d4@1.00x + Created with sketchtool. + + + + + + + + \ No newline at end of file diff --git a/src/svg/icon_source_page.svg b/src/svg/icon_source_page.svg new file mode 100644 index 00000000..dbec55d0 --- /dev/null +++ b/src/svg/icon_source_page.svg @@ -0,0 +1,11 @@ + + + + 1f96940f-ca50-4e02-84bd-79bc02579d53@1.00x + Created with sketchtool. + + + + + + \ No newline at end of file diff --git a/src/svg/icon_source_recent.svg b/src/svg/icon_source_recent.svg new file mode 100644 index 00000000..8e16b686 --- /dev/null +++ b/src/svg/icon_source_recent.svg @@ -0,0 +1,16 @@ + + + + ee397e5b-5117-4da7-bdc8-0d42da41349d@1.00x + Created with sketchtool. + + + + + + + + + + + \ No newline at end of file diff --git a/src/svg/icon_tree_child.svg b/src/svg/icon_tree_child.svg new file mode 100644 index 00000000..c6951fca --- /dev/null +++ b/src/svg/icon_tree_child.svg @@ -0,0 +1,12 @@ + + + + 8d4bc779-eae6-4075-8edb-f964556f9cd5@1.00x + Created with sketchtool. + + + + + + + \ No newline at end of file diff --git a/src/svg/icon_tree_last-child.svg b/src/svg/icon_tree_last-child.svg new file mode 100644 index 00000000..6f9a4b2f --- /dev/null +++ b/src/svg/icon_tree_last-child.svg @@ -0,0 +1,12 @@ + + + + 658830a4-2f41-4e2b-b15f-517e3365664b@1.00x + Created with sketchtool. + + + + + + + \ No newline at end of file diff --git a/src/svg/light_footer_key_arrow-down.svg b/src/svg/light_footer_key_arrow-down.svg new file mode 100644 index 00000000..7463f23d --- /dev/null +++ b/src/svg/light_footer_key_arrow-down.svg @@ -0,0 +1,12 @@ + + + + ba2c8f8b-aab6-4715-b010-a3c910a1a4ea@1.00x + Created with sketchtool. + + + + + + + \ No newline at end of file diff --git a/src/svg/light_footer_key_arrow-up.svg b/src/svg/light_footer_key_arrow-up.svg new file mode 100644 index 00000000..1c931049 --- /dev/null +++ b/src/svg/light_footer_key_arrow-up.svg @@ -0,0 +1,12 @@ + + + + 67191d53-11cf-4c86-9c2e-8580c0fd5d08@1.00x + Created with sketchtool. + + + + + + + \ No newline at end of file diff --git a/src/svg/light_footer_key_enter.svg b/src/svg/light_footer_key_enter.svg new file mode 100644 index 00000000..85226e53 --- /dev/null +++ b/src/svg/light_footer_key_enter.svg @@ -0,0 +1,14 @@ + + + + f879c873-9cca-4202-b665-1ec6b93bb126@1.00x + Created with sketchtool. + + + + + + + + + \ No newline at end of file diff --git a/src/svg/light_footer_key_esc.svg b/src/svg/light_footer_key_esc.svg new file mode 100644 index 00000000..f693037c --- /dev/null +++ b/src/svg/light_footer_key_esc.svg @@ -0,0 +1,11 @@ + + + + 67ddc381-274d-431d-a5d3-cf7d76b0c2cd@1.00x + Created with sketchtool. + + + + + + \ No newline at end of file diff --git a/src/svg/logo-alogia.svg b/src/svg/logo-alogia.svg new file mode 100644 index 00000000..84502dc6 --- /dev/null +++ b/src/svg/logo-alogia.svg @@ -0,0 +1,11 @@ + + + + EE8C1461-B989-46C5-8B49-7EEB61DCD522@1.00x + Created with sketchtool. + + + + + + \ No newline at end of file diff --git a/src/types/DocSearchHit.ts b/src/types/DocSearchHit.ts new file mode 100644 index 00000000..71be3c93 --- /dev/null +++ b/src/types/DocSearchHit.ts @@ -0,0 +1,81 @@ +type ContentType = + | 'content' + | 'lvl0' + | 'lvl1' + | 'lvl2' + | 'lvl3' + | 'lvl4' + | 'lvl5' + | 'lvl6'; + +interface DocSearchHitAttributeHighlightResult { + value: string; + matchLevel: 'none' | 'partial' | 'full'; + matchedWords: string[]; + fullyHighlighted?: boolean; +} + +interface DocSearchHitHighlightResultHierarchy { + lvl0: DocSearchHitAttributeHighlightResult; + lvl1: DocSearchHitAttributeHighlightResult; + lvl2: DocSearchHitAttributeHighlightResult; + lvl3: DocSearchHitAttributeHighlightResult; + lvl4: DocSearchHitAttributeHighlightResult; + lvl5: DocSearchHitAttributeHighlightResult; + lvl6: DocSearchHitAttributeHighlightResult; +} + +interface DocSearchHitHighlightResult { + content: DocSearchHitAttributeHighlightResult; + hierarchy: DocSearchHitHighlightResultHierarchy; + hierarchy_camel: DocSearchHitHighlightResultHierarchy[]; +} + +interface DocSearchHitAttributeSnippetResult { + value: string; + matchLevel: 'none' | 'partial' | 'full'; +} + +interface DocSearchHitSnippetResult { + content: DocSearchHitAttributeSnippetResult; + hierarchy: DocSearchHitHighlightResultHierarchy; + hierarchy_camel: DocSearchHitHighlightResultHierarchy[]; +} + +export interface DocSearchHit { + objectID: string; + content: string | null; + url: string; + url_without_anchor: string; + type: ContentType; + anchor: string | null; + hierarchy: { + lvl0: string; + lvl1: string; + lvl2: string | null; + lvl3: string | null; + lvl4: string | null; + lvl5: string | null; + lvl6: string | null; + }; + _highlightResult: DocSearchHitHighlightResult; + _snippetResult: DocSearchHitSnippetResult; + _rankingInfo?: { + promoted: boolean; + nbTypos: number; + firstMatchedWord: number; + proximityDistance?: number; + geoDistance: number; + geoPrecision?: number; + nbExactWords: number; + words: number; + filters: number; + userScore: number; + matchedGeoLocation?: { + lat: number; + lng: number; + distance: number; + }; + }; + _distinctSeqID?: number; +} diff --git a/src/types/InternalDocSearchHit.tsx b/src/types/InternalDocSearchHit.tsx new file mode 100644 index 00000000..bf518ce9 --- /dev/null +++ b/src/types/InternalDocSearchHit.tsx @@ -0,0 +1,5 @@ +import { DocSearchHit } from './DocSearchHit'; + +export type InternalDocSearchHit = DocSearchHit & { + __docsearch_parent: null | DocSearchHit; +}; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 00000000..7ccf2033 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,2 @@ +export * from './DocSearchHit'; +export * from './InternalDocSearchHit'; diff --git a/src/utils/createSearchClient.ts b/src/utils/createSearchClient.ts new file mode 100644 index 00000000..439f66b6 --- /dev/null +++ b/src/utils/createSearchClient.ts @@ -0,0 +1,13 @@ +import algoliasearch from 'algoliasearch/dist/algoliasearch-lite.esm.browser'; + +import { version } from '../version'; + +export function createSearchClient(appId: string, apiKey: string) { + const searchClient = algoliasearch(appId, apiKey); + + if (typeof searchClient.addAlgoliaAgent === 'function') { + searchClient.addAlgoliaAgent(`docsearch-react (${version})`); + } + + return searchClient; +} diff --git a/src/utils/groupBy.ts b/src/utils/groupBy.ts new file mode 100644 index 00000000..5ea2731e --- /dev/null +++ b/src/utils/groupBy.ts @@ -0,0 +1,16 @@ +export function groupBy( + values: TValue[], + predicate: (value: TValue) => string +): Record { + return values.reduce>((acc, item) => { + const key = predicate(item); + + if (!acc.hasOwnProperty(key)) { + acc[key] = []; + } + + acc[key].push(item); + + return acc; + }, {}); +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..58e751ef --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './createSearchClient'; +export * from './groupBy'; +export * from './noop'; diff --git a/src/utils/noop.ts b/src/utils/noop.ts new file mode 100644 index 00000000..cadb2cd0 --- /dev/null +++ b/src/utils/noop.ts @@ -0,0 +1 @@ +export function noop(..._args: any[]): void {} diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 00000000..03d93047 --- /dev/null +++ b/src/version.ts @@ -0,0 +1 @@ +export const version = '1.0.0-alpha.10'; diff --git a/tsconfig.declaration.json b/tsconfig.declaration.json new file mode 100644 index 00000000..9bd7baf2 --- /dev/null +++ b/tsconfig.declaration.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..41716a7d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig" +} From a448e9ee728a157af6e672e8123d051c754375a3 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 002/151] feat(docsearch): add search suggestions --- src/DocSearch.tsx | 15 +++++++++- src/Dropdown/Dropdown.tsx | 12 +++++++- src/NoResults/NoResults.tsx | 56 +++++++++++++++++++++++++++++++++---- src/style.css | 11 ++++++++ 4 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index c9883122..1e3ce73e 100644 --- a/src/DocSearch.tsx +++ b/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/src/Dropdown/Dropdown.tsx b/src/Dropdown/Dropdown.tsx index 9aefaa8a..889acf40 100644 --- a/src/Dropdown/Dropdown.tsx +++ b/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/src/NoResults/NoResults.tsx b/src/NoResults/NoResults.tsx index 3c2793e3..a296ea1f 100644 --- a/src/NoResults/NoResults.tsx +++ b/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/src/style.css b/src/style.css index 8ae3e3e8..fc01c325 100644 --- a/src/style.css +++ b/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; From 7ef73c3a967e1f6cc2eb45dd8039a278040dbf5b Mon Sep 17 00:00:00 2001 From: Shipow Date: Wed, 1 Apr 2020 18:16:46 +0200 Subject: [PATCH 003/151] fix(design): footer, hit spacing --- src/Results/Results.tsx | 40 ++++++++++++------------ src/style.css | 67 +++++++++++++++++------------------------ 2 files changed, 47 insertions(+), 60 deletions(-) diff --git a/src/Results/Results.tsx b/src/Results/Results.tsx index def174b4..c8a310ad 100644 --- a/src/Results/Results.tsx +++ b/src/Results/Results.tsx @@ -41,28 +41,28 @@ export function Results(props: ResultsProps) { source, })} > - {item.__docsearch_parent && ( - - - {item.__docsearch_parent !== - items[index + 1]?.__docsearch_parent ? ( - - ) : ( - - )} - - - )} - +
+ {item.__docsearch_parent && ( + + + {item.__docsearch_parent !== + items[index + 1]?.__docsearch_parent ? ( + + ) : ( + + )} + + + )}
diff --git a/src/style.css b/src/style.css index fc01c325..d253cb7c 100644 --- a/src/style.css +++ b/src/style.css @@ -1,15 +1,5 @@ /* Variables */ -/* primary -secondary -bg -bg light -bg dark -text default -text muted -spacing -font size */ - :root { --docsearch-input-color: var(--ifm-color-emphasis-800); --docsearch-highlight-color: var(--ifm-color-primary); @@ -81,15 +71,15 @@ html[data-theme='dark'] { .DocSearch-SearchButton { display: flex; height: 40px; - margin: 0 0 0 20px; + margin: 0 0 0 16px; padding: 0 12px; - margin-left: 2em; border: none; border-radius: 20px; cursor: pointer; align-items: center; background: var(--docsearch-searchbox-background); color: var(--ifm-color-emphasis-900); + font-weight: 500; transition: background-color 0.4s ease-out, box-shadow 0.4s ease-out; } @@ -299,10 +289,8 @@ html[data-theme='dark'] { var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) * 2 - var(--docsearch-footer-height) ); - padding: 0 var(--docsearch-spacing) var(--docsearch-hit-height); - margin-bottom: var(--docsearch-footer-height); + padding: 0 var(--docsearch-spacing); overflow-y: scroll; - letter-spacing: 0.02em; } .DocSearch-Dropdown ul { @@ -318,12 +306,12 @@ html[data-theme='dark'] { /* Modal Footer */ .DocSearch-Footer { - position: absolute; - bottom: 0; + position: relative; display: flex; width: 100%; height: var(--docsearch-footer-height); padding: 0 var(--docsearch-spacing); + z-index: 300; flex-direction: row-reverse; flex-shrink: 0; justify-content: space-between; @@ -331,7 +319,6 @@ html[data-theme='dark'] { border-radius: 0 0 8px 8px; background: var(--docsearch-footer-background); box-shadow: var(--docsearch-footer-shadow); - z-index: var(--ifm-z-index-fixed); } .DocSearch-Commands { @@ -379,12 +366,16 @@ html[data-theme='dark'] { .DocSearch-Label { font-size: 0.75em; - line-height: 1.8em; + line-height: 1.6em; color: var(--docsearch-muted-color); } /* Hit */ +.DocSearch-Hits:last-of-type{ + margin-bottom: 40px; + } + .DocSearch-Hits mark { background: none; color: var(--docsearch-highlight-color); @@ -394,36 +385,40 @@ html[data-theme='dark'] { display: flex; position: relative; border-radius: 4px; + padding-bottom: 4px; +} + +.DocSearch-Hit a { + display: block; + border-radius: 4px; + width: 100%; + padding-left: var(--docsearch-spacing); background: var(--docsearch-hit-background); box-shadow: var(--docsearch-hit-shadow); - padding-left: var(--docsearch-spacing); - margin-bottom: 4px; + transition: background-color ease 0.1s; } .DocSearch-Hit-source { + position: sticky; + top: 0; + z-index: var(--ifm-z-index-fixed); margin: 0 calc(var(--docsearch-spacing) * -1); padding: 8px var(--docsearch-spacing) 0; line-height: 32px; font-size: 0.85em; font-weight: 600; color: var(--docsearch-highlight-color); - letter-spacing: 0; - position: sticky; - top: 0; - z-index: var(--ifm-z-index-fixed); background: var(--docsearch-modal-background); } .DocSearch-Hit-Tree { width: 24px; height: var(--docsearch-hit-height); - margin-right: 4px; color: var(--docsearch-muted-color); - opacity: 0.5; + opacity: 0.4; } -.DocSearch-Hit[aria-selected='true'] { - /* transition: background-color ease 0.1s; */ +.DocSearch-Hit[aria-selected='true'] a{ background-color: var(--docsearch-highlight-color); } @@ -436,12 +431,6 @@ html[data-theme='dark'] { color: var(--docsearch-hit-active-color) !important; } -.DocSearch-Hit a { - display: block; - border-radius: 4px; - width: 100%; -} - .DocSearch-Hit[aria-selected='true'] mark { text-decoration: underline; } @@ -470,7 +459,7 @@ html[data-theme='dark'] { line-height: 1.1em; flex: 1 0 auto; font-weight: 500; - overflow: hidden; + overflow-x: hidden; white-space: nowrap; text-overflow: ellipsis; justify-content: center; @@ -497,7 +486,6 @@ html[data-theme='dark'] { } /* Responsive */ - @media (max-width: 750px) { :root { /* quickfix https://stackoverflow.com/questions/37112218/css3-100vh-not-constant-in-mobile-browser */ @@ -529,7 +517,6 @@ html[data-theme='dark'] { font: inherit; font-size: 1em; font-weight: 500; - letter-spacing: 0.02em; color: var(--docsearch-highlight-color); animation: cancel-button ease-out 0.2s forwards; } @@ -543,8 +530,8 @@ html[data-theme='dark'] { .DocSearch-Commands { display: none; } - .DocSearch-Hit { - letter-spacing: 0; + .DocSearch-Hit-Tree { + display: none; } } From ba6bdde0418f90cf28190de4448b674670fa9ad7 Mon Sep 17 00:00:00 2001 From: Shipow Date: Wed, 1 Apr 2020 20:12:18 +0200 Subject: [PATCH 004/151] fix(design): contrast + darkmode --- src/style.css | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/style.css b/src/style.css index d253cb7c..875b3d7b 100644 --- a/src/style.css +++ b/src/style.css @@ -8,10 +8,8 @@ --docsearch-modal-background: var(--ifm-color-emphasis-100); --docsearch-modal-shadow: inset 1px 1px 0px 0px rgba(255, 255, 255, 0.5), 0px 3px 8px 0px rgba(85, 90, 100, 1); - --docsearch-searchbox-background: rgb(210, 215, 225); - --docsearch-searchbox-active-background: rgb(195, 200, 220); - --docsearch-searchbox-shadow: inset 1px 2px 6px 0px rgb(190, 195, 215), - inset 0px 1px 1px 0px rgb(134, 146, 221), 1px 1px 0px 0px white; + --docsearch-searchbox-background: var(--ifm-color-emphasis-300); + --docsearch-searchbox-shadow: inset 0px 0px 0px 2px var(--ifm-color-primary); --docsearch-hit-color: var(--ifm-color-emphasis-800); --docsearch-hit-active-color: white; --docsearch-hit-background: white; @@ -26,7 +24,7 @@ --docsearch-footer-background: white; --docsearch-footer-shadow: 0px -5px 7px 0px rgba(69, 98, 155, 0.12); --docsearch-logo-color: #5468ff; - --docsearch-muted-color: rgb(190, 195, 215); + --docsearch-muted-color: rgb(150, 159, 175); --docsearch-modal-width: 560px; --docsearch-modal-height: 600px; --docsearch-searchbox-height: 56px; @@ -43,8 +41,6 @@ html[data-theme='dark'] { --docsearch-modal-shadow: inset 1px 1px 0px 0px rgb(44, 46, 64), 0px 3px 8px 0px rgb(0, 3, 9); --docsearch-searchbox-background: rgb(9, 10, 17); - --docsearch-searchbox-shadow: inset 0px 0px 0 2px var(--ifm-color-primary); - --docsearch-searchbox-active-background: rgb(9, 10, 17); --docsearch-hit-color: var(--ifm-color-emphasis-500); --docsearch-hit-shadow: none; --docsearch-hit-background: rgb(9, 10, 17); @@ -59,7 +55,7 @@ html[data-theme='dark'] { --docsearch-footer-shadow: inset 0px 1px 0px 0px rgba(73, 76, 106, 0.52), 0px -4px 8px 0px rgba(0, 0, 0, 0.34); --docsearch-logo-color: #fff; - --docsearch-muted-color: rgb(100, 105, 125); + --docsearch-muted-color: rgb(127, 132, 151); } .DocSearch--active .main-wrapper { @@ -80,7 +76,7 @@ html[data-theme='dark'] { background: var(--docsearch-searchbox-background); color: var(--ifm-color-emphasis-900); font-weight: 500; - transition: background-color 0.4s ease-out, box-shadow 0.4s ease-out; + transition: box-shadow 0.4s ease-out; } .DocSearch-SearchButton:hover, @@ -88,7 +84,6 @@ html[data-theme='dark'] { .DocSearch-SearchButton:focus { outline: none; box-shadow: var(--docsearch-searchbox-shadow); - background: var(--docsearch-searchbox-active-background); } .DocSearch-SearchButton-Icon { @@ -253,6 +248,7 @@ html[data-theme='dark'] { border-radius: 50%; background: none; cursor: pointer; + color: var(--docsearch-icon); } .DocSearch-Reset[hidden] { @@ -287,7 +283,7 @@ html[data-theme='dark'] { .DocSearch-Dropdown { height: calc( var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - - var(--docsearch-spacing) * 2 - var(--docsearch-footer-height) + var(--docsearch-spacing) - var(--docsearch-footer-height) ); padding: 0 var(--docsearch-spacing); overflow-y: scroll; @@ -402,8 +398,8 @@ html[data-theme='dark'] { position: sticky; top: 0; z-index: var(--ifm-z-index-fixed); - margin: 0 calc(var(--docsearch-spacing) * -1); - padding: 8px var(--docsearch-spacing) 0; + margin: 0 calc(var(--docsearch-spacing) * -1 + 1px); + padding: 8px calc(var(--docsearch-spacing) - 1px) 0; line-height: 32px; font-size: 0.85em; font-weight: 600; From 4c0aa3b71ee451eeeb4df8b87ee3f8f30d47d397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 2 Apr 2020 11:52:13 +0200 Subject: [PATCH 005/151] feat(docsearch): add recent searches (#40) --- src/DocSearch.tsx | 66 +++++--- src/Dropdown/Dropdown.tsx | 59 +++---- src/EmptyScreen/EmptyScreen.tsx | 147 ++++++++++++++++++ src/EmptyScreen/index.ts | 1 + src/NoResults/NoResults.tsx | 23 ++- src/Results/Results.tsx | 106 ++++++------- src/SearchBox/SearchBox.tsx | 23 ++- src/Snippet.tsx | 32 ++++ src/recent-searches.ts | 80 ++++++++++ src/style.css | 9 ++ ...cSearchHit.tsx => InternalDocSearchHit.ts} | 0 src/types/RecentDocSearchHit.ts | 6 + src/types/index.ts | 1 + 13 files changed, 430 insertions(+), 123 deletions(-) create mode 100644 src/EmptyScreen/EmptyScreen.tsx create mode 100644 src/EmptyScreen/index.ts create mode 100644 src/Snippet.tsx create mode 100644 src/recent-searches.ts rename src/types/{InternalDocSearchHit.tsx => InternalDocSearchHit.ts} (100%) create mode 100644 src/types/RecentDocSearchHit.ts diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 1e3ce73e..a616fa64 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -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; @@ -42,18 +48,9 @@ export function DocSearch({ appId, apiKey, ]); + const recentSearches = useRef(createRecentSearches()); - const { - getEnvironmentProps, - getRootProps, - getFormProps, - getLabelProps, - getInputProps, - getMenuProps, - getItemProps, - setQuery, - refresh, - } = React.useMemo( + const autocomplete = React.useMemo( () => createAutocomplete< InternalDocSearchHit, @@ -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(sources).map(items => { return { - onSelect() { + onSelect({ suggestion }) { + recentSearches.current.saveSearch(suggestion); onClose(); }, getSuggestionUrl({ suggestion }) { @@ -165,6 +180,8 @@ export function DocSearch({ [indexName, searchParameters, searchClient, onClose] ); + const { getEnvironmentProps, getRootProps } = autocomplete; + useEffect(() => { const isMobileMediaQuery = window.matchMedia('(max-width: 750px)'); @@ -219,23 +236,26 @@ export function DocSearch({
{ + recentSearches.current.saveSearch(item); + onClose(); + }} + onAction={item => { + recentSearches.current.deleteSearch(item); + autocomplete.refresh(); + }} />
diff --git a/src/Dropdown/Dropdown.tsx b/src/Dropdown/Dropdown.tsx index 889acf40..0643b814 100644 --- a/src/Dropdown/Dropdown.tsx +++ b/src/Dropdown/Dropdown.tsx @@ -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; - getMenuProps: GetMenuProps; - getItemProps: GetItemProps; - setQuery(value: string): void; - refresh(): Promise; - inputRef: React.MutableRefObject; +interface DropdownProps + extends AutocompleteApi< + TItem, + React.FormEvent, + React.MouseEvent, + React.KeyboardEvent + > { + state: AutocompleteState; + onItemClick(search: RecentDocSearchHit): void; + onAction(search: RecentDocSearchHit): void; + inputRef: React.MutableRefObject; } -export function Dropdown(props: DropdownProps) { +export function Dropdown(props: DropdownProps) { if (props.state.status === 'error') { return ; } - 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 ( - )} + hasSuggestions={hasSuggestions} /> ); } - return ( - - ); + if (props.state.status === 'idle' && hasSuggestions === false) { + return ; + } + + return ; } diff --git a/src/EmptyScreen/EmptyScreen.tsx b/src/EmptyScreen/EmptyScreen.tsx new file mode 100644 index 00000000..c2bbb7f3 --- /dev/null +++ b/src/EmptyScreen/EmptyScreen.tsx @@ -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; + hasSuggestions: boolean; + onItemClick(item: RecentDocSearchHit): void; + onAction(search: RecentDocSearchHit): void; +} + +export function EmptyScreen(props: EmptyScreenProps) { + if (props.state.status === 'idle' && props.hasSuggestions === false) { + return ( +
+

Select results and your history will appear here.

+
+ ); + } + + return ( +
+ ); +} diff --git a/src/EmptyScreen/index.ts b/src/EmptyScreen/index.ts new file mode 100644 index 00000000..f184be92 --- /dev/null +++ b/src/EmptyScreen/index.ts @@ -0,0 +1 @@ +export * from './EmptyScreen'; diff --git a/src/NoResults/NoResults.tsx b/src/NoResults/NoResults.tsx index a296ea1f..147d4e70 100644 --- a/src/NoResults/NoResults.tsx +++ b/src/NoResults/NoResults.tsx @@ -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; - setQuery(value: string): void; - refresh(): Promise; - inputRef: React.MutableRefObject; +import { InternalDocSearchHit } from '../types'; + +interface NoResultsProps + extends AutocompleteApi< + InternalDocSearchHit, + React.FormEvent, + React.MouseEvent, + React.KeyboardEvent + > { + state: AutocompleteState; + inputRef: React.MutableRefObject; } export function NoResults(props: NoResultsProps) { @@ -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} diff --git a/src/Results/Results.tsx b/src/Results/Results.tsx index c8a310ad..d63b024b 100644 --- a/src/Results/Results.tsx +++ b/src/Results/Results.tsx @@ -1,24 +1,34 @@ import React from 'react'; import { - GetMenuProps, - GetItemProps, - AutocompleteSuggestion, + AutocompleteApi, + AutocompleteState, } from '@francoischalifour/autocomplete-core'; +import { Snippet } from '../Snippet'; import { SourceIcon } from './SourceIcon'; import { SelectIcon } from './ActionIcon'; import { InternalDocSearchHit } from '../types'; -interface ResultsProps { - suggestions: Array>; - getMenuProps: GetMenuProps; - getItemProps: GetItemProps; +interface ResultsProps + extends AutocompleteApi< + InternalDocSearchHit, + React.FormEvent, + React.MouseEvent, + React.KeyboardEvent + > { + state: AutocompleteState; + onItemClick(item: InternalDocSearchHit): void; + onAction(search: InternalDocSearchHit): void; } export function Results(props: ResultsProps) { return (
- {props.suggestions.map(({ source, items }) => { + {props.state.suggestions.map(({ source, items }) => { + if (items.length === 0) { + return null; + } + const title = items[0].hierarchy.lvl0; return ( @@ -39,49 +49,49 @@ export function Results(props: ResultsProps) { {...props.getItemProps({ item, source, + onClick() { + props.onItemClick(item); + }, })} > -
- {item.__docsearch_parent && ( - - - {item.__docsearch_parent !== + {item.__docsearch_parent && ( + + + {item.__docsearch_parent !== items[index + 1]?.__docsearch_parent ? ( ) : ( )} - - - )} + + + )} +
{item.hierarchy[item.type] && item.type === 'lvl1' && (
- {item.content && ( - )}
@@ -94,38 +104,30 @@ export function Results(props: ResultsProps) { item.type === 'lvl5' || item.type === 'lvl6') && (
- -
)} {item.type === 'content' && (
- -
)} diff --git a/src/SearchBox/SearchBox.tsx b/src/SearchBox/SearchBox.tsx index 8952d4ab..12b6b23e 100644 --- a/src/SearchBox/SearchBox.tsx +++ b/src/SearchBox/SearchBox.tsx @@ -1,24 +1,23 @@ import React, { MutableRefObject } from 'react'; import { - GetFormProps, - GetLabelProps, - GetInputProps, + AutocompleteApi, + AutocompleteState, } from '@francoischalifour/autocomplete-core'; +import { InternalDocSearchHit } from '../types'; import { SearchIcon } from './SearchIcon'; import { ResetIcon } from './ResetIcon'; import { LoadingIcon } from './LoadingIcon'; -interface SearchBoxProps { - inputRef: MutableRefObject; - query: string; - getFormProps: GetFormProps; - getLabelProps: GetLabelProps; - getInputProps: GetInputProps< - React.ChangeEvent, +interface SearchBoxProps + extends AutocompleteApi< + InternalDocSearchHit, + React.FormEvent, React.MouseEvent, React.KeyboardEvent - >; + > { + state: AutocompleteState; + inputRef: MutableRefObject; onClose(): void; } @@ -59,7 +58,7 @@ export function SearchBox(props: SearchBoxProps) { type="reset" title="Clear the query" className="DocSearch-Reset" - hidden={!props.query} + hidden={!props.state.query} onClick={onReset} > diff --git a/src/Snippet.tsx b/src/Snippet.tsx new file mode 100644 index 00000000..807ca1d2 --- /dev/null +++ b/src/Snippet.tsx @@ -0,0 +1,32 @@ +import { createElement } from 'react'; + +import { InternalDocSearchHit } from './types'; + +function getPropertyByPath(object: object, path: string): any { + const parts = path.split('.'); + + return parts.reduce((current, key) => current && current[key], object); +} + +interface SnippetProps { + [prop: string]: unknown; + hit: InternalDocSearchHit; + attribute: string; + tagName?: string; +} + +export function Snippet({ + hit, + attribute, + tagName = 'span', + ...rest +}: SnippetProps) { + return createElement(tagName, { + ...rest, + dangerouslySetInnerHTML: { + __html: + getPropertyByPath(hit, `_snippetResult.${attribute}.value`) || + hit[attribute], + }, + }); +} diff --git a/src/recent-searches.ts b/src/recent-searches.ts new file mode 100644 index 00000000..73a77db1 --- /dev/null +++ b/src/recent-searches.ts @@ -0,0 +1,80 @@ +import { DocSearchHit, RecentDocSearchHit } from './types'; + +function isLocalStorageSupported() { + const key = '__TEST_KEY__'; + + try { + localStorage.setItem(key, ''); + localStorage.removeItem(key); + + return true; + } catch (error) { + return false; + } +} + +function createStorage() { + if (isLocalStorageSupported() === false) { + return { + setItem() {}, + getItem() { + return []; + }, + }; + } + + const STORAGE_KEY = '__AUTOCOMPLETE_RECENT_SEARCHES__'; + + return { + setItem(item: TItem[]) { + return window.localStorage.setItem(STORAGE_KEY, JSON.stringify(item)); + }, + getItem(): TItem[] { + const item = window.localStorage.getItem(STORAGE_KEY); + + return item ? JSON.parse(item) : []; + }, + }; +} + +export function createRecentSearches() { + const storage = createStorage(); + let items = storage.getItem(); + + return { + saveSearch(search: TItem) { + const { + _highlightResult, + _snippetResult, + ...item + } = (search as unknown) as DocSearchHit; + + if (item.type === 'content') { + return false; + } + + const isQueryAlreadySaved = items.findIndex( + x => x.objectID === item.objectID + ); + + if (isQueryAlreadySaved > -1) { + items.splice(isQueryAlreadySaved, 1); + } + + items.unshift(item as TItem); + items = items.slice(0, 5); + + storage.setItem(items); + + return true; + }, + deleteSearch(item: TItem) { + items = items.filter(x => x.objectID !== item.objectID); + + storage.setItem(items); + }, + getSearches() { + return items; + }, + }; +} diff --git a/src/style.css b/src/style.css index 875b3d7b..27d5eb4f 100644 --- a/src/style.css +++ b/src/style.css @@ -447,6 +447,15 @@ html[data-theme='dark'] { color: var(--docsearch-muted-color); } +.DocSearch-Hit-action-button { + appearance: none; + background: none; + color: inherit; + border: none; + padding: 0; + cursor: pointer; +} + .DocSearch-Hit-content-wrapper { position: relative; display: flex; diff --git a/src/types/InternalDocSearchHit.tsx b/src/types/InternalDocSearchHit.ts similarity index 100% rename from src/types/InternalDocSearchHit.tsx rename to src/types/InternalDocSearchHit.ts diff --git a/src/types/RecentDocSearchHit.ts b/src/types/RecentDocSearchHit.ts new file mode 100644 index 00000000..e00c482b --- /dev/null +++ b/src/types/RecentDocSearchHit.ts @@ -0,0 +1,6 @@ +import { DocSearchHit } from './DocSearchHit'; + +export type RecentDocSearchHit = Omit< + DocSearchHit, + '_highlightResult' | '_snippetResult' +>; diff --git a/src/types/index.ts b/src/types/index.ts index 7ccf2033..76849a24 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,2 +1,3 @@ export * from './DocSearchHit'; export * from './InternalDocSearchHit'; +export * from './RecentDocSearchHit'; From 321955979f44bf63f24cd2537a2200a4e325460f Mon Sep 17 00:00:00 2001 From: Shipow Date: Thu, 2 Apr 2020 18:15:00 +0200 Subject: [PATCH 006/151] fix(design): icons --- src/EmptyScreen/EmptyScreen.tsx | 23 ++++++----------------- src/Results/ActionIcon.tsx | 5 ++--- src/Results/Results.tsx | 1 - src/Results/SourceIcon.tsx | 5 +---- src/SearchBox/ResetIcon.tsx | 9 ++++++--- src/SearchBox/SearchIcon.tsx | 10 +++++++--- src/SearchButton/SearchButton.tsx | 19 ++----------------- src/style.css | 29 ++++++++++++++++------------- src/svg/icon_action_clear.svg | 11 +++++++++++ src/svg/icon_action_microphone.svg | 13 +++++++++++++ src/svg/icon_search_primary.svg | 11 +++++++++++ 11 files changed, 75 insertions(+), 61 deletions(-) create mode 100644 src/svg/icon_action_clear.svg create mode 100644 src/svg/icon_action_microphone.svg create mode 100644 src/svg/icon_search_primary.svg diff --git a/src/EmptyScreen/EmptyScreen.tsx b/src/EmptyScreen/EmptyScreen.tsx index c2bbb7f3..159ef3fe 100644 --- a/src/EmptyScreen/EmptyScreen.tsx +++ b/src/EmptyScreen/EmptyScreen.tsx @@ -8,10 +8,10 @@ import { RecentDocSearchHit } from '../types'; interface EmptyScreenProps extends AutocompleteApi< - RecentDocSearchHit, - React.FormEvent, - React.MouseEvent, - React.KeyboardEvent + RecentDocSearchHit, + React.FormEvent, + React.MouseEvent, + React.KeyboardEvent > { state: AutocompleteState; hasSuggestions: boolean; @@ -52,10 +52,9 @@ export function EmptyScreen(props: EmptyScreenProps) {
- + - - - +
diff --git a/src/Results/ActionIcon.tsx b/src/Results/ActionIcon.tsx index 15e955e0..3027c50d 100644 --- a/src/Results/ActionIcon.tsx +++ b/src/Results/ActionIcon.tsx @@ -5,14 +5,13 @@ export function SelectIcon() { - - + + ); diff --git a/src/Results/Results.tsx b/src/Results/Results.tsx index d63b024b..4d12fbcf 100644 --- a/src/Results/Results.tsx +++ b/src/Results/Results.tsx @@ -60,7 +60,6 @@ export function Results(props: ResultsProps) { + ); diff --git a/src/SearchBox/SearchIcon.tsx b/src/SearchBox/SearchIcon.tsx index fab22ae7..b95097de 100644 --- a/src/SearchBox/SearchIcon.tsx +++ b/src/SearchBox/SearchIcon.tsx @@ -2,11 +2,15 @@ import React from 'react'; export function SearchIcon() { return ( - + - - - - + Search - {key} K diff --git a/src/style.css b/src/style.css index 27d5eb4f..50040876 100644 --- a/src/style.css +++ b/src/style.css @@ -4,7 +4,7 @@ --docsearch-input-color: var(--ifm-color-emphasis-800); --docsearch-highlight-color: var(--ifm-color-primary); --docsearch-placeholder-color: rgb(150, 155, 175); - --docsearch-container-background: rgb(60, 65, 85, 0.8); + --docsearch-container-background: rgba(101, 108, 133, 0.8); --docsearch-modal-background: var(--ifm-color-emphasis-100); --docsearch-modal-shadow: inset 1px 1px 0px 0px rgba(255, 255, 255, 0.5), 0px 3px 8px 0px rgba(85, 90, 100, 1); @@ -31,6 +31,7 @@ --docsearch-hit-height: 56px; --docsearch-footer-height: 44px; --docsearch-spacing: 12px; + --docsearch-icon-stroke-width: 1.6; } /* Darkmode */ @@ -86,12 +87,12 @@ html[data-theme='dark'] { box-shadow: var(--docsearch-searchbox-shadow); } -.DocSearch-SearchButton-Icon { - color: var(--docsearch-highlight-color); +.DocSearch-Search-Icon { + stroke-width: var(--docsearch-icon-stroke-width); } .DocSearch-SearchButton-Placeholder { - margin: 0 8px; + margin: 0 1em 0 .5em; font-size: 1rem; } @@ -222,8 +223,6 @@ html[data-theme='dark'] { .DocSearch-LoadingIndicator, .DocSearch-MagnifierLabel, .DocSearch-Reset { - height: 20px; - width: 20px; margin: 0; padding: 0; } @@ -248,7 +247,14 @@ html[data-theme='dark'] { border-radius: 50%; background: none; cursor: pointer; + stroke-width: var(--docsearch-icon-stroke-width); color: var(--docsearch-icon); + animation: reset-button .2s ease-in forwards; +} + +@keyframes reset-button { + 0% {opacity: 0} + 100% { opacity: 1} } .DocSearch-Reset[hidden] { @@ -259,15 +265,10 @@ html[data-theme='dark'] { outline: none; } -.DocSearch-Reset svg { - height: 10px; - width: 10px; -} - .DocSearch-LoadingIndicator svg, .DocSearch-MagnifierLabel svg { - height: 24px; - width: 24px; + width:24px; + height:24px; } .DocSearch-MagnifierLabel { @@ -445,6 +446,7 @@ html[data-theme='dark'] { height: 20px; width: 20px; color: var(--docsearch-muted-color); + stroke-width: var(--docsearch-icon-stroke-width); } .DocSearch-Hit-action-button { @@ -524,6 +526,7 @@ html[data-theme='dark'] { font-weight: 500; color: var(--docsearch-highlight-color); animation: cancel-button ease-out 0.2s forwards; + outline: none; } @keyframes cancel-button { diff --git a/src/svg/icon_action_clear.svg b/src/svg/icon_action_clear.svg new file mode 100644 index 00000000..be16976c --- /dev/null +++ b/src/svg/icon_action_clear.svg @@ -0,0 +1,11 @@ + + + + 2b0d15d6-5f05-49e4-8d6b-a70e8854fdaf@1.00x + Created with sketchtool. + + + + + + \ No newline at end of file diff --git a/src/svg/icon_action_microphone.svg b/src/svg/icon_action_microphone.svg new file mode 100644 index 00000000..5955319a --- /dev/null +++ b/src/svg/icon_action_microphone.svg @@ -0,0 +1,13 @@ + + + + 026603da-1296-49a5-8a02-d1059668a8c1@1.00x + Created with sketchtool. + + + + + + + + \ No newline at end of file diff --git a/src/svg/icon_search_primary.svg b/src/svg/icon_search_primary.svg new file mode 100644 index 00000000..833528d3 --- /dev/null +++ b/src/svg/icon_search_primary.svg @@ -0,0 +1,11 @@ + + + + a9ce3a89-b607-4099-acb0-89761f0cf8fa@1.00x + Created with sketchtool. + + + + + + \ No newline at end of file From cf7864d7f5057d7c2682522b8cc17f1811843d64 Mon Sep 17 00:00:00 2001 From: Shipow Date: Thu, 2 Apr 2020 22:41:49 +0200 Subject: [PATCH 007/151] fix(design): no results, empty screen + details --- src/EmptyScreen/EmptyScreen.tsx | 10 +++++----- src/NoResults/NoResults.tsx | 12 +++++------- src/style.css | 27 +++++++++++++++++++-------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/EmptyScreen/EmptyScreen.tsx b/src/EmptyScreen/EmptyScreen.tsx index 159ef3fe..2722ef0a 100644 --- a/src/EmptyScreen/EmptyScreen.tsx +++ b/src/EmptyScreen/EmptyScreen.tsx @@ -6,6 +6,8 @@ import { import { RecentDocSearchHit } from '../types'; +import { ResetIcon } from '../SearchBox/ResetIcon'; + interface EmptyScreenProps extends AutocompleteApi< RecentDocSearchHit, @@ -22,8 +24,8 @@ interface EmptyScreenProps export function EmptyScreen(props: EmptyScreenProps) { if (props.state.status === 'idle' && props.hasSuggestions === false) { return ( -
-

Select results and your history will appear here.

+
+

Your search history will appear here.

); } @@ -117,9 +119,7 @@ export function EmptyScreen(props: EmptyScreenProps) { props.onAction(item); }} > - - - +
diff --git a/src/NoResults/NoResults.tsx b/src/NoResults/NoResults.tsx index 147d4e70..e4f3d7a0 100644 --- a/src/NoResults/NoResults.tsx +++ b/src/NoResults/NoResults.tsx @@ -20,8 +20,8 @@ interface NoResultsProps export function NoResults(props: NoResultsProps) { return (
-

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

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

@@ -32,7 +32,6 @@ export function NoResults(props: NoResultsProps) { (acc, search) => [ ...acc, acc.length > 0 ? ', ' : '', - '“', , - '“', ], [] )} - . +  ...

-

- If you believe this query should return results, please{' '} +

+ If you believe this query should return results,
please{' '}
Date: Thu, 2 Apr 2020 23:37:29 +0200 Subject: [PATCH 008/151] fix(docsearch): update local storage key --- src/recent-searches.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recent-searches.ts b/src/recent-searches.ts index 73a77db1..0dbd32f9 100644 --- a/src/recent-searches.ts +++ b/src/recent-searches.ts @@ -23,7 +23,7 @@ function createStorage() { }; } - const STORAGE_KEY = '__AUTOCOMPLETE_RECENT_SEARCHES__'; + const STORAGE_KEY = '__DOCSEARCH_RECENT_SEARCHES__'; return { setItem(item: TItem[]) { From 3cc88fa471fca79495566d41d70b19a69429742b Mon Sep 17 00:00:00 2001 From: Shipow Date: Fri, 3 Apr 2020 17:21:24 +0200 Subject: [PATCH 009/151] feat(design): new icons --- src/svg/icon_action_microphone.svg | 2 +- src/svg/icon_action_pin.svg | 12 ++++++++++++ src/svg/icon_action_star.svg | 11 +++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/svg/icon_action_pin.svg create mode 100644 src/svg/icon_action_star.svg diff --git a/src/svg/icon_action_microphone.svg b/src/svg/icon_action_microphone.svg index 5955319a..68115211 100644 --- a/src/svg/icon_action_microphone.svg +++ b/src/svg/icon_action_microphone.svg @@ -1,7 +1,7 @@ - 026603da-1296-49a5-8a02-d1059668a8c1@1.00x + 0047a2f9-df1e-4b3d-b438-1986c74c1e83@1.00x Created with sketchtool. diff --git a/src/svg/icon_action_pin.svg b/src/svg/icon_action_pin.svg new file mode 100644 index 00000000..63bed7f6 --- /dev/null +++ b/src/svg/icon_action_pin.svg @@ -0,0 +1,12 @@ + + + + 489a6d45-9af5-4385-8778-bc34fc9ebfee@1.00x + Created with sketchtool. + + + + + + + \ No newline at end of file diff --git a/src/svg/icon_action_star.svg b/src/svg/icon_action_star.svg new file mode 100644 index 00000000..d9b620d4 --- /dev/null +++ b/src/svg/icon_action_star.svg @@ -0,0 +1,11 @@ + + + + d9b99ef7-6194-43f4-8ab6-be1cc103dc86@1.00x + Created with sketchtool. + + + + + + \ No newline at end of file From 708b470cc11dc9f266a4a7f635aa93fa18a370ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 3 Apr 2020 17:42:30 +0200 Subject: [PATCH 010/151] feat(docsearch): introduce favorite searches --- src/{Footer => }/AlgoliaLogo.tsx | 0 src/DocSearch.tsx | 58 +++++-- src/Dropdown/Dropdown.tsx | 49 ------ src/Dropdown/index.ts | 1 - src/EmptyScreen.tsx | 110 +++++++++++++ src/EmptyScreen/EmptyScreen.tsx | 136 ---------------- src/EmptyScreen/index.ts | 1 - src/Error/index.ts | 1 - src/{Error/Error.tsx => ErrorScreen.tsx} | 2 +- src/{Footer => }/Footer.tsx | 0 src/Footer/index.ts | 1 - src/Footer/index.tsx | 1 - src/NoResults/index.ts | 1 - .../NoResults.tsx => NoResultsScreen.tsx} | 8 +- src/Results.tsx | 120 ++++++++++++++ src/Results/Results.tsx | 148 ------------------ src/Results/index.ts | 1 - src/ResultsScreen.tsx | 74 +++++++++ src/ScreenState.tsx | 51 ++++++ src/{SearchBox => }/SearchBox.tsx | 8 +- src/SearchBox/index.tsx | 1 - src/{SearchButton => }/SearchButton.tsx | 3 +- src/SearchButton/index.ts | 1 - src/Snippet.tsx | 12 +- src/icons/GoToExternalIcon.tsx | 13 ++ src/{SearchBox => icons}/LoadingIcon.tsx | 0 src/icons/RecentIcon.tsx | 18 +++ src/{SearchBox => icons}/ResetIcon.tsx | 0 src/{SearchBox => icons}/SearchIcon.tsx | 0 .../ActionIcon.tsx => icons/SelectIcon.tsx} | 12 -- src/{Results => icons}/SourceIcon.tsx | 0 src/icons/StarIcon.tsx | 16 ++ src/icons/index.ts | 8 + ...{recent-searches.ts => stored-searches.ts} | 50 +++--- src/style.css | 4 + ...tDocSearchHit.ts => StoredDocSearchHit.ts} | 2 +- src/types/index.ts | 2 +- 37 files changed, 507 insertions(+), 406 deletions(-) rename src/{Footer => }/AlgoliaLogo.tsx (100%) delete mode 100644 src/Dropdown/Dropdown.tsx delete mode 100644 src/Dropdown/index.ts create mode 100644 src/EmptyScreen.tsx delete mode 100644 src/EmptyScreen/EmptyScreen.tsx delete mode 100644 src/EmptyScreen/index.ts delete mode 100644 src/Error/index.ts rename src/{Error/Error.tsx => ErrorScreen.tsx} (83%) rename src/{Footer => }/Footer.tsx (100%) delete mode 100644 src/Footer/index.ts delete mode 100644 src/Footer/index.tsx delete mode 100644 src/NoResults/index.ts rename src/{NoResults/NoResults.tsx => NoResultsScreen.tsx} (88%) create mode 100644 src/Results.tsx delete mode 100644 src/Results/Results.tsx delete mode 100644 src/Results/index.ts create mode 100644 src/ResultsScreen.tsx create mode 100644 src/ScreenState.tsx rename src/{SearchBox => }/SearchBox.tsx (89%) delete mode 100644 src/SearchBox/index.tsx rename src/{SearchButton => }/SearchButton.tsx (94%) delete mode 100644 src/SearchButton/index.ts create mode 100644 src/icons/GoToExternalIcon.tsx rename src/{SearchBox => icons}/LoadingIcon.tsx (100%) create mode 100644 src/icons/RecentIcon.tsx rename src/{SearchBox => icons}/ResetIcon.tsx (100%) rename src/{SearchBox => icons}/SearchIcon.tsx (100%) rename src/{Results/ActionIcon.tsx => icons/SelectIcon.tsx} (53%) rename src/{Results => icons}/SourceIcon.tsx (100%) create mode 100644 src/icons/StarIcon.tsx create mode 100644 src/icons/index.ts rename src/{recent-searches.ts => stored-searches.ts} (50%) rename src/types/{RecentDocSearchHit.ts => StoredDocSearchHit.ts} (73%) diff --git a/src/Footer/AlgoliaLogo.tsx b/src/AlgoliaLogo.tsx similarity index 100% rename from src/Footer/AlgoliaLogo.tsx rename to src/AlgoliaLogo.tsx diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index a616fa64..8358a0b4 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -8,14 +8,14 @@ import { getAlgoliaHits } from '@francoischalifour/autocomplete-preset-algolia'; import { DocSearchHit, InternalDocSearchHit, - RecentDocSearchHit, + StoredDocSearchHit, } from './types'; import { createSearchClient, groupBy, noop } from './utils'; import { SearchBox } from './SearchBox'; -import { Dropdown } from './Dropdown'; +import { ScreenState } from './ScreenState'; import { Footer } from './Footer'; -import { createRecentSearches } from './recent-searches'; +import { createStoredSearches } from './stored-searches'; interface DocSearchProps { appId?: string; @@ -48,7 +48,18 @@ export function DocSearch({ appId, apiKey, ]); - const recentSearches = useRef(createRecentSearches()); + const recentSearches = useRef( + createStoredSearches({ + key: '__DOCSEARCH_RECENT_SEARCHES__', + limit: 5, + }) + ).current; + const favoriteSearches = useRef( + createStoredSearches({ + key: '__DOCSEARCH_FAVORITE_SEARCHES__', + limit: 10, + }) + ).current; const autocomplete = React.useMemo( () => @@ -128,14 +139,26 @@ export function DocSearch({ return [ { onSelect({ suggestion }) { - recentSearches.current.saveSearch(suggestion); + recentSearches.add(suggestion); onClose(); }, getSuggestionUrl({ suggestion }) { return suggestion.url; }, getSuggestions() { - return recentSearches.current.getSearches(); + return recentSearches.getAll(); + }, + }, + { + onSelect({ suggestion }) { + recentSearches.add(suggestion); + onClose(); + }, + getSuggestionUrl({ suggestion }) { + return suggestion.url; + }, + getSuggestions() { + return favoriteSearches.getAll(); }, }, ]; @@ -144,7 +167,7 @@ export function DocSearch({ return Object.values(sources).map(items => { return { onSelect({ suggestion }) { - recentSearches.current.saveSearch(suggestion); + recentSearches.add(suggestion); onClose(); }, getSuggestionUrl({ suggestion }) { @@ -177,7 +200,14 @@ export function DocSearch({ }); }, }), - [indexName, searchParameters, searchClient, onClose] + [ + indexName, + searchParameters, + searchClient, + onClose, + recentSearches, + favoriteSearches, + ] ); const { getEnvironmentProps, getRootProps } = autocomplete; @@ -244,18 +274,16 @@ export function DocSearch({

diff --git a/src/Dropdown/Dropdown.tsx b/src/Dropdown/Dropdown.tsx deleted file mode 100644 index 0643b814..00000000 --- a/src/Dropdown/Dropdown.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { - AutocompleteApi, - AutocompleteState, -} from '@francoischalifour/autocomplete-core'; - -import { InternalDocSearchHit, RecentDocSearchHit } from '../types'; -import { EmptyScreen } from '../EmptyScreen'; -import { Results } from '../Results'; -import { NoResults } from '../NoResults'; -import { Error } from '../Error'; - -interface DropdownProps - extends AutocompleteApi< - TItem, - React.FormEvent, - React.MouseEvent, - React.KeyboardEvent - > { - state: AutocompleteState; - onItemClick(search: RecentDocSearchHit): void; - onAction(search: RecentDocSearchHit): void; - inputRef: React.MutableRefObject; -} - -export function Dropdown(props: DropdownProps) { - if (props.state.status === 'error') { - return ; - } - - const hasSuggestions = props.state.suggestions.some( - source => source.items.length > 0 - ); - - if (!props.state.query) { - return ( - )} - hasSuggestions={hasSuggestions} - /> - ); - } - - if (props.state.status === 'idle' && hasSuggestions === false) { - return ; - } - - return ; -} diff --git a/src/Dropdown/index.ts b/src/Dropdown/index.ts deleted file mode 100644 index 2f29bad4..00000000 --- a/src/Dropdown/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Dropdown'; diff --git a/src/EmptyScreen.tsx b/src/EmptyScreen.tsx new file mode 100644 index 00000000..c0967dd3 --- /dev/null +++ b/src/EmptyScreen.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { + AutocompleteApi, + AutocompleteState, +} from '@francoischalifour/autocomplete-core'; + +import { StoredDocSearchHit } from './types'; +import { StoredSearchPlugin } from './stored-searches'; +import { Results } from './Results'; +import { ResetIcon, RecentIcon, StarIcon } from './icons'; + +interface EmptyScreenProps + extends AutocompleteApi< + StoredDocSearchHit, + React.FormEvent, + React.MouseEvent, + React.KeyboardEvent + > { + state: AutocompleteState; + hasSuggestions: boolean; + onItemClick(item: StoredDocSearchHit): void; + recentSearches: StoredSearchPlugin; + favoriteSearches: StoredSearchPlugin; +} + +export function EmptyScreen(props: EmptyScreenProps) { + if (props.state.status === 'idle' && props.hasSuggestions === false) { + return ( +
+

Your search history will appear here.

+
+ ); + } + + if (props.hasSuggestions === false) { + return null; + } + + return ( +
+ ( +
+ +
+ )} + renderAction={({ item }) => ( + <> +
+ +
+
+ +
+ + )} + /> + + ( +
+ +
+ )} + renderAction={({ item }) => ( + + )} + /> +
+ ); +} diff --git a/src/EmptyScreen/EmptyScreen.tsx b/src/EmptyScreen/EmptyScreen.tsx deleted file mode 100644 index 2722ef0a..00000000 --- a/src/EmptyScreen/EmptyScreen.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React from 'react'; -import { - AutocompleteApi, - AutocompleteState, -} from '@francoischalifour/autocomplete-core'; - -import { RecentDocSearchHit } from '../types'; - -import { ResetIcon } from '../SearchBox/ResetIcon'; - -interface EmptyScreenProps - extends AutocompleteApi< - RecentDocSearchHit, - React.FormEvent, - React.MouseEvent, - React.KeyboardEvent - > { - state: AutocompleteState; - hasSuggestions: boolean; - onItemClick(item: RecentDocSearchHit): void; - onAction(search: RecentDocSearchHit): void; -} - -export function EmptyScreen(props: EmptyScreenProps) { - if (props.state.status === 'idle' && props.hasSuggestions === false) { - return ( -
-

Your search history will appear here.

-
- ); - } - - return ( -
- ); -} diff --git a/src/EmptyScreen/index.ts b/src/EmptyScreen/index.ts deleted file mode 100644 index f184be92..00000000 --- a/src/EmptyScreen/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './EmptyScreen'; diff --git a/src/Error/index.ts b/src/Error/index.ts deleted file mode 100644 index ae6e95d0..00000000 --- a/src/Error/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Error'; diff --git a/src/Error/Error.tsx b/src/ErrorScreen.tsx similarity index 83% rename from src/Error/Error.tsx rename to src/ErrorScreen.tsx index 12d983b2..0248090f 100644 --- a/src/Error/Error.tsx +++ b/src/ErrorScreen.tsx @@ -1,6 +1,6 @@ import React from 'react'; -export function Error() { +export function ErrorScreen() { return (

We‘re unable to fetch results. You might want to check your network diff --git a/src/Footer/Footer.tsx b/src/Footer.tsx similarity index 100% rename from src/Footer/Footer.tsx rename to src/Footer.tsx diff --git a/src/Footer/index.ts b/src/Footer/index.ts deleted file mode 100644 index ddcc5a9c..00000000 --- a/src/Footer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Footer'; diff --git a/src/Footer/index.tsx b/src/Footer/index.tsx deleted file mode 100644 index ddcc5a9c..00000000 --- a/src/Footer/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './Footer'; diff --git a/src/NoResults/index.ts b/src/NoResults/index.ts deleted file mode 100644 index 20eae4db..00000000 --- a/src/NoResults/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './NoResults'; diff --git a/src/NoResults/NoResults.tsx b/src/NoResultsScreen.tsx similarity index 88% rename from src/NoResults/NoResults.tsx rename to src/NoResultsScreen.tsx index e4f3d7a0..f32eaf6b 100644 --- a/src/NoResults/NoResults.tsx +++ b/src/NoResultsScreen.tsx @@ -4,7 +4,7 @@ import { AutocompleteState, } from '@francoischalifour/autocomplete-core'; -import { InternalDocSearchHit } from '../types'; +import { InternalDocSearchHit } from './types'; interface NoResultsProps extends AutocompleteApi< @@ -17,7 +17,7 @@ interface NoResultsProps inputRef: React.MutableRefObject; } -export function NoResults(props: NoResultsProps) { +export function NoResultsScreen(props: NoResultsProps) { return (

@@ -50,7 +50,9 @@ export function NoResults(props: NoResultsProps) {

- If you believe this query should return results,
please{' '} + If you believe this query should return results, +
+ please{' '} + extends AutocompleteApi< + TItem, + React.FormEvent, + React.MouseEvent, + React.KeyboardEvent + > { + title: string; + suggestion: AutocompleteState['suggestions'][0]; + renderIcon(props: { item: TItem; index: number }): React.ReactNode; + renderAction(props: { item: TItem }): React.ReactNode; + onItemClick(item: TItem): void; +} + +export function Results( + props: ResultsProps +) { + if (props.suggestion.items.length === 0) { + return null; + } + + return ( +

+
{props.title}
+ +
+
+ ); +} diff --git a/src/Results/Results.tsx b/src/Results/Results.tsx deleted file mode 100644 index 4d12fbcf..00000000 --- a/src/Results/Results.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React from 'react'; -import { - AutocompleteApi, - AutocompleteState, -} from '@francoischalifour/autocomplete-core'; - -import { Snippet } from '../Snippet'; -import { SourceIcon } from './SourceIcon'; -import { SelectIcon } from './ActionIcon'; -import { InternalDocSearchHit } from '../types'; - -interface ResultsProps - extends AutocompleteApi< - InternalDocSearchHit, - React.FormEvent, - React.MouseEvent, - React.KeyboardEvent - > { - state: AutocompleteState; - onItemClick(item: InternalDocSearchHit): void; - onAction(search: InternalDocSearchHit): void; -} - -export function Results(props: ResultsProps) { - return ( - - ); -} diff --git a/src/Results/index.ts b/src/Results/index.ts deleted file mode 100644 index 9646dd49..00000000 --- a/src/Results/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Results'; diff --git a/src/ResultsScreen.tsx b/src/ResultsScreen.tsx new file mode 100644 index 00000000..f7fc529a --- /dev/null +++ b/src/ResultsScreen.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { + AutocompleteApi, + AutocompleteState, +} from '@francoischalifour/autocomplete-core'; + +import { InternalDocSearchHit } from './types'; +import { Results } from './Results'; +import { SourceIcon, SelectIcon } from './icons'; + +interface ResultsProps + extends AutocompleteApi< + InternalDocSearchHit, + React.FormEvent, + React.MouseEvent, + React.KeyboardEvent + > { + state: AutocompleteState; + onItemClick(item: InternalDocSearchHit): void; +} + +export function ResultsScreen(props: ResultsProps) { + return ( +
+ {props.state.suggestions.map((suggestion, index) => { + if (suggestion.items.length === 0) { + return null; + } + + const title = suggestion.items[0].hierarchy.lvl0; + + return ( + ( + <> + {item.__docsearch_parent && ( + + + {item.__docsearch_parent !== + suggestion.items[index + 1]?.__docsearch_parent ? ( + + ) : ( + + )} + + + )} + +
+ +
+ + )} + renderAction={() => ( +
+ +
+ )} + /> + ); + })} +
+ ); +} diff --git a/src/ScreenState.tsx b/src/ScreenState.tsx new file mode 100644 index 00000000..3ad42169 --- /dev/null +++ b/src/ScreenState.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { + AutocompleteApi, + AutocompleteState, +} from '@francoischalifour/autocomplete-core'; + +import { StoredSearchPlugin } from './stored-searches'; +import { InternalDocSearchHit, StoredDocSearchHit } from './types'; +import { EmptyScreen } from './EmptyScreen'; +import { ResultsScreen } from './ResultsScreen'; +import { NoResultsScreen } from './NoResultsScreen'; +import { ErrorScreen } from './ErrorScreen'; + +interface DropdownProps + extends AutocompleteApi< + TItem, + React.FormEvent, + React.MouseEvent, + React.KeyboardEvent + > { + state: AutocompleteState; + recentSearches: StoredSearchPlugin; + favoriteSearches: StoredSearchPlugin; + onItemClick(item: StoredDocSearchHit): void; + inputRef: React.MutableRefObject; +} + +export function ScreenState(props: DropdownProps) { + if (props.state.status === 'error') { + return ; + } + + const hasSuggestions = props.state.suggestions.some( + suggestion => suggestion.items.length > 0 + ); + + if (!props.state.query) { + return ( + )} + hasSuggestions={hasSuggestions} + /> + ); + } + + if (props.state.status === 'idle' && hasSuggestions === false) { + return ; + } + + return ; +} diff --git a/src/SearchBox/SearchBox.tsx b/src/SearchBox.tsx similarity index 89% rename from src/SearchBox/SearchBox.tsx rename to src/SearchBox.tsx index 12b6b23e..2189d9c6 100644 --- a/src/SearchBox/SearchBox.tsx +++ b/src/SearchBox.tsx @@ -4,10 +4,10 @@ import { AutocompleteState, } from '@francoischalifour/autocomplete-core'; -import { InternalDocSearchHit } from '../types'; -import { SearchIcon } from './SearchIcon'; -import { ResetIcon } from './ResetIcon'; -import { LoadingIcon } from './LoadingIcon'; +import { InternalDocSearchHit } from './types'; +import { SearchIcon } from './icons/SearchIcon'; +import { ResetIcon } from './icons/ResetIcon'; +import { LoadingIcon } from './icons/LoadingIcon'; interface SearchBoxProps extends AutocompleteApi< diff --git a/src/SearchBox/index.tsx b/src/SearchBox/index.tsx deleted file mode 100644 index ffc46c99..00000000 --- a/src/SearchBox/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './SearchBox'; diff --git a/src/SearchButton/SearchButton.tsx b/src/SearchButton.tsx similarity index 94% rename from src/SearchButton/SearchButton.tsx rename to src/SearchButton.tsx index 4179cda9..364b4fba 100644 --- a/src/SearchButton/SearchButton.tsx +++ b/src/SearchButton.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { SearchIcon } from '../SearchBox/SearchIcon'; + +import { SearchIcon } from './icons/SearchIcon'; interface SearchButtonProps { onClick(): void; diff --git a/src/SearchButton/index.ts b/src/SearchButton/index.ts deleted file mode 100644 index 6f4ca098..00000000 --- a/src/SearchButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './SearchButton'; diff --git a/src/Snippet.tsx b/src/Snippet.tsx index 807ca1d2..39d1ceae 100644 --- a/src/Snippet.tsx +++ b/src/Snippet.tsx @@ -1,6 +1,6 @@ import { createElement } from 'react'; -import { InternalDocSearchHit } from './types'; +import { StoredDocSearchHit } from './types'; function getPropertyByPath(object: object, path: string): any { const parts = path.split('.'); @@ -8,25 +8,25 @@ function getPropertyByPath(object: object, path: string): any { return parts.reduce((current, key) => current && current[key], object); } -interface SnippetProps { +interface SnippetProps { [prop: string]: unknown; - hit: InternalDocSearchHit; + hit: TItem; attribute: string; tagName?: string; } -export function Snippet({ +export function Snippet({ hit, attribute, tagName = 'span', ...rest -}: SnippetProps) { +}: SnippetProps) { return createElement(tagName, { ...rest, dangerouslySetInnerHTML: { __html: getPropertyByPath(hit, `_snippetResult.${attribute}.value`) || - hit[attribute], + getPropertyByPath(hit, attribute), }, }); } diff --git a/src/icons/GoToExternalIcon.tsx b/src/icons/GoToExternalIcon.tsx new file mode 100644 index 00000000..4b47c7f8 --- /dev/null +++ b/src/icons/GoToExternalIcon.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export function GoToExternal() { + return ( + + + + ); +} diff --git a/src/SearchBox/LoadingIcon.tsx b/src/icons/LoadingIcon.tsx similarity index 100% rename from src/SearchBox/LoadingIcon.tsx rename to src/icons/LoadingIcon.tsx diff --git a/src/icons/RecentIcon.tsx b/src/icons/RecentIcon.tsx new file mode 100644 index 00000000..51f7ba31 --- /dev/null +++ b/src/icons/RecentIcon.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +export function RecentIcon() { + return ( + + + + + + + ); +} diff --git a/src/SearchBox/ResetIcon.tsx b/src/icons/ResetIcon.tsx similarity index 100% rename from src/SearchBox/ResetIcon.tsx rename to src/icons/ResetIcon.tsx diff --git a/src/SearchBox/SearchIcon.tsx b/src/icons/SearchIcon.tsx similarity index 100% rename from src/SearchBox/SearchIcon.tsx rename to src/icons/SearchIcon.tsx diff --git a/src/Results/ActionIcon.tsx b/src/icons/SelectIcon.tsx similarity index 53% rename from src/Results/ActionIcon.tsx rename to src/icons/SelectIcon.tsx index 3027c50d..3835f015 100644 --- a/src/Results/ActionIcon.tsx +++ b/src/icons/SelectIcon.tsx @@ -16,15 +16,3 @@ export function SelectIcon() { ); } - -export function GoToExternal() { - return ( - - - - ); -} diff --git a/src/Results/SourceIcon.tsx b/src/icons/SourceIcon.tsx similarity index 100% rename from src/Results/SourceIcon.tsx rename to src/icons/SourceIcon.tsx diff --git a/src/icons/StarIcon.tsx b/src/icons/StarIcon.tsx new file mode 100644 index 00000000..01d795b0 --- /dev/null +++ b/src/icons/StarIcon.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +export function StarIcon() { + return ( + + + + ); +} diff --git a/src/icons/index.ts b/src/icons/index.ts new file mode 100644 index 00000000..7f1f7c8b --- /dev/null +++ b/src/icons/index.ts @@ -0,0 +1,8 @@ +export * from './GoToExternalIcon'; +export * from './LoadingIcon'; +export * from './RecentIcon'; +export * from './ResetIcon'; +export * from './SearchIcon'; +export * from './SelectIcon'; +export * from './SourceIcon'; +export * from './StarIcon'; diff --git a/src/recent-searches.ts b/src/stored-searches.ts similarity index 50% rename from src/recent-searches.ts rename to src/stored-searches.ts index 0dbd32f9..5e45ef4b 100644 --- a/src/recent-searches.ts +++ b/src/stored-searches.ts @@ -1,4 +1,4 @@ -import { DocSearchHit, RecentDocSearchHit } from './types'; +import { DocSearchHit, StoredDocSearchHit } from './types'; function isLocalStorageSupported() { const key = '__TEST_KEY__'; @@ -13,7 +13,7 @@ function isLocalStorageSupported() { } } -function createStorage() { +function createStorage(key: string) { if (isLocalStorageSupported() === false) { return { setItem() {}, @@ -23,57 +23,67 @@ function createStorage() { }; } - const STORAGE_KEY = '__DOCSEARCH_RECENT_SEARCHES__'; - return { setItem(item: TItem[]) { - return window.localStorage.setItem(STORAGE_KEY, JSON.stringify(item)); + return window.localStorage.setItem(key, JSON.stringify(item)); }, getItem(): TItem[] { - const item = window.localStorage.getItem(STORAGE_KEY); + const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : []; }, }; } -export function createRecentSearches() { - const storage = createStorage(); +type CreateStoredSearchesOptions = { + key: string; + limit?: number; +}; + +export type StoredSearchPlugin = { + add(item: TItem): void; + remove(item: TItem): void; + getAll(): TItem[]; +}; + +export function createStoredSearches({ + key, + limit = 5, +}: CreateStoredSearchesOptions): StoredSearchPlugin { + const storage = createStorage(key); let items = storage.getItem(); return { - saveSearch(search: TItem) { + add(item: TItem) { const { _highlightResult, _snippetResult, - ...item - } = (search as unknown) as DocSearchHit; + ...hit + } = (item as unknown) as DocSearchHit; - if (item.type === 'content') { - return false; + if (hit.type === 'content') { + return; } const isQueryAlreadySaved = items.findIndex( - x => x.objectID === item.objectID + x => x.objectID === hit.objectID ); if (isQueryAlreadySaved > -1) { items.splice(isQueryAlreadySaved, 1); } - items.unshift(item as TItem); - items = items.slice(0, 5); + items.unshift(hit as TItem); + items = items.slice(0, limit); storage.setItem(items); - - return true; }, - deleteSearch(item: TItem) { + remove(item: TItem) { items = items.filter(x => x.objectID !== item.objectID); storage.setItem(items); }, - getSearches() { + getAll() { return items; }, }; diff --git a/src/style.css b/src/style.css index c4efc847..6f0471d6 100644 --- a/src/style.css +++ b/src/style.css @@ -460,6 +460,10 @@ html[data-theme='dark'] { stroke-width: var(--docsearch-icon-stroke-width); } +.DocSearch-Hit-action + .DocSearch-Hit-action { + margin-left: 5px; +} + .DocSearch-Hit-action-button { appearance: none; background: none; diff --git a/src/types/RecentDocSearchHit.ts b/src/types/StoredDocSearchHit.ts similarity index 73% rename from src/types/RecentDocSearchHit.ts rename to src/types/StoredDocSearchHit.ts index e00c482b..db41dbf4 100644 --- a/src/types/RecentDocSearchHit.ts +++ b/src/types/StoredDocSearchHit.ts @@ -1,6 +1,6 @@ import { DocSearchHit } from './DocSearchHit'; -export type RecentDocSearchHit = Omit< +export type StoredDocSearchHit = Omit< DocSearchHit, '_highlightResult' | '_snippetResult' >; diff --git a/src/types/index.ts b/src/types/index.ts index 76849a24..93285bd1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,3 @@ export * from './DocSearchHit'; export * from './InternalDocSearchHit'; -export * from './RecentDocSearchHit'; +export * from './StoredDocSearchHit'; From 858a49820f93141be1bbee4ed58d645d35a86e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 3 Apr 2020 18:05:33 +0200 Subject: [PATCH 011/151] fix(docsearch): remove recent search when favorited --- src/EmptyScreen.tsx | 27 +++++++++++++++------------ src/NoResultsScreen.tsx | 4 ++-- src/ResultsScreen.tsx | 4 ++-- src/ScreenState.tsx | 6 +++--- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/EmptyScreen.tsx b/src/EmptyScreen.tsx index c0967dd3..9175b703 100644 --- a/src/EmptyScreen.tsx +++ b/src/EmptyScreen.tsx @@ -57,6 +57,7 @@ export function EmptyScreen(props: EmptyScreenProps) { event.preventDefault(); event.stopPropagation(); props.favoriteSearches.add(item); + props.recentSearches.remove(item); props.refresh(); }} > @@ -91,18 +92,20 @@ export function EmptyScreen(props: EmptyScreenProps) {
)} renderAction={({ item }) => ( - +
+ +
)} />
diff --git a/src/NoResultsScreen.tsx b/src/NoResultsScreen.tsx index f32eaf6b..01f73cb7 100644 --- a/src/NoResultsScreen.tsx +++ b/src/NoResultsScreen.tsx @@ -6,7 +6,7 @@ import { import { InternalDocSearchHit } from './types'; -interface NoResultsProps +interface NoResultsScreenProps extends AutocompleteApi< InternalDocSearchHit, React.FormEvent, @@ -17,7 +17,7 @@ interface NoResultsProps inputRef: React.MutableRefObject; } -export function NoResultsScreen(props: NoResultsProps) { +export function NoResultsScreen(props: NoResultsScreenProps) { return (

diff --git a/src/ResultsScreen.tsx b/src/ResultsScreen.tsx index f7fc529a..51af931b 100644 --- a/src/ResultsScreen.tsx +++ b/src/ResultsScreen.tsx @@ -8,7 +8,7 @@ import { InternalDocSearchHit } from './types'; import { Results } from './Results'; import { SourceIcon, SelectIcon } from './icons'; -interface ResultsProps +interface ResultsScreenProps extends AutocompleteApi< InternalDocSearchHit, React.FormEvent, @@ -19,7 +19,7 @@ interface ResultsProps onItemClick(item: InternalDocSearchHit): void; } -export function ResultsScreen(props: ResultsProps) { +export function ResultsScreen(props: ResultsScreenProps) { return (

{props.state.suggestions.map((suggestion, index) => { diff --git a/src/ScreenState.tsx b/src/ScreenState.tsx index 3ad42169..7c4faec6 100644 --- a/src/ScreenState.tsx +++ b/src/ScreenState.tsx @@ -11,7 +11,7 @@ import { ResultsScreen } from './ResultsScreen'; import { NoResultsScreen } from './NoResultsScreen'; import { ErrorScreen } from './ErrorScreen'; -interface DropdownProps +interface ScreenStateProps extends AutocompleteApi< TItem, React.FormEvent, @@ -25,7 +25,7 @@ interface DropdownProps inputRef: React.MutableRefObject; } -export function ScreenState(props: DropdownProps) { +export function ScreenState(props: ScreenStateProps) { if (props.state.status === 'error') { return ; } @@ -37,7 +37,7 @@ export function ScreenState(props: DropdownProps) { if (!props.state.query) { return ( )} + {...(props as ScreenStateProps)} hasSuggestions={hasSuggestions} /> ); From f00672ae554141fb585cd3efaaef614c4c61f6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 3 Apr 2020 18:22:14 +0200 Subject: [PATCH 012/151] fix(docsearch): don't add search to recent searches if favorited --- src/DocSearch.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 8358a0b4..672677a1 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -61,6 +61,18 @@ export function DocSearch({ }) ).current; + function saveRecentSearch(item: StoredDocSearchHit) { + // We save the recent search only if it's not favorited. + if ( + favoriteSearches + .getAll() + .findIndex(search => search.objectID === item.objectID) === -1 + ) { + console.log('SAVED SEARCH'); + recentSearches.add(item); + } + } + const autocomplete = React.useMemo( () => createAutocomplete< @@ -139,7 +151,7 @@ export function DocSearch({ return [ { onSelect({ suggestion }) { - recentSearches.add(suggestion); + saveRecentSearch(suggestion); onClose(); }, getSuggestionUrl({ suggestion }) { @@ -151,7 +163,7 @@ export function DocSearch({ }, { onSelect({ suggestion }) { - recentSearches.add(suggestion); + saveRecentSearch(suggestion); onClose(); }, getSuggestionUrl({ suggestion }) { @@ -167,7 +179,7 @@ export function DocSearch({ return Object.values(sources).map(items => { return { onSelect({ suggestion }) { - recentSearches.add(suggestion); + saveRecentSearch(suggestion); onClose(); }, getSuggestionUrl({ suggestion }) { @@ -280,7 +292,7 @@ export function DocSearch({ recentSearches={recentSearches} favoriteSearches={favoriteSearches} onItemClick={item => { - recentSearches.add(item); + saveRecentSearch(item); onClose(); }} inputRef={inputRef} From 44558ec1f951d108004f024f4a484bdca57d7d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 3 Apr 2020 18:35:13 +0200 Subject: [PATCH 013/151] refactor(docsearch): refactor favorite searches --- src/DocSearch.tsx | 55 +++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 672677a1..56e1cbbb 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useEffect } from 'react'; +import React from 'react'; import { createAutocomplete, AutocompleteState, @@ -39,39 +39,41 @@ export function DocSearch({ suggestions: [], } as any); - const searchBoxRef = useRef(null); - const dropdownRef = useRef(null); - const inputRef = useRef(null); - const snipetLength = useRef(10); + const searchBoxRef = React.useRef(null); + const dropdownRef = React.useRef(null); + const inputRef = React.useRef(null); + const snipetLength = React.useRef(10); const searchClient = React.useMemo(() => createSearchClient(appId, apiKey), [ appId, apiKey, ]); - const recentSearches = useRef( - createStoredSearches({ - key: '__DOCSEARCH_RECENT_SEARCHES__', - limit: 5, - }) - ).current; - const favoriteSearches = useRef( + const favoriteSearches = React.useRef( createStoredSearches({ key: '__DOCSEARCH_FAVORITE_SEARCHES__', limit: 10, }) ).current; + const recentSearches = React.useRef( + createStoredSearches({ + key: '__DOCSEARCH_RECENT_SEARCHES__', + limit: 5, + }) + ).current; - function saveRecentSearch(item: StoredDocSearchHit) { - // We save the recent search only if it's not favorited. - if ( - favoriteSearches - .getAll() - .findIndex(search => search.objectID === item.objectID) === -1 - ) { - console.log('SAVED SEARCH'); - recentSearches.add(item); - } - } + const saveRecentSearch = React.useCallback( + function saveRecentSearch(item: StoredDocSearchHit) { + // We save the recent search only if it's not favorited. + if ( + favoriteSearches + .getAll() + .findIndex(search => search.objectID === item.objectID) === -1 + ) { + recentSearches.add(item); + } + }, + [favoriteSearches, recentSearches] + ); const autocomplete = React.useMemo( () => @@ -219,12 +221,13 @@ export function DocSearch({ onClose, recentSearches, favoriteSearches, + saveRecentSearch, ] ); const { getEnvironmentProps, getRootProps } = autocomplete; - useEffect(() => { + React.useEffect(() => { const isMobileMediaQuery = window.matchMedia('(max-width: 750px)'); if (isMobileMediaQuery.matches) { @@ -232,13 +235,13 @@ export function DocSearch({ } }, []); - useEffect(() => { + React.useEffect(() => { if (dropdownRef.current) { dropdownRef.current.scrollTop = 0; } }, [state.query]); - useEffect(() => { + React.useEffect(() => { if (!(searchBoxRef.current && dropdownRef.current && inputRef.current)) { return undefined; } From 164a0ce8f053f90ec82897caac208847bede9be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 3 Apr 2020 18:42:37 +0200 Subject: [PATCH 014/151] feat(docsearch): display more recent searches when no favorites --- src/DocSearch.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 56e1cbbb..83d5baad 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -57,7 +57,9 @@ export function DocSearch({ const recentSearches = React.useRef( createStoredSearches({ key: '__DOCSEARCH_RECENT_SEARCHES__', - limit: 5, + // We display 7 recent searches and there's no favorites, but only + // 4 when there are favorites. + limit: favoriteSearches.getAll().length === 0 ? 7 : 4, }) ).current; From 6eca63971eb5157271cf172b027a9187c5c3ad8f Mon Sep 17 00:00:00 2001 From: Shipow Date: Sat, 4 Apr 2020 09:50:09 +0200 Subject: [PATCH 015/151] feat(design): icon actions --- src/icons/SelectIcon.tsx | 2 +- src/icons/StarIcon.tsx | 3 +-- src/style.css | 37 +++++++++++++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/icons/SelectIcon.tsx b/src/icons/SelectIcon.tsx index 3835f015..c71d42f2 100644 --- a/src/icons/SelectIcon.tsx +++ b/src/icons/SelectIcon.tsx @@ -2,7 +2,7 @@ import React from 'react'; export function SelectIcon() { return ( - + Date: Sat, 4 Apr 2020 11:02:52 +0200 Subject: [PATCH 016/151] fix(design): tablet responsive --- src/style.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/style.css b/src/style.css index 679796cd..9da8b428 100644 --- a/src/style.css +++ b/src/style.css @@ -538,7 +538,7 @@ html[data-theme='dark'] { @media (max-width: 750px) { :root { /* quickfix https://stackoverflow.com/questions/37112218/css3-100vh-not-constant-in-mobile-browser */ - --docsearch-modal-height: calc(100vh - 110px); + --docsearch-modal-height: 100vh; --docsearch-spacing: 10px; --docsearch-footer-height: 40px; } @@ -549,7 +549,9 @@ html[data-theme='dark'] { .DocSearch-Modal { border-radius: 0; box-shadow: none; - margin: 0 auto; + margin: 0; + width: 100%; + max-width: 100%; } .DocSearch-Cancel { display: inline-block; From f944f30af7e20dd5ac28ebc6f42c4d8b3cb9fa02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Sat, 4 Apr 2020 12:40:21 +0200 Subject: [PATCH 017/151] feat(docsearch): introduce Selection Search --- src/DocSearch.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 83d5baad..a9193fd5 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -89,6 +89,12 @@ export function DocSearch({ autoFocus: true, placeholder: 'Search docs...', openOnFocus: true, + initialState: { + query: + typeof window !== 'undefined' + ? window.getSelection()!.toString() + : '', + }, onStateChange({ state }) { setState(state as any); }, From d7d698476246d76f1038415c960877774314eb8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Sat, 4 Apr 2020 12:40:40 +0200 Subject: [PATCH 018/151] fix(docsearch): hide search suggestions if none --- src/NoResultsScreen.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/NoResultsScreen.tsx b/src/NoResultsScreen.tsx index 01f73cb7..1fa839b6 100644 --- a/src/NoResultsScreen.tsx +++ b/src/NoResultsScreen.tsx @@ -18,17 +18,18 @@ interface NoResultsScreenProps } export function NoResultsScreen(props: NoResultsScreenProps) { + const searchSuggestions: string[] = props.state.context.searchSuggestions; + return (

No results for "{props.state.query}".

-

- Try searching for{' '} - {(props.state.context.searchSuggestions as string[]) - .slice(0, 3) - .reduce( + {searchSuggestions.length > 0 && ( +

+ Try searching for{' '} + {searchSuggestions.slice(0, 3).reduce( (acc, search) => [ ...acc, acc.length > 0 ? ', ' : '', @@ -46,8 +47,9 @@ export function NoResultsScreen(props: NoResultsScreenProps) { ], [] )} -  ... -

+  ... +

+ )}

If you believe this query should return results, From 80983f524565813edc1a931f7ee330b7ed177db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Sat, 4 Apr 2020 14:11:01 +0200 Subject: [PATCH 019/151] fix(docsearch): don't focus input if initial query --- src/DocSearch.tsx | 10 ++++++---- src/SearchBox.tsx | 13 +++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index a9193fd5..432a162f 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -43,6 +43,9 @@ export function DocSearch({ const dropdownRef = React.useRef(null); const inputRef = React.useRef(null); const snipetLength = React.useRef(10); + const initialQuery = React.useRef( + typeof window !== 'undefined' ? window.getSelection()!.toString() : '' + ).current; const searchClient = React.useMemo(() => createSearchClient(appId, apiKey), [ appId, @@ -90,10 +93,7 @@ export function DocSearch({ placeholder: 'Search docs...', openOnFocus: true, initialState: { - query: - typeof window !== 'undefined' - ? window.getSelection()!.toString() - : '', + query: initialQuery, }, onStateChange({ state }) { setState(state as any); @@ -230,6 +230,7 @@ export function DocSearch({ recentSearches, favoriteSearches, saveRecentSearch, + initialQuery, ] ); @@ -291,6 +292,7 @@ export function DocSearch({ diff --git a/src/SearchBox.tsx b/src/SearchBox.tsx index 2189d9c6..b1419913 100644 --- a/src/SearchBox.tsx +++ b/src/SearchBox.tsx @@ -17,6 +17,7 @@ interface SearchBoxProps React.KeyboardEvent > { state: AutocompleteState; + initialQuery: string; inputRef: MutableRefObject; onClose(): void; } @@ -25,6 +26,17 @@ export function SearchBox(props: SearchBoxProps) { const { onSubmit, onReset } = props.getFormProps({ inputElement: props.inputRef.current, }); + const { initialQuery, refresh } = props; + + // We don't focus the input when there's an initial query because users + // rather want to see the results directly. We therefore need to refresh + // the autocomplete instance to load all the results, which is usually + // triggered on focus. + React.useEffect(() => { + if (initialQuery.length > 0) { + refresh(); + } + }, [initialQuery, refresh]); return ( <> @@ -48,6 +60,7 @@ export function SearchBox(props: SearchBoxProps) { className="DocSearch-Input" ref={props.inputRef} {...props.getInputProps({ + autoFocus: props.initialQuery.length === 0, inputElement: props.inputRef.current!, type: 'search', maxLength: '512', From 49c6347d8a960e71676ee237c7e234ff3b4cc501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Sat, 4 Apr 2020 14:42:41 +0200 Subject: [PATCH 020/151] fix(docsearch): limit query size --- src/DocSearch.tsx | 26 +++++++++++++++++++++----- src/EmptyScreen.tsx | 2 +- src/SearchBox.tsx | 18 ++++-------------- src/constants.ts | 1 + 4 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 src/constants.ts diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 432a162f..57a4547d 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -5,18 +5,18 @@ import { } from '@francoischalifour/autocomplete-core'; import { getAlgoliaHits } from '@francoischalifour/autocomplete-preset-algolia'; +import { MAX_QUERY_SIZE } from './constants'; import { DocSearchHit, InternalDocSearchHit, StoredDocSearchHit, } from './types'; import { createSearchClient, groupBy, noop } from './utils'; +import { createStoredSearches } from './stored-searches'; import { SearchBox } from './SearchBox'; import { ScreenState } from './ScreenState'; import { Footer } from './Footer'; -import { createStoredSearches } from './stored-searches'; - interface DocSearchProps { appId?: string; apiKey: string; @@ -44,7 +44,12 @@ export function DocSearch({ const inputRef = React.useRef(null); const snipetLength = React.useRef(10); const initialQuery = React.useRef( - typeof window !== 'undefined' ? window.getSelection()!.toString() : '' + typeof window !== 'undefined' + ? window + .getSelection()! + .toString() + .slice(0, MAX_QUERY_SIZE) + : '' ).current; const searchClient = React.useMemo(() => createSearchClient(appId, apiKey), [ @@ -234,7 +239,7 @@ export function DocSearch({ ] ); - const { getEnvironmentProps, getRootProps } = autocomplete; + const { getEnvironmentProps, getRootProps, refresh } = autocomplete; React.useEffect(() => { const isMobileMediaQuery = window.matchMedia('(max-width: 750px)'); @@ -250,6 +255,17 @@ export function DocSearch({ } }, [state.query]); + // We don't focus the input when there's an initial query (i.e. Selection + // Search) because users rather want to see the results directly, without the + // keyboard appearing. + // We therefore need to refresh the autocomplete instance to load all the + // results, which is usually triggered on focus. + React.useEffect(() => { + if (initialQuery.length > 0) { + refresh(); + } + }, [initialQuery, refresh]); + React.useEffect(() => { if (!(searchBoxRef.current && dropdownRef.current && inputRef.current)) { return undefined; @@ -292,7 +308,7 @@ export function DocSearch({ diff --git a/src/EmptyScreen.tsx b/src/EmptyScreen.tsx index 9175b703..96303064 100644 --- a/src/EmptyScreen.tsx +++ b/src/EmptyScreen.tsx @@ -95,7 +95,7 @@ export function EmptyScreen(props: EmptyScreenProps) {

)} - renderAction={({ item }) => ( + renderAction={({ item, runDeleteTransition }) => (
, - ], - [] - )} -

+
+

Try searching for:

+
    + {searchSuggestions.slice(0, 3).reduce( + (acc, search) => [ + ...acc, +
  • , + ], + [] + )} +
+
)}

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

diff --git a/src/StartScreen.tsx b/src/StartScreen.tsx index 12bc73aa..11f36787 100644 --- a/src/StartScreen.tsx +++ b/src/StartScreen.tsx @@ -27,7 +27,7 @@ export function StartScreen(props: StartScreenProps) { if (props.state.status === 'idle' && props.hasSuggestions === false) { return (
-

Your search history will appear here.

+

No recent searches

); } diff --git a/src/icons/SelectIcon.tsx b/src/icons/SelectIcon.tsx index c71d42f2..5d5dfbe9 100644 --- a/src/icons/SelectIcon.tsx +++ b/src/icons/SelectIcon.tsx @@ -2,7 +2,7 @@ import React from 'react'; export function SelectIcon() { return ( - + Date: Fri, 24 Apr 2020 11:22:52 +0200 Subject: [PATCH 049/151] fix(design): error screen, loading indicator, no results links --- src/ErrorScreen.tsx | 12 +++++++++--- src/NoResultsScreen.tsx | 2 +- src/style.css | 13 ++++--------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/ErrorScreen.tsx b/src/ErrorScreen.tsx index d2602fad..7ef7a943 100644 --- a/src/ErrorScreen.tsx +++ b/src/ErrorScreen.tsx @@ -1,11 +1,17 @@ import React from 'react'; +import { ErrorIcon } from './icons'; export function ErrorScreen() { return (
-

- We‘re unable to fetch results. You might want to check your network - connection. +

+ +
+

+ Unable to fetch results +

+

+ You might want to check your network connection.

); diff --git a/src/NoResultsScreen.tsx b/src/NoResultsScreen.tsx index 1aa0f667..3158a844 100644 --- a/src/NoResultsScreen.tsx +++ b/src/NoResultsScreen.tsx @@ -29,7 +29,7 @@ export function NoResultsScreen(props: NoResultsScreenProps) {

- No results for "{props.state.query}". + No results for "{props.state.query}"

{searchSuggestions && searchSuggestions.length > 0 && ( diff --git a/src/style.css b/src/style.css index fe82d77d..9ff4e4b3 100644 --- a/src/style.css +++ b/src/style.css @@ -256,6 +256,7 @@ html[data-theme='dark'] { .DocSearch-Container--Stalled .DocSearch-LoadingIndicator, .DocSearch-MagnifierLabel, .DocSearch-Reset { + color: var(--docsearch-highlight-color); display: flex; align-items: center; justify-content: center; @@ -305,10 +306,6 @@ html[data-theme='dark'] { height:24px; } -.DocSearch-MagnifierLabel { - color: var(--docsearch-highlight-color); -} - .DocSearch-Cancel { display: none; } @@ -587,7 +584,7 @@ svg.DocSearch-Hit-Select-Icon { width: 80%; margin: 0 auto; text-align: center; - padding: 24px 0; + padding: 36px 0; font-size: .9em; } @@ -618,8 +615,6 @@ svg.DocSearch-Hit-Select-Icon { .DocSearch-NoResults-Prefill-List li{ list-style-type: '- '; list-style-position: inside; - padding: 0; - margin:0; } .DocSearch-Prefill { @@ -630,13 +625,13 @@ svg.DocSearch-Hit-Select-Icon { border-radius: 1em; font-size: 1em; background: none; - /* background-color: var(--docsearch-muted-color); */ + color: var(--docsearch-primary-color); + text-decoration: underline; padding: 0; } .DocSearch-Prefill:hover { outline: none; - color: var(--docsearch-primary-color); } /* Responsive */ From aa71abdbe819b047ada265513226e0816a4335db Mon Sep 17 00:00:00 2001 From: Shipow Date: Fri, 24 Apr 2020 11:46:48 +0200 Subject: [PATCH 050/151] fix(design): No results cosmetics --- src/style.css | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/style.css b/src/style.css index 9ff4e4b3..bd5f9aa4 100644 --- a/src/style.css +++ b/src/style.css @@ -595,25 +595,17 @@ svg.DocSearch-Hit-Select-Icon { .DocSearch-NoResults-Prefill-List { text-align: left; - display: flex; padding-bottom: 24px; - align-items: flex-start; - flex-wrap: nowrap; -} - -.DocSearch-NoResults-Prefill-List p{ - width: 50%; - text-align: right; + display: inline-block; } .DocSearch-NoResults-Prefill-List ul{ - padding: 0 12px; - flex-grow: 1; - width: 50%; + padding: 8px 0 0; + display: inline-block; } .DocSearch-NoResults-Prefill-List li{ - list-style-type: '- '; + list-style-type: '» '; list-style-position: inside; } @@ -624,13 +616,14 @@ svg.DocSearch-Hit-Select-Icon { display: inline-block; border-radius: 1em; font-size: 1em; + font-weight: 700; background: none; color: var(--docsearch-primary-color); - text-decoration: underline; padding: 0; } .DocSearch-Prefill:hover { + text-decoration: underline; outline: none; } From a0699fd9795f4450c5f1691ce3070c5486cab2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 24 Apr 2020 13:52:17 +0200 Subject: [PATCH 051/151] chore: prepare DocSearch for release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d9e6bdc6..ae3c7304 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "docsearch-react", + "name": "@francoischalifour/docsearch-react", "version": "1.0.0-alpha.10", "license": "MIT", "homepage": "https://github.com/algolia/autocomplete.js", From 15ddd0aa5f61c2f31683fa5388f21ae1f5fc9587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 24 Apr 2020 14:58:50 +0200 Subject: [PATCH 052/151] refactor: export DocSearch components as Button and Modal --- src/{SearchButton.tsx => DocSearchButton.tsx} | 0 src/{DocSearch.tsx => DocSearchModal.tsx} | 3 ++- src/index.ts | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) rename src/{SearchButton.tsx => DocSearchButton.tsx} (100%) rename src/{DocSearch.tsx => DocSearchModal.tsx} (99%) diff --git a/src/SearchButton.tsx b/src/DocSearchButton.tsx similarity index 100% rename from src/SearchButton.tsx rename to src/DocSearchButton.tsx diff --git a/src/DocSearch.tsx b/src/DocSearchModal.tsx similarity index 99% rename from src/DocSearch.tsx rename to src/DocSearchModal.tsx index 9776fcd6..a23c154e 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearchModal.tsx @@ -37,7 +37,7 @@ interface DocSearchProps }): JSX.Element; } -export function DocSearch({ +export function DocSearchModal({ appId = 'BH4D9OD16A', apiKey, indexName, @@ -126,6 +126,7 @@ export function DocSearch({ onStateChange({ state }) { setState(state as any); }, + // @ts-ignore Temporarily ignore bad typing in autocomplete-core. getSources({ query, state, setContext, setStatus }) { if (!query) { return [ diff --git a/src/index.ts b/src/index.ts index c305abfc..f3a6716a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -export * from './DocSearch'; -export * from './SearchButton'; +export * from './DocSearchModal'; +export * from './DocSearchButton'; From dbcebed048bcee6de146d3273eb42333fa0e52fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 24 Apr 2020 15:29:56 +0200 Subject: [PATCH 053/151] feat: create clean exports --- button.js | 1 + modal.js | 1 + src/DocSearchButton.tsx | 2 +- style.js | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 button.js create mode 100644 modal.js create mode 100644 style.js diff --git a/button.js b/button.js new file mode 100644 index 00000000..5614c2a7 --- /dev/null +++ b/button.js @@ -0,0 +1 @@ +export { DocSearchButton } from './dist/esm/DocSearchButton.js'; diff --git a/modal.js b/modal.js new file mode 100644 index 00000000..7ae1e01d --- /dev/null +++ b/modal.js @@ -0,0 +1 @@ +export { DocSearchModal } from './dist/esm/DocSearchModal.js'; diff --git a/src/DocSearchButton.tsx b/src/DocSearchButton.tsx index 364b4fba..bb91a214 100644 --- a/src/DocSearchButton.tsx +++ b/src/DocSearchButton.tsx @@ -17,7 +17,7 @@ function isAppleDevice() { return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform); } -export function SearchButton(props: SearchButtonProps) { +export function DocSearchButton(props: SearchButtonProps) { const [key, setKey] = useState(() => isAppleDevice() ? ACTION_KEY_APPLE : ACTION_KEY_DEFAULT ); diff --git a/style.js b/style.js new file mode 100644 index 00000000..e3212c1b --- /dev/null +++ b/style.js @@ -0,0 +1 @@ +import './dist/esm/style.css'; From 07e606a00c46bc01eee7a549f4c10d745fe207f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 24 Apr 2020 15:55:17 +0200 Subject: [PATCH 054/151] chore: format code --- babel.config.js | 2 +- src/DocSearchModal.tsx | 31 +++++++++--------- src/ErrorScreen.tsx | 4 +-- src/NoResultsScreen.tsx | 32 ++++++++++--------- src/ScreenState.tsx | 2 +- src/StartScreen.tsx | 6 ++-- src/icons/ErrorIcon.tsx | 3 +- src/icons/NoResultsIcon.tsx | 3 +- src/icons/SelectIcon.tsx | 7 ++++- src/stored-searches.ts | 4 +-- src/style.css | 63 +++++++++++++++++++++---------------- 11 files changed, 85 insertions(+), 72 deletions(-) diff --git a/babel.config.js b/babel.config.js index 7d68e20c..602182e1 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,6 +1,6 @@ /* eslint-disable import/no-commonjs */ -module.exports = api => { +module.exports = (api) => { const isTest = api.env('test'); const modules = isTest ? 'commonjs' : false; const targets = {}; diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index a23c154e..668a76a4 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -44,7 +44,7 @@ export function DocSearchModal({ placeholder = 'Search docs', searchParameters, onClose = noop, - transformItems = x => x, + transformItems = (x) => x, hitComponent = Hit, navigator, }: DocSearchProps) { @@ -62,10 +62,7 @@ export function DocSearchModal({ const snipetLength = React.useRef(10); const initialQuery = React.useRef( typeof window !== 'undefined' - ? window - .getSelection()! - .toString() - .slice(0, MAX_QUERY_SIZE) + ? window.getSelection()!.toString().slice(0, MAX_QUERY_SIZE) : '' ).current; @@ -95,7 +92,7 @@ export function DocSearchModal({ search && favoriteSearches .getAll() - .findIndex(x => x.objectID === search.objectID) === -1 + .findIndex((x) => x.objectID === search.objectID) === -1 ) { recentSearches.add(search); } @@ -195,7 +192,7 @@ export function DocSearchModal({ }, ], }) - .catch(error => { + .catch((error) => { // The Algolia `RetryError` happens when all the servers have // failed, meaning that there's no chance the response comes // back. This is the right time to display an error. @@ -207,7 +204,7 @@ export function DocSearchModal({ throw error; }) .then((hits: DocSearchHit[]) => { - const sources = groupBy(hits, hit => hit.hierarchy.lvl0); + const sources = groupBy(hits, (hit) => hit.hierarchy.lvl0); // We store the `lvl0`s to display them as search suggestions // in the “no results“ screen. @@ -220,7 +217,7 @@ export function DocSearchModal({ }); } - return Object.values(sources).map(items => { + return Object.values(sources).map((items) => { return { onSelect({ suggestion }) { saveRecentSearch(suggestion); @@ -231,10 +228,10 @@ export function DocSearchModal({ }, getSuggestions() { return Object.values( - groupBy(items, item => item.hierarchy.lvl1) + groupBy(items, (item) => item.hierarchy.lvl1) ) - .map(items => { - return items.map(item => { + .map((items) => { + return items.map((item) => { const url = new URL(item.url); return { @@ -244,15 +241,15 @@ export function DocSearchModal({ }); }) .map(transformItems) - .map(hits => - hits.map(item => { + .map((hits) => + hits.map((item) => { return { ...item, // eslint-disable-next-line @typescript-eslint/camelcase __docsearch_parent: item.type !== 'lvl1' && hits.find( - siblingItem => + (siblingItem) => siblingItem.type === 'lvl1' && siblingItem.hierarchy.lvl1 === item.hierarchy.lvl1 @@ -328,7 +325,7 @@ export function DocSearchModal({ ] .filter(Boolean) .join(' ')} - onClick={event => { + onClick={(event) => { if (event.target === event.currentTarget) { onClose(); } @@ -352,7 +349,7 @@ export function DocSearchModal({ hitComponent={hitComponent} recentSearches={recentSearches} favoriteSearches={favoriteSearches} - onItemClick={item => { + onItemClick={(item) => { saveRecentSearch(item); onClose(); }} diff --git a/src/ErrorScreen.tsx b/src/ErrorScreen.tsx index 7ef7a943..632382a3 100644 --- a/src/ErrorScreen.tsx +++ b/src/ErrorScreen.tsx @@ -7,9 +7,7 @@ export function ErrorScreen() {
-

- Unable to fetch results -

+

Unable to fetch results

You might want to check your network connection.

diff --git a/src/NoResultsScreen.tsx b/src/NoResultsScreen.tsx index 3158a844..165e7e53 100644 --- a/src/NoResultsScreen.tsx +++ b/src/NoResultsScreen.tsx @@ -10,10 +10,10 @@ import { NoResultsIcon } from './icons'; interface NoResultsScreenProps extends AutocompleteApi< - InternalDocSearchHit, - React.FormEvent, - React.MouseEvent, - React.KeyboardEvent + InternalDocSearchHit, + React.FormEvent, + React.MouseEvent, + React.KeyboardEvent > { state: AutocompleteState; inputRef: React.MutableRefObject; @@ -39,17 +39,19 @@ export function NoResultsScreen(props: NoResultsScreenProps) { {searchSuggestions.slice(0, 3).reduce( (acc, search) => [ ...acc, -
  • , +
  • + +
  • , ], [] )} diff --git a/src/ScreenState.tsx b/src/ScreenState.tsx index 5b5d8c89..f0b087b6 100644 --- a/src/ScreenState.tsx +++ b/src/ScreenState.tsx @@ -32,7 +32,7 @@ export const ScreenState = React.memo( } const hasSuggestions = props.state.suggestions.some( - suggestion => suggestion.items.length > 0 + (suggestion) => suggestion.items.length > 0 ); if (!props.state.query) { diff --git a/src/StartScreen.tsx b/src/StartScreen.tsx index 11f36787..fee8bac7 100644 --- a/src/StartScreen.tsx +++ b/src/StartScreen.tsx @@ -57,7 +57,7 @@ export function StartScreen(props: StartScreenProps) { ); diff --git a/src/icons/ControlKeyIcon.tsx b/src/icons/ControlKeyIcon.tsx new file mode 100644 index 00000000..de1f4e34 --- /dev/null +++ b/src/icons/ControlKeyIcon.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +export function ControlKeyIcon() { + return ( + + + + ); +} From 9eaf18a81ffff04b7fb8d8389463b56d823f7e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 5 Jun 2020 14:42:59 +0200 Subject: [PATCH 082/151] feat(docsearch): introduce `resultsFooterComponent` option --- src/DocSearchModal.tsx | 12 +++++++++--- src/ResultsScreen.tsx | 7 +++++++ src/style.css | 13 +++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index d88fdef6..7d82560f 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -3,7 +3,7 @@ import { createAutocomplete, AutocompleteState, } from '@francoischalifour/autocomplete-core'; -import { getAlgoliaHits } from '@francoischalifour/autocomplete-preset-algolia'; +import { getAlgoliaResults } from '@francoischalifour/autocomplete-preset-algolia'; import { MAX_QUERY_SIZE } from './constants'; import { @@ -35,6 +35,7 @@ export function DocSearchModal({ onClose = noop, transformItems = (x) => x, hitComponent = Hit, + resultsFooterComponent = () => null, navigator, }: DocSearchModalProps) { const [state, setState] = React.useState< @@ -144,7 +145,7 @@ export function DocSearchModal({ ]; } - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { @@ -193,7 +194,9 @@ export function DocSearchModal({ throw error; }) - .then((hits: DocSearchHit[]) => { + .then((results) => { + const hits: DocSearchHit[] = results[0].hits; + const nbHits: number = results[0].nbHits; const sources = groupBy(hits, (hit) => hit.hierarchy.lvl0); // We store the `lvl0`s to display them as search suggestions @@ -207,6 +210,8 @@ export function DocSearchModal({ }); } + setContext({ nbHits }); + return Object.values(sources).map((items) => { return { onSelect({ suggestion }) { @@ -339,6 +344,7 @@ export function DocSearchModal({ {...autocomplete} state={state} hitComponent={hitComponent} + resultsFooterComponent={resultsFooterComponent} recentSearches={recentSearches} favoriteSearches={favoriteSearches} onItemClick={(item) => { diff --git a/src/ResultsScreen.tsx b/src/ResultsScreen.tsx index ce2ac88c..45ff2047 100644 --- a/src/ResultsScreen.tsx +++ b/src/ResultsScreen.tsx @@ -16,6 +16,9 @@ interface ResultsScreenProps React.KeyboardEvent > { state: AutocompleteState; + resultsFooterComponent(props: { + state: AutocompleteState; + }): JSX.Element; onItemClick(item: InternalDocSearchHit): void; } @@ -69,6 +72,10 @@ export function ResultsScreen(props: ResultsScreenProps) { /> ); })} + +
    + +
    ); } diff --git a/src/style.css b/src/style.css index 4eaa08d3..00e8a8f9 100644 --- a/src/style.css +++ b/src/style.css @@ -412,6 +412,19 @@ html[data-theme='dark'] { color: var(--docsearch-highlight-color); } +.DocSearch-HitsFooter { + margin-bottom: var(--docsearch-spacing); + padding: var(--docsearch-spacing); + color: var(--docsearch-muted-color); + display: flex; + justify-content: center; +} + +.DocSearch-HitsFooter a { + color: inherit; + border-bottom: 1px solid; +} + .DocSearch-Hit { display: flex; position: relative; From e5896579be18e65b2f44918f125800e40d4c66bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 5 Jun 2020 14:44:55 +0200 Subject: [PATCH 083/151] feat(docsearch): display 5 hits per category maximum --- src/utils/groupBy.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/groupBy.ts b/src/utils/groupBy.ts index 5ea2731e..f8837400 100644 --- a/src/utils/groupBy.ts +++ b/src/utils/groupBy.ts @@ -9,7 +9,11 @@ export function groupBy( acc[key] = []; } - acc[key].push(item); + // We limit each section to show 5 hits maximum. + // This acts as a frontend alternative to `distinct`. + if (acc[key].length < 5) { + acc[key].push(item); + } return acc; }, {}); From 1e31dde43228e9a33dac3db7334816459f30a7e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 8 Jun 2020 10:43:17 +0200 Subject: [PATCH 084/151] refactor(docsearch): refactor search button --- src/DocSearchButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DocSearchButton.tsx b/src/DocSearchButton.tsx index af993abc..5865bc33 100644 --- a/src/DocSearchButton.tsx +++ b/src/DocSearchButton.tsx @@ -10,6 +10,7 @@ function isAppleDevice() { if (typeof navigator === 'undefined') { return ACTION_KEY_DEFAULT; } + return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform); } @@ -35,8 +36,7 @@ export function DocSearchButton( Search - {key === 'Ctrl' && } - {key !== 'Ctrl' && key} + {key === ACTION_KEY_DEFAULT ? : key} K From 9893bb3c49ce06f2125c29e20f1d1c1b6088c3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 8 Jun 2020 10:43:34 +0200 Subject: [PATCH 085/151] fix(dosearch): don't add `distinct` search parameter --- src/DocSearchModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index 7d82560f..fad69a22 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -177,7 +177,6 @@ export function DocSearchModal({ highlightPreTag: '', highlightPostTag: '', hitsPerPage: 20, - distinct: 4, ...searchParameters, }, }, From f9a5b6b76e9aef1f50dd878e982caff4d668e2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 8 Jun 2020 11:10:32 +0200 Subject: [PATCH 086/151] refactor(docsearch): format CSS --- src/style.css | 313 +++++++++++++++++++++++++------------------------- 1 file changed, 157 insertions(+), 156 deletions(-) diff --git a/src/style.css b/src/style.css index 00e8a8f9..197c075e 100644 --- a/src/style.css +++ b/src/style.css @@ -1,15 +1,14 @@ /* Variables */ :root { - - --docsearch-primary-color: rgb(84,104,255); + --docsearch-primary-color: rgb(84, 104, 255); --docsearch-spacing: 12px; --docsearch-icon-stroke-width: 1.4; --docsearch-highlight-color: var(--docsearch-primary-color); --docsearch-text-color: var(--ifm-color-emphasis-900); --docsearch-muted-color: rgb(150, 159, 175); --docsearch-container-background: rgba(101, 108, 133, 0.8); - --docsearch-logo-color: rgba(84,104,255); + --docsearch-logo-color: rgba(84, 104, 255); /* modal */ --docsearch-modal-width: 560px; @@ -22,7 +21,8 @@ --docsearch-searchbox-height: 56px; --docsearch-searchbox-background: var(--ifm-color-emphasis-200); --docsearch-searchbox-focus-background: white; - --docsearch-searchbox-shadow: inset 0px 0px 0px 2px var(--docsearch-primary-color); + --docsearch-searchbox-shadow: inset 0px 0px 0px 2px + var(--docsearch-primary-color); /* hit */ --docsearch-hit-height: 56px; @@ -45,7 +45,6 @@ --docsearch-footer-background: white; --docsearch-footer-shadow: 0px -1px 0px 0px rgb(224, 227, 232), 0px -3px 6px 0px rgba(69, 98, 155, 0.12); - } /* Darkmode */ @@ -70,35 +69,35 @@ html[data-theme='dark'] { --docsearch-footer-background: rgb(30, 33, 54); --docsearch-footer-shadow: inset 0px 1px 0px 0px rgba(73, 76, 106, 0.5), 0px -4px 8px 0px rgba(0, 0, 0, 0.2); - --docsearch-logo-color: rgb(255,255,255); + --docsearch-logo-color: rgb(255, 255, 255); --docsearch-muted-color: rgb(127, 132, 151); } /* Search Button */ .DocSearch-SearchButton { + align-items: center; + background: var(--docsearch-searchbox-background); + border: none; + border-radius: 40px; + color: var(--docsearch-text-color); + color: var(--docsearch-muted-color); + cursor: pointer; display: flex; + font-weight: 500; height: 36px; margin: 0 0 0 16px; padding: 0 8px; - border: none; - border-radius: 40px; - cursor: pointer; - align-items: center; - background: var(--docsearch-searchbox-background); - color: var(--docsearch-text-color); - font-weight: 500; user-select: none; - color: var(--docsearch-muted-color); } .DocSearch-SearchButton:hover, .DocSearch-SearchButton:active, .DocSearch-SearchButton:focus { - outline: none; - color: var(--docsearch-text-color); background: var(--docsearch-searchbox-focus-background); box-shadow: var(--docsearch-searchbox-shadow); + color: var(--docsearch-text-color); + outline: none; } .DocSearch-Search-Icon { @@ -110,24 +109,24 @@ html[data-theme='dark'] { } .DocSearch-SearchButton-Placeholder { - padding: 0 12px 0 6px; font-size: 1rem; + padding: 0 12px 0 6px; } .DocSearch-SearchButton-Key { - display: flex; align-items: center; - justify-content: center; - width: 20px; - height: 18px; - margin-right: 0.4em; - padding-bottom: 2px; - border-radius: 3px; - position: relative; - top: -1px; background: var(--docsearch-key-gradient); + border-radius: 3px; box-shadow: var(--docsearch-key-shadow); color: var(--docsearch-muted-color); + display: flex; + height: 18px; + justify-content: center; + margin-right: 0.4em; + padding-bottom: 2px; + position: relative; + top: -1px; + width: 20px; } /* Body modifier */ @@ -148,13 +147,13 @@ html[data-theme='dark'] { } .DocSearch-Container { - z-index: var(--ifm-z-index-fixed); - height: 100vh; - width: 100vw; - position: fixed; - left: 0; - top: 0; background-color: var(--docsearch-container-background); + height: 100vh; + left: 0; + position: fixed; + top: 0; + width: 100vw; + z-index: var(--ifm-z-index-fixed); } .DocSearch-Container a { @@ -173,13 +172,13 @@ html[data-theme='dark'] { } .DocSearch-Modal { - position: relative; - flex-direction: column; - max-width: var(--docsearch-modal-width); - margin: 60px auto auto auto; - border-radius: 6px; background: var(--docsearch-modal-background); + border-radius: 6px; box-shadow: var(--docsearch-modal-shadow); + flex-direction: column; + margin: 60px auto auto auto; + max-width: var(--docsearch-modal-width); + position: relative; } /* Modal Searchbox */ @@ -190,29 +189,29 @@ html[data-theme='dark'] { } .DocSearch-Form { + align-items: center; + background: var(--docsearch-searchbox-focus-background); + border-radius: 4px; + box-shadow: var(--docsearch-searchbox-shadow); display: flex; - width: 100%; - position: relative; height: var(--docsearch-searchbox-height); padding: 0 var(--docsearch-spacing); - align-items: center; - border-radius: 4px; - background: var(--docsearch-searchbox-focus-background); - box-shadow: var(--docsearch-searchbox-shadow); + position: relative; + width: 100%; } .DocSearch-Input { - height: 100%; + appearance: none; + background: transparent; + border: none; + color: var(--docsearch-text-color); flex: 1; - width: 80%; - padding-left: 8px; font: inherit; font-size: 1.2em; - appearance: none; - border: none; + height: 100%; outline: none; - background: transparent; - color: var(--docsearch-text-color); + padding-left: 8px; + width: 80%; } .DocSearch-Input::placeholder { @@ -236,9 +235,9 @@ html[data-theme='dark'] { .DocSearch-Container--Stalled .DocSearch-LoadingIndicator, .DocSearch-MagnifierLabel, .DocSearch-Reset { + align-items: center; color: var(--docsearch-highlight-color); display: flex; - align-items: center; justify-content: center; } @@ -248,15 +247,15 @@ html[data-theme='dark'] { } .DocSearch-Reset { + animation: fade-in 0.1s ease-in forwards; appearance: none; - right: 0; + background: none; border: none; border-radius: 50%; - background: none; - cursor: pointer; - stroke-width: var(--docsearch-icon-stroke-width); color: var(--docsearch-icon-color); - animation: fade-in 0.1s ease-in forwards; + cursor: pointer; + right: 0; + stroke-width: var(--docsearch-icon-stroke-width); } .DocSearch-Reset[hidden] { @@ -268,11 +267,11 @@ html[data-theme='dark'] { } .DocSearch-Reset { - color: inherit; border: none; - padding: 2px; - cursor: pointer; border-radius: 50%; + color: inherit; + cursor: pointer; + padding: 2px; } .DocSearch-Reset:hover { @@ -281,8 +280,8 @@ html[data-theme='dark'] { .DocSearch-LoadingIndicator svg, .DocSearch-MagnifierLabel svg { - width: 24px; height: 24px; + width: 24px; } .DocSearch-Cancel { @@ -296,10 +295,11 @@ html[data-theme='dark'] { var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height) ); - padding: 0 var(--docsearch-spacing); overflow-y: overlay; + padding: 0 var(--docsearch-spacing); + scrollbar-color: var(--docsearch-muted-color) + var(--docsearch-modal-background); scrollbar-width: thin; - scrollbar-color: var(--docsearch-muted-color) var(--docsearch-modal-background); } .DocSearch-Dropdown::-webkit-scrollbar { @@ -312,40 +312,40 @@ html[data-theme='dark'] { .DocSearch-Dropdown::-webkit-scrollbar-thumb { background-color: var(--docsearch-muted-color); - border-radius: 20px; border: 3px solid var(--docsearch-modal-background); + border-radius: 20px; } .DocSearch-Dropdown ul { + list-style: none; margin: 0; padding: 0; - list-style: none; } /* Modal Footer */ .DocSearch-Footer { - position: relative; + align-items: center; + background: var(--docsearch-footer-background); + border-radius: 0 0 8px 8px; + box-shadow: var(--docsearch-footer-shadow); display: flex; - width: 100%; - height: var(--docsearch-footer-height); - padding: 0 var(--docsearch-spacing); - z-index: 300; flex-direction: row-reverse; flex-shrink: 0; + height: var(--docsearch-footer-height); justify-content: space-between; - align-items: center; - border-radius: 0 0 8px 8px; - background: var(--docsearch-footer-background); - box-shadow: var(--docsearch-footer-shadow); + padding: 0 var(--docsearch-spacing); + position: relative; user-select: none; + width: 100%; + z-index: 300; } .DocSearch-Commands { display: flex; + list-style: none; margin: 0; padding: 0; - list-style: none; } .DocSearch-Commands li:not(:last-of-type) { @@ -353,26 +353,26 @@ html[data-theme='dark'] { } .DocSearch-Commands { - display: flex; color: var(--docsearch-muted-color); + display: flex; } .DocSearch-Commands li { - display: flex; align-items: center; + display: flex; } .DocSearch-Commands-Key { + align-items: center; + background: var(--docsearch-key-gradient); + border-radius: 2px; + box-shadow: var(--docsearch-key-shadow); display: flex; - width: 20px; height: 18px; + justify-content: center; margin-right: 0.4em; padding-bottom: 2px; - align-items: center; - justify-content: center; - border-radius: 2px; - background: var(--docsearch-key-gradient); - box-shadow: var(--docsearch-key-shadow); + width: 20px; } .DocSearch-Logo a { @@ -380,21 +380,21 @@ html[data-theme='dark'] { } .DocSearch-Logo svg { - margin-left: 8px; color: var(--docsearch-logo-color); + margin-left: 8px; } .DocSearch-Label { + color: var(--docsearch-muted-color); font-size: 0.75em; line-height: 1.6em; - color: var(--docsearch-muted-color); } .DocSearch-Help { - font-size: 0.9em; color: var(--docsearch-muted-color); - user-select: none; + font-size: 0.9em; margin: 0; + user-select: none; } .DocSearch-Title { @@ -413,65 +413,66 @@ html[data-theme='dark'] { } .DocSearch-HitsFooter { - margin-bottom: var(--docsearch-spacing); - padding: var(--docsearch-spacing); color: var(--docsearch-muted-color); display: flex; + font-size: 0.85em; justify-content: center; + margin-bottom: var(--docsearch-spacing); + padding: var(--docsearch-spacing); } .DocSearch-HitsFooter a { - color: inherit; border-bottom: 1px solid; + color: inherit; } .DocSearch-Hit { - display: flex; - position: relative; border-radius: 4px; + display: flex; padding-bottom: 4px; + position: relative; } .DocSearch-Hit--deleting { - transition: all 250ms linear; opacity: 0; + transition: all 250ms linear; } .DocSearch-Hit--favoriting { - transition: all 250ms linear; transform: scale(0); - transition-delay: 250ms; transform-origin: top center; + transition: all 250ms linear; + transition-delay: 250ms; } .DocSearch-Hit a { - display: block; - border-radius: 4px; - width: 100%; - padding-left: var(--docsearch-spacing); background: var(--docsearch-hit-background); + border-radius: 4px; box-shadow: var(--docsearch-hit-shadow); + display: block; + padding-left: var(--docsearch-spacing); + width: 100%; } .DocSearch-Hit-source { + background: var(--docsearch-modal-background); + color: var(--docsearch-highlight-color); + font-size: 0.85em; + font-weight: 600; + line-height: 32px; + margin: 0 -4px; + padding: 8px 4px 0; position: sticky; top: 0; z-index: 10; - margin: 0 -4px; - padding: 8px 4px 0; - line-height: 32px; - font-size: 0.85em; - font-weight: 600; - color: var(--docsearch-highlight-color); - background: var(--docsearch-modal-background); } .DocSearch-Hit-Tree { - width: 24px; - height: var(--docsearch-hit-height); color: var(--docsearch-muted-color); + height: var(--docsearch-hit-height); opacity: 0.5; stroke-width: var(--docsearch-icon-stroke-width); + width: 24px; } .DocSearch-Hit[aria-selected='true'] a { @@ -493,12 +494,12 @@ html[data-theme='dark'] { } .DocSearch-Hit-Container { - display: flex; - height: var(--docsearch-hit-height); - padding: 0 var(--docsearch-spacing) 0 0; - flex-direction: row; align-items: center; color: var(--docsearch-hit-color); + display: flex; + flex-direction: row; + height: var(--docsearch-hit-height); + padding: 0 var(--docsearch-spacing) 0 0; } .DocSearch-Hit-icon, @@ -513,16 +514,16 @@ html[data-theme='dark'] { } .DocSearch-Hit-action { + align-items: center; + display: flex; height: 22px; width: 22px; - display: flex; - align-items: center; } .DocSearch-Hit-action svg { - width: 18px; - height: 18px; display: block; + height: 18px; + width: 18px; } .DocSearch-Hit-action + .DocSearch-Hit-action { @@ -532,11 +533,11 @@ html[data-theme='dark'] { .DocSearch-Hit-action-button { appearance: none; background: none; - color: inherit; border: none; - padding: 2px; - cursor: pointer; border-radius: 50%; + color: inherit; + cursor: pointer; + padding: 2px; } svg.DocSearch-Hit-Select-Icon { @@ -547,8 +548,8 @@ svg.DocSearch-Hit-Select-Icon { } .DocSearch-Hit-action-button:hover { - transition: background-color 0.1s ease-in; background: rgba(0, 0, 0, 0.2); + transition: background-color 0.1s ease-in; } .DocSearch-Hit-action-button:hover path { @@ -556,18 +557,18 @@ svg.DocSearch-Hit-Select-Icon { } .DocSearch-Hit-content-wrapper { - position: relative; display: flex; - width: 80%; - margin: 0 8px; - line-height: 1.2em; flex: 1 1 auto; - font-weight: 500; - overflow-x: hidden; - white-space: nowrap; - text-overflow: ellipsis; - justify-content: center; flex-direction: column; + font-weight: 500; + justify-content: center; + line-height: 1.2em; + margin: 0 8px; + overflow-x: hidden; + position: relative; + text-overflow: ellipsis; + white-space: nowrap; + width: 80%; } .DocSearch-Hit-title { @@ -575,8 +576,8 @@ svg.DocSearch-Hit-Select-Icon { } .DocSearch-Hit-path { - font-size: 0.75em; color: var(--docsearch-muted-color); + font-size: 0.75em; } /* No Results - Start Screen - Error Screen */ @@ -584,11 +585,11 @@ svg.DocSearch-Hit-Select-Icon { .DocSearch-NoResults, .DocSearch-StartScreen, .DocSearch-ErrorScreen { - width: 80%; - margin: 0 auto; - text-align: center; - padding: 36px 0; font-size: 0.9em; + margin: 0 auto; + padding: 36px 0; + text-align: center; + width: 80%; } .DocSearch-Screen-Icon { @@ -597,37 +598,37 @@ svg.DocSearch-Hit-Select-Icon { } .DocSearch-NoResults-Prefill-List { - text-align: left; - padding-bottom: 24px; display: inline-block; + padding-bottom: 24px; + text-align: left; } .DocSearch-NoResults-Prefill-List ul { - padding: 8px 0 0; display: inline-block; + padding: 8px 0 0; } .DocSearch-NoResults-Prefill-List li { - list-style-type: '» '; list-style-position: inside; + list-style-type: '» '; } .DocSearch-Prefill { appearance: none; - cursor: pointer; + background: none; border: none; - display: inline-block; border-radius: 1em; + color: var(--docsearch-highlight-color); + cursor: pointer; + display: inline-block; font-size: 1em; font-weight: 700; - background: none; - color: var(--docsearch-highlight-color); padding: 0; } .DocSearch-Prefill:hover { - text-decoration: underline; outline: none; + text-decoration: underline; } /* Responsive */ @@ -646,7 +647,7 @@ svg.DocSearch-Hit-Select-Icon { display: none; } - .DocSearch-SearchButton-Placeholder{ + .DocSearch-SearchButton-Placeholder { display: none; } @@ -661,42 +662,42 @@ svg.DocSearch-Hit-Select-Icon { .DocSearch-Footer { border-radius: 0; - position: absolute; bottom: 0; + position: absolute; } .DocSearch-Hit-content-wrapper { - position: relative; display: flex; + position: relative; width: 80%; } .DocSearch-Modal { border-radius: 0; box-shadow: none; - margin: 0; - width: 100%; - max-width: 100%; height: -webkit-fill-available; + margin: 0; + max-width: 100%; + width: 100%; } .DocSearch-Cancel { - display: inline-block; - white-space: nowrap; - overflow: hidden; - margin-left: var(--docsearch-spacing); - padding: 0; appearance: none; - border: 0; - cursor: pointer; background: none; + border: 0; + color: var(--docsearch-highlight-color); + cursor: pointer; + display: inline-block; flex: none; font: inherit; font-size: 1em; font-weight: 500; - color: var(--docsearch-highlight-color); + margin-left: var(--docsearch-spacing); outline: none; + overflow: hidden; + padding: 0; user-select: none; + white-space: nowrap; } .DocSearch-Commands { From ad3dc51914eb2ecceb2cf8993b679b6f6b5146b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 8 Jun 2020 11:55:18 +0200 Subject: [PATCH 087/151] chore: release v1.0.0-alpha.16 (#47) --- package.json | 6 +++--- src/version.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 810b4606..8ab6f9e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@docsearch/react", - "version": "1.0.0-alpha.15", + "version": "1.0.0-alpha.16", "license": "MIT", "homepage": "https://github.com/algolia/autocomplete.js", "repository": "algolia/autocomplete.js", @@ -36,8 +36,8 @@ "watch": "concurrently \"yarn build:esm:watch\" \"yarn build:types:watch\" \"yarn build:css:watch\"" }, "dependencies": { - "@francoischalifour/autocomplete-core": "^1.0.0-alpha.15", - "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.15", + "@francoischalifour/autocomplete-core": "^1.0.0-alpha.16", + "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.16", "algoliasearch": "^4.0.0" }, "peerDependencies": { diff --git a/src/version.ts b/src/version.ts index 4c1bbc3d..fdb3803b 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '1.0.0-alpha.15'; +export const version = '1.0.0-alpha.16'; From 1f9deb13225d7fefb72ea8692634bd56c12bd748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 8 Jun 2020 15:46:17 +0200 Subject: [PATCH 088/151] fix(docsearch): use `scrollTo` when unmounting modal --- src/DocSearchModal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index fad69a22..e5cf2da3 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -279,7 +279,9 @@ export function DocSearchModal({ return () => { document.body.classList.remove('DocSearch--active'); - document.body.scrollTop = scrollY.current; + // IE11 doesn't support `scrollTo` so we check that the method exists + // first. + window.scrollTo?.(0, scrollY.current); }; }, []); From 042f5c47aeed1228448db4a6a87e3531d47950e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 8 Jun 2020 15:49:42 +0200 Subject: [PATCH 089/151] chore: release v1.0.0-alpha.17 (#48) --- package.json | 6 +++--- src/version.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 8ab6f9e6..77b58703 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@docsearch/react", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "license": "MIT", "homepage": "https://github.com/algolia/autocomplete.js", "repository": "algolia/autocomplete.js", @@ -36,8 +36,8 @@ "watch": "concurrently \"yarn build:esm:watch\" \"yarn build:types:watch\" \"yarn build:css:watch\"" }, "dependencies": { - "@francoischalifour/autocomplete-core": "^1.0.0-alpha.16", - "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.16", + "@francoischalifour/autocomplete-core": "^1.0.0-alpha.17", + "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.17", "algoliasearch": "^4.0.0" }, "peerDependencies": { diff --git a/src/version.ts b/src/version.ts index fdb3803b..b979210e 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '1.0.0-alpha.16'; +export const version = '1.0.0-alpha.17'; From cca770052acd103d47a4ae33c069e6e0ac6c105d Mon Sep 17 00:00:00 2001 From: Shipow Date: Tue, 9 Jun 2020 10:37:56 +0200 Subject: [PATCH 090/151] fix(css): overflow overlay not supported on gecko --- src/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/style.css b/src/style.css index 197c075e..38bd8a34 100644 --- a/src/style.css +++ b/src/style.css @@ -295,6 +295,7 @@ html[data-theme='dark'] { var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height) ); + overflow-y: auto; /* firefox */ overflow-y: overlay; padding: 0 var(--docsearch-spacing); scrollbar-color: var(--docsearch-muted-color) From 37a0bf661a02594c868a46adc4c32f95edc76fe8 Mon Sep 17 00:00:00 2001 From: Shipow Date: Wed, 10 Jun 2020 19:18:09 +0200 Subject: [PATCH 091/151] test(cypress): Fix tests Recent and Favorites actions --- src/StartScreen.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/StartScreen.tsx b/src/StartScreen.tsx index fee8bac7..84773e60 100644 --- a/src/StartScreen.tsx +++ b/src/StartScreen.tsx @@ -55,12 +55,12 @@ export function StartScreen(props: StartScreenProps) { <>
    ); -} +}); diff --git a/src/useDocSearchKeyboardEvents.ts b/src/useDocSearchKeyboardEvents.ts index 61c38772..ac86a72a 100644 --- a/src/useDocSearchKeyboardEvents.ts +++ b/src/useDocSearchKeyboardEvents.ts @@ -1,6 +1,11 @@ import React from 'react'; -export function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }) { +export function useDocSearchKeyboardEvents({ + isOpen, + onOpen, + onClose, + searchButtonRef, +}) { React.useEffect(() => { function onKeyDown(event: KeyboardEvent) { if ( @@ -19,6 +24,12 @@ export function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }) { onOpen(); } } + + if (searchButtonRef.current === document.activeElement) { + if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) { + onOpen(); + } + } } window.addEventListener('keydown', onKeyDown); @@ -26,5 +37,5 @@ export function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }) { return () => { window.removeEventListener('keydown', onKeyDown); }; - }, [isOpen, onOpen, onClose]); + }, [isOpen, onOpen, onClose, searchButtonRef]); } From 2a00d3f3c068c314d928924a7bfa3f969f342973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 7 Jul 2020 15:04:11 +0200 Subject: [PATCH 108/151] refactor(docsearch): manage input focus with effects The `autoFocus` prop is not standardize when coming from a component that gets mounted after the first page load. Although this works with React, it does with Preact. This makes it work with both libraries. --- src/DocSearchModal.tsx | 1 - src/SearchBox.tsx | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index 300f7569..85dfeae3 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -106,7 +106,6 @@ export function DocSearchModal({ >({ id: 'docsearch', defaultHighlightedIndex: 0, - autoFocus: true, placeholder, openOnFocus: true, initialState: { diff --git a/src/SearchBox.tsx b/src/SearchBox.tsx index acf53f34..948266d6 100644 --- a/src/SearchBox.tsx +++ b/src/SearchBox.tsx @@ -28,6 +28,12 @@ export function SearchBox(props: SearchBoxProps) { inputElement: props.inputRef.current, }); + React.useEffect(() => { + if (props.autoFocus && props.inputRef.current) { + props.inputRef.current.focus(); + } + }, [props.autoFocus, props.inputRef]); + return ( <>
    Date: Tue, 7 Jul 2020 15:04:57 +0200 Subject: [PATCH 109/151] feat(docsearch): support keyboard on focus on default integration --- src/DocSearch.tsx | 5 +++-- src/useDocSearchKeyboardEvents.ts | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 7a174a6e..bce446bc 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -29,6 +29,7 @@ export interface DocSearchProps export function DocSearch(props: DocSearchProps) { const [isOpen, setIsOpen] = React.useState(false); + const searchButtonRef = React.useRef(null); const onOpen = React.useCallback(() => { setIsOpen(true); @@ -38,11 +39,11 @@ export function DocSearch(props: DocSearchProps) { setIsOpen(false); }, [setIsOpen]); - useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }); + useDocSearchKeyboardEvents({ isOpen, onOpen, onClose, searchButtonRef }); return ( <> - + {isOpen && createPortal( diff --git a/src/useDocSearchKeyboardEvents.ts b/src/useDocSearchKeyboardEvents.ts index ac86a72a..79040e3e 100644 --- a/src/useDocSearchKeyboardEvents.ts +++ b/src/useDocSearchKeyboardEvents.ts @@ -25,7 +25,10 @@ export function useDocSearchKeyboardEvents({ } } - if (searchButtonRef.current === document.activeElement) { + if ( + searchButtonRef && + searchButtonRef.current === document.activeElement + ) { if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) { onOpen(); } From 7eeb30bf22d283dd6a9cad45dff574545784be46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 7 Jul 2020 16:46:47 +0200 Subject: [PATCH 110/151] chore: release v1.0.0-alpha.21 (#55) --- package.json | 6 +++--- src/version.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 101155d4..21c49f94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@docsearch/react", - "version": "1.0.0-alpha.20", + "version": "1.0.0-alpha.21", "license": "MIT", "homepage": "https://github.com/algolia/autocomplete.js", "repository": "algolia/autocomplete.js", @@ -36,8 +36,8 @@ "watch": "concurrently \"yarn build:esm:watch\" \"yarn build:types:watch\" \"yarn build:css:watch\"" }, "dependencies": { - "@francoischalifour/autocomplete-core": "^1.0.0-alpha.20", - "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.20", + "@francoischalifour/autocomplete-core": "^1.0.0-alpha.21", + "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.21", "algoliasearch": "^4.0.0" }, "peerDependencies": { diff --git a/src/version.ts b/src/version.ts index 3b788eaa..9a747757 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '1.0.0-alpha.20'; +export const version = '1.0.0-alpha.21'; From cf84a256a4541b4bcee7df6129a6007a6c034dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 7 Jul 2020 15:32:47 +0200 Subject: [PATCH 111/151] fix(docsearch): support initial query --- src/DocSearch.tsx | 12 +++++++++--- src/DocSearchModal.tsx | 4 +++- src/useDocSearchKeyboardEvents.ts | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index bce446bc..9ff7e544 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -30,10 +30,15 @@ export interface DocSearchProps export function DocSearch(props: DocSearchProps) { const [isOpen, setIsOpen] = React.useState(false); const searchButtonRef = React.useRef(null); + const [initialQuery, setInitialQuery] = React.useState(''); - const onOpen = React.useCallback(() => { - setIsOpen(true); - }, [setIsOpen]); + const onOpen = React.useCallback( + ({ query = '' } = {}) => { + setIsOpen(true); + setInitialQuery(query); + }, + [setIsOpen] + ); const onClose = React.useCallback(() => { setIsOpen(false); @@ -49,6 +54,7 @@ export function DocSearch(props: DocSearchProps) { createPortal( , diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index 85dfeae3..afd4ac9c 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -23,6 +23,7 @@ import { ScreenState } from './ScreenState'; import { Footer } from './Footer'; interface DocSearchModalProps extends DocSearchProps { + initialQuery?: string; initialScrollY: number; onClose?(): void; } @@ -42,6 +43,7 @@ export function DocSearchModal({ hitComponent = Hit, resultsFooterComponent = () => null, navigator, + initialQuery: queryFromProp = '', initialScrollY = 0, }: DocSearchModalProps) { const [state, setState] = React.useState< @@ -57,7 +59,7 @@ export function DocSearchModal({ const inputRef = React.useRef(null); const snipetLength = React.useRef(10); const initialQuery = React.useRef( - typeof window !== 'undefined' + queryFromProp || typeof window !== 'undefined' ? window.getSelection()!.toString().slice(0, MAX_QUERY_SIZE) : '' ).current; diff --git a/src/useDocSearchKeyboardEvents.ts b/src/useDocSearchKeyboardEvents.ts index 79040e3e..99f9417d 100644 --- a/src/useDocSearchKeyboardEvents.ts +++ b/src/useDocSearchKeyboardEvents.ts @@ -30,7 +30,7 @@ export function useDocSearchKeyboardEvents({ searchButtonRef.current === document.activeElement ) { if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) { - onOpen(); + onOpen({ query: event.keyCode }); } } } From ae3c713ed4f42afca3c2a721bd633e0506c6edea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Wed, 8 Jul 2020 14:34:38 +0200 Subject: [PATCH 112/151] refactor: remove unused API --- src/DocSearch.tsx | 12 +++--------- src/DocSearchModal.tsx | 4 +--- src/useDocSearchKeyboardEvents.ts | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 9ff7e544..bce446bc 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -30,15 +30,10 @@ export interface DocSearchProps export function DocSearch(props: DocSearchProps) { const [isOpen, setIsOpen] = React.useState(false); const searchButtonRef = React.useRef(null); - const [initialQuery, setInitialQuery] = React.useState(''); - const onOpen = React.useCallback( - ({ query = '' } = {}) => { - setIsOpen(true); - setInitialQuery(query); - }, - [setIsOpen] - ); + const onOpen = React.useCallback(() => { + setIsOpen(true); + }, [setIsOpen]); const onClose = React.useCallback(() => { setIsOpen(false); @@ -54,7 +49,6 @@ export function DocSearch(props: DocSearchProps) { createPortal( , diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index afd4ac9c..85dfeae3 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -23,7 +23,6 @@ import { ScreenState } from './ScreenState'; import { Footer } from './Footer'; interface DocSearchModalProps extends DocSearchProps { - initialQuery?: string; initialScrollY: number; onClose?(): void; } @@ -43,7 +42,6 @@ export function DocSearchModal({ hitComponent = Hit, resultsFooterComponent = () => null, navigator, - initialQuery: queryFromProp = '', initialScrollY = 0, }: DocSearchModalProps) { const [state, setState] = React.useState< @@ -59,7 +57,7 @@ export function DocSearchModal({ const inputRef = React.useRef(null); const snipetLength = React.useRef(10); const initialQuery = React.useRef( - queryFromProp || typeof window !== 'undefined' + typeof window !== 'undefined' ? window.getSelection()!.toString().slice(0, MAX_QUERY_SIZE) : '' ).current; diff --git a/src/useDocSearchKeyboardEvents.ts b/src/useDocSearchKeyboardEvents.ts index 99f9417d..79040e3e 100644 --- a/src/useDocSearchKeyboardEvents.ts +++ b/src/useDocSearchKeyboardEvents.ts @@ -30,7 +30,7 @@ export function useDocSearchKeyboardEvents({ searchButtonRef.current === document.activeElement ) { if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) { - onOpen({ query: event.keyCode }); + onOpen(); } } } From a358695c256595bd256a029f3e3cd1a3e9f84026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 9 Jul 2020 10:54:38 +0200 Subject: [PATCH 113/151] feat(docsearch): introduce DocSearch.js v3 (#56) --- package.json | 8 +- src/style.css | 762 ----------------------------------- src/styles/_variables.css | 74 ---- src/styles/modal.css | 624 ---------------------------- src/styles/search-button.css | 66 --- style.js | 4 +- 6 files changed, 4 insertions(+), 1534 deletions(-) delete mode 100644 src/style.css delete mode 100644 src/styles/_variables.css delete mode 100644 src/styles/modal.css delete mode 100644 src/styles/search-button.css diff --git a/package.json b/package.json index 21c49f94..34da1e1d 100644 --- a/package.json +++ b/package.json @@ -23,19 +23,17 @@ "unpkg": "dist/umd/index.js", "jsdelivr": "dist/umd/index.js", "scripts": { - "build": "yarn build:clean && yarn build:umd && yarn build:esm && yarn build:css", - "": "// TODO: have a proper build:css, probably including autoprefixer?", - "build:css": "cp src/styles/*.css dist/", - "build:css:watch": "chokidar src/styles/*.css --command \"yarn build:css\"", + "build": "yarn build:clean && yarn build:umd && yarn build:esm", "build:esm": "babel src --root-mode upward --extensions '.ts,.tsx' --out-dir dist/esm", "build:esm:watch": "yarn build:esm --watch", "build:umd": "rollup --config", "build:types": "tsc -p ./tsconfig.declaration.json --outDir ./dist/esm", "build:types:watch": "chokidar \"**/*.ts\" \"**/*.tsx\" --command \"yarn build:types\" --ignore \"dist\"", "build:clean": "rm -rf ./dist", - "watch": "concurrently \"yarn build:esm:watch\" \"yarn build:types:watch\" \"yarn build:css:watch\"" + "watch": "concurrently \"yarn build:esm:watch\" \"yarn build:types:watch\"" }, "dependencies": { + "@docsearch/css": "^1.0.0-alpha.21", "@francoischalifour/autocomplete-core": "^1.0.0-alpha.21", "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.21", "algoliasearch": "^4.0.0" diff --git a/src/style.css b/src/style.css deleted file mode 100644 index 81cbffd0..00000000 --- a/src/style.css +++ /dev/null @@ -1,762 +0,0 @@ -/* Variables */ - -:root { - --docsearch-primary-color: rgb(84, 104, 255); - --docsearch-spacing: 12px; - --docsearch-icon-stroke-width: 1.4; - --docsearch-highlight-color: var(--docsearch-primary-color); - --docsearch-text-color: var(--ifm-color-emphasis-900); - --docsearch-muted-color: rgb(150, 159, 175); - --docsearch-container-background: rgba(101, 108, 133, 0.8); - --docsearch-logo-color: rgba(84, 104, 255); - - /* modal */ - --docsearch-modal-width: 560px; - --docsearch-modal-height: 600px; - --docsearch-modal-background: var(--ifm-color-emphasis-100); - --docsearch-modal-shadow: inset 1px 1px 0 0 rgba(255, 255, 255, 0.5), - 0 3px 8px 0 rgba(85, 90, 100, 1); - - /* searchbox */ - --docsearch-searchbox-height: 56px; - --docsearch-searchbox-background: var(--ifm-color-emphasis-200); - --docsearch-searchbox-focus-background: #fff; - --docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color); - - /* hit */ - --docsearch-hit-height: 56px; - --docsearch-hit-color: var(--ifm-color-emphasis-800); - --docsearch-hit-active-color: #fff; - --docsearch-hit-background: #fff; - --docsearch-hit-shadow: 0 1px 3px 0 rgb(212, 217, 225); - - /* key */ - --docsearch-key-gradient: linear-gradient( - -225deg, - rgb(213, 219, 228) 0%, - rgb(248, 248, 248) 100% - ); - --docsearch-key-shadow: inset 0 -2px 0 0 rgb(205, 205, 230), - inset 0 0 1px 1px #fff, 0 1px 2px 1px rgba(30, 35, 90, 0.4); - - /* footer */ - --docsearch-footer-height: 44px; - --docsearch-footer-background: #fff; - --docsearch-footer-shadow: 0 -1px 0 0 rgb(224, 227, 232), - 0 -3px 6px 0 rgba(69, 98, 155, 0.12); -} - -/* Darkmode */ - -html[data-theme='dark'] { - --docsearch-container-background: rgba(9, 10, 17, 0.8); - --docsearch-modal-background: rgb(21, 23, 42); - --docsearch-modal-shadow: inset 1px 1px 0 0 rgb(44, 46, 64), - 0 3px 8px 0 rgb(0, 3, 9); - --docsearch-searchbox-background: rgb(9, 10, 17); - --docsearch-searchbox-focus-background: #000; - --docsearch-hit-color: var(--ifm-color-emphasis-500); - --docsearch-hit-shadow: none; - --docsearch-hit-background: rgb(9, 10, 17); - --docsearch-key-gradient: linear-gradient( - -26.56505117707799deg, - rgb(86, 88, 114) 0%, - rgb(49, 53, 91) 100% - ); - --docsearch-key-shadow: inset 0 -2px 0 0 rgb(40, 45, 85), - inset 0 0 1px 1px rgb(81, 87, 125), 0 2px 2px 0 rgba(3, 4, 9, 0.3); - --docsearch-footer-background: rgb(30, 33, 54); - --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5), - 0 -4px 8px 0 rgba(0, 0, 0, 0.2); - --docsearch-logo-color: rgb(255, 255, 255); - --docsearch-muted-color: rgb(127, 132, 151); -} - -/* Search Button */ - -.DocSearch-SearchButton { - align-items: center; - background: var(--docsearch-searchbox-background); - border: 0; - border-radius: 40px; - color: var(--docsearch-text-color); - color: var(--docsearch-muted-color); - cursor: pointer; - display: flex; - font-weight: 500; - height: 36px; - margin: 0 0 0 16px; - padding: 0 8px; - user-select: none; -} - -.DocSearch-SearchButton:hover, -.DocSearch-SearchButton:active, -.DocSearch-SearchButton:focus { - background: var(--docsearch-searchbox-focus-background); - box-shadow: var(--docsearch-searchbox-shadow); - color: var(--docsearch-text-color); - outline: none; -} - -.DocSearch-Search-Icon { - stroke-width: 1.6; -} - -.DocSearch-SearchButton .DocSearch-Search-Icon { - color: var(--docsearch-text-color); -} - -.DocSearch-SearchButton-Placeholder { - font-size: 1rem; - padding: 0 12px 0 6px; -} - -.DocSearch-SearchButton-Key { - align-items: center; - background: var(--docsearch-key-gradient); - border-radius: 3px; - box-shadow: var(--docsearch-key-shadow); - color: var(--docsearch-muted-color); - display: flex; - height: 18px; - justify-content: center; - margin-right: 0.4em; - padding-bottom: 2px; - position: relative; - top: -1px; - width: 20px; -} - -/* Body modifier */ - -.DocSearch--active { - /* - * We need to mark it as important because some websites override the - * `style` attribute (e.g. Docusaurus). - */ - overflow: hidden !important; -} - -/* Container & Modal */ - -.DocSearch-Container, -.DocSearch-Container * { - box-sizing: border-box; -} - -.DocSearch-Container { - background-color: var(--docsearch-container-background); - height: 100vh; - left: 0; - position: fixed; - top: 0; - width: 100vw; - z-index: var(--ifm-z-index-fixed); -} - -.DocSearch-Container a { - text-decoration: none; -} - -.DocSearch-Link { - appearance: none; - background: none; - border: 0; - color: var(--docsearch-highlight-color); - cursor: pointer; - font: inherit; - margin: 0; - padding: 0; -} - -.DocSearch-Modal { - background: var(--docsearch-modal-background); - border-radius: 6px; - box-shadow: var(--docsearch-modal-shadow); - flex-direction: column; - margin: 60px auto auto; - max-width: var(--docsearch-modal-width); - position: relative; -} - -/* Modal Searchbox */ - -.DocSearch-SearchBar { - display: flex; - padding: var(--docsearch-spacing) var(--docsearch-spacing) 0; -} - -.DocSearch-Form { - align-items: center; - background: var(--docsearch-searchbox-focus-background); - border-radius: 4px; - box-shadow: var(--docsearch-searchbox-shadow); - display: flex; - height: var(--docsearch-searchbox-height); - padding: 0 var(--docsearch-spacing); - position: relative; - width: 100%; -} - -.DocSearch-Input { - appearance: none; - background: transparent; - border: 0; - color: var(--docsearch-text-color); - flex: 1; - font: inherit; - font-size: 1.2em; - height: 100%; - outline: none; - padding-left: 8px; - width: 80%; -} - -.DocSearch-Input::placeholder { - color: var(--docsearch-muted-color); -} - -.DocSearch-Input::-webkit-search-cancel-button, -.DocSearch-Input::-webkit-search-decoration, -.DocSearch-Input::-webkit-search-results-button, -.DocSearch-Input::-webkit-search-results-decoration { - display: none; -} - -.DocSearch-LoadingIndicator, -.DocSearch-MagnifierLabel, -.DocSearch-Reset { - margin: 0; - padding: 0; -} - -.DocSearch-MagnifierLabel, -.DocSearch-Reset { - align-items: center; - color: var(--docsearch-highlight-color); - display: flex; - justify-content: center; -} - -.DocSearch-Container--Stalled .DocSearch-MagnifierLabel { - display: none; -} - -.DocSearch-LoadingIndicator { - display: none; -} - -.DocSearch-Container--Stalled .DocSearch-LoadingIndicator { - align-items: center; - color: var(--docsearch-highlight-color); - display: flex; - justify-content: center; -} - -@media screen and (prefers-reduced-motion: reduce) { - .DocSearch-Reset { - animation: none; - appearance: none; - background: none; - border: 0; - border-radius: 50%; - color: var(--docsearch-icon-color); - cursor: pointer; - right: 0; - stroke-width: var(--docsearch-icon-stroke-width); - } -} - -.DocSearch-Reset { - animation: fade-in 0.1s ease-in forwards; - appearance: none; - background: none; - border: 0; - border-radius: 50%; - color: var(--docsearch-icon-color); - cursor: pointer; - padding: 2px; - right: 0; - stroke-width: var(--docsearch-icon-stroke-width); -} - -.DocSearch-Reset[hidden] { - display: none; -} - -.DocSearch-Reset:focus { - outline: none; -} - -.DocSearch-Reset:hover { - color: var(--docsearch-highlight-color); -} - -.DocSearch-LoadingIndicator svg, -.DocSearch-MagnifierLabel svg { - height: 24px; - width: 24px; -} - -.DocSearch-Cancel { - display: none; -} - -/* Modal Dropdown */ - -.DocSearch-Dropdown { - max-height: calc( - var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - - var(--docsearch-spacing) - var(--docsearch-footer-height) - ); - overflow-y: auto; /* firefox */ - overflow-y: overlay; - padding: 0 var(--docsearch-spacing); - scrollbar-color: var(--docsearch-muted-color) - var(--docsearch-modal-background); - scrollbar-width: thin; -} - -.DocSearch-Dropdown::-webkit-scrollbar { - width: 12px; -} - -.DocSearch-Dropdown::-webkit-scrollbar-track { - background: transparent; -} - -.DocSearch-Dropdown::-webkit-scrollbar-thumb { - background-color: var(--docsearch-muted-color); - border: 3px solid var(--docsearch-modal-background); - border-radius: 20px; -} - -.DocSearch-Dropdown ul { - list-style: none; - margin: 0; - padding: 0; -} - -.DocSearch-Label { - color: var(--docsearch-muted-color); - font-size: 0.75em; - line-height: 1.6em; -} - -.DocSearch-Help { - color: var(--docsearch-muted-color); - font-size: 0.9em; - margin: 0; - user-select: none; -} - -.DocSearch-Title { - font-size: 1.2em; -} - -.DocSearch-Logo a { - display: flex; -} - -.DocSearch-Logo svg { - color: var(--docsearch-logo-color); - margin-left: 8px; -} - -/* Hit */ - -.DocSearch-Hits:last-of-type { - margin-bottom: 24px; -} - -.DocSearch-Hits mark { - background: none; - color: var(--docsearch-highlight-color); -} - -.DocSearch-HitsFooter { - color: var(--docsearch-muted-color); - display: flex; - font-size: 0.85em; - justify-content: center; - margin-bottom: var(--docsearch-spacing); - padding: var(--docsearch-spacing); -} - -.DocSearch-HitsFooter a { - border-bottom: 1px solid; - color: inherit; -} - -.DocSearch-Hit { - border-radius: 4px; - display: flex; - padding-bottom: 4px; - position: relative; -} - -@media screen and (prefers-reduced-motion: reduce) { - .DocSearch-Hit--deleting { - transition: none; - } -} - -.DocSearch-Hit--deleting { - opacity: 0; - transition: all 250ms linear; -} - -@media screen and (prefers-reduced-motion: reduce) { - .DocSearch-Hit--favoriting { - transition: none; - } -} - -.DocSearch-Hit--favoriting { - transform: scale(0); - transform-origin: top center; - transition: all 250ms linear; - transition-delay: 250ms; -} - -.DocSearch-Hit a { - background: var(--docsearch-hit-background); - border-radius: 4px; - box-shadow: var(--docsearch-hit-shadow); - display: block; - padding-left: var(--docsearch-spacing); - width: 100%; -} - -.DocSearch-Hit-source { - background: var(--docsearch-modal-background); - color: var(--docsearch-highlight-color); - font-size: 0.85em; - font-weight: 600; - line-height: 32px; - margin: 0 -4px; - padding: 8px 4px 0; - position: sticky; - top: 0; - z-index: 10; -} - -.DocSearch-Hit-Tree { - color: var(--docsearch-muted-color); - height: var(--docsearch-hit-height); - opacity: 0.5; - stroke-width: var(--docsearch-icon-stroke-width); - width: 24px; -} - -.DocSearch-Hit[aria-selected='true'] a { - background-color: var(--docsearch-highlight-color); -} - -.DocSearch-Hit[aria-selected='true'] mark { - text-decoration: underline; -} - -.DocSearch-Hit-Container { - align-items: center; - color: var(--docsearch-hit-color); - display: flex; - flex-direction: row; - height: var(--docsearch-hit-height); - padding: 0 var(--docsearch-spacing) 0 0; -} - -.DocSearch-Hit-icon { - color: var(--docsearch-muted-color); - height: 20px; - stroke-width: var(--docsearch-icon-stroke-width); - width: 20px; -} - -.DocSearch-Hit-action { - align-items: center; - color: var(--docsearch-muted-color); - display: flex; - height: 22px; - stroke-width: var(--docsearch-icon-stroke-width); - width: 22px; -} - -.DocSearch-Hit-action svg { - display: block; - height: 18px; - width: 18px; -} - -.DocSearch-Hit-action + .DocSearch-Hit-action { - margin-left: 6px; -} - -.DocSearch-Hit-action-button { - appearance: none; - background: none; - border: 0; - border-radius: 50%; - color: inherit; - cursor: pointer; - padding: 2px; -} - -svg.DocSearch-Hit-Select-Icon { - display: none; -} - -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-Select-Icon { - display: block; -} - -.DocSearch-Hit-action-button:hover, -.DocSearch-Hit-action-button:focus { - background: rgba(0, 0, 0, 0.2); - transition: background-color 0.1s ease-in; -} - -@media screen and (prefers-reduced-motion: reduce) { - .DocSearch-Hit-action-button:hover, - .DocSearch-Hit-action-button:focus { - transition: none; - } -} - -.DocSearch-Hit-action-button:hover path, -.DocSearch-Hit-action-button:focus path { - fill: #fff; -} - -.DocSearch-Hit-content-wrapper { - display: flex; - flex: 1 1 auto; - flex-direction: column; - font-weight: 500; - justify-content: center; - line-height: 1.2em; - margin: 0 8px; - overflow-x: hidden; - position: relative; - text-overflow: ellipsis; - white-space: nowrap; - width: 80%; -} - -.DocSearch-Hit-title { - font-size: 0.9em; -} - -.DocSearch-Hit-path { - color: var(--docsearch-muted-color); - font-size: 0.75em; -} - -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-title, -.DocSearch-Hit[aria-selected='true'] mark, -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-text, -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-path, -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-icon, -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-action, -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-Tree { - color: var(--docsearch-hit-active-color) !important; -} - -@media screen and (prefers-reduced-motion: reduce) { - .DocSearch-Hit-action-button:hover, - .DocSearch-Hit-action-button:focus { - background: rgba(0, 0, 0, 0.2); - transition: none; - } -} - -/* No Results - Start Screen - Error Screen */ - -.DocSearch-NoResults, -.DocSearch-StartScreen, -.DocSearch-ErrorScreen { - font-size: 0.9em; - margin: 0 auto; - padding: 36px 0; - text-align: center; - width: 80%; -} - -.DocSearch-Screen-Icon { - color: var(--docsearch-muted-color); - padding-bottom: 12px; -} - -.DocSearch-NoResults-Prefill-List { - display: inline-block; - padding-bottom: 24px; - text-align: left; -} - -.DocSearch-NoResults-Prefill-List ul { - display: inline-block; - padding: 8px 0 0; -} - -.DocSearch-NoResults-Prefill-List li { - list-style-position: inside; - list-style-type: '» '; -} - -.DocSearch-Prefill { - appearance: none; - background: none; - border: 0; - border-radius: 1em; - color: var(--docsearch-highlight-color); - cursor: pointer; - display: inline-block; - font-size: 1em; - font-weight: 700; - padding: 0; -} - -.DocSearch-Prefill:hover, -.DocSearch-Prefill:focus { - outline: none; - text-decoration: underline; -} - -/* Modal Footer */ - -.DocSearch-Footer { - align-items: center; - background: var(--docsearch-footer-background); - border-radius: 0 0 8px 8px; - box-shadow: var(--docsearch-footer-shadow); - display: flex; - flex-direction: row-reverse; - flex-shrink: 0; - height: var(--docsearch-footer-height); - justify-content: space-between; - padding: 0 var(--docsearch-spacing); - position: relative; - user-select: none; - width: 100%; - z-index: 300; -} - -.DocSearch-Commands { - color: var(--docsearch-muted-color); - display: flex; - list-style: none; - margin: 0; - padding: 0; -} - -.DocSearch-Commands li { - align-items: center; - display: flex; -} - -.DocSearch-Commands li:not(:last-of-type) { - margin-right: 0.8em; -} - -.DocSearch-Commands-Key { - align-items: center; - background: var(--docsearch-key-gradient); - border-radius: 2px; - box-shadow: var(--docsearch-key-shadow); - display: flex; - height: 18px; - justify-content: center; - margin-right: 0.4em; - padding-bottom: 2px; - width: 20px; -} - -/* Responsive */ -@media (max-width: 750px) { - :root { - --docsearch-spacing: 10px; - --docsearch-footer-height: 40px; - } - - .DocSearch--active #__docusaurus { - display: none; - } - - .DocSearch-SearchButton-KeySeparator, - .DocSearch-SearchButton-Key { - display: none; - } - - .DocSearch-SearchButton-Placeholder { - display: none; - } - - .DocSearch-Dropdown { - height: 100%; - } - - .DocSearch-Container { - height: 100vh; - height: -webkit-fill-available; - position: absolute; - } - - .DocSearch-Footer { - border-radius: 0; - bottom: 0; - position: absolute; - } - - .DocSearch-Hit-content-wrapper { - display: flex; - position: relative; - width: 80%; - } - - .DocSearch-Modal { - border-radius: 0; - box-shadow: none; - height: 100vh; - height: -webkit-fill-available; - margin: 0; - max-width: 100%; - width: 100%; - } - - .DocSearch-Cancel { - appearance: none; - background: none; - border: 0; - color: var(--docsearch-highlight-color); - cursor: pointer; - display: inline-block; - flex: none; - font: inherit; - font-size: 1em; - font-weight: 500; - margin-left: var(--docsearch-spacing); - outline: none; - overflow: hidden; - padding: 0; - user-select: none; - white-space: nowrap; - } - - .DocSearch-Commands { - display: none; - } - - .DocSearch-Hit-Tree { - display: none; - } -} - -@keyframes fade-in { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} diff --git a/src/styles/_variables.css b/src/styles/_variables.css deleted file mode 100644 index d59766d9..00000000 --- a/src/styles/_variables.css +++ /dev/null @@ -1,74 +0,0 @@ -/* Variables */ - -:root { - --docsearch-primary-color: rgb(84, 104, 255); - --docsearch-text-color: rgb(28, 30, 33); - --docsearch-spacing: 12px; - --docsearch-icon-stroke-width: 1.4; - --docsearch-highlight-color: var(--docsearch-primary-color); - --docsearch-muted-color: rgb(150, 159, 175); - --docsearch-container-background: rgba(101, 108, 133, 0.8); - --docsearch-logo-color: rgba(84, 104, 255); - - /* modal */ - --docsearch-modal-width: 560px; - --docsearch-modal-height: 600px; - --docsearch-modal-background: rgb(245, 246, 247); - --docsearch-modal-shadow: inset 1px 1px 0 0 rgba(255, 255, 255, 0.5), - 0 3px 8px 0 rgba(85, 90, 100, 1); - - /* searchbox */ - --docsearch-searchbox-height: 56px; - --docsearch-searchbox-background: rgb(235, 237, 240); - --docsearch-searchbox-focus-background: #fff; - --docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color); - - /* hit */ - --docsearch-hit-height: 56px; - --docsearch-hit-color: rgb(68, 73, 80); - --docsearch-hit-active-color: #fff; - --docsearch-hit-background: #fff; - --docsearch-hit-shadow: 0 1px 3px 0 rgb(212, 217, 225); - - /* key */ - --docsearch-key-gradient: linear-gradient( - -225deg, - rgb(213, 219, 228) 0%, - rgb(248, 248, 248) 100% - ); - --docsearch-key-shadow: inset 0 -2px 0 0 rgb(205, 205, 230), - inset 0 0 1px 1px #fff, 0 1px 2px 1px rgba(30, 35, 90, 0.4); - - /* footer */ - --docsearch-footer-height: 44px; - --docsearch-footer-background: #fff; - --docsearch-footer-shadow: 0 -1px 0 0 rgb(224, 227, 232), - 0 -3px 6px 0 rgba(69, 98, 155, 0.12); -} - -/* Darkmode */ - -html[data-theme='dark'] { - --docsearch-text-color: rgb(245, 246, 247); - --docsearch-container-background: rgba(9, 10, 17, 0.8); - --docsearch-modal-background: rgb(21, 23, 42); - --docsearch-modal-shadow: inset 1px 1px 0 0 rgb(44, 46, 64), - 0 3px 8px 0 rgb(0, 3, 9); - --docsearch-searchbox-background: rgb(9, 10, 17); - --docsearch-searchbox-focus-background: #000; - --docsearch-hit-color: rgb(190, 195, 201); - --docsearch-hit-shadow: none; - --docsearch-hit-background: rgb(9, 10, 17); - --docsearch-key-gradient: linear-gradient( - -26.5deg, - rgb(86, 88, 114) 0%, - rgb(49, 53, 91) 100% - ); - --docsearch-key-shadow: inset 0 -2px 0 0 rgb(40, 45, 85), - inset 0 0 1px 1px rgb(81, 87, 125), 0 2px 2px 0 rgba(3, 4, 9, 0.3); - --docsearch-footer-background: rgb(30, 33, 54); - --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5), - 0 -4px 8px 0 rgba(0, 0, 0, 0.2); - --docsearch-logo-color: rgb(255, 255, 255); - --docsearch-muted-color: rgb(127, 132, 151); -} diff --git a/src/styles/modal.css b/src/styles/modal.css deleted file mode 100644 index 807d0236..00000000 --- a/src/styles/modal.css +++ /dev/null @@ -1,624 +0,0 @@ -/* Body modifier */ - -.DocSearch--active { - /* - * We need to mark it as important because some websites override the - * `style` attribute (e.g. Docusaurus). - */ - overflow: hidden !important; -} - -/* Container & Modal */ - -.DocSearch-Container, -.DocSearch-Container * { - box-sizing: border-box; -} - -.DocSearch-Container { - background-color: var(--docsearch-container-background); - height: 100vh; - left: 0; - position: fixed; - top: 0; - width: 100vw; - z-index: 200; -} - -.DocSearch-Container a { - text-decoration: none; -} - -.DocSearch-Link { - appearance: none; - background: none; - border: 0; - color: var(--docsearch-highlight-color); - cursor: pointer; - font: inherit; - margin: 0; - padding: 0; -} - -.DocSearch-Modal { - background: var(--docsearch-modal-background); - border-radius: 6px; - box-shadow: var(--docsearch-modal-shadow); - flex-direction: column; - margin: 60px auto auto; - max-width: var(--docsearch-modal-width); - position: relative; -} - -/* Modal Searchbox */ - -.DocSearch-SearchBar { - display: flex; - padding: var(--docsearch-spacing) var(--docsearch-spacing) 0; -} - -.DocSearch-Form { - align-items: center; - background: var(--docsearch-searchbox-focus-background); - border-radius: 4px; - box-shadow: var(--docsearch-searchbox-shadow); - display: flex; - height: var(--docsearch-searchbox-height); - padding: 0 var(--docsearch-spacing); - position: relative; - width: 100%; -} - -.DocSearch-Input { - appearance: none; - background: transparent; - border: 0; - color: var(--docsearch-text-color); - flex: 1; - font: inherit; - font-size: 1.2em; - height: 100%; - outline: none; - padding: 0 0 0 8px; - width: 80%; -} - -.DocSearch-Input::placeholder { - color: var(--docsearch-muted-color); - opacity: 1; /* Firefox */ -} - -.DocSearch-Input::-webkit-search-cancel-button, -.DocSearch-Input::-webkit-search-decoration, -.DocSearch-Input::-webkit-search-results-button, -.DocSearch-Input::-webkit-search-results-decoration { - display: none; -} - -.DocSearch-LoadingIndicator, -.DocSearch-MagnifierLabel, -.DocSearch-Reset { - margin: 0; - padding: 0; -} - -.DocSearch-MagnifierLabel, -.DocSearch-Reset { - align-items: center; - color: var(--docsearch-highlight-color); - display: flex; - justify-content: center; -} - -.DocSearch-Container--Stalled .DocSearch-MagnifierLabel { - display: none; -} - -.DocSearch-LoadingIndicator { - display: none; -} - -.DocSearch-Container--Stalled .DocSearch-LoadingIndicator { - align-items: center; - color: var(--docsearch-highlight-color); - display: flex; - justify-content: center; -} - -@media screen and (prefers-reduced-motion: reduce) { - .DocSearch-Reset { - animation: none; - appearance: none; - background: none; - border: 0; - border-radius: 50%; - color: var(--docsearch-icon-color); - cursor: pointer; - right: 0; - stroke-width: var(--docsearch-icon-stroke-width); - } -} - -.DocSearch-Reset { - animation: fade-in 0.1s ease-in forwards; - appearance: none; - background: none; - border: 0; - border-radius: 50%; - color: var(--docsearch-icon-color); - cursor: pointer; - padding: 2px; - right: 0; - stroke-width: var(--docsearch-icon-stroke-width); -} - -.DocSearch-Reset[hidden] { - display: none; -} - -.DocSearch-Reset:focus { - outline: none; -} - -.DocSearch-Reset:hover { - color: var(--docsearch-highlight-color); -} - -.DocSearch-LoadingIndicator svg, -.DocSearch-MagnifierLabel svg { - height: 24px; - width: 24px; -} - -.DocSearch-Cancel { - display: none; -} - -/* Modal Dropdown */ - -.DocSearch-Dropdown { - max-height: calc( - var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - - var(--docsearch-spacing) - var(--docsearch-footer-height) - ); - overflow-y: auto; /* firefox */ - overflow-y: overlay; - padding: 0 var(--docsearch-spacing); - scrollbar-color: var(--docsearch-muted-color) - var(--docsearch-modal-background); - scrollbar-width: thin; -} - -.DocSearch-Dropdown::-webkit-scrollbar { - width: 12px; -} - -.DocSearch-Dropdown::-webkit-scrollbar-track { - background: transparent; -} - -.DocSearch-Dropdown::-webkit-scrollbar-thumb { - background-color: var(--docsearch-muted-color); - border: 3px solid var(--docsearch-modal-background); - border-radius: 20px; -} - -.DocSearch-Dropdown ul { - list-style: none; - margin: 0; - padding: 0; -} - -.DocSearch-Label { - color: var(--docsearch-muted-color); - font-size: 0.75em; - line-height: 1.6em; -} - -.DocSearch-Help { - color: var(--docsearch-muted-color); - font-size: 0.9em; - margin: 0; - user-select: none; -} - -.DocSearch-Title { - font-size: 1.2em; -} - -.DocSearch-Logo a { - display: flex; -} - -.DocSearch-Logo svg { - color: var(--docsearch-logo-color); - margin-left: 8px; -} - -/* Hit */ - -.DocSearch-Hits:last-of-type { - margin-bottom: 24px; -} - -.DocSearch-Hits mark { - background: none; - color: var(--docsearch-highlight-color); -} - -.DocSearch-HitsFooter { - color: var(--docsearch-muted-color); - display: flex; - font-size: 0.85em; - justify-content: center; - margin-bottom: var(--docsearch-spacing); - padding: var(--docsearch-spacing); -} - -.DocSearch-HitsFooter a { - border-bottom: 1px solid; - color: inherit; -} - -.DocSearch-Hit { - border-radius: 4px; - display: flex; - padding-bottom: 4px; - position: relative; -} - -@media screen and (prefers-reduced-motion: reduce) { - .DocSearch-Hit--deleting { - transition: none; - } -} - -.DocSearch-Hit--deleting { - opacity: 0; - transition: all 250ms linear; -} - -@media screen and (prefers-reduced-motion: reduce) { - .DocSearch-Hit--favoriting { - transition: none; - } -} - -.DocSearch-Hit--favoriting { - transform: scale(0); - transform-origin: top center; - transition: all 250ms linear; - transition-delay: 250ms; -} - -.DocSearch-Hit a { - background: var(--docsearch-hit-background); - border-radius: 4px; - box-shadow: var(--docsearch-hit-shadow); - display: block; - padding-left: var(--docsearch-spacing); - width: 100%; -} - -.DocSearch-Hit-source { - background: var(--docsearch-modal-background); - color: var(--docsearch-highlight-color); - font-size: 0.85em; - font-weight: 600; - line-height: 32px; - margin: 0 -4px; - padding: 8px 4px 0; - position: sticky; - top: 0; - z-index: 10; -} - -.DocSearch-Hit-Tree { - color: var(--docsearch-muted-color); - height: var(--docsearch-hit-height); - opacity: 0.5; - stroke-width: var(--docsearch-icon-stroke-width); - width: 24px; -} - -.DocSearch-Hit[aria-selected='true'] a { - background-color: var(--docsearch-highlight-color); -} - -.DocSearch-Hit[aria-selected='true'] mark { - text-decoration: underline; -} - -.DocSearch-Hit-Container { - align-items: center; - color: var(--docsearch-hit-color); - display: flex; - flex-direction: row; - height: var(--docsearch-hit-height); - padding: 0 var(--docsearch-spacing) 0 0; -} - -.DocSearch-Hit-icon { - color: var(--docsearch-muted-color); - height: 20px; - stroke-width: var(--docsearch-icon-stroke-width); - width: 20px; -} - -.DocSearch-Hit-action { - align-items: center; - color: var(--docsearch-muted-color); - display: flex; - height: 22px; - stroke-width: var(--docsearch-icon-stroke-width); - width: 22px; -} - -.DocSearch-Hit-action svg { - display: block; - height: 18px; - width: 18px; -} - -.DocSearch-Hit-action + .DocSearch-Hit-action { - margin-left: 6px; -} - -.DocSearch-Hit-action-button { - appearance: none; - background: none; - border: 0; - border-radius: 50%; - color: inherit; - cursor: pointer; - padding: 2px; -} - -svg.DocSearch-Hit-Select-Icon { - display: none; -} - -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-Select-Icon { - display: block; -} - -.DocSearch-Hit-action-button:hover, -.DocSearch-Hit-action-button:focus { - background: rgba(0, 0, 0, 0.2); - transition: background-color 0.1s ease-in; -} - -@media screen and (prefers-reduced-motion: reduce) { - .DocSearch-Hit-action-button:hover, - .DocSearch-Hit-action-button:focus { - transition: none; - } -} - -.DocSearch-Hit-action-button:hover path, -.DocSearch-Hit-action-button:focus path { - fill: #fff; -} - -.DocSearch-Hit-content-wrapper { - display: flex; - flex: 1 1 auto; - flex-direction: column; - font-weight: 500; - justify-content: center; - line-height: 1.2em; - margin: 0 8px; - overflow-x: hidden; - position: relative; - text-overflow: ellipsis; - white-space: nowrap; - width: 80%; -} - -.DocSearch-Hit-title { - font-size: 0.9em; -} - -.DocSearch-Hit-path { - color: var(--docsearch-muted-color); - font-size: 0.75em; -} - -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-title, -.DocSearch-Hit[aria-selected='true'] mark, -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-text, -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-path, -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-icon, -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-action, -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-Tree { - color: var(--docsearch-hit-active-color) !important; -} - -@media screen and (prefers-reduced-motion: reduce) { - .DocSearch-Hit-action-button:hover, - .DocSearch-Hit-action-button:focus { - background: rgba(0, 0, 0, 0.2); - transition: none; - } -} - -/* No Results - Start Screen - Error Screen */ - -.DocSearch-NoResults, -.DocSearch-StartScreen, -.DocSearch-ErrorScreen { - font-size: 0.9em; - margin: 0 auto; - padding: 36px 0; - text-align: center; - width: 80%; -} - -.DocSearch-Screen-Icon { - color: var(--docsearch-muted-color); - padding-bottom: 12px; -} - -.DocSearch-NoResults-Prefill-List { - display: inline-block; - padding-bottom: 24px; - text-align: left; -} - -.DocSearch-NoResults-Prefill-List ul { - display: inline-block; - padding: 8px 0 0; -} - -.DocSearch-NoResults-Prefill-List li { - list-style-position: inside; - list-style-type: '» '; -} - -.DocSearch-Prefill { - appearance: none; - background: none; - border: 0; - border-radius: 1em; - color: var(--docsearch-highlight-color); - cursor: pointer; - display: inline-block; - font-size: 1em; - font-weight: 700; - padding: 0; -} - -.DocSearch-Prefill:hover, -.DocSearch-Prefill:focus { - outline: none; - text-decoration: underline; -} - -/* Modal Footer */ - -.DocSearch-Footer { - align-items: center; - background: var(--docsearch-footer-background); - border-radius: 0 0 8px 8px; - box-shadow: var(--docsearch-footer-shadow); - display: flex; - flex-direction: row-reverse; - flex-shrink: 0; - height: var(--docsearch-footer-height); - justify-content: space-between; - padding: 0 var(--docsearch-spacing); - position: relative; - user-select: none; - width: 100%; - z-index: 300; -} - -.DocSearch-Commands { - color: var(--docsearch-muted-color); - display: flex; - list-style: none; - margin: 0; - padding: 0; -} - -.DocSearch-Commands li { - align-items: center; - display: flex; -} - -.DocSearch-Commands li:not(:last-of-type) { - margin-right: 0.8em; -} - -.DocSearch-Commands-Key { - align-items: center; - background: var(--docsearch-key-gradient); - border-radius: 2px; - box-shadow: var(--docsearch-key-shadow); - display: flex; - height: 18px; - justify-content: center; - margin-right: 0.4em; - padding-bottom: 2px; - width: 20px; -} - -/* Responsive */ -@media (max-width: 750px) { - :root { - --docsearch-spacing: 10px; - --docsearch-footer-height: 40px; - } - - .DocSearch--active #__docusaurus { - display: none; - } - - .DocSearch-Dropdown { - height: 100%; - } - - .DocSearch-Container { - height: 100vh; - height: -webkit-fill-available; - position: absolute; - } - - .DocSearch-Footer { - border-radius: 0; - bottom: 0; - position: absolute; - } - - .DocSearch-Hit-content-wrapper { - display: flex; - position: relative; - width: 80%; - } - - .DocSearch-Modal { - border-radius: 0; - box-shadow: none; - height: 100vh; - height: -webkit-fill-available; - margin: 0; - max-width: 100%; - width: 100%; - } - - .DocSearch-Cancel { - appearance: none; - background: none; - border: 0; - color: var(--docsearch-highlight-color); - cursor: pointer; - display: inline-block; - flex: none; - font: inherit; - font-size: 1em; - font-weight: 500; - margin-left: var(--docsearch-spacing); - outline: none; - overflow: hidden; - padding: 0; - user-select: none; - white-space: nowrap; - } - - .DocSearch-Commands { - display: none; - } - - .DocSearch-Hit-Tree { - display: none; - } -} - -@keyframes fade-in { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} diff --git a/src/styles/search-button.css b/src/styles/search-button.css deleted file mode 100644 index d53099e1..00000000 --- a/src/styles/search-button.css +++ /dev/null @@ -1,66 +0,0 @@ -/* Search Button */ - -.DocSearch-SearchButton { - align-items: center; - background: var(--docsearch-searchbox-background); - border: 0; - border-radius: 40px; - color: var(--docsearch-text-color); - color: var(--docsearch-muted-color); - cursor: pointer; - display: flex; - font-weight: 500; - height: 36px; - margin: 0 0 0 16px; - padding: 0 8px; - user-select: none; -} - -.DocSearch-SearchButton:hover, -.DocSearch-SearchButton:active, -.DocSearch-SearchButton:focus { - background: var(--docsearch-searchbox-focus-background); - box-shadow: var(--docsearch-searchbox-shadow); - color: var(--docsearch-text-color); - outline: none; -} - -.DocSearch-Search-Icon { - stroke-width: 1.6; -} - -.DocSearch-SearchButton .DocSearch-Search-Icon { - color: var(--docsearch-text-color); -} - -.DocSearch-SearchButton-Placeholder { - font-size: 1rem; - padding: 0 12px 0 6px; -} - -.DocSearch-SearchButton-Key { - align-items: center; - background: var(--docsearch-key-gradient); - border-radius: 3px; - box-shadow: var(--docsearch-key-shadow); - color: var(--docsearch-muted-color); - display: flex; - height: 18px; - justify-content: center; - margin-right: 0.4em; - padding-bottom: 2px; - position: relative; - top: -1px; - width: 20px; -} - -@media (max-width: 750px) { - .DocSearch-SearchButton-KeySeparator, - .DocSearch-SearchButton-Key { - display: none; - } - - .DocSearch-SearchButton-Placeholder { - display: none; - } -} diff --git a/style.js b/style.js index 5e43eda1..26d58a9d 100644 --- a/style.js +++ b/style.js @@ -1,3 +1 @@ -export * from './dist/_variables.css'; -export * from './dist/modal.css'; -export * from './dist/search-button.css'; +export * from '@docsearch/css'; From 70b4de690d2dcecb15f7c0f3664d56b87352a335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 9 Jul 2020 11:10:44 +0200 Subject: [PATCH 114/151] feat(docsearch): introduce `transformSearchClient` API `transformSearchClient` allows to attach user agents to the search client, to manipulate the cache, etc. --- src/DocSearchModal.tsx | 13 ++++++------- src/types/SearchClient.ts | 1 + src/types/index.ts | 1 + src/useSearchClient.ts | 11 ++++++++--- src/utils/identity.ts | 3 +++ src/utils/index.ts | 1 + 6 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 src/types/SearchClient.ts create mode 100644 src/utils/identity.ts diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index 85dfeae3..39f20445 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -9,9 +9,10 @@ import { MAX_QUERY_SIZE } from './constants'; import { DocSearchHit, InternalDocSearchHit, + SearchClient, StoredDocSearchHit, } from './types'; -import { groupBy, noop } from './utils'; +import { groupBy, identity, noop } from './utils'; import { createStoredSearches } from './stored-searches'; import { useSearchClient } from './useSearchClient'; import { useTrapFocus } from './useTrapFocus'; @@ -23,14 +24,11 @@ import { ScreenState } from './ScreenState'; import { Footer } from './Footer'; interface DocSearchModalProps extends DocSearchProps { + transformSearchClient?(searchClient: SearchClient): SearchClient; initialScrollY: number; onClose?(): void; } -function defaultTransformItems(x: DocSearchHit[]) { - return x; -} - export function DocSearchModal({ appId = 'BH4D9OD16A', apiKey, @@ -38,11 +36,12 @@ export function DocSearchModal({ placeholder = 'Search docs', searchParameters, onClose = noop, - transformItems = defaultTransformItems, + transformItems = identity, hitComponent = Hit, resultsFooterComponent = () => null, navigator, initialScrollY = 0, + transformSearchClient = identity, }: DocSearchModalProps) { const [state, setState] = React.useState< AutocompleteState @@ -62,7 +61,7 @@ export function DocSearchModal({ : '' ).current; - const searchClient = useSearchClient(appId, apiKey); + const searchClient = useSearchClient(appId, apiKey, transformSearchClient); const favoriteSearches = React.useRef( createStoredSearches({ key: `__DOCSEARCH_FAVORITE_SEARCHES__${indexName}`, diff --git a/src/types/SearchClient.ts b/src/types/SearchClient.ts new file mode 100644 index 00000000..d39c22c7 --- /dev/null +++ b/src/types/SearchClient.ts @@ -0,0 +1 @@ +export type SearchClient = any; diff --git a/src/types/index.ts b/src/types/index.ts index 93285bd1..779c5649 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,4 @@ export * from './DocSearchHit'; export * from './InternalDocSearchHit'; +export * from './SearchClient'; export * from './StoredDocSearchHit'; diff --git a/src/useSearchClient.ts b/src/useSearchClient.ts index a1a72879..01583370 100644 --- a/src/useSearchClient.ts +++ b/src/useSearchClient.ts @@ -1,15 +1,20 @@ import React from 'react'; import algoliasearch from 'algoliasearch/dist/algoliasearch-lite.esm.browser'; +import { SearchClient } from './types'; import { version } from './version'; -export function useSearchClient(appId: string, apiKey: string) { +export function useSearchClient( + appId: string, + apiKey: string, + transformSearchClient: (searchClient: SearchClient) => SearchClient +): SearchClient { const searchClient = React.useMemo(() => { const client = algoliasearch(appId, apiKey); client.addAlgoliaAgent(`docsearch (${version})`); - return client; - }, [appId, apiKey]); + return transformSearchClient(client); + }, [appId, apiKey, transformSearchClient]); return searchClient; } diff --git a/src/utils/identity.ts b/src/utils/identity.ts new file mode 100644 index 00000000..440329ed --- /dev/null +++ b/src/utils/identity.ts @@ -0,0 +1,3 @@ +export function identity(x: TParam): TParam { + return x; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index a684cfed..877f02d6 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,3 @@ export * from './groupBy'; +export * from './identity'; export * from './noop'; From 267c22f27f5b1a7512492ee45b302abd8c05a6a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 9 Jul 2020 11:11:10 +0200 Subject: [PATCH 115/151] feat(docsearch): attach `docsearch.js` user agent to vanilla renderer --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 7097959d..d4e93969 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,4 @@ export * from './DocSearch'; export * from './DocSearchButton'; export * from './DocSearchModal'; export * from './useDocSearchKeyboardEvents'; +export * from './version'; From 79a972b520ad6c45d1c4070035c3588c839ca966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 9 Jul 2020 12:04:12 +0200 Subject: [PATCH 116/151] feat(docsearch): track `docsearch-react` UA --- src/useSearchClient.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/useSearchClient.ts b/src/useSearchClient.ts index 01583370..afa9b244 100644 --- a/src/useSearchClient.ts +++ b/src/useSearchClient.ts @@ -11,7 +11,18 @@ export function useSearchClient( ): SearchClient { const searchClient = React.useMemo(() => { const client = algoliasearch(appId, apiKey); - client.addAlgoliaAgent(`docsearch (${version})`); + client.addAlgoliaAgent('docsearch', version); + + // Since DocSearch.js relies on DocSearch React with an alias to Preact, + // we cannot add the `docsearch-react` user agent by default, otherwise + // it would also be sent on a DocSearch.js integration. + // We therefore only add the `docsearch-react` user agent if `docsearch.js` + // is not present. + if ( + /docsearch.js \(.*\)/.test(client.transporter.userAgent.value) === false + ) { + client.addAlgoliaAgent('docsearch-react', version); + } return transformSearchClient(client); }, [appId, apiKey, transformSearchClient]); From 9a2188548c446fc195bd009f220a1958a4a3e934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 9 Jul 2020 12:15:04 +0200 Subject: [PATCH 117/151] feat(website): lazy load DocSearch styles --- package.json | 4 ++-- style/button.js | 1 + style.js => style/index.js | 0 style/modal.js | 1 + style/variables.js | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 style/button.js rename style.js => style/index.js (100%) create mode 100644 style/modal.js create mode 100644 style/variables.js diff --git a/package.json b/package.json index 34da1e1d..39d9a684 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,9 @@ "sideEffects": false, "files": [ "dist/", + "style/", "button.js", - "modal.js", - "style.js" + "modal.js" ], "source": "src/index.ts", "types": "dist/esm/index.d.ts", diff --git a/style/button.js b/style/button.js new file mode 100644 index 00000000..86e69d1f --- /dev/null +++ b/style/button.js @@ -0,0 +1 @@ +export * from '@docsearch/css/dist/button.css'; diff --git a/style.js b/style/index.js similarity index 100% rename from style.js rename to style/index.js diff --git a/style/modal.js b/style/modal.js new file mode 100644 index 00000000..fb35bb60 --- /dev/null +++ b/style/modal.js @@ -0,0 +1 @@ +export * from '@docsearch/css/dist/modal.css'; diff --git a/style/variables.js b/style/variables.js new file mode 100644 index 00000000..ecbb7b20 --- /dev/null +++ b/style/variables.js @@ -0,0 +1 @@ +export * from '@docsearch/css/dist/_variables.css'; From 77daceeb2323e0273e8f06e8079299375eaf6130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 9 Jul 2020 17:19:33 +0200 Subject: [PATCH 118/151] chore: release v1.0.0-alpha.22 (#57) --- package.json | 8 ++++---- src/version.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 39d9a684..89a2a33a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@docsearch/react", - "version": "1.0.0-alpha.21", + "version": "1.0.0-alpha.22", "license": "MIT", "homepage": "https://github.com/algolia/autocomplete.js", "repository": "algolia/autocomplete.js", @@ -33,9 +33,9 @@ "watch": "concurrently \"yarn build:esm:watch\" \"yarn build:types:watch\"" }, "dependencies": { - "@docsearch/css": "^1.0.0-alpha.21", - "@francoischalifour/autocomplete-core": "^1.0.0-alpha.21", - "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.21", + "@docsearch/css": "^1.0.0-alpha.22", + "@francoischalifour/autocomplete-core": "^1.0.0-alpha.22", + "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.22", "algoliasearch": "^4.0.0" }, "peerDependencies": { diff --git a/src/version.ts b/src/version.ts index 9a747757..03d45419 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '1.0.0-alpha.21'; +export const version = '1.0.0-alpha.22'; From a40cc0351e2605e4afd7ee72bdcc8bff8f72243b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 9 Jul 2020 17:49:52 +0200 Subject: [PATCH 119/151] feat(docsearch): introduce `disableUserPersonalization` API --- src/DocSearch.tsx | 4 +++- src/DocSearchModal.tsx | 15 ++++++++++++--- src/StartScreen.tsx | 5 ++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index bce446bc..008f5e1b 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -5,7 +5,7 @@ import { AutocompleteState, } from '@francoischalifour/autocomplete-core'; -import { DocSearchHit, InternalDocSearchHit } from './types'; +import { DocSearchHit, InternalDocSearchHit, SearchClient } from './types'; import { DocSearchButton } from './DocSearchButton'; import { DocSearchModal } from './DocSearchModal'; import { useDocSearchKeyboardEvents } from './useDocSearchKeyboardEvents'; @@ -25,6 +25,8 @@ export interface DocSearchProps resultsFooterComponent?(props: { state: AutocompleteState; }): JSX.Element | null; + transformSearchClient?(searchClient: SearchClient): SearchClient; + disableUserPersonalization?: boolean; } export function DocSearch(props: DocSearchProps) { diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index 39f20445..0007a3f5 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -9,7 +9,6 @@ import { MAX_QUERY_SIZE } from './constants'; import { DocSearchHit, InternalDocSearchHit, - SearchClient, StoredDocSearchHit, } from './types'; import { groupBy, identity, noop } from './utils'; @@ -24,7 +23,6 @@ import { ScreenState } from './ScreenState'; import { Footer } from './Footer'; interface DocSearchModalProps extends DocSearchProps { - transformSearchClient?(searchClient: SearchClient): SearchClient; initialScrollY: number; onClose?(): void; } @@ -42,6 +40,7 @@ export function DocSearchModal({ navigator, initialScrollY = 0, transformSearchClient = identity, + disableUserPersonalization = false, }: DocSearchModalProps) { const [state, setState] = React.useState< AutocompleteState @@ -79,6 +78,10 @@ export function DocSearchModal({ const saveRecentSearch = React.useCallback( function saveRecentSearch(item: InternalDocSearchHit) { + if (disableUserPersonalization) { + return; + } + // We don't store `content` record, but their parent if available. const search = item.type === 'content' ? item.__docsearch_parent : item; @@ -92,7 +95,7 @@ export function DocSearchModal({ recentSearches.add(search); } }, - [favoriteSearches, recentSearches] + [favoriteSearches, recentSearches, disableUserPersonalization] ); const autocomplete = React.useMemo( @@ -120,6 +123,10 @@ export function DocSearchModal({ // @ts-ignore Temporarily ignore bad typing in autocomplete-core. getSources({ query, state, setContext, setStatus }) { if (!query) { + if (disableUserPersonalization) { + return []; + } + return [ { onSelect({ suggestion }) { @@ -263,6 +270,7 @@ export function DocSearchModal({ placeholder, navigator, transformItems, + disableUserPersonalization, ] ); @@ -351,6 +359,7 @@ export function DocSearchModal({ state={state} hitComponent={hitComponent} resultsFooterComponent={resultsFooterComponent} + disableUserPersonalization={disableUserPersonalization} recentSearches={recentSearches} favoriteSearches={favoriteSearches} onItemClick={(item) => { diff --git a/src/StartScreen.tsx b/src/StartScreen.tsx index 84773e60..3a14a361 100644 --- a/src/StartScreen.tsx +++ b/src/StartScreen.tsx @@ -21,13 +21,16 @@ interface StartScreenProps onItemClick(item: StoredDocSearchHit): void; recentSearches: StoredSearchPlugin; favoriteSearches: StoredSearchPlugin; + disableUserPersonalization: boolean; } export function StartScreen(props: StartScreenProps) { if (props.state.status === 'idle' && props.hasSuggestions === false) { return (
    -

    No recent searches

    + {!props.disableUserPersonalization && ( +

    No recent searches

    + )}
    ); } From 1cec65bbe0cb60cb6ad5d78d2039acbe88dbd5fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 20 Jul 2020 11:40:00 +0200 Subject: [PATCH 120/151] fix(search): hide content when `disableUserPersonalization --- src/StartScreen.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/StartScreen.tsx b/src/StartScreen.tsx index 3a14a361..70e9f1e8 100644 --- a/src/StartScreen.tsx +++ b/src/StartScreen.tsx @@ -26,11 +26,13 @@ interface StartScreenProps export function StartScreen(props: StartScreenProps) { if (props.state.status === 'idle' && props.hasSuggestions === false) { + if (props.disableUserPersonalization) { + return null; + } + return (
    - {!props.disableUserPersonalization && ( -

    No recent searches

    - )} +

    No recent searches

    ); } From 81092060d5c3416deb3bbf7414cace09313f3189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 20 Jul 2020 15:36:56 +0200 Subject: [PATCH 121/151] feat(docsearch): support `initialQuery` Closes #51 --- src/DocSearch.tsx | 18 ++++++++++++++++-- src/DocSearchModal.tsx | 3 ++- src/useDocSearchKeyboardEvents.ts | 18 ++++++++++++++---- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 008f5e1b..8398b43e 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -27,11 +27,13 @@ export interface DocSearchProps }): JSX.Element | null; transformSearchClient?(searchClient: SearchClient): SearchClient; disableUserPersonalization?: boolean; + initialQuery?: string; } export function DocSearch(props: DocSearchProps) { - const [isOpen, setIsOpen] = React.useState(false); const searchButtonRef = React.useRef(null); + const [isOpen, setIsOpen] = React.useState(false); + const [initialQuery, setInitialQuery] = React.useState(undefined); const onOpen = React.useCallback(() => { setIsOpen(true); @@ -41,7 +43,18 @@ export function DocSearch(props: DocSearchProps) { setIsOpen(false); }, [setIsOpen]); - useDocSearchKeyboardEvents({ isOpen, onOpen, onClose, searchButtonRef }); + function onInput(event) { + setIsOpen(true); + setInitialQuery(event.key); + } + + useDocSearchKeyboardEvents({ + isOpen, + onOpen, + onClose, + onInput, + searchButtonRef, + }); return ( <> @@ -52,6 +65,7 @@ export function DocSearch(props: DocSearchProps) { , document.body diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index 0007a3f5..9fa566ff 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -41,6 +41,7 @@ export function DocSearchModal({ initialScrollY = 0, transformSearchClient = identity, disableUserPersonalization = false, + initialQuery: initialQueryFromProp = '', }: DocSearchModalProps) { const [state, setState] = React.useState< AutocompleteState @@ -55,7 +56,7 @@ export function DocSearchModal({ const inputRef = React.useRef(null); const snipetLength = React.useRef(10); const initialQuery = React.useRef( - typeof window !== 'undefined' + initialQueryFromProp || typeof window !== 'undefined' ? window.getSelection()!.toString().slice(0, MAX_QUERY_SIZE) : '' ).current; diff --git a/src/useDocSearchKeyboardEvents.ts b/src/useDocSearchKeyboardEvents.ts index 79040e3e..1bc286b2 100644 --- a/src/useDocSearchKeyboardEvents.ts +++ b/src/useDocSearchKeyboardEvents.ts @@ -1,11 +1,20 @@ import React from 'react'; +interface UseDocSearchKeyboardEventsProps { + isOpen: boolean; + onOpen(): void; + onClose(): void; + onInput?(event: KeyboardEvent): void; + searchButtonRef?: React.RefObject; +} + export function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose, + onInput, searchButtonRef, -}) { +}: UseDocSearchKeyboardEventsProps) { React.useEffect(() => { function onKeyDown(event: KeyboardEvent) { if ( @@ -27,10 +36,11 @@ export function useDocSearchKeyboardEvents({ if ( searchButtonRef && - searchButtonRef.current === document.activeElement + searchButtonRef.current === document.activeElement && + onInput ) { if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) { - onOpen(); + onInput(event); } } } @@ -40,5 +50,5 @@ export function useDocSearchKeyboardEvents({ return () => { window.removeEventListener('keydown', onKeyDown); }; - }, [isOpen, onOpen, onClose, searchButtonRef]); + }, [isOpen, onOpen, onClose, onInput, searchButtonRef]); } From 00742f671166778d2cd51bf12fc1c4b346446afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 20 Jul 2020 15:37:29 +0200 Subject: [PATCH 122/151] refactor: fix "snippet" typoe --- src/DocSearchModal.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index 9fa566ff..8689dc0d 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -54,7 +54,7 @@ export function DocSearchModal({ const searchBoxRef = React.useRef(null); const dropdownRef = React.useRef(null); const inputRef = React.useRef(null); - const snipetLength = React.useRef(10); + const snippetLength = React.useRef(10); const initialQuery = React.useRef( initialQueryFromProp || typeof window !== 'undefined' ? window.getSelection()!.toString().slice(0, MAX_QUERY_SIZE) @@ -176,13 +176,13 @@ export function DocSearchModal({ 'url', ], attributesToSnippet: [ - `hierarchy.lvl1:${snipetLength.current}`, - `hierarchy.lvl2:${snipetLength.current}`, - `hierarchy.lvl3:${snipetLength.current}`, - `hierarchy.lvl4:${snipetLength.current}`, - `hierarchy.lvl5:${snipetLength.current}`, - `hierarchy.lvl6:${snipetLength.current}`, - `content:${snipetLength.current}`, + `hierarchy.lvl1:${snippetLength.current}`, + `hierarchy.lvl2:${snippetLength.current}`, + `hierarchy.lvl3:${snippetLength.current}`, + `hierarchy.lvl4:${snippetLength.current}`, + `hierarchy.lvl5:${snippetLength.current}`, + `hierarchy.lvl6:${snippetLength.current}`, + `content:${snippetLength.current}`, ], snippetEllipsisText: '…', highlightPreTag: '', @@ -303,7 +303,7 @@ export function DocSearchModal({ const isMobileMediaQuery = window.matchMedia('(max-width: 750px)'); if (isMobileMediaQuery.matches) { - snipetLength.current = 5; + snippetLength.current = 5; } }, []); From 5411367582502611ae0191fbf2464d23fafa789f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 20 Jul 2020 15:45:27 +0200 Subject: [PATCH 123/151] fix(website): memoize `onInput` callback --- src/DocSearch.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 8398b43e..4c4130b1 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -43,10 +43,13 @@ export function DocSearch(props: DocSearchProps) { setIsOpen(false); }, [setIsOpen]); - function onInput(event) { - setIsOpen(true); - setInitialQuery(event.key); - } + const onInput = React.useCallback( + (event) => { + setIsOpen(true); + setInitialQuery(event.key); + }, + [setIsOpen, setInitialQuery] + ); useDocSearchKeyboardEvents({ isOpen, From 145593a295f07878d4b5dcfad2d945b0e5d99241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 21 Jul 2020 10:42:58 +0200 Subject: [PATCH 124/151] chore(build): update shared Rollup config --- rollup.config.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index a64b3b2b..b1a132c7 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,7 +1,5 @@ -import json from '@rollup/plugin-json'; - import { name } from './package.json'; -import { sharedPlugins } from '../autocomplete-core/rollup.config'; +import { plugins } from '../../rollup.base.config'; export default { input: 'src/index.ts', @@ -16,5 +14,5 @@ export default { 'react-dom': 'ReactDOM', }, }, - plugins: [json(), ...sharedPlugins], + plugins, }; From 2b2cbcf984456d7160c842a323936e1630700d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 21 Jul 2020 11:01:02 +0200 Subject: [PATCH 125/151] chore(lint): sort imports --- rollup.config.js | 3 ++- src/DocSearch.tsx | 10 +++++----- src/DocSearchButton.tsx | 4 ++-- src/DocSearchModal.tsx | 20 ++++++++++---------- src/ErrorScreen.tsx | 1 + src/NoResultsScreen.tsx | 5 ++--- src/Results.tsx | 4 ++-- src/ResultsScreen.tsx | 6 +++--- src/ScreenState.tsx | 10 +++++----- src/SearchBox.tsx | 8 ++++---- src/StartScreen.tsx | 8 ++++---- src/useSearchClient.ts | 2 +- src/useTouchEvents.ts | 2 +- 13 files changed, 42 insertions(+), 41 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index b1a132c7..d6d2ca29 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,6 +1,7 @@ -import { name } from './package.json'; import { plugins } from '../../rollup.base.config'; +import { name } from './package.json'; + export default { input: 'src/index.ts', external: ['react', 'react-dom'], diff --git a/src/DocSearch.tsx b/src/DocSearch.tsx index 4c4130b1..bf472adf 100644 --- a/src/DocSearch.tsx +++ b/src/DocSearch.tsx @@ -1,13 +1,13 @@ +import { + AutocompleteState, + PublicAutocompleteOptions, +} from '@francoischalifour/autocomplete-core'; import React from 'react'; import { createPortal } from 'react-dom'; -import { - PublicAutocompleteOptions, - AutocompleteState, -} from '@francoischalifour/autocomplete-core'; -import { DocSearchHit, InternalDocSearchHit, SearchClient } from './types'; import { DocSearchButton } from './DocSearchButton'; import { DocSearchModal } from './DocSearchModal'; +import { DocSearchHit, InternalDocSearchHit, SearchClient } from './types'; import { useDocSearchKeyboardEvents } from './useDocSearchKeyboardEvents'; export interface DocSearchProps diff --git a/src/DocSearchButton.tsx b/src/DocSearchButton.tsx index 678314d8..a6ecc5f0 100644 --- a/src/DocSearchButton.tsx +++ b/src/DocSearchButton.tsx @@ -1,7 +1,7 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; -import { SearchIcon } from './icons/SearchIcon'; import { ControlKeyIcon } from './icons/ControlKeyIcon'; +import { SearchIcon } from './icons/SearchIcon'; const ACTION_KEY_DEFAULT = 'Ctrl'; const ACTION_KEY_APPLE = '⌘'; diff --git a/src/DocSearchModal.tsx b/src/DocSearchModal.tsx index 8689dc0d..992de797 100644 --- a/src/DocSearchModal.tsx +++ b/src/DocSearchModal.tsx @@ -1,26 +1,26 @@ -import React from 'react'; import { - createAutocomplete, AutocompleteState, + createAutocomplete, } from '@francoischalifour/autocomplete-core'; import { getAlgoliaResults } from '@francoischalifour/autocomplete-preset-algolia'; +import React from 'react'; import { MAX_QUERY_SIZE } from './constants'; +import { DocSearchProps } from './DocSearch'; +import { Footer } from './Footer'; +import { Hit } from './Hit'; +import { ScreenState } from './ScreenState'; +import { SearchBox } from './SearchBox'; +import { createStoredSearches } from './stored-searches'; import { DocSearchHit, InternalDocSearchHit, StoredDocSearchHit, } from './types'; -import { groupBy, identity, noop } from './utils'; -import { createStoredSearches } from './stored-searches'; import { useSearchClient } from './useSearchClient'; -import { useTrapFocus } from './useTrapFocus'; import { useTouchEvents } from './useTouchEvents'; -import { DocSearchProps } from './DocSearch'; -import { Hit } from './Hit'; -import { SearchBox } from './SearchBox'; -import { ScreenState } from './ScreenState'; -import { Footer } from './Footer'; +import { useTrapFocus } from './useTrapFocus'; +import { groupBy, identity, noop } from './utils'; interface DocSearchModalProps extends DocSearchProps { initialScrollY: number; diff --git a/src/ErrorScreen.tsx b/src/ErrorScreen.tsx index 632382a3..0c55af06 100644 --- a/src/ErrorScreen.tsx +++ b/src/ErrorScreen.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { ErrorIcon } from './icons'; export function ErrorScreen() { diff --git a/src/NoResultsScreen.tsx b/src/NoResultsScreen.tsx index 05f80f58..0fb57c75 100644 --- a/src/NoResultsScreen.tsx +++ b/src/NoResultsScreen.tsx @@ -1,12 +1,11 @@ -import React from 'react'; import { AutocompleteApi, AutocompleteState, } from '@francoischalifour/autocomplete-core'; - -import { InternalDocSearchHit } from './types'; +import React from 'react'; import { NoResultsIcon } from './icons'; +import { InternalDocSearchHit } from './types'; interface NoResultsScreenProps extends AutocompleteApi< diff --git a/src/Results.tsx b/src/Results.tsx index 26c66674..8767d849 100644 --- a/src/Results.tsx +++ b/src/Results.tsx @@ -1,15 +1,15 @@ -import React from 'react'; import { AutocompleteApi, AutocompleteState, } from '@francoischalifour/autocomplete-core'; +import React from 'react'; +import { Snippet } from './Snippet'; import { DocSearchHit, InternalDocSearchHit, StoredDocSearchHit, } from './types'; -import { Snippet } from './Snippet'; interface ResultsProps extends AutocompleteApi< diff --git a/src/ResultsScreen.tsx b/src/ResultsScreen.tsx index 45ff2047..fed1a12d 100644 --- a/src/ResultsScreen.tsx +++ b/src/ResultsScreen.tsx @@ -1,12 +1,12 @@ -import React from 'react'; import { AutocompleteApi, AutocompleteState, } from '@francoischalifour/autocomplete-core'; +import React from 'react'; -import { InternalDocSearchHit } from './types'; +import { SelectIcon, SourceIcon } from './icons'; import { Results } from './Results'; -import { SourceIcon, SelectIcon } from './icons'; +import { InternalDocSearchHit } from './types'; interface ResultsScreenProps extends AutocompleteApi< diff --git a/src/ScreenState.tsx b/src/ScreenState.tsx index f0b087b6..347f3b11 100644 --- a/src/ScreenState.tsx +++ b/src/ScreenState.tsx @@ -1,15 +1,15 @@ -import React from 'react'; import { AutocompleteApi, AutocompleteState, } from '@francoischalifour/autocomplete-core'; +import React from 'react'; +import { ErrorScreen } from './ErrorScreen'; +import { NoResultsScreen } from './NoResultsScreen'; +import { ResultsScreen } from './ResultsScreen'; +import { StartScreen } from './StartScreen'; import { StoredSearchPlugin } from './stored-searches'; import { InternalDocSearchHit, StoredDocSearchHit } from './types'; -import { StartScreen } from './StartScreen'; -import { ResultsScreen } from './ResultsScreen'; -import { NoResultsScreen } from './NoResultsScreen'; -import { ErrorScreen } from './ErrorScreen'; interface ScreenStateProps extends AutocompleteApi< diff --git a/src/SearchBox.tsx b/src/SearchBox.tsx index 948266d6..44920f1e 100644 --- a/src/SearchBox.tsx +++ b/src/SearchBox.tsx @@ -1,14 +1,14 @@ -import React, { MutableRefObject } from 'react'; import { AutocompleteApi, AutocompleteState, } from '@francoischalifour/autocomplete-core'; +import React, { MutableRefObject } from 'react'; import { MAX_QUERY_SIZE } from './constants'; -import { InternalDocSearchHit } from './types'; -import { SearchIcon } from './icons/SearchIcon'; -import { ResetIcon } from './icons/ResetIcon'; import { LoadingIcon } from './icons/LoadingIcon'; +import { ResetIcon } from './icons/ResetIcon'; +import { SearchIcon } from './icons/SearchIcon'; +import { InternalDocSearchHit } from './types'; interface SearchBoxProps extends AutocompleteApi< diff --git a/src/StartScreen.tsx b/src/StartScreen.tsx index 70e9f1e8..0e901667 100644 --- a/src/StartScreen.tsx +++ b/src/StartScreen.tsx @@ -1,13 +1,13 @@ -import React from 'react'; import { AutocompleteApi, AutocompleteState, } from '@francoischalifour/autocomplete-core'; +import React from 'react'; -import { StoredDocSearchHit } from './types'; -import { StoredSearchPlugin } from './stored-searches'; +import { RecentIcon, ResetIcon, StarIcon } from './icons'; import { Results } from './Results'; -import { ResetIcon, RecentIcon, StarIcon } from './icons'; +import { StoredSearchPlugin } from './stored-searches'; +import { StoredDocSearchHit } from './types'; interface StartScreenProps extends AutocompleteApi< diff --git a/src/useSearchClient.ts b/src/useSearchClient.ts index afa9b244..8013d5ae 100644 --- a/src/useSearchClient.ts +++ b/src/useSearchClient.ts @@ -1,5 +1,5 @@ -import React from 'react'; import algoliasearch from 'algoliasearch/dist/algoliasearch-lite.esm.browser'; +import React from 'react'; import { SearchClient } from './types'; import { version } from './version'; diff --git a/src/useTouchEvents.ts b/src/useTouchEvents.ts index 523f1d04..0dedd1c2 100644 --- a/src/useTouchEvents.ts +++ b/src/useTouchEvents.ts @@ -1,5 +1,5 @@ -import React from 'react'; import { AutocompleteApi } from '@francoischalifour/autocomplete-core'; +import React from 'react'; interface UseTouchEventsProps { getEnvironmentProps: AutocompleteApi['getEnvironmentProps']; From 8588b965e995bc50d79efd318c7142685cd3cd15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Wed, 22 Jul 2020 10:36:13 +0200 Subject: [PATCH 126/151] fix(docsearch): rename `DocSearch-Button` CSS class --- src/DocSearchButton.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/DocSearchButton.tsx b/src/DocSearchButton.tsx index a6ecc5f0..ea033dff 100644 --- a/src/DocSearchButton.tsx +++ b/src/DocSearchButton.tsx @@ -32,19 +32,14 @@ export const DocSearchButton = React.forwardRef< }, []); return ( - ); }); From 26bc858de664e871235c739d58d647bb9b2947f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Wed, 22 Jul 2020 10:36:51 +0200 Subject: [PATCH 127/151] feat(DocSearch): add `DocSearch` CSS class to DocSearch elements --- src/DocSearchButton.tsx | 7 ++++++- src/DocSearchModal.tsx | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/DocSearchButton.tsx b/src/DocSearchButton.tsx index ea033dff..aca6e887 100644 --- a/src/DocSearchButton.tsx +++ b/src/DocSearchButton.tsx @@ -32,7 +32,12 @@ export const DocSearchButton = React.forwardRef< }, []); return ( -