import React, { useCallback, useContext, useEffect, useRef, useState, useMemo } from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { FormattedMessage, injectIntl } from 'react-intl'; import moment from 'moment'; import classNames from 'classnames'; import { Select, Row, Col, Drawer, Button, Modal } from 'antd'; import { MenuOutlined } from '@ant-design/icons'; import canUseDom from 'rc-util/lib/Dom/canUseDom'; import type { DirectionType } from 'antd/es/config-provider'; import * as utils 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 { ping } from '../../utils'; import { AlgoliaConfig } from './algolia-config'; import './index.less'; const RESPONSIVE_XS = 1120; const RESPONSIVE_SM = 1200; const { Option } = Select; const antdVersion: string = packageJson.version; export interface HeaderProps { intl: { locale: string }; location: { pathname: string; query: any }; router: any; themeConfig?: { docVersions: Record }; changeDirection: (direction: DirectionType) => void; } let docsearch: any; const triggerDocSearchImport = () => { if (docsearch) { return Promise.resolve(); } return import('docsearch.js').then(ds => { docsearch = ds.default; }); }; function initDocSearch({ isZhCN, router }: { isZhCN: boolean; router: 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) { router.push(suggestion.url); setTimeout(() => { input.setVal(''); }); }, }); }); } const ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL_UNTIL = 'ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL_UNTIL'; function disableAntdMirrorModal() { window.localStorage.setItem( ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL_UNTIL, moment().add(7, 'days').toString(), ); } function shouldOpenAntdMirrorModal() { const until = window.localStorage.getItem(ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL_UNTIL); const deadline = moment(until); if (!until || !deadline.isValid()) { return true; } return deadline.diff(moment()) < 0; } interface HeaderState { menuVisible: boolean; windowWidth: number; searching: boolean; showTechUIButton: boolean; } const Header: React.FC> = props => { const { intl, router, location, themeConfig, changeDirection } = props; const [headerState, setHeaderState] = useState({ menuVisible: false, windowWidth: 1400, searching: false, showTechUIButton: false, }); const { direction } = useContext(SiteContext); const pingTimer = useRef(null); const handleHideMenu = useCallback(() => { setHeaderState(prev => ({ ...prev, menuVisible: false })); }, []); const onWindowResize = useCallback(() => { setHeaderState(prev => ({ ...prev, windowWidth: window.innerWidth })); }, []); const onTriggerSearching = useCallback((searching: boolean) => { setHeaderState(prev => ({ ...prev, searching })); }, []); const handleShowMenu = useCallback(() => { setHeaderState(prev => ({ ...prev, menuVisible: true })); }, []); const onDirectionChange = useCallback(() => { changeDirection(direction !== 'rtl' ? 'rtl' : 'ltr'); }, [direction]); useEffect(() => { router.listen(handleHideMenu); initDocSearch({ isZhCN: intl.locale === 'zh-CN', router }); onWindowResize(); window.addEventListener('resize', onWindowResize); pingTimer.current = ping(status => { if (status !== 'timeout' && status !== 'error') { setHeaderState(prev => ({ ...prev, showTechUIButton: true })); if ( process.env.NODE_ENV === 'production' && shouldOpenAntdMirrorModal() && window.location.host !== 'ant-design.antgroup.com' && window.location.host.indexOf('surge.sh') === -1 ) { Modal.confirm({ title: '提示', content: '内网用户推荐访问国内镜像以获得极速体验~', okText: '🚀 立刻前往', cancelText: '不再弹出', closable: true, onOk() { window.location.href = 'https://ant-design.antgroup.com'; disableAntdMirrorModal(); }, onCancel() { disableAntdMirrorModal(); }, }); } } }); return () => { window.removeEventListener('resize', onWindowResize); if (pingTimer.current) { clearTimeout(pingTimer.current); } }; }, []); // eslint-disable-next-line class-methods-use-this const handleVersionChange = useCallback((url: string) => { const currentUrl = window.location.href; const currentPathname = window.location.pathname; if (/overview/.test(currentPathname) && /0?[1-39][0-3]?x/.test(url)) { window.location.href = currentUrl .replace(window.location.origin, url) .replace(/\/components\/overview/, `/docs${/0(9|10)x/.test(url) ? '' : '/react'}/introduce`) .replace(/\/$/, ''); return; } window.location.href = currentUrl.replace(window.location.origin, url); }, []); const onLangChange = useCallback(() => { const { pathname, query } = location; const currentProtocol = `${window.location.protocol}//`; const currentHref = window.location.href.slice(currentProtocol.length); if (utils.isLocalStorageNameSupported()) { localStorage.setItem('locale', utils.isZhCN(pathname) ? 'en-US' : 'zh-CN'); } window.location.href = currentProtocol + currentHref.replace( window.location.pathname, utils.getLocalizedPathname(pathname, !utils.isZhCN(pathname), query).pathname, ); }, [location]); const nextDirectionText = useMemo( () => (direction !== 'rtl' ? 'RTL' : 'LTR'), [direction], ); const getDropdownStyle = useMemo( () => (direction === 'rtl' ? { direction: 'ltr', textAlign: 'right' } : {}), [direction], ); return ( {({ isMobile }) => { const { menuVisible, windowWidth, searching, showTechUIButton } = headerState; const docVersions: Record = { [antdVersion]: antdVersion, ...themeConfig?.docVersions, }; const versionOptions = Object.keys(docVersions).map(version => ( )); const pathname = location.pathname.replace(/(^\/|\/$)/g, ''); const isHome = ['', 'index', 'index-cn'].includes(pathname); const isZhCN = intl.locale === 'zh-CN'; const isRTL = direction === 'rtl'; let responsive: null | 'narrow' | 'crowded' = null; if (windowWidth < RESPONSIVE_XS) { responsive = 'crowded'; } else if (windowWidth < RESPONSIVE_SM) { responsive = 'narrow'; } const headerClassName = classNames({ clearfix: true, 'home-header': isHome, }); const sharedProps = { isZhCN, isRTL, }; const navigationNode = ( ); let menu: (React.ReactElement | null)[] = [ navigationNode, , , , , , ]; if (windowWidth < RESPONSIVE_XS) { menu = searching ? [] : [navigationNode]; } else if (windowWidth < RESPONSIVE_SM) { menu = searching ? [] : menu; } const colProps = isHome ? [{ flex: 'none' }, { flex: 'auto' }] : [ { xxl: 4, xl: 5, lg: 6, md: 6, sm: 24, xs: 24 }, { xxl: 20, xl: 19, lg: 18, md: 18, sm: 0, xs: 0 }, ]; return ( ); }} ); }; export default injectIntl(Header);