mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-06 00:44:17 +08:00
refactor(breadcrumb): rewrite with hook (#24512)
* refactor(breadcrumb): rewrite with hook * Update BreadcrumbItem.tsx
This commit is contained in:
parent
81bde547cd
commit
56c42e496b
@ -1,11 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import toArray from 'rc-util/lib/Children/toArray';
|
||||
import omit from 'omit.js';
|
||||
import BreadcrumbItem from './BreadcrumbItem';
|
||||
import BreadcrumbSeparator from './BreadcrumbSeparator';
|
||||
import Menu from '../menu';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import devWarning from '../_util/devWarning';
|
||||
import { Omit } from '../_util/type';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
@ -49,41 +48,48 @@ function defaultItemRender(route: Route, params: any, routes: Route[], paths: st
|
||||
return isLastItem ? <span>{name}</span> : <a href={`#/${paths.join('/')}`}>{name}</a>;
|
||||
}
|
||||
|
||||
export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
|
||||
static Item: typeof BreadcrumbItem;
|
||||
const getPath = (path: string, params: any) => {
|
||||
path = (path || '').replace(/^\//, '');
|
||||
Object.keys(params).forEach(key => {
|
||||
path = path.replace(`:${key}`, params[key]);
|
||||
});
|
||||
return path;
|
||||
};
|
||||
|
||||
static Separator: typeof BreadcrumbSeparator;
|
||||
const addChildPath = (paths: string[], childPath: string = '', params: any) => {
|
||||
const originalPaths = [...paths];
|
||||
const path = getPath(childPath, params);
|
||||
if (path) {
|
||||
originalPaths.push(path);
|
||||
}
|
||||
return originalPaths;
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
separator: '/',
|
||||
};
|
||||
interface BreadcrumbInterface extends React.FC<BreadcrumbProps> {
|
||||
Item: typeof BreadcrumbItem;
|
||||
Separator: typeof BreadcrumbSeparator;
|
||||
}
|
||||
|
||||
getPath = (path: string, params: any) => {
|
||||
path = (path || '').replace(/^\//, '');
|
||||
Object.keys(params).forEach(key => {
|
||||
path = path.replace(`:${key}`, params[key]);
|
||||
});
|
||||
return path;
|
||||
};
|
||||
const Breadcrumb: BreadcrumbInterface = ({
|
||||
prefixCls: customizePrefixCls,
|
||||
separator = '/',
|
||||
style,
|
||||
className,
|
||||
routes,
|
||||
children,
|
||||
itemRender = defaultItemRender,
|
||||
params = {},
|
||||
...restProps
|
||||
}) => {
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
|
||||
addChildPath = (paths: string[], childPath: string = '', params: any) => {
|
||||
const originalPaths = [...paths];
|
||||
const path = this.getPath(childPath, params);
|
||||
if (path) {
|
||||
originalPaths.push(path);
|
||||
}
|
||||
return originalPaths;
|
||||
};
|
||||
|
||||
genForRoutes = ({
|
||||
routes = [],
|
||||
params = {},
|
||||
separator,
|
||||
itemRender = defaultItemRender,
|
||||
}: BreadcrumbProps) => {
|
||||
let crumbs;
|
||||
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
||||
if (routes && routes.length > 0) {
|
||||
// generated by route
|
||||
const paths: string[] = [];
|
||||
return routes.map(route => {
|
||||
const path = this.getPath(route.path, params);
|
||||
crumbs = routes.map(route => {
|
||||
const path = getPath(route.path, params);
|
||||
|
||||
if (path) {
|
||||
paths.push(path);
|
||||
@ -95,7 +101,7 @@ export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
|
||||
<Menu>
|
||||
{route.children.map(child => (
|
||||
<Menu.Item key={child.path || child.breadcrumbName}>
|
||||
{itemRender(child, params, routes, this.addChildPath(paths, child.path, params))}
|
||||
{itemRender(child, params, routes, addChildPath(paths, child.path, params))}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
@ -108,58 +114,40 @@ export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
});
|
||||
};
|
||||
} else if (children) {
|
||||
crumbs = toArray(children).map((element: any, index) => {
|
||||
if (!element) {
|
||||
return element;
|
||||
}
|
||||
|
||||
renderBreadcrumb = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
let crumbs;
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
separator,
|
||||
style,
|
||||
className,
|
||||
routes,
|
||||
children,
|
||||
...restProps
|
||||
} = this.props;
|
||||
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
||||
if (routes && routes.length > 0) {
|
||||
// generated by route
|
||||
crumbs = this.genForRoutes(this.props);
|
||||
} else if (children) {
|
||||
crumbs = toArray(children).map((element: any, index) => {
|
||||
if (!element) {
|
||||
return element;
|
||||
}
|
||||
devWarning(
|
||||
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",
|
||||
);
|
||||
|
||||
devWarning(
|
||||
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,
|
||||
});
|
||||
return cloneElement(element, {
|
||||
separator,
|
||||
key: index,
|
||||
});
|
||||
}
|
||||
const breadcrumbClassName = classNames(className, prefixCls, {
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
});
|
||||
return (
|
||||
<div
|
||||
className={breadcrumbClassName}
|
||||
style={style}
|
||||
{...omit(restProps, ['itemRender', 'linkRender', 'nameRender', 'params'])}
|
||||
>
|
||||
{crumbs}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderBreadcrumb}</ConfigConsumer>;
|
||||
}
|
||||
}
|
||||
|
||||
const breadcrumbClassName = classNames(className, prefixCls, {
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={breadcrumbClassName} style={style} {...restProps}>
|
||||
{crumbs}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Breadcrumb.Item = BreadcrumbItem;
|
||||
|
||||
Breadcrumb.Separator = BreadcrumbSeparator;
|
||||
|
||||
export default Breadcrumb;
|
||||
|
@ -1,9 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import DownOutlined from '@ant-design/icons/DownOutlined';
|
||||
import omit from 'omit.js';
|
||||
|
||||
import DropDown, { DropDownProps } from '../dropdown/dropdown';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
|
||||
export interface BreadcrumbItemProps {
|
||||
prefixCls?: string;
|
||||
@ -13,53 +12,24 @@ export interface BreadcrumbItemProps {
|
||||
dropdownProps?: DropDownProps;
|
||||
onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLSpanElement>;
|
||||
}
|
||||
|
||||
export default class BreadcrumbItem extends React.Component<BreadcrumbItemProps, any> {
|
||||
static __ANT_BREADCRUMB_ITEM = true;
|
||||
|
||||
static defaultProps = {
|
||||
separator: '/',
|
||||
};
|
||||
|
||||
renderBreadcrumbItem = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const { prefixCls: customizePrefixCls, separator, children, ...restProps } = this.props;
|
||||
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
||||
let link;
|
||||
if ('href' in this.props) {
|
||||
link = (
|
||||
<a className={`${prefixCls}-link`} {...omit(restProps, ['overlay'])}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
link = (
|
||||
<span className={`${prefixCls}-link`} {...omit(restProps, ['overlay'])}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// wrap to dropDown
|
||||
link = this.renderBreadcrumbNode(link, prefixCls);
|
||||
if (children) {
|
||||
return (
|
||||
<span>
|
||||
{link}
|
||||
{separator && separator !== '' && (
|
||||
<span className={`${prefixCls}-separator`}>{separator}</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
interface BreadcrumbItemInterface extends React.FC<BreadcrumbItemProps> {
|
||||
__ANT_BREADCRUMB_ITEM: boolean;
|
||||
}
|
||||
const BreadcrumbItem: BreadcrumbItemInterface = ({
|
||||
prefixCls: customizePrefixCls,
|
||||
separator,
|
||||
children,
|
||||
overlay,
|
||||
dropdownProps,
|
||||
...restProps
|
||||
}) => {
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
||||
/**
|
||||
* if overlay is have
|
||||
* Wrap a DropDown
|
||||
*/
|
||||
renderBreadcrumbNode = (breadcrumbItem: React.ReactNode, prefixCls: string) => {
|
||||
const { overlay, dropdownProps } = this.props;
|
||||
const renderBreadcrumbNode = (breadcrumbItem: React.ReactNode) => {
|
||||
if (overlay) {
|
||||
return (
|
||||
<DropDown overlay={overlay} placement="bottomCenter" {...dropdownProps}>
|
||||
@ -73,7 +43,36 @@ export default class BreadcrumbItem extends React.Component<BreadcrumbItemProps,
|
||||
return breadcrumbItem;
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderBreadcrumbItem}</ConfigConsumer>;
|
||||
let link;
|
||||
if ('href' in restProps) {
|
||||
link = (
|
||||
<a className={`${prefixCls}-link`} {...restProps}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
link = (
|
||||
<span className={`${prefixCls}-link`} {...restProps}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// wrap to dropDown
|
||||
link = renderBreadcrumbNode(link);
|
||||
if (children) {
|
||||
return (
|
||||
<span>
|
||||
{link}
|
||||
{separator && separator !== '' && (
|
||||
<span className={`${prefixCls}-separator`}>{separator}</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
BreadcrumbItem.__ANT_BREADCRUMB_ITEM = true;
|
||||
|
||||
export default BreadcrumbItem;
|
||||
|
@ -1,17 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
|
||||
export default class BreadcrumbSeparator extends React.Component<any> {
|
||||
static __ANT_BREADCRUMB_SEPARATOR = true;
|
||||
|
||||
renderSeparator = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const { children } = this.props;
|
||||
const prefixCls = getPrefixCls('breadcrumb');
|
||||
|
||||
return <span className={`${prefixCls}-separator`}>{children || '/'}</span>;
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderSeparator}</ConfigConsumer>;
|
||||
}
|
||||
interface BreadcrumbSeparatorInterface extends React.FC {
|
||||
__ANT_BREADCRUMB_SEPARATOR: boolean;
|
||||
}
|
||||
|
||||
const BreadcrumbSeparator: BreadcrumbSeparatorInterface = ({ children }) => {
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('breadcrumb');
|
||||
|
||||
return <span className={`${prefixCls}-separator`}>{children || '/'}</span>;
|
||||
};
|
||||
|
||||
BreadcrumbSeparator.__ANT_BREADCRUMB_SEPARATOR = true;
|
||||
|
||||
export default BreadcrumbSeparator;
|
||||
|
@ -111,21 +111,6 @@ describe('Breadcrumb', () => {
|
||||
it('should accept undefined routes', () => {
|
||||
const wrapper = render(<Breadcrumb routes={undefined} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
})
|
||||
|
||||
it('props#linkRender and props#nameRender do not warn anymore', () => {
|
||||
const linkRender = jest.fn();
|
||||
const nameRender = jest.fn();
|
||||
mount(
|
||||
<Breadcrumb linkRender={linkRender} nameRender={nameRender}>
|
||||
<Breadcrumb.Item />
|
||||
<Breadcrumb.Item>xxx</Breadcrumb.Item>
|
||||
</Breadcrumb>,
|
||||
);
|
||||
|
||||
expect(errorSpy.mock.calls.length).toBe(0);
|
||||
expect(linkRender).not.toHaveBeenCalled();
|
||||
expect(nameRender).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should support custom attribute', () => {
|
||||
|
@ -73,7 +73,6 @@ exports[`react router react router 3 1`] = `
|
||||
},
|
||||
]
|
||||
}
|
||||
separator="/"
|
||||
>
|
||||
<div
|
||||
className="ant-breadcrumb"
|
||||
|
@ -1,10 +1,6 @@
|
||||
import Breadcrumb from './Breadcrumb';
|
||||
import BreadcrumbItem from './BreadcrumbItem';
|
||||
import BreadcrumbSeparator from './BreadcrumbSeparator';
|
||||
|
||||
export { BreadcrumbProps } from './Breadcrumb';
|
||||
export { BreadcrumbItemProps } from './BreadcrumbItem';
|
||||
|
||||
Breadcrumb.Item = BreadcrumbItem;
|
||||
Breadcrumb.Separator = BreadcrumbSeparator;
|
||||
export default Breadcrumb;
|
||||
|
Loading…
Reference in New Issue
Block a user