import React, { useContext, useMemo } from 'react' import { Menu, Dropdown } from 'antd' import { Link, matchPath, PathPattern, useLocation } from 'react-router-dom' import { MenuUnfoldOutlined, MenuFoldOutlined, UnorderedListOutlined, } from '@ant-design/icons' import s from './index.module.less' import { renderMenuHelper } from './renderMenu' import type { MenuConfig } from './renderMenu' import { LayoutContext } from './ctx' import { themeConfigCtx, useThemeCtx } from '../ctx' import { useLocaleSelector } from './useLocaleSelector' import Search from './Search' const renderMenu = renderMenuHelper(true) interface Props {} const AppHeader: React.FC> = (props) => { const themeConfig = useContext(themeConfigCtx) const { TopBarExtra, topNavs } = themeConfig const layoutCtxVal = useContext(LayoutContext) const { render: renderLocaleSelector } = useLocaleSelector() const themeCtxValue = useThemeCtx() const { resolvedLocale: { locale }, staticData, } = themeCtxValue // use location.pathname instead of loadState.routePath // to calculate the active nav menu items // because loadState.routePath may have param placeholder like /users/[uid] // and it can't tell difference between /users/1 and /users/2 const { pathname } = useLocation() const renderLogo = (() => { const logoLink = (() => { let result: string | undefined | null if (typeof themeConfig.logoLink === 'function') { result = themeConfig.logoLink(themeCtxValue) } else { result = themeConfig.logoLink } if (result === undefined) { // infer home page path based on current matched locale // for example, /page1 will infer home path to / // /zh/page1 will infer home path to /zh result = locale?.routePrefix || '/' // if the infered page path doesn't exist, drop it if (!staticData[result]) result = null } return result })() const resolvedLogo = (() => { if (typeof themeConfig.logo === 'function') return themeConfig.logo(themeCtxValue) return themeConfig.logo })() if (logoLink) { return ( {resolvedLogo} ) } return resolvedLogo })() const resolvedTopNavs = useMemo(() => { if (typeof topNavs === 'function') return topNavs(themeCtxValue) return topNavs }, [themeCtxValue]) const activeKeys: string[] = useMemo(() => { const result = (resolvedTopNavs ?? []) .map(getActiveKeyIfMatch) .filter(Boolean) if (!result.includes(pathname)) result.push(pathname) return result as string[] function getActiveKeyIfMatch(item: MenuConfig) { if ('path' in item) { const matcher = item.activeIfMatch ?? ({ path: item.path, // if activeIfMatch is not given, // do exact match by default end: true, } as PathPattern) const matchResult = matchUtil(matcher) if (matchResult) return item.path } else if ('subMenu' in item) { if (item.activeIfMatch) { const matchResult = matchUtil(item.activeIfMatch) if (matchResult) return item.subMenu } const childrenMatchResult = item.children.some(getActiveKeyIfMatch) if (childrenMatchResult) return item.subMenu } return false function matchUtil( matcher: string | string[] | PathPattern | PathPattern[] ): boolean { if (!Array.isArray(matcher)) { let actualMatcher: PathPattern if (typeof matcher === 'string') { actualMatcher = { path: matcher, // if users pass activeIfMatch as string // do prefix match (instead of exact match) end: false, } } else { actualMatcher = matcher } return !!matchPath(actualMatcher, pathname) } else { return matcher.some((oneMatcher) => { return !!matchPath(oneMatcher, pathname) }) } } } }, [pathname, resolvedTopNavs]) return (
{ layoutCtxVal.setIsSlideSiderOpen((v) => !v) }} > {React.createElement( layoutCtxVal.isSlideSiderOpen ? MenuUnfoldOutlined : MenuFoldOutlined )}
{renderLogo}
{themeConfig.search && (
)}
{resolvedTopNavs && ( <>
)}
{renderLocaleSelector()}
{TopBarExtra ? (
) : (
)}
) } export default AppHeader