mirror of
https://github.com/ant-design/ant-design.git
synced 2024-12-24 15:46:10 +08:00
236 lines
6.4 KiB
TypeScript
Executable File
236 lines
6.4 KiB
TypeScript
Executable File
import * as React from 'react';
|
|
import classNames from 'classnames';
|
|
import toArray from 'rc-util/lib/Children/toArray';
|
|
import pickAttrs from 'rc-util/lib/pickAttrs';
|
|
|
|
import { cloneElement } from '../_util/reactNode';
|
|
import type { AnyObject } from '../_util/type';
|
|
import { devUseWarning } from '../_util/warning';
|
|
import { ConfigContext } from '../config-provider';
|
|
import type { DropdownProps } from '../dropdown';
|
|
import type { BreadcrumbItemProps } from './BreadcrumbItem';
|
|
import BreadcrumbItem, { InternalBreadcrumbItem } from './BreadcrumbItem';
|
|
import BreadcrumbSeparator from './BreadcrumbSeparator';
|
|
import useStyle from './style';
|
|
import useItemRender from './useItemRender';
|
|
import useItems from './useItems';
|
|
|
|
export interface BreadcrumbItemType {
|
|
key?: React.Key;
|
|
/**
|
|
* Different with `path`. Directly set the link of this item.
|
|
*/
|
|
href?: string;
|
|
/**
|
|
* Different with `href`. It will concat all prev `path` to the current one.
|
|
*/
|
|
path?: string;
|
|
title?: React.ReactNode;
|
|
/** @deprecated Please use `title` instead */
|
|
breadcrumbName?: string;
|
|
menu?: BreadcrumbItemProps['menu'];
|
|
/** @deprecated Please use `menu` instead */
|
|
overlay?: React.ReactNode;
|
|
className?: string;
|
|
dropdownProps?: DropdownProps;
|
|
onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLSpanElement>;
|
|
|
|
/** @deprecated Please use `menu` instead */
|
|
children?: Omit<BreadcrumbItemType, 'children'>[];
|
|
}
|
|
export interface BreadcrumbSeparatorType {
|
|
type: 'separator';
|
|
separator?: React.ReactNode;
|
|
}
|
|
|
|
export type ItemType = Partial<BreadcrumbItemType & BreadcrumbSeparatorType>;
|
|
|
|
export type InternalRouteType = Partial<BreadcrumbItemType & BreadcrumbSeparatorType>;
|
|
|
|
export interface BreadcrumbProps<T extends AnyObject = AnyObject> {
|
|
prefixCls?: string;
|
|
params?: T;
|
|
separator?: React.ReactNode;
|
|
style?: React.CSSProperties;
|
|
className?: string;
|
|
rootClassName?: string;
|
|
children?: React.ReactNode;
|
|
|
|
/** @deprecated Please use `items` instead */
|
|
routes?: ItemType[];
|
|
|
|
items?: ItemType[];
|
|
|
|
itemRender?: (route: ItemType, params: T, routes: ItemType[], paths: string[]) => React.ReactNode;
|
|
}
|
|
|
|
const getPath = <T extends AnyObject = AnyObject>(params: T, path?: string) => {
|
|
if (path === undefined) {
|
|
return path;
|
|
}
|
|
let mergedPath = (path || '').replace(/^\//, '');
|
|
Object.keys(params).forEach((key) => {
|
|
mergedPath = mergedPath.replace(`:${key}`, params[key]!);
|
|
});
|
|
return mergedPath;
|
|
};
|
|
|
|
const Breadcrumb = <T extends AnyObject = AnyObject>(props: BreadcrumbProps<T>) => {
|
|
const {
|
|
prefixCls: customizePrefixCls,
|
|
separator = '/',
|
|
style,
|
|
className,
|
|
rootClassName,
|
|
routes: legacyRoutes,
|
|
items,
|
|
children,
|
|
itemRender,
|
|
params = {},
|
|
...restProps
|
|
} = props;
|
|
|
|
const { getPrefixCls, direction, breadcrumb } = React.useContext(ConfigContext);
|
|
|
|
let crumbs: React.ReactNode;
|
|
|
|
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
|
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
|
|
|
const mergedItems = useItems(items, legacyRoutes);
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
const warning = devUseWarning('Breadcrumb');
|
|
warning.deprecated(!legacyRoutes, 'routes', 'items');
|
|
|
|
// Deprecated warning for breadcrumb children
|
|
if (!mergedItems || mergedItems.length === 0) {
|
|
const childList = toArray(children);
|
|
|
|
warning.deprecated(
|
|
childList.length === 0,
|
|
'Breadcrumb.Item and Breadcrumb.Separator',
|
|
'items',
|
|
);
|
|
|
|
childList.forEach((element: any) => {
|
|
if (element) {
|
|
warning(
|
|
element.type &&
|
|
(element.type.__ANT_BREADCRUMB_ITEM === true ||
|
|
element.type.__ANT_BREADCRUMB_SEPARATOR === true),
|
|
'usage',
|
|
"Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children",
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
const mergedItemRender = useItemRender(prefixCls, itemRender);
|
|
|
|
if (mergedItems && mergedItems.length > 0) {
|
|
// generated by route
|
|
const paths: string[] = [];
|
|
|
|
const itemRenderRoutes = items || legacyRoutes;
|
|
|
|
crumbs = mergedItems.map((item, index) => {
|
|
const {
|
|
path,
|
|
key,
|
|
type,
|
|
menu,
|
|
overlay,
|
|
onClick,
|
|
className: itemClassName,
|
|
separator: itemSeparator,
|
|
dropdownProps,
|
|
} = item;
|
|
const mergedPath = getPath(params, path);
|
|
|
|
if (mergedPath !== undefined) {
|
|
paths.push(mergedPath);
|
|
}
|
|
|
|
const mergedKey = key ?? index;
|
|
|
|
if (type === 'separator') {
|
|
return <BreadcrumbSeparator key={mergedKey}>{itemSeparator}</BreadcrumbSeparator>;
|
|
}
|
|
|
|
const itemProps: BreadcrumbItemProps = {};
|
|
const isLastItem = index === mergedItems.length - 1;
|
|
|
|
if (menu) {
|
|
itemProps.menu = menu;
|
|
} else if (overlay) {
|
|
itemProps.overlay = overlay as any;
|
|
}
|
|
|
|
let { href } = item;
|
|
if (paths.length && mergedPath !== undefined) {
|
|
href = `#/${paths.join('/')}`;
|
|
}
|
|
|
|
return (
|
|
<InternalBreadcrumbItem
|
|
key={mergedKey}
|
|
{...itemProps}
|
|
{...pickAttrs(item, { data: true, aria: true })}
|
|
className={itemClassName}
|
|
dropdownProps={dropdownProps}
|
|
href={href}
|
|
separator={isLastItem ? '' : separator}
|
|
onClick={onClick}
|
|
prefixCls={prefixCls}
|
|
>
|
|
{mergedItemRender(item, params, itemRenderRoutes!, paths, href)}
|
|
</InternalBreadcrumbItem>
|
|
);
|
|
});
|
|
} else if (children) {
|
|
const childrenLength = toArray(children).length;
|
|
crumbs = toArray(children).map((element: any, index) => {
|
|
if (!element) {
|
|
return element;
|
|
}
|
|
|
|
const isLastItem = index === childrenLength - 1;
|
|
return cloneElement(element, {
|
|
separator: isLastItem ? '' : separator,
|
|
key: index,
|
|
});
|
|
});
|
|
}
|
|
|
|
const breadcrumbClassName = classNames(
|
|
prefixCls,
|
|
breadcrumb?.className,
|
|
{
|
|
[`${prefixCls}-rtl`]: direction === 'rtl',
|
|
},
|
|
className,
|
|
rootClassName,
|
|
hashId,
|
|
cssVarCls,
|
|
);
|
|
|
|
const mergedStyle: React.CSSProperties = { ...breadcrumb?.style, ...style };
|
|
|
|
return wrapCSSVar(
|
|
<nav className={breadcrumbClassName} style={mergedStyle} {...restProps}>
|
|
<ol>{crumbs}</ol>
|
|
</nav>,
|
|
);
|
|
};
|
|
|
|
Breadcrumb.Item = BreadcrumbItem;
|
|
Breadcrumb.Separator = BreadcrumbSeparator;
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
Breadcrumb.displayName = 'Breadcrumb';
|
|
}
|
|
|
|
export default Breadcrumb;
|