forked from ant-design/ant-design
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new algolia SearchBar (ant-design#30942)
close ant-design#30812
- Loading branch information
Showing
11 changed files
with
388 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ | |
} | ||
|
||
.algolia-docsearch-suggestion--highlight { | ||
padding: 0; | ||
color: @primary-color; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// theme customization for DocSearch Modal | ||
.customDocSearch() { | ||
.DocSearch { | ||
--docsearch-primary-color: @primary-color; | ||
--docsearch-text-color: @text-color; | ||
--docsearch-muted-color: @text-color-secondary; | ||
--docsearch-container-background: @modal-mask-bg; | ||
--docsearch-searchbox-shadow: inset 0 0 0 2px @primary-color; | ||
|
||
// Modal | ||
--docsearch-modal-background: @modal-content-bg; | ||
|
||
// Search box | ||
--docsearch-searchbox-background: @input-bg; | ||
--docsearch-searchbox-focus-background: @input-bg; | ||
|
||
// Footer | ||
--docsearch-footer-background: @modal-content-bg; | ||
|
||
// Hits | ||
--docsearch-hit-background: @modal-content-bg; | ||
--docsearch-hit-color: @primary-color; | ||
--docsearch-hit-active-color: @text-color-inverse; | ||
--docsearch-highlight-color: @primary-color; | ||
|
||
// apply antd modal style | ||
.DocSearch-Footer { | ||
border-top: @modal-footer-border-width @modal-footer-border-style | ||
@modal-footer-border-color-split; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
import * as React from 'react'; | ||
import * as ReactDOM from 'react-dom'; | ||
import { Link, browserHistory } from 'bisheng/router'; | ||
import classNames from 'classnames'; | ||
import { Helmet } from 'react-helmet-async'; | ||
import canUseDom from 'rc-util/lib/Dom/canUseDom'; | ||
import { Input, Tooltip, Typography } from 'antd'; | ||
import { SearchOutlined } from '@ant-design/icons'; | ||
import { DocSearchProps, useDocSearchKeyboardEvents, DocSearchModalProps } from '@docsearch/react'; | ||
import '@docsearch/css'; | ||
|
||
import { SharedProps } from './interface'; | ||
import { IAlgoliaConfig, transformHitUrl } from './algolia-config'; | ||
|
||
import './SearchBar.less'; | ||
|
||
const { Text } = Typography; | ||
|
||
export interface SearchBarProps extends SharedProps { | ||
onTriggerFocus?: (focus: boolean) => void; | ||
responsive: null | 'narrow' | 'crowded'; | ||
algoliaConfig: IAlgoliaConfig; | ||
} | ||
|
||
let SearchModal: React.FC<DocSearchModalProps> | null = null; | ||
|
||
const Hit: DocSearchProps['hitComponent'] = ({ hit, children }) => { | ||
const toUrl = React.useMemo(() => transformHitUrl(hit.url), [hit.url]); | ||
return <Link to={toUrl}>{children}</Link>; | ||
}; | ||
|
||
const CTRL_KEY = 'Ctrl'; | ||
const CMD_KEY = '⌘'; | ||
|
||
function isAppleDevice() { | ||
return /(mac|iphone|ipod|ipad)/i.test(navigator.platform); | ||
} | ||
|
||
/** | ||
* Recompose for algolia DocSearch Component Inspiring by | ||
* | ||
* - [@docusaurus-theme-search-algolia](https://docusaurus.io/docs/api/themes/@docusaurus/theme-search-algolia) | ||
* - [DocSearchModal Docs](https://autocomplete-experimental.netlify.app/docs/DocSearchModal) | ||
*/ | ||
export const SearchBar = ({ | ||
isZhCN, | ||
responsive, | ||
onTriggerFocus, | ||
algoliaConfig, | ||
}: SearchBarProps) => { | ||
const [isInputFocus, setInputFocus] = React.useState(false); | ||
const [inputSearch, setInputSearch] = React.useState(''); | ||
|
||
const [isModalOpen, setModalOpen] = React.useState(false); | ||
const [searchModalQuery, setSearchModalQuery] = React.useState(''); | ||
const searchPlaceholder = isZhCN ? '在 ant.design 中搜索' : 'Search in ant.design'; | ||
const searchInputPlaceholder = isZhCN ? '搜索' : 'Search'; | ||
|
||
const triggerSearchModalImport = React.useCallback(() => { | ||
if (SearchModal) { | ||
return Promise.resolve(); | ||
} | ||
|
||
return import('@docsearch/react/modal').then(({ DocSearchModal }) => { | ||
SearchModal = DocSearchModal; | ||
}); | ||
}, []); | ||
|
||
const handleInputFocus = React.useCallback((focus: boolean) => { | ||
setInputFocus(focus); | ||
onTriggerFocus?.(focus); | ||
}, []); | ||
|
||
const handleInputChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => { | ||
triggerSearchModalImport(); | ||
setInputSearch(event.target.value); | ||
}, []); | ||
|
||
const searchModalContainer = React.useMemo(() => { | ||
if (!canUseDom()) { | ||
return; | ||
} | ||
const id = 'antd_algolia_search_modal'; | ||
let searchModalContainer$ = document.querySelector(`#${id}`); | ||
if (!searchModalContainer$) { | ||
const containerDiv = document.createElement('div'); | ||
containerDiv.id = id; | ||
document.body.appendChild(containerDiv); | ||
searchModalContainer$ = containerDiv; | ||
} | ||
return searchModalContainer$; | ||
}, []); | ||
|
||
const handleModalOpen = React.useCallback(() => { | ||
triggerSearchModalImport().then(() => { | ||
handleInputFocus(true); | ||
setModalOpen(true); | ||
}); | ||
}, []); | ||
|
||
const handleModalClose = React.useCallback(() => { | ||
// clear search value in SearchModal | ||
setSearchModalQuery(''); | ||
setModalOpen(false); | ||
}, []); | ||
|
||
useDocSearchKeyboardEvents({ | ||
isOpen: isModalOpen, | ||
onOpen: handleModalOpen, | ||
onClose: handleModalClose, | ||
}); | ||
|
||
const searchParameters = React.useMemo(() => algoliaConfig.getSearchParams(isZhCN), [isZhCN]); | ||
|
||
const navigator = React.useRef({ | ||
navigate({ itemUrl }: { itemUrl: string }) { | ||
browserHistory.push(itemUrl); | ||
}, | ||
}).current; | ||
|
||
return ( | ||
<div | ||
id="search-box" | ||
className={classNames({ | ||
'narrow-mode': responsive, | ||
focused: isInputFocus, | ||
})} | ||
> | ||
<Helmet> | ||
{/* pre-connect to algolia server */} | ||
<link | ||
rel="preconnect" | ||
href={`https://${algoliaConfig.appId}-dsn.algolia.net`} | ||
crossOrigin="anonymous" | ||
/> | ||
</Helmet> | ||
|
||
<Input | ||
placeholder={searchInputPlaceholder} | ||
onTouchStart={triggerSearchModalImport} | ||
onMouseOver={triggerSearchModalImport} | ||
value={inputSearch} | ||
onChange={handleInputChange} | ||
onFocus={() => { | ||
triggerSearchModalImport(); | ||
handleInputFocus(true); | ||
}} | ||
onBlur={() => { | ||
handleInputFocus(false); | ||
}} | ||
prefix={<SearchOutlined />} | ||
suffix={ | ||
typeof window !== 'undefined' && ( | ||
<Tooltip placement="right" title={isZhCN ? '唤起搜索窗' : 'Search in doc modal'}> | ||
<span | ||
className="keybindings" | ||
onClick={() => { | ||
// move userSearch to SearchModal | ||
setSearchModalQuery(inputSearch); | ||
setInputSearch(''); | ||
handleModalOpen(); | ||
}} | ||
> | ||
<Text keyboard className="keybinding"> | ||
{isAppleDevice() ? CMD_KEY : CTRL_KEY} | ||
</Text> | ||
<Text keyboard className="keybinding"> | ||
K | ||
</Text> | ||
</span> | ||
</Tooltip> | ||
) | ||
} | ||
/> | ||
|
||
{SearchModal && | ||
searchModalContainer && | ||
isModalOpen && | ||
ReactDOM.createPortal( | ||
<SearchModal | ||
navigator={navigator} | ||
onClose={handleModalClose} | ||
initialScrollY={window.scrollY} | ||
initialQuery={searchModalQuery} | ||
placeholder={searchPlaceholder} | ||
hitComponent={Hit} | ||
apiKey={algoliaConfig.apiKey} | ||
indexName={algoliaConfig.indexName} | ||
transformItems={algoliaConfig.transformData} | ||
searchParameters={searchParameters} | ||
/>, | ||
searchModalContainer, | ||
)} | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.