mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 09:26:06 +08:00
feat: Tag support aria-* in closable (#47678)
* feat: Tag support aria-* in closable * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * refactor: useClosable * chore: modal * fix: check logic * chore: clean up * feat: optimize code * feat: optimize code --------- Signed-off-by: kiner-tang <1127031143@qq.com> Co-authored-by: 二货机器人 <smith3816@gmail.com>
This commit is contained in:
parent
1b36fd22e5
commit
506753c3ef
@ -6,9 +6,9 @@ import type { UseClosableParams } from '../hooks/useClosable';
|
||||
import useClosable from '../hooks/useClosable';
|
||||
|
||||
type ParamsOfUseClosable = [
|
||||
UseClosableParams['closable'],
|
||||
UseClosableParams['closeIcon'],
|
||||
UseClosableParams['defaultClosable'],
|
||||
closable: UseClosableParams['closable'],
|
||||
closeIcon: UseClosableParams['closeIcon'],
|
||||
defaultClosable: UseClosableParams['defaultClosable'],
|
||||
];
|
||||
|
||||
describe('hooks test', () => {
|
||||
@ -81,7 +81,7 @@ describe('hooks test', () => {
|
||||
res: [false, ''],
|
||||
},
|
||||
|
||||
// test case like: <Component closeIcon={null | false | element} />
|
||||
// test case like: <Component closeIcon={null | false | element | true} />
|
||||
{
|
||||
params: [undefined, null, undefined],
|
||||
res: [false, ''],
|
||||
@ -90,6 +90,10 @@ describe('hooks test', () => {
|
||||
params: [undefined, false, undefined],
|
||||
res: [false, ''],
|
||||
},
|
||||
{
|
||||
params: [undefined, true, undefined],
|
||||
res: [true, '.anticon-close'],
|
||||
},
|
||||
{
|
||||
params: [
|
||||
undefined,
|
||||
@ -138,11 +142,16 @@ describe('hooks test', () => {
|
||||
React.isValidElement(params[1]) ? 'element' : params[1]
|
||||
},defaultClosable=${params[2]}. the result should be ${res}`, () => {
|
||||
const App = () => {
|
||||
const [closable, closeIcon] = useClosable({
|
||||
closable: params[0],
|
||||
closeIcon: params[1],
|
||||
defaultClosable: params[2],
|
||||
});
|
||||
const [closable, closeIcon] = useClosable(
|
||||
{
|
||||
closable: params[0],
|
||||
closeIcon: params[1],
|
||||
},
|
||||
null,
|
||||
{
|
||||
closable: params[2],
|
||||
},
|
||||
);
|
||||
useEffect(() => {
|
||||
expect(closable).toBe(res[0]);
|
||||
}, [closable]);
|
||||
@ -159,10 +168,15 @@ describe('hooks test', () => {
|
||||
|
||||
it('useClosable with defaultCloseIcon', () => {
|
||||
const App = () => {
|
||||
const [closable, closeIcon] = useClosable({
|
||||
closable: true,
|
||||
defaultCloseIcon: <CloseOutlined className="custom-close-icon" />,
|
||||
});
|
||||
const [closable, closeIcon] = useClosable(
|
||||
{
|
||||
closable: true,
|
||||
},
|
||||
null,
|
||||
{
|
||||
closeIcon: <CloseOutlined className="custom-close-icon" />,
|
||||
},
|
||||
);
|
||||
useEffect(() => {
|
||||
expect(closable).toBe(true);
|
||||
}, [closable]);
|
||||
@ -171,16 +185,37 @@ describe('hooks test', () => {
|
||||
const { container } = render(<App />);
|
||||
expect(container.querySelector('.custom-close-icon')).toBeTruthy();
|
||||
});
|
||||
it('useClosable without defaultCloseIcon', () => {
|
||||
const App = () => {
|
||||
const [closable, closeIcon] = useClosable(
|
||||
{
|
||||
closable: true,
|
||||
},
|
||||
null,
|
||||
);
|
||||
useEffect(() => {
|
||||
expect(closable).toBe(true);
|
||||
}, [closable]);
|
||||
return <div>hooks test {closeIcon}</div>;
|
||||
};
|
||||
const { container } = render(<App />);
|
||||
expect(container.querySelector('.anticon-close')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('useClosable with customCloseIconRender', () => {
|
||||
const App = () => {
|
||||
const customCloseIconRender = (icon: React.ReactNode) => (
|
||||
<span className="custom-close-wrapper">{icon}</span>
|
||||
);
|
||||
const [closable, closeIcon] = useClosable({
|
||||
closable: true,
|
||||
customCloseIconRender,
|
||||
});
|
||||
const [closable, closeIcon] = useClosable(
|
||||
{
|
||||
closable: true,
|
||||
},
|
||||
null,
|
||||
{
|
||||
closeIconRender: customCloseIconRender,
|
||||
},
|
||||
);
|
||||
useEffect(() => {
|
||||
expect(closable).toBe(true);
|
||||
}, [closable]);
|
||||
|
@ -3,7 +3,23 @@ import React from 'react';
|
||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||
import pickAttrs from 'rc-util/lib/pickAttrs';
|
||||
|
||||
export type ClosableType = boolean | ({ closeIcon?: React.ReactNode } & React.AriaAttributes);
|
||||
export type BaseClosableType = { closeIcon?: React.ReactNode } & React.AriaAttributes;
|
||||
export type ClosableType = boolean | BaseClosableType;
|
||||
|
||||
export type ContextClosable<T extends { closable?: ClosableType; closeIcon?: ReactNode } = any> =
|
||||
Partial<Pick<T, 'closable' | 'closeIcon'>>;
|
||||
|
||||
export function pickClosable<T extends { closable?: ClosableType; closeIcon?: ReactNode }>(
|
||||
context?: ContextClosable<T>,
|
||||
): ContextClosable<T> | undefined {
|
||||
if (!context) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
closable: context.closable,
|
||||
closeIcon: context.closeIcon,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseClosableParams = {
|
||||
closable?: ClosableType;
|
||||
@ -11,63 +27,150 @@ export type UseClosableParams = {
|
||||
defaultClosable?: boolean;
|
||||
defaultCloseIcon?: ReactNode;
|
||||
customCloseIconRender?: (closeIcon: ReactNode) => ReactNode;
|
||||
context?: ContextClosable;
|
||||
};
|
||||
|
||||
function useInnerClosable(
|
||||
closable?: UseClosableParams['closable'],
|
||||
closeIcon?: ReactNode,
|
||||
defaultClosable?: boolean,
|
||||
) {
|
||||
if (typeof closable === 'boolean') {
|
||||
return closable;
|
||||
}
|
||||
if (typeof closable === 'object') {
|
||||
return true;
|
||||
}
|
||||
if (closeIcon === undefined) {
|
||||
return !!defaultClosable;
|
||||
}
|
||||
return closeIcon !== false && closeIcon !== null;
|
||||
/** Convert `closable` and `closeIcon` to config object */
|
||||
function useClosableConfig(closableCollection?: ClosableCollection | null) {
|
||||
const { closable, closeIcon } = closableCollection || {};
|
||||
|
||||
return React.useMemo(() => {
|
||||
if (
|
||||
// If `closable`, whatever rest be should be true
|
||||
!closable &&
|
||||
(closable === false || closeIcon === false || closeIcon === null)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (closable === undefined && closeIcon === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let closableConfig: BaseClosableType = {
|
||||
closeIcon: typeof closeIcon !== 'boolean' && closeIcon !== null ? closeIcon : undefined,
|
||||
};
|
||||
if (closable && typeof closable === 'object') {
|
||||
closableConfig = {
|
||||
...closableConfig,
|
||||
...closable,
|
||||
};
|
||||
}
|
||||
|
||||
return closableConfig;
|
||||
}, [closable, closeIcon]);
|
||||
}
|
||||
|
||||
function useClosable({
|
||||
closable,
|
||||
closeIcon,
|
||||
customCloseIconRender,
|
||||
defaultCloseIcon = <CloseOutlined />,
|
||||
defaultClosable = false,
|
||||
}: UseClosableParams): [closable: boolean, closeIcon: React.ReactNode | null] {
|
||||
const mergedClosable = useInnerClosable(closable, closeIcon, defaultClosable);
|
||||
/**
|
||||
* Assign object without `undefined` field. Will skip if is `false`.
|
||||
* This helps to handle both closableConfig or false
|
||||
*/
|
||||
function assignWithoutUndefined<T extends object>(
|
||||
...objList: (Partial<T> | false | null | undefined)[]
|
||||
): Partial<T> {
|
||||
const target: Partial<T> = {};
|
||||
|
||||
if (!mergedClosable) {
|
||||
return [false, null];
|
||||
}
|
||||
const { closeIcon: closableIcon, ...restProps } =
|
||||
typeof closable === 'object'
|
||||
? closable
|
||||
: ({} as { closeIcon: React.ReactNode } & React.AriaAttributes);
|
||||
// Priority: closable.closeIcon > closeIcon > defaultCloseIcon
|
||||
const mergedCloseIcon: ReactNode = (() => {
|
||||
if (typeof closable === 'object' && closableIcon !== undefined) {
|
||||
return closableIcon;
|
||||
objList.forEach((obj) => {
|
||||
if (obj) {
|
||||
(Object.keys(obj) as (keyof T)[]).forEach((key) => {
|
||||
if (obj[key] !== undefined) {
|
||||
target[key] = obj[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
return typeof closeIcon === 'boolean' || closeIcon === undefined || closeIcon === null
|
||||
? defaultCloseIcon
|
||||
: closeIcon;
|
||||
})();
|
||||
const ariaProps = pickAttrs(restProps, true);
|
||||
});
|
||||
|
||||
const plainCloseIcon = customCloseIconRender
|
||||
? customCloseIconRender(mergedCloseIcon)
|
||||
: mergedCloseIcon;
|
||||
return target;
|
||||
}
|
||||
|
||||
const closeIconWithAria = React.isValidElement(plainCloseIcon) ? (
|
||||
React.cloneElement(plainCloseIcon, ariaProps)
|
||||
) : (
|
||||
<span {...ariaProps}>{plainCloseIcon}</span>
|
||||
/** Collection contains the all the props related with closable. e.g. `closable`, `closeIcon` */
|
||||
interface ClosableCollection {
|
||||
closable?: ClosableType;
|
||||
closeIcon?: ReactNode;
|
||||
}
|
||||
|
||||
/** Use same object to support `useMemo` optimization */
|
||||
const EmptyFallbackCloseCollection: ClosableCollection = {};
|
||||
|
||||
export default function useClosable(
|
||||
propCloseCollection?: ClosableCollection,
|
||||
contextCloseCollection?: ClosableCollection | null,
|
||||
fallbackCloseCollection: ClosableCollection & {
|
||||
/**
|
||||
* Some components need to wrap CloseIcon twice,
|
||||
* this method will be executed once after the final CloseIcon is calculated
|
||||
*/
|
||||
closeIconRender?: (closeIcon: ReactNode) => ReactNode;
|
||||
} = EmptyFallbackCloseCollection,
|
||||
): [closable: boolean, closeIcon: React.ReactNode | null] {
|
||||
// Align the `props`, `context` `fallback` to config object first
|
||||
const propCloseConfig = useClosableConfig(propCloseCollection);
|
||||
const contextCloseConfig = useClosableConfig(contextCloseCollection);
|
||||
const mergedFallbackCloseCollection = React.useMemo(
|
||||
() => ({
|
||||
closeIcon: <CloseOutlined />,
|
||||
...fallbackCloseCollection,
|
||||
}),
|
||||
[fallbackCloseCollection],
|
||||
);
|
||||
|
||||
return [true, closeIconWithAria];
|
||||
}
|
||||
// Use fallback logic to fill the config
|
||||
const mergedClosableConfig = React.useMemo(() => {
|
||||
// ================ Props First ================
|
||||
// Skip if prop is disabled
|
||||
if (propCloseConfig === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export default useClosable;
|
||||
if (propCloseConfig) {
|
||||
return assignWithoutUndefined(
|
||||
mergedFallbackCloseCollection,
|
||||
contextCloseConfig,
|
||||
propCloseConfig,
|
||||
);
|
||||
}
|
||||
|
||||
// =============== Context Second ==============
|
||||
// Skip if context is disabled
|
||||
if (contextCloseConfig === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (contextCloseConfig) {
|
||||
return assignWithoutUndefined(mergedFallbackCloseCollection, contextCloseConfig);
|
||||
}
|
||||
|
||||
// ============= Fallback Default ==============
|
||||
return !mergedFallbackCloseCollection.closable ? false : mergedFallbackCloseCollection;
|
||||
}, [propCloseConfig, contextCloseConfig, mergedFallbackCloseCollection]);
|
||||
|
||||
// Calculate the final closeIcon
|
||||
return React.useMemo(() => {
|
||||
if (mergedClosableConfig === false) {
|
||||
return [false, null];
|
||||
}
|
||||
|
||||
const { closeIconRender } = mergedFallbackCloseCollection;
|
||||
const { closeIcon } = mergedClosableConfig;
|
||||
|
||||
let mergedCloseIcon: ReactNode = closeIcon;
|
||||
if (mergedCloseIcon !== null && mergedCloseIcon !== undefined) {
|
||||
// Wrap the closeIcon if needed
|
||||
if (closeIconRender) {
|
||||
mergedCloseIcon = closeIconRender(closeIcon);
|
||||
}
|
||||
|
||||
// Wrap the closeIcon with aria props
|
||||
const ariaProps = pickAttrs(mergedClosableConfig, true);
|
||||
if (Object.keys(ariaProps).length) {
|
||||
mergedCloseIcon = React.isValidElement(mergedCloseIcon) ? (
|
||||
React.cloneElement(mergedCloseIcon, ariaProps)
|
||||
) : (
|
||||
<span {...ariaProps}>{mergedCloseIcon}</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return [true, mergedCloseIcon];
|
||||
}, [mergedClosableConfig, mergedFallbackCloseCollection]);
|
||||
}
|
||||
|
@ -1086,6 +1086,59 @@ describe('ConfigProvider support style and className props', () => {
|
||||
expect(element?.querySelector<HTMLSpanElement>('.cp-test-closeIcon')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should Tag support aria-* in closable', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider
|
||||
tag={{
|
||||
closable: {
|
||||
closeIcon: <span className="cp-test-closeIcon">cp-test-closeIcon</span>,
|
||||
'aria-label': 'Close Tag',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tag>Test</Tag>
|
||||
<Tag.CheckableTag checked>CheckableTag</Tag.CheckableTag>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
const element = container.querySelector<HTMLSpanElement>('.ant-tag');
|
||||
expect(element?.querySelector('.ant-tag-close-icon')).toBeTruthy();
|
||||
expect(element?.querySelector('.ant-tag-close-icon')?.getAttribute('aria-label')).toBe(
|
||||
'Close Tag',
|
||||
);
|
||||
expect(element?.querySelector('.cp-test-closeIcon')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should Tag hide closeIcon when closeIcon=false', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider
|
||||
tag={{
|
||||
closeIcon: false,
|
||||
}}
|
||||
>
|
||||
<Tag>Test</Tag>
|
||||
<Tag.CheckableTag checked>CheckableTag</Tag.CheckableTag>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
const element = container.querySelector<HTMLSpanElement>('.ant-tag');
|
||||
expect(element?.querySelector('.ant-tag-close-icon')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('Should Tag show default closeIcon when closeIcon=true', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider
|
||||
tag={{
|
||||
closeIcon: true,
|
||||
}}
|
||||
>
|
||||
<Tag>Test</Tag>
|
||||
<Tag.CheckableTag checked>CheckableTag</Tag.CheckableTag>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
const element = container.querySelector<HTMLSpanElement>('.ant-tag');
|
||||
expect(element?.querySelector('.ant-tag-close-icon')).toBeTruthy();
|
||||
expect(element?.querySelector('.anticon-close')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should Table className & style works', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider
|
||||
|
@ -125,7 +125,7 @@ export type MenuConfig = ComponentStyleConfig & Pick<MenuProps, 'expandIcon'>;
|
||||
export type TourConfig = Pick<TourProps, 'closeIcon'>;
|
||||
|
||||
export type ModalConfig = ComponentStyleConfig &
|
||||
Pick<ModalProps, 'classNames' | 'styles' | 'closeIcon'>;
|
||||
Pick<ModalProps, 'classNames' | 'styles' | 'closeIcon' | 'closable'>;
|
||||
|
||||
export type TabsConfig = ComponentStyleConfig &
|
||||
Pick<TabsProps, 'indicator' | 'indicatorSize' | 'moreIcon' | 'addIcon' | 'removeIcon'>;
|
||||
@ -144,7 +144,7 @@ export type ButtonConfig = ComponentStyleConfig & Pick<ButtonProps, 'classNames'
|
||||
|
||||
export type NotificationConfig = ComponentStyleConfig & Pick<ArgsProps, 'closeIcon'>;
|
||||
|
||||
export type TagConfig = ComponentStyleConfig & Pick<TagProps, 'closeIcon'>;
|
||||
export type TagConfig = ComponentStyleConfig & Pick<TagProps, 'closeIcon' | 'closable'>;
|
||||
|
||||
export type CardConfig = ComponentStyleConfig & Pick<CardProps, 'classNames' | 'styles'>;
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { DrawerProps as RCDrawerProps } from 'rc-drawer';
|
||||
import useClosable, { type ClosableType } from '../_util/hooks/useClosable';
|
||||
|
||||
import useClosable, { pickClosable, type ClosableType } from '../_util/hooks/useClosable';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
|
||||
export interface DrawerClassNames extends NonNullable<RCDrawerProps['classNames']> {
|
||||
@ -57,8 +58,6 @@ const DrawerPanel: React.FC<DrawerPanelProps> = (props) => {
|
||||
title,
|
||||
footer,
|
||||
extra,
|
||||
closeIcon,
|
||||
closable,
|
||||
onClose,
|
||||
headerStyle,
|
||||
bodyStyle,
|
||||
@ -78,19 +77,14 @@ const DrawerPanel: React.FC<DrawerPanelProps> = (props) => {
|
||||
[onClose],
|
||||
);
|
||||
|
||||
const mergedContextCloseIcon = React.useMemo(() => {
|
||||
if (typeof drawerContext?.closable === 'object' && drawerContext.closable.closeIcon) {
|
||||
return drawerContext.closable.closeIcon;
|
||||
}
|
||||
return drawerContext?.closeIcon;
|
||||
}, [drawerContext?.closable, drawerContext?.closeIcon]);
|
||||
|
||||
const [mergedClosable, mergedCloseIcon] = useClosable({
|
||||
closable: closable ?? drawerContext?.closable,
|
||||
closeIcon: typeof closeIcon !== 'undefined' ? closeIcon : mergedContextCloseIcon,
|
||||
customCloseIconRender,
|
||||
defaultClosable: true,
|
||||
});
|
||||
const [mergedClosable, mergedCloseIcon] = useClosable(
|
||||
pickClosable(props),
|
||||
pickClosable(drawerContext),
|
||||
{
|
||||
closable: true,
|
||||
closeIconRender: customCloseIconRender,
|
||||
},
|
||||
);
|
||||
|
||||
const headerNode = React.useMemo<React.ReactNode>(() => {
|
||||
if (!title && !mergedClosable) {
|
||||
|
@ -3,7 +3,7 @@ import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||
import classNames from 'classnames';
|
||||
import Dialog from 'rc-dialog';
|
||||
|
||||
import useClosable from '../_util/hooks/useClosable';
|
||||
import useClosable, { pickClosable } from '../_util/hooks/useClosable';
|
||||
import { useZIndex } from '../_util/hooks/useZIndex';
|
||||
import { getTransitionName } from '../_util/motion';
|
||||
import { canUseDocElement } from '../_util/styleChecker';
|
||||
@ -44,7 +44,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
||||
getPopupContainer: getContextPopupContainer,
|
||||
getPrefixCls,
|
||||
direction,
|
||||
modal,
|
||||
modal: modalContext,
|
||||
} = React.useContext(ConfigContext);
|
||||
|
||||
const handleCancel = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
@ -77,8 +77,6 @@ const Modal: React.FC<ModalProps> = (props) => {
|
||||
wrapClassName,
|
||||
centered,
|
||||
getContainer,
|
||||
closeIcon,
|
||||
closable,
|
||||
focusTriggerAfterClose = true,
|
||||
style,
|
||||
// Deprecated
|
||||
@ -106,13 +104,15 @@ const Modal: React.FC<ModalProps> = (props) => {
|
||||
<Footer {...props} onOk={handleOk} onCancel={handleCancel} />
|
||||
);
|
||||
|
||||
const [mergedClosable, mergedCloseIcon] = useClosable({
|
||||
closable,
|
||||
closeIcon: typeof closeIcon !== 'undefined' ? closeIcon : modal?.closeIcon,
|
||||
customCloseIconRender: (icon) => renderCloseIcon(prefixCls, icon),
|
||||
defaultCloseIcon: <CloseOutlined className={`${prefixCls}-close-icon`} />,
|
||||
defaultClosable: true,
|
||||
});
|
||||
const [mergedClosable, mergedCloseIcon] = useClosable(
|
||||
pickClosable(props),
|
||||
pickClosable(modalContext),
|
||||
{
|
||||
closable: true,
|
||||
closeIcon: <CloseOutlined className={`${prefixCls}-close-icon`} />,
|
||||
closeIconRender: (icon) => renderCloseIcon(prefixCls, icon),
|
||||
},
|
||||
);
|
||||
|
||||
// ============================ Refs ============================
|
||||
// Select `ant-modal-content` by `panelRef`
|
||||
@ -142,15 +142,15 @@ const Modal: React.FC<ModalProps> = (props) => {
|
||||
focusTriggerAfterClose={focusTriggerAfterClose}
|
||||
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
|
||||
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
|
||||
className={classNames(hashId, className, modal?.className)}
|
||||
style={{ ...modal?.style, ...style }}
|
||||
className={classNames(hashId, className, modalContext?.className)}
|
||||
style={{ ...modalContext?.style, ...style }}
|
||||
classNames={{
|
||||
...modal?.classNames,
|
||||
...modalContext?.classNames,
|
||||
...modalClassNames,
|
||||
wrapper: classNames(wrapClassNameExtended, modalClassNames?.wrapper),
|
||||
}}
|
||||
styles={{
|
||||
...modal?.styles,
|
||||
...modalContext?.styles,
|
||||
...modalStyles,
|
||||
}}
|
||||
panelRef={panelRef}
|
||||
|
@ -178,28 +178,25 @@ Array [
|
||||
>
|
||||
Tag 2
|
||||
<span
|
||||
class="ant-tag-close-icon"
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-tag-close-icon"
|
||||
role="img"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle"
|
||||
role="img"
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm0 76c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm128.01 198.83c.03 0 .05.01.09.06l45.02 45.01a.2.2 0 01.05.09.12.12 0 010 .07c0 .02-.01.04-.05.08L557.25 512l127.87 127.86a.27.27 0 01.05.06v.02a.12.12 0 010 .07c0 .03-.01.05-.05.09l-45.02 45.02a.2.2 0 01-.09.05.12.12 0 01-.07 0c-.02 0-.04-.01-.08-.05L512 557.25 384.14 685.12c-.04.04-.06.05-.08.05a.12.12 0 01-.07 0c-.03 0-.05-.01-.09-.05l-45.02-45.02a.2.2 0 01-.05-.09.12.12 0 010-.07c0-.02.01-.04.06-.08L466.75 512 338.88 384.14a.27.27 0 01-.05-.06l-.01-.02a.12.12 0 010-.07c0-.03.01-.05.05-.09l45.02-45.02a.2.2 0 01.09-.05.12.12 0 01.07 0c.02 0 .04.01.08.06L512 466.75l127.86-127.86c.04-.05.06-.06.08-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<path
|
||||
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm0 76c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm128.01 198.83c.03 0 .05.01.09.06l45.02 45.01a.2.2 0 01.05.09.12.12 0 010 .07c0 .02-.01.04-.05.08L557.25 512l127.87 127.86a.27.27 0 01.05.06v.02a.12.12 0 010 .07c0 .03-.01.05-.05.09l-45.02 45.02a.2.2 0 01-.09.05.12.12 0 01-.07 0c-.02 0-.04-.01-.08-.05L512 557.25 384.14 685.12c-.04.04-.06.05-.08.05a.12.12 0 01-.07 0c-.03 0-.05-.01-.09-.05l-45.02-45.02a.2.2 0 01-.05-.09.12.12 0 010-.07c0-.02.01-.04.06-.08L466.75 512 338.88 384.14a.27.27 0 01-.05-.06l-.01-.02a.12.12 0 010-.07c0-.03.01-.05.05-.09l45.02-45.02a.2.2 0 01.09-.05.12.12 0 01.07 0c.02 0 .04.01.08.06L512 466.75l127.86-127.86c.04-.05.06-.06.08-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>,
|
||||
]
|
||||
@ -845,28 +842,25 @@ exports[`renders components/tag/demo/customize.tsx extend context correctly 1`]
|
||||
>
|
||||
Tag2
|
||||
<span
|
||||
class="ant-tag-close-icon"
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-tag-close-icon"
|
||||
role="img"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle"
|
||||
role="img"
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm0 76c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm128.01 198.83c.03 0 .05.01.09.06l45.02 45.01a.2.2 0 01.05.09.12.12 0 010 .07c0 .02-.01.04-.05.08L557.25 512l127.87 127.86a.27.27 0 01.05.06v.02a.12.12 0 010 .07c0 .03-.01.05-.05.09l-45.02 45.02a.2.2 0 01-.09.05.12.12 0 01-.07 0c-.02 0-.04-.01-.08-.05L512 557.25 384.14 685.12c-.04.04-.06.05-.08.05a.12.12 0 01-.07 0c-.03 0-.05-.01-.09-.05l-45.02-45.02a.2.2 0 01-.05-.09.12.12 0 010-.07c0-.02.01-.04.06-.08L466.75 512 338.88 384.14a.27.27 0 01-.05-.06l-.01-.02a.12.12 0 010-.07c0-.03.01-.05.05-.09l45.02-45.02a.2.2 0 01.09-.05.12.12 0 01.07 0c.02 0 .04.01.08.06L512 466.75l127.86-127.86c.04-.05.06-.06.08-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<path
|
||||
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm0 76c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm128.01 198.83c.03 0 .05.01.09.06l45.02 45.01a.2.2 0 01.05.09.12.12 0 010 .07c0 .02-.01.04-.05.08L557.25 512l127.87 127.86a.27.27 0 01.05.06v.02a.12.12 0 010 .07c0 .03-.01.05-.05.09l-45.02 45.02a.2.2 0 01-.09.05.12.12 0 01-.07 0c-.02 0-.04-.01-.08-.05L512 557.25 384.14 685.12c-.04.04-.06.05-.08.05a.12.12 0 01-.07 0c-.03 0-.05-.01-.09-.05l-45.02-45.02a.2.2 0 01-.05-.09.12.12 0 010-.07c0-.02.01-.04.06-.08L466.75 512 338.88 384.14a.27.27 0 01-.05-.06l-.01-.02a.12.12 0 010-.07c0-.03.01-.05.05-.09l45.02-45.02a.2.2 0 01.09-.05.12.12 0 01.07 0c.02 0 .04.01.08.06L512 466.75l127.86-127.86c.04-.05.06-.06.08-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -176,28 +176,25 @@ Array [
|
||||
>
|
||||
Tag 2
|
||||
<span
|
||||
class="ant-tag-close-icon"
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-tag-close-icon"
|
||||
role="img"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle"
|
||||
role="img"
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm0 76c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm128.01 198.83c.03 0 .05.01.09.06l45.02 45.01a.2.2 0 01.05.09.12.12 0 010 .07c0 .02-.01.04-.05.08L557.25 512l127.87 127.86a.27.27 0 01.05.06v.02a.12.12 0 010 .07c0 .03-.01.05-.05.09l-45.02 45.02a.2.2 0 01-.09.05.12.12 0 01-.07 0c-.02 0-.04-.01-.08-.05L512 557.25 384.14 685.12c-.04.04-.06.05-.08.05a.12.12 0 01-.07 0c-.03 0-.05-.01-.09-.05l-45.02-45.02a.2.2 0 01-.05-.09.12.12 0 010-.07c0-.02.01-.04.06-.08L466.75 512 338.88 384.14a.27.27 0 01-.05-.06l-.01-.02a.12.12 0 010-.07c0-.03.01-.05.05-.09l45.02-45.02a.2.2 0 01.09-.05.12.12 0 01.07 0c.02 0 .04.01.08.06L512 466.75l127.86-127.86c.04-.05.06-.06.08-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<path
|
||||
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm0 76c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm128.01 198.83c.03 0 .05.01.09.06l45.02 45.01a.2.2 0 01.05.09.12.12 0 010 .07c0 .02-.01.04-.05.08L557.25 512l127.87 127.86a.27.27 0 01.05.06v.02a.12.12 0 010 .07c0 .03-.01.05-.05.09l-45.02 45.02a.2.2 0 01-.09.05.12.12 0 01-.07 0c-.02 0-.04-.01-.08-.05L512 557.25 384.14 685.12c-.04.04-.06.05-.08.05a.12.12 0 01-.07 0c-.03 0-.05-.01-.09-.05l-45.02-45.02a.2.2 0 01-.05-.09.12.12 0 010-.07c0-.02.01-.04.06-.08L466.75 512 338.88 384.14a.27.27 0 01-.05-.06l-.01-.02a.12.12 0 010-.07c0-.03.01-.05.05-.09l45.02-45.02a.2.2 0 01.09-.05.12.12 0 01.07 0c.02 0 .04.01.08.06L512 466.75l127.86-127.86c.04-.05.06-.06.08-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>,
|
||||
]
|
||||
@ -829,28 +826,25 @@ exports[`renders components/tag/demo/customize.tsx correctly 1`] = `
|
||||
>
|
||||
Tag2
|
||||
<span
|
||||
class="ant-tag-close-icon"
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-tag-close-icon"
|
||||
role="img"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle"
|
||||
role="img"
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm0 76c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm128.01 198.83c.03 0 .05.01.09.06l45.02 45.01a.2.2 0 01.05.09.12.12 0 010 .07c0 .02-.01.04-.05.08L557.25 512l127.87 127.86a.27.27 0 01.05.06v.02a.12.12 0 010 .07c0 .03-.01.05-.05.09l-45.02 45.02a.2.2 0 01-.09.05.12.12 0 01-.07 0c-.02 0-.04-.01-.08-.05L512 557.25 384.14 685.12c-.04.04-.06.05-.08.05a.12.12 0 01-.07 0c-.03 0-.05-.01-.09-.05l-45.02-45.02a.2.2 0 01-.05-.09.12.12 0 010-.07c0-.02.01-.04.06-.08L466.75 512 338.88 384.14a.27.27 0 01-.05-.06l-.01-.02a.12.12 0 010-.07c0-.03.01-.05.05-.09l45.02-45.02a.2.2 0 01.09-.05.12.12 0 01.07 0c.02 0 .04.01.08.06L512 466.75l127.86-127.86c.04-.05.06-.06.08-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<path
|
||||
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm0 76c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm128.01 198.83c.03 0 .05.01.09.06l45.02 45.01a.2.2 0 01.05.09.12.12 0 010 .07c0 .02-.01.04-.05.08L557.25 512l127.87 127.86a.27.27 0 01.05.06v.02a.12.12 0 010 .07c0 .03-.01.05-.05.09l-45.02 45.02a.2.2 0 01-.09.05.12.12 0 01-.07 0c-.02 0-.04-.01-.08-.05L512 557.25 384.14 685.12c-.04.04-.06.05-.08.05a.12.12 0 01-.07 0c-.03 0-.05-.01-.09-.05l-45.02-45.02a.2.2 0 01-.05-.09.12.12 0 010-.07c0-.02.01-.04.06-.08L466.75 512 338.88 384.14a.27.27 0 01-.05-.06l-.01-.02a.12.12 0 010-.07c0-.03.01-.05.05-.09l45.02-45.02a.2.2 0 01.09-.05.12.12 0 01.07 0c.02 0 .04.01.08.06L512 466.75l127.86-127.86c.04-.05.06-.06.08-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import { CheckCircleOutlined } from '@ant-design/icons';
|
||||
import { Simulate } from 'react-dom/test-utils';
|
||||
|
||||
import { CheckCircleOutlined } from '@ant-design/icons';
|
||||
import Tag from '..';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { act, fireEvent, render } from '../../../tests/utils';
|
||||
@ -219,4 +218,11 @@ describe('Tag', () => {
|
||||
waitRaf();
|
||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||
});
|
||||
it('should support aria-* in closable', () => {
|
||||
const { container } = render(<Tag closable={{ closeIcon: 'X', 'aria-label': 'CloseBtn' }} />);
|
||||
expect(container.querySelector('.ant-tag-close-icon')?.getAttribute('aria-label')).toEqual(
|
||||
'CloseBtn',
|
||||
);
|
||||
expect(container.querySelector('.ant-tag-close-icon')?.textContent).toEqual('X');
|
||||
});
|
||||
});
|
||||
|
@ -1,10 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||
import classNames from 'classnames';
|
||||
import omit from 'rc-util/lib/omit';
|
||||
|
||||
import type { PresetColorType, PresetStatusColorType } from '../_util/colors';
|
||||
import { isPresetColor, isPresetStatusColor } from '../_util/colors';
|
||||
import useClosable from '../_util/hooks/useClosable';
|
||||
import useClosable, { pickClosable } from '../_util/hooks/useClosable';
|
||||
import { replaceElement } from '../_util/reactNode';
|
||||
import type { LiteralUnion } from '../_util/type';
|
||||
import { devUseWarning } from '../_util/warning';
|
||||
import Wave from '../_util/wave';
|
||||
@ -21,7 +22,7 @@ export interface TagProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
className?: string;
|
||||
rootClassName?: string;
|
||||
color?: LiteralUnion<PresetColorType | PresetStatusColorType>;
|
||||
closable?: boolean;
|
||||
closable?: boolean | ({ closeIcon?: React.ReactNode } & React.AriaAttributes);
|
||||
/** Advised to use closeIcon instead. */
|
||||
closeIcon?: React.ReactNode;
|
||||
/** @deprecated `visible` will be removed in next major version. */
|
||||
@ -47,26 +48,27 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
|
||||
icon,
|
||||
color,
|
||||
onClose,
|
||||
closeIcon,
|
||||
closable,
|
||||
bordered = true,
|
||||
visible: deprecatedVisible,
|
||||
...props
|
||||
} = tagProps;
|
||||
const { getPrefixCls, direction, tag } = React.useContext(ConfigContext);
|
||||
const { getPrefixCls, direction, tag: tagContext } = React.useContext(ConfigContext);
|
||||
const [visible, setVisible] = React.useState(true);
|
||||
|
||||
const domProps = omit(props, ['closeIcon', 'closable']);
|
||||
|
||||
// Warning for deprecated usage
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('Tag');
|
||||
|
||||
warning.deprecated(!('visible' in props), 'visible', 'visible && <Tag />');
|
||||
warning.deprecated(!('visible' in tagProps), 'visible', 'visible && <Tag />');
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if ('visible' in props) {
|
||||
setVisible(props.visible!);
|
||||
if (deprecatedVisible !== undefined) {
|
||||
setVisible(deprecatedVisible!);
|
||||
}
|
||||
}, [props.visible]);
|
||||
}, [deprecatedVisible]);
|
||||
|
||||
const isPreset = isPresetColor(color);
|
||||
const isStatus = isPresetStatusColor(color);
|
||||
@ -74,7 +76,7 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
|
||||
|
||||
const tagStyle: React.CSSProperties = {
|
||||
backgroundColor: color && !isInternalColor ? color : undefined,
|
||||
...tag?.style,
|
||||
...tagContext?.style,
|
||||
...style,
|
||||
};
|
||||
|
||||
@ -84,7 +86,7 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
|
||||
|
||||
const tagClassName = classNames(
|
||||
prefixCls,
|
||||
tag?.className,
|
||||
tagContext?.className,
|
||||
{
|
||||
[`${prefixCls}-${color}`]: isInternalColor,
|
||||
[`${prefixCls}-has-color`]: color && !isInternalColor,
|
||||
@ -108,19 +110,22 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const [, mergedCloseIcon] = useClosable({
|
||||
closable,
|
||||
closeIcon: closeIcon ?? tag?.closeIcon,
|
||||
customCloseIconRender: (iconNode: React.ReactNode) =>
|
||||
iconNode === null ? (
|
||||
<CloseOutlined className={`${prefixCls}-close-icon`} onClick={handleCloseClick} />
|
||||
) : (
|
||||
const [, mergedCloseIcon] = useClosable(pickClosable(tagProps), pickClosable(tagContext), {
|
||||
closable: false,
|
||||
closeIconRender: (iconNode: React.ReactNode) => {
|
||||
const replacement = (
|
||||
<span className={`${prefixCls}-close-icon`} onClick={handleCloseClick}>
|
||||
{iconNode}
|
||||
</span>
|
||||
),
|
||||
defaultCloseIcon: null,
|
||||
defaultClosable: false,
|
||||
);
|
||||
return replaceElement(iconNode, replacement, (originProps) => ({
|
||||
onClick: (e: React.MouseEvent<HTMLElement>) => {
|
||||
originProps?.onClick?.(e);
|
||||
handleCloseClick(e);
|
||||
},
|
||||
className: classNames(originProps?.className, `${prefixCls}-close-icon`),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
const isNeedWave =
|
||||
@ -139,7 +144,7 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
|
||||
);
|
||||
|
||||
const tagNode: React.ReactNode = (
|
||||
<span {...props} ref={ref} className={tagClassName} style={tagStyle}>
|
||||
<span {...domProps} ref={ref} className={tagClassName} style={tagStyle}>
|
||||
{kids}
|
||||
{mergedCloseIcon}
|
||||
{isPreset && <PresetCmp key="preset" prefixCls={prefixCls} />}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import useClosable from '../_util/hooks/useClosable';
|
||||
@ -31,17 +30,14 @@ const PurePanel: React.FC<PurePanelProps> = (props) => {
|
||||
|
||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
||||
|
||||
const [mergedClosable, mergedCloseIcon] = useClosable({
|
||||
closable,
|
||||
closeIcon,
|
||||
customCloseIconRender: (icon) =>
|
||||
const [mergedClosable, mergedCloseIcon] = useClosable({ closable, closeIcon }, null, {
|
||||
closable: true,
|
||||
closeIconRender: (icon) =>
|
||||
React.isValidElement(icon)
|
||||
? cloneElement(icon, {
|
||||
className: classNames(icon.props.className, `${prefixCls}-close-icon`),
|
||||
})
|
||||
: icon,
|
||||
defaultCloseIcon: <CloseOutlined />,
|
||||
defaultClosable: true,
|
||||
});
|
||||
|
||||
return wrapCSSVar(
|
||||
|
Loading…
Reference in New Issue
Block a user