mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 17:44:35 +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 * as React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import toArray from 'rc-util/lib/Children/toArray';
|
import toArray from 'rc-util/lib/Children/toArray';
|
||||||
import omit from 'omit.js';
|
|
||||||
import BreadcrumbItem from './BreadcrumbItem';
|
import BreadcrumbItem from './BreadcrumbItem';
|
||||||
import BreadcrumbSeparator from './BreadcrumbSeparator';
|
import BreadcrumbSeparator from './BreadcrumbSeparator';
|
||||||
import Menu from '../menu';
|
import Menu from '../menu';
|
||||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
import { ConfigContext } from '../config-provider';
|
||||||
import devWarning from '../_util/devWarning';
|
import devWarning from '../_util/devWarning';
|
||||||
import { Omit } from '../_util/type';
|
import { Omit } from '../_util/type';
|
||||||
import { cloneElement } from '../_util/reactNode';
|
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>;
|
return isLastItem ? <span>{name}</span> : <a href={`#/${paths.join('/')}`}>{name}</a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
|
const getPath = (path: string, params: any) => {
|
||||||
static Item: typeof BreadcrumbItem;
|
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 = {
|
interface BreadcrumbInterface extends React.FC<BreadcrumbProps> {
|
||||||
separator: '/',
|
Item: typeof BreadcrumbItem;
|
||||||
};
|
Separator: typeof BreadcrumbSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
getPath = (path: string, params: any) => {
|
const Breadcrumb: BreadcrumbInterface = ({
|
||||||
path = (path || '').replace(/^\//, '');
|
prefixCls: customizePrefixCls,
|
||||||
Object.keys(params).forEach(key => {
|
separator = '/',
|
||||||
path = path.replace(`:${key}`, params[key]);
|
style,
|
||||||
});
|
className,
|
||||||
return path;
|
routes,
|
||||||
};
|
children,
|
||||||
|
itemRender = defaultItemRender,
|
||||||
|
params = {},
|
||||||
|
...restProps
|
||||||
|
}) => {
|
||||||
|
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||||
|
|
||||||
addChildPath = (paths: string[], childPath: string = '', params: any) => {
|
let crumbs;
|
||||||
const originalPaths = [...paths];
|
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
||||||
const path = this.getPath(childPath, params);
|
if (routes && routes.length > 0) {
|
||||||
if (path) {
|
// generated by route
|
||||||
originalPaths.push(path);
|
|
||||||
}
|
|
||||||
return originalPaths;
|
|
||||||
};
|
|
||||||
|
|
||||||
genForRoutes = ({
|
|
||||||
routes = [],
|
|
||||||
params = {},
|
|
||||||
separator,
|
|
||||||
itemRender = defaultItemRender,
|
|
||||||
}: BreadcrumbProps) => {
|
|
||||||
const paths: string[] = [];
|
const paths: string[] = [];
|
||||||
return routes.map(route => {
|
crumbs = routes.map(route => {
|
||||||
const path = this.getPath(route.path, params);
|
const path = getPath(route.path, params);
|
||||||
|
|
||||||
if (path) {
|
if (path) {
|
||||||
paths.push(path);
|
paths.push(path);
|
||||||
@ -95,7 +101,7 @@ export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
|
|||||||
<Menu>
|
<Menu>
|
||||||
{route.children.map(child => (
|
{route.children.map(child => (
|
||||||
<Menu.Item key={child.path || child.breadcrumbName}>
|
<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.Item>
|
||||||
))}
|
))}
|
||||||
</Menu>
|
</Menu>
|
||||||
@ -108,58 +114,40 @@ export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
|
|||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
} else if (children) {
|
||||||
|
crumbs = toArray(children).map((element: any, index) => {
|
||||||
|
if (!element) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
renderBreadcrumb = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
devWarning(
|
||||||
let crumbs;
|
element.type &&
|
||||||
const {
|
(element.type.__ANT_BREADCRUMB_ITEM === true ||
|
||||||
prefixCls: customizePrefixCls,
|
element.type.__ANT_BREADCRUMB_SEPARATOR === true),
|
||||||
separator,
|
'Breadcrumb',
|
||||||
style,
|
"Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children",
|
||||||
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(
|
return cloneElement(element, {
|
||||||
element.type &&
|
separator,
|
||||||
(element.type.__ANT_BREADCRUMB_ITEM === true ||
|
key: index,
|
||||||
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(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 * as React from 'react';
|
||||||
import DownOutlined from '@ant-design/icons/DownOutlined';
|
import DownOutlined from '@ant-design/icons/DownOutlined';
|
||||||
import omit from 'omit.js';
|
|
||||||
|
|
||||||
import DropDown, { DropDownProps } from '../dropdown/dropdown';
|
import DropDown, { DropDownProps } from '../dropdown/dropdown';
|
||||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
import { ConfigContext } from '../config-provider';
|
||||||
|
|
||||||
export interface BreadcrumbItemProps {
|
export interface BreadcrumbItemProps {
|
||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
@ -13,53 +12,24 @@ export interface BreadcrumbItemProps {
|
|||||||
dropdownProps?: DropDownProps;
|
dropdownProps?: DropDownProps;
|
||||||
onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLSpanElement>;
|
onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLSpanElement>;
|
||||||
}
|
}
|
||||||
|
interface BreadcrumbItemInterface extends React.FC<BreadcrumbItemProps> {
|
||||||
export default class BreadcrumbItem extends React.Component<BreadcrumbItemProps, any> {
|
__ANT_BREADCRUMB_ITEM: boolean;
|
||||||
static __ANT_BREADCRUMB_ITEM = true;
|
}
|
||||||
|
const BreadcrumbItem: BreadcrumbItemInterface = ({
|
||||||
static defaultProps = {
|
prefixCls: customizePrefixCls,
|
||||||
separator: '/',
|
separator,
|
||||||
};
|
children,
|
||||||
|
overlay,
|
||||||
renderBreadcrumbItem = ({ getPrefixCls }: ConfigConsumerProps) => {
|
dropdownProps,
|
||||||
const { prefixCls: customizePrefixCls, separator, children, ...restProps } = this.props;
|
...restProps
|
||||||
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
}) => {
|
||||||
let link;
|
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||||
if ('href' in this.props) {
|
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if overlay is have
|
* if overlay is have
|
||||||
* Wrap a DropDown
|
* Wrap a DropDown
|
||||||
*/
|
*/
|
||||||
renderBreadcrumbNode = (breadcrumbItem: React.ReactNode, prefixCls: string) => {
|
const renderBreadcrumbNode = (breadcrumbItem: React.ReactNode) => {
|
||||||
const { overlay, dropdownProps } = this.props;
|
|
||||||
if (overlay) {
|
if (overlay) {
|
||||||
return (
|
return (
|
||||||
<DropDown overlay={overlay} placement="bottomCenter" {...dropdownProps}>
|
<DropDown overlay={overlay} placement="bottomCenter" {...dropdownProps}>
|
||||||
@ -73,7 +43,36 @@ export default class BreadcrumbItem extends React.Component<BreadcrumbItemProps,
|
|||||||
return breadcrumbItem;
|
return breadcrumbItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
let link;
|
||||||
return <ConfigConsumer>{this.renderBreadcrumbItem}</ConfigConsumer>;
|
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 * as React from 'react';
|
||||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
import { ConfigContext } from '../config-provider';
|
||||||
|
|
||||||
export default class BreadcrumbSeparator extends React.Component<any> {
|
interface BreadcrumbSeparatorInterface extends React.FC {
|
||||||
static __ANT_BREADCRUMB_SEPARATOR = true;
|
__ANT_BREADCRUMB_SEPARATOR: boolean;
|
||||||
|
|
||||||
renderSeparator = ({ getPrefixCls }: ConfigConsumerProps) => {
|
|
||||||
const { children } = this.props;
|
|
||||||
const prefixCls = getPrefixCls('breadcrumb');
|
|
||||||
|
|
||||||
return <span className={`${prefixCls}-separator`}>{children || '/'}</span>;
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <ConfigConsumer>{this.renderSeparator}</ConfigConsumer>;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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', () => {
|
it('should accept undefined routes', () => {
|
||||||
const wrapper = render(<Breadcrumb routes={undefined} />);
|
const wrapper = render(<Breadcrumb routes={undefined} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
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', () => {
|
it('should support custom attribute', () => {
|
||||||
|
@ -73,7 +73,6 @@ exports[`react router react router 3 1`] = `
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
separator="/"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ant-breadcrumb"
|
className="ant-breadcrumb"
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import Breadcrumb from './Breadcrumb';
|
import Breadcrumb from './Breadcrumb';
|
||||||
import BreadcrumbItem from './BreadcrumbItem';
|
|
||||||
import BreadcrumbSeparator from './BreadcrumbSeparator';
|
|
||||||
|
|
||||||
export { BreadcrumbProps } from './Breadcrumb';
|
export { BreadcrumbProps } from './Breadcrumb';
|
||||||
export { BreadcrumbItemProps } from './BreadcrumbItem';
|
export { BreadcrumbItemProps } from './BreadcrumbItem';
|
||||||
|
|
||||||
Breadcrumb.Item = BreadcrumbItem;
|
|
||||||
Breadcrumb.Separator = BreadcrumbSeparator;
|
|
||||||
export default Breadcrumb;
|
export default Breadcrumb;
|
||||||
|
Loading…
Reference in New Issue
Block a user