import classNames from 'classnames'; import toArray from 'rc-util/lib/Children/toArray'; import * as React from 'react'; import { ConfigContext } from '../config-provider'; import type { DropdownProps } from '../dropdown'; import Menu from '../menu'; import { cloneElement } from '../_util/reactNode'; import warning from '../_util/warning'; import type { BreadcrumbItemProps } from './BreadcrumbItem'; import BreadcrumbItem from './BreadcrumbItem'; import BreadcrumbSeparator from './BreadcrumbSeparator'; import useStyle from './style'; export interface Route { path: string; breadcrumbName: string; children?: Omit[]; } export interface BreadcrumbProps { prefixCls?: string; routes?: Route[]; params?: any; separator?: React.ReactNode; itemRender?: ( route: Route, params: any, routes: Array, paths: Array, ) => React.ReactNode; style?: React.CSSProperties; className?: string; rootClassName?: string; children?: React.ReactNode; } function getBreadcrumbName(route: Route, params: any) { if (!route.breadcrumbName) { return null; } const paramsKeys = Object.keys(params).join('|'); const name = route.breadcrumbName.replace( new RegExp(`:(${paramsKeys})`, 'g'), (replacement, key) => params[key] || replacement, ); return name; } function defaultItemRender(route: Route, params: any, routes: Route[], paths: string[]) { const isLastItem = routes.indexOf(route) === routes.length - 1; const name = getBreadcrumbName(route, params); return isLastItem ? {name} : {name}; } const getPath = (path: string, params: any) => { path = (path || '').replace(/^\//, ''); Object.keys(params).forEach((key) => { path = path.replace(`:${key}`, params[key]); }); return path; }; const addChildPath = (paths: string[], childPath: string, params: any) => { const originalPaths = [...paths]; const path = getPath(childPath || '', params); if (path) { originalPaths.push(path); } return originalPaths; }; type CompoundedComponent = React.FC & { Item: typeof BreadcrumbItem; Separator: typeof BreadcrumbSeparator; }; const Breadcrumb: CompoundedComponent = ({ prefixCls: customizePrefixCls, separator = '/', style, className, rootClassName, routes, children, itemRender = defaultItemRender, params = {}, ...restProps }) => { const { getPrefixCls, direction } = React.useContext(ConfigContext); let crumbs: React.ReactNode; const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls); const [wrapSSR, hashId] = useStyle(prefixCls); if (routes && routes.length > 0) { // generated by route const paths: string[] = []; crumbs = routes.map((route) => { const path = getPath(route.path, params); if (path) { paths.push(path); } // generated overlay by route.children let overlay: DropdownProps['overlay']; if (route.children && route.children.length) { overlay = ( ({ key: child.path || child.breadcrumbName, label: itemRender(child, params, routes, addChildPath(paths, child.path, params)), }))} /> ); } const itemProps: BreadcrumbItemProps = { separator }; if (overlay) { itemProps.overlay = overlay; } return ( {itemRender(route, params, routes, paths)} ); }); } else if (children) { crumbs = toArray(children).map((element: any, index) => { if (!element) { return element; } warning( element.type && (element.type.__ANT_BREADCRUMB_ITEM === true || element.type.__ANT_BREADCRUMB_SEPARATOR === true), 'Breadcrumb', "Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children", ); return cloneElement(element, { separator, key: index, }); }); } const breadcrumbClassName = classNames( prefixCls, { [`${prefixCls}-rtl`]: direction === 'rtl', }, className, rootClassName, hashId, ); return wrapSSR( , ); }; Breadcrumb.Item = BreadcrumbItem; Breadcrumb.Separator = BreadcrumbSeparator; if (process.env.NODE_ENV !== 'production') { Breadcrumb.displayName = 'Breadcrumb'; } export default Breadcrumb;