From 3b27471a85fd82fbc8ede346634e20f5ca0f000d Mon Sep 17 00:00:00 2001 From: Peach Date: Fri, 18 Nov 2022 11:29:46 +0800 Subject: [PATCH] docs: use dumi built-in search bar (#38658) * docs: use dumi built-in search bar * docs: detailed styles for search bar --- .dumi/theme/slots/Header/SearchBar.tsx | 331 --------------------- .dumi/theme/slots/Header/algolia-config.ts | 28 -- .dumi/theme/slots/Header/index.tsx | 99 +++--- 3 files changed, 53 insertions(+), 405 deletions(-) delete mode 100644 .dumi/theme/slots/Header/SearchBar.tsx delete mode 100644 .dumi/theme/slots/Header/algolia-config.ts diff --git a/.dumi/theme/slots/Header/SearchBar.tsx b/.dumi/theme/slots/Header/SearchBar.tsx deleted file mode 100644 index d59f17950b17..000000000000 --- a/.dumi/theme/slots/Header/SearchBar.tsx +++ /dev/null @@ -1,331 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { Link, useNavigate } from 'dumi'; -import canUseDom from 'rc-util/lib/Dom/canUseDom'; -import { Input, Tooltip, Typography } from 'antd'; -import { SearchOutlined } from '@ant-design/icons'; -import type { DocSearchModalProps, DocSearchProps } from 'docsearch-react-fork'; -import { useDocSearchKeyboardEvents } from 'docsearch-react-fork'; -import '@docsearch/css'; -import type { SharedProps } from './interface'; -import type { IAlgoliaConfig } from './algolia-config'; -import { transformHitUrl } from './algolia-config'; -import WrapHelmet from '../../common/Helmet'; -import useSiteToken from '../../../hooks/useSiteToken'; -import { css } from '@emotion/react'; - -const { Text } = Typography; - -const useStyle = () => { - const { token } = useSiteToken(); - const searchIconColor = '#ced4d9'; - - const { antCls, iconCls } = token; - - return { - searchBox: css` - position: relative; - display: flex; - flex: auto !important; - align-items: center; - height: 22px; - margin: 0 auto 0 0 !important; - padding-left: 16px; - line-height: 22px; - white-space: nowrap; - border-left: 1px solid ${searchIconColor}; - transition: width 0.5s; - - ${antCls}-row-rtl & { - margin: 0 0 0 auto !important; - padding-right: 16px; - padding-left: 0; - border-right: 1px solid ${token.colorSplit}; - border-left: none; - } - - > * { - flex: auto; - } - - ${iconCls} { - position: absolute; - top: 50%; - z-index: 1; - flex: none; - color: ${searchIconColor}; - transform: translateY(-50%); - pointer-events: none; - } - - ${antCls}-input-affix-wrapper { - background: transparent; - border: 0; - box-shadow: none; - } - - input { - width: 100%; - max-width: 200px; - padding-left: 20px; - font-size: 14px; - background: transparent; - border: 0; - box-shadow: none !important; - - ${antCls}-row-rtl & { - padding-right: 20px; - padding-left: 11px; - } - - &::placeholder { - color: #a3b1bf; - } - } - `, - keybindings: css` - cursor: pointer; - `, - keybinding: css` - color: ${searchIconColor}; - - kbd { - display: inline-block; - box-sizing: border-box; - width: 20px; - height: 20px; - padding: 0; - // better keybinding font display using \`Arial\` - font-family: Arial; /* stylelint-disable-line font-family-no-missing-generic-family-keyword */ - text-align: center; - } - `, - narrowMode: css` - flex: none !important; - width: 30px; - - &:hover { - ${iconCls} { - color: #a3b1bf; - } - } - - ${iconCls} { - right: 0; - left: auto; - - ${antCls}-row-rtl & { - right: auto; - left: 0; - } - } - - input { - max-width: none; - padding-right: 20px; - padding-left: 11px; - cursor: pointer; - - ${antCls}-row-rtl & { - padding-right: 11px; - padding-left: 20px; - } - } - `, - focused: css` - width: 500px; - - ${iconCls} { - color: @search-icon-color; - } - - input { - cursor: text; - } - `, - }; -}; - -export interface SearchBarProps extends SharedProps { - onTriggerFocus?: (focus: boolean) => void; - responsive: null | 'narrow' | 'crowded'; - algoliaConfig: IAlgoliaConfig; -} - -let SearchModal: React.FC | null = null; - -const Hit: DocSearchProps['hitComponent'] = ({ hit, children }) => { - const toUrl = React.useMemo(() => transformHitUrl(hit.url), [hit.url]); - return {children}; -}; - -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) - */ -const SearchBar = ({ - isZhCN, - isClient, - 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 navigate = useNavigate(); - - const style = useStyle(); - - const triggerSearchModalImport = React.useCallback(() => { - if (SearchModal) { - return Promise.resolve(); - } - - // @ts-ignore - return import('docsearch-react-fork/modal').then(({ DocSearchModal }) => { - SearchModal = DocSearchModal; - }); - }, []); - - const handleInputFocus = React.useCallback((focus: boolean) => { - setInputFocus(focus); - onTriggerFocus?.(focus); - }, []); - - const handleInputChange = React.useCallback((event: React.ChangeEvent) => { - 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 }) { - navigate(itemUrl); - }, - }).current; - - return ( - - ); -}; - -export default SearchBar; diff --git a/.dumi/theme/slots/Header/algolia-config.ts b/.dumi/theme/slots/Header/algolia-config.ts deleted file mode 100644 index d668371a5640..000000000000 --- a/.dumi/theme/slots/Header/algolia-config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { DocSearchHit } from 'docsearch-react-fork/dist/esm/types'; - -let _internalATag: HTMLAnchorElement | null; - -export function transformHitUrl(hitUrl: string) { - _internalATag = _internalATag || document.createElement('a'); - // `new URL` is not supported in IE - _internalATag.href = hitUrl; - return `${_internalATag.pathname}${window.location.search || ''}${_internalATag.hash}`; -} - -export const AlgoliaConfig = { - appId: 'BH4D9OD16A', - apiKey: '60ac2c1a7d26ab713757e4a081e133d0', - indexName: 'ant_design', - getSearchParams(isZhCN: boolean) { - return { facetFilters: [`tags:${isZhCN ? 'cn' : 'en'}`] }; - }, - transformData(hits: DocSearchHit[]) { - hits.forEach(hit => { - hit.url = transformHitUrl(hit.url); - }); - return hits; - }, - debug: false, // Set debug to true if you want to inspect the dropdown -}; - -export type IAlgoliaConfig = typeof AlgoliaConfig; diff --git a/.dumi/theme/slots/Header/index.tsx b/.dumi/theme/slots/Header/index.tsx index 127feede4197..13636ced4198 100644 --- a/.dumi/theme/slots/Header/index.tsx +++ b/.dumi/theme/slots/Header/index.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { FormattedMessage, useIntl } from 'dumi'; +import DumiSearchBar from 'dumi/theme-default/slots/SearchBar'; import classNames from 'classnames'; import { Button, Col, Modal, Popover, Row, Select } from 'antd'; import { MenuOutlined } from '@ant-design/icons'; @@ -9,13 +10,11 @@ import * as utils from '../../utils'; import { getThemeConfig, ping } from '../../utils'; import packageJson from '../../../../package.json'; import Logo from './Logo'; -import SearchBar from './SearchBar'; import More from './More'; import Navigation from './Navigation'; import Github from './Github'; import type { SiteContextProps } from '../SiteContext'; import SiteContext from '../SiteContext'; -import { AlgoliaConfig } from './algolia-config'; import { useLocation, useNavigate } from 'dumi'; import { ClassNames, css } from '@emotion/react'; import useSiteToken from '../../../hooks/useSiteToken'; @@ -30,6 +29,7 @@ const antdVersion: string = packageJson.version; const useStyle = () => { const { token } = useSiteToken(); + const searchIconColor = '#ced4d9'; return { header: css` @@ -42,6 +42,45 @@ const useStyle = () => { @media only screen and (max-width: ${token.mobileMaxWidth}px) { text-align: center; } + + .dumi-default-search-bar { + border-inline-start: 1px solid rgba(0,0,0,.06); + + > svg { + width: 14px; + fill: ${searchIconColor}; + } + + > input { + height: 22px; + border: 0; + + &:focus { + box-shadow: none; + } + + &::placeholder { + color: ${searchIconColor}; + } + } + + .dumi-default-search-shortcut { + color: ${searchIconColor}; + background-color: rgba(150, 150, 150, 0.06); + border-color: rgba(100, 100, 100, 0.2); + border-radius: 4px; + } + + .dumi-default-search-popover { + inset-inline-start: 11px; + inset-inline-end: unset; + + &::before { + inset-inline-start: 100px; + inset-inline-end: unset; + } + } + } `, menuRow: css` display: flex; @@ -91,36 +130,11 @@ const triggerDocSearchImport = () => { } // @ts-ignore - return import('docsearch.js').then(ds => { + return import('docsearch.js').then((ds) => { docsearch = ds.default; }); }; -function initDocSearch({ isZhCN, navigate }: { isZhCN: boolean; navigate: any }) { - if (!canUseDom()) { - return; - } - - triggerDocSearchImport().then(() => { - docsearch({ - appId: AlgoliaConfig.appId, - apiKey: AlgoliaConfig.apiKey, - indexName: AlgoliaConfig.indexName, - inputSelector: '#search-box input', - algoliaOptions: AlgoliaConfig.getSearchParams(isZhCN), - transformData: AlgoliaConfig.transformData, - debug: AlgoliaConfig.debug, - // https://docsearch.algolia.com/docs/behavior#handleselected - handleSelected(input: any, _$1: unknown, suggestion: any) { - navigate(suggestion.url); - setTimeout(() => { - input.setVal(''); - }); - }, - }); - }); -} - const SHOULD_OPEN_ANT_DESIGN_MIRROR_MODAL = 'ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL'; function disableAntdMirrorModal() { @@ -138,7 +152,7 @@ interface HeaderState { showTechUIButton: boolean; } -const Header: React.FC = props => { +const Header: React.FC = (props) => { const intl = useIntl(); const { changeDirection } = props; const [, lang] = useLocale(); @@ -160,19 +174,19 @@ const Header: React.FC = props => { const style = useStyle(); const handleHideMenu = useCallback(() => { - setHeaderState(prev => ({ ...prev, menuVisible: false })); + setHeaderState((prev) => ({ ...prev, menuVisible: false })); }, []); const onWindowResize = useCallback(() => { - setHeaderState(prev => ({ ...prev, windowWidth: window.innerWidth })); + setHeaderState((prev) => ({ ...prev, windowWidth: window.innerWidth })); }, []); const onTriggerSearching = useCallback((searching: boolean) => { - setHeaderState(prev => ({ ...prev, searching })); + setHeaderState((prev) => ({ ...prev, searching })); }, []); const handleShowMenu = useCallback(() => { - setHeaderState(prev => ({ ...prev, menuVisible: true })); + setHeaderState((prev) => ({ ...prev, menuVisible: true })); }, []); const onMenuVisibleChange = useCallback((visible: boolean) => { - setHeaderState(prev => ({ ...prev, menuVisible: visible })); + setHeaderState((prev) => ({ ...prev, menuVisible: visible })); }, []); const onDirectionChange = useCallback(() => { changeDirection?.(direction !== 'rtl' ? 'rtl' : 'ltr'); @@ -184,12 +198,11 @@ const Header: React.FC = props => { useEffect(() => { setIsClient(typeof window !== 'undefined'); - initDocSearch({ isZhCN: lang === 'cn', navigate }); onWindowResize(); window.addEventListener('resize', onWindowResize); - pingTimer.current = ping(status => { + pingTimer.current = ping((status) => { if (status !== 'timeout' && status !== 'error') { - setHeaderState(prev => ({ ...prev, showTechUIButton: true })); + setHeaderState((prev) => ({ ...prev, showTechUIButton: true })); if ( process.env.NODE_ENV === 'production' && window.location.host !== 'ant-design.antgroup.com' && @@ -264,7 +277,7 @@ const Header: React.FC = props => { [antdVersion]: antdVersion, ...themeConfig?.docVersions, }; - const versionOptions = Object.keys(docVersions).map(version => ( + const versionOptions = Object.keys(docVersions).map((version) => ( @@ -314,7 +327,7 @@ const Header: React.FC = props => { defaultValue={antdVersion} onChange={handleVersionChange} dropdownStyle={getDropdownStyle} - getPopupContainer={trigger => trigger.parentNode} + getPopupContainer={(trigger) => trigger.parentNode} > {versionOptions} , @@ -370,13 +383,7 @@ const Header: React.FC = props => { - + {!isMobile && menu}