mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-12 04:13:13 +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';
|
import useClosable from '../hooks/useClosable';
|
||||||
|
|
||||||
type ParamsOfUseClosable = [
|
type ParamsOfUseClosable = [
|
||||||
UseClosableParams['closable'],
|
closable: UseClosableParams['closable'],
|
||||||
UseClosableParams['closeIcon'],
|
closeIcon: UseClosableParams['closeIcon'],
|
||||||
UseClosableParams['defaultClosable'],
|
defaultClosable: UseClosableParams['defaultClosable'],
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('hooks test', () => {
|
describe('hooks test', () => {
|
||||||
@ -81,7 +81,7 @@ describe('hooks test', () => {
|
|||||||
res: [false, ''],
|
res: [false, ''],
|
||||||
},
|
},
|
||||||
|
|
||||||
// test case like: <Component closeIcon={null | false | element} />
|
// test case like: <Component closeIcon={null | false | element | true} />
|
||||||
{
|
{
|
||||||
params: [undefined, null, undefined],
|
params: [undefined, null, undefined],
|
||||||
res: [false, ''],
|
res: [false, ''],
|
||||||
@ -90,6 +90,10 @@ describe('hooks test', () => {
|
|||||||
params: [undefined, false, undefined],
|
params: [undefined, false, undefined],
|
||||||
res: [false, ''],
|
res: [false, ''],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
params: [undefined, true, undefined],
|
||||||
|
res: [true, '.anticon-close'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
params: [
|
params: [
|
||||||
undefined,
|
undefined,
|
||||||
@ -138,11 +142,16 @@ describe('hooks test', () => {
|
|||||||
React.isValidElement(params[1]) ? 'element' : params[1]
|
React.isValidElement(params[1]) ? 'element' : params[1]
|
||||||
},defaultClosable=${params[2]}. the result should be ${res}`, () => {
|
},defaultClosable=${params[2]}. the result should be ${res}`, () => {
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [closable, closeIcon] = useClosable({
|
const [closable, closeIcon] = useClosable(
|
||||||
closable: params[0],
|
{
|
||||||
closeIcon: params[1],
|
closable: params[0],
|
||||||
defaultClosable: params[2],
|
closeIcon: params[1],
|
||||||
});
|
},
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
closable: params[2],
|
||||||
|
},
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
expect(closable).toBe(res[0]);
|
expect(closable).toBe(res[0]);
|
||||||
}, [closable]);
|
}, [closable]);
|
||||||
@ -159,10 +168,15 @@ describe('hooks test', () => {
|
|||||||
|
|
||||||
it('useClosable with defaultCloseIcon', () => {
|
it('useClosable with defaultCloseIcon', () => {
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [closable, closeIcon] = useClosable({
|
const [closable, closeIcon] = useClosable(
|
||||||
closable: true,
|
{
|
||||||
defaultCloseIcon: <CloseOutlined className="custom-close-icon" />,
|
closable: true,
|
||||||
});
|
},
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
closeIcon: <CloseOutlined className="custom-close-icon" />,
|
||||||
|
},
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
expect(closable).toBe(true);
|
expect(closable).toBe(true);
|
||||||
}, [closable]);
|
}, [closable]);
|
||||||
@ -171,16 +185,37 @@ describe('hooks test', () => {
|
|||||||
const { container } = render(<App />);
|
const { container } = render(<App />);
|
||||||
expect(container.querySelector('.custom-close-icon')).toBeTruthy();
|
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', () => {
|
it('useClosable with customCloseIconRender', () => {
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const customCloseIconRender = (icon: React.ReactNode) => (
|
const customCloseIconRender = (icon: React.ReactNode) => (
|
||||||
<span className="custom-close-wrapper">{icon}</span>
|
<span className="custom-close-wrapper">{icon}</span>
|
||||||
);
|
);
|
||||||
const [closable, closeIcon] = useClosable({
|
const [closable, closeIcon] = useClosable(
|
||||||
closable: true,
|
{
|
||||||
customCloseIconRender,
|
closable: true,
|
||||||
});
|
},
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
closeIconRender: customCloseIconRender,
|
||||||
|
},
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
expect(closable).toBe(true);
|
expect(closable).toBe(true);
|
||||||
}, [closable]);
|
}, [closable]);
|
||||||
|
@ -3,7 +3,23 @@ import React from 'react';
|
|||||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||||
import pickAttrs from 'rc-util/lib/pickAttrs';
|
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 = {
|
export type UseClosableParams = {
|
||||||
closable?: ClosableType;
|
closable?: ClosableType;
|
||||||
@ -11,63 +27,150 @@ export type UseClosableParams = {
|
|||||||
defaultClosable?: boolean;
|
defaultClosable?: boolean;
|
||||||
defaultCloseIcon?: ReactNode;
|
defaultCloseIcon?: ReactNode;
|
||||||
customCloseIconRender?: (closeIcon: ReactNode) => ReactNode;
|
customCloseIconRender?: (closeIcon: ReactNode) => ReactNode;
|
||||||
|
context?: ContextClosable;
|
||||||
};
|
};
|
||||||
|
|
||||||
function useInnerClosable(
|
/** Convert `closable` and `closeIcon` to config object */
|
||||||
closable?: UseClosableParams['closable'],
|
function useClosableConfig(closableCollection?: ClosableCollection | null) {
|
||||||
closeIcon?: ReactNode,
|
const { closable, closeIcon } = closableCollection || {};
|
||||||
defaultClosable?: boolean,
|
|
||||||
) {
|
return React.useMemo(() => {
|
||||||
if (typeof closable === 'boolean') {
|
if (
|
||||||
return closable;
|
// If `closable`, whatever rest be should be true
|
||||||
}
|
!closable &&
|
||||||
if (typeof closable === 'object') {
|
(closable === false || closeIcon === false || closeIcon === null)
|
||||||
return true;
|
) {
|
||||||
}
|
return false;
|
||||||
if (closeIcon === undefined) {
|
}
|
||||||
return !!defaultClosable;
|
|
||||||
}
|
if (closable === undefined && closeIcon === undefined) {
|
||||||
return closeIcon !== false && closeIcon !== null;
|
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,
|
* Assign object without `undefined` field. Will skip if is `false`.
|
||||||
closeIcon,
|
* This helps to handle both closableConfig or false
|
||||||
customCloseIconRender,
|
*/
|
||||||
defaultCloseIcon = <CloseOutlined />,
|
function assignWithoutUndefined<T extends object>(
|
||||||
defaultClosable = false,
|
...objList: (Partial<T> | false | null | undefined)[]
|
||||||
}: UseClosableParams): [closable: boolean, closeIcon: React.ReactNode | null] {
|
): Partial<T> {
|
||||||
const mergedClosable = useInnerClosable(closable, closeIcon, defaultClosable);
|
const target: Partial<T> = {};
|
||||||
|
|
||||||
if (!mergedClosable) {
|
objList.forEach((obj) => {
|
||||||
return [false, null];
|
if (obj) {
|
||||||
}
|
(Object.keys(obj) as (keyof T)[]).forEach((key) => {
|
||||||
const { closeIcon: closableIcon, ...restProps } =
|
if (obj[key] !== undefined) {
|
||||||
typeof closable === 'object'
|
target[key] = obj[key];
|
||||||
? closable
|
}
|
||||||
: ({} as { closeIcon: React.ReactNode } & React.AriaAttributes);
|
});
|
||||||
// Priority: closable.closeIcon > closeIcon > defaultCloseIcon
|
|
||||||
const mergedCloseIcon: ReactNode = (() => {
|
|
||||||
if (typeof closable === 'object' && closableIcon !== undefined) {
|
|
||||||
return closableIcon;
|
|
||||||
}
|
}
|
||||||
return typeof closeIcon === 'boolean' || closeIcon === undefined || closeIcon === null
|
});
|
||||||
? defaultCloseIcon
|
|
||||||
: closeIcon;
|
|
||||||
})();
|
|
||||||
const ariaProps = pickAttrs(restProps, true);
|
|
||||||
|
|
||||||
const plainCloseIcon = customCloseIconRender
|
return target;
|
||||||
? customCloseIconRender(mergedCloseIcon)
|
}
|
||||||
: mergedCloseIcon;
|
|
||||||
|
|
||||||
const closeIconWithAria = React.isValidElement(plainCloseIcon) ? (
|
/** Collection contains the all the props related with closable. e.g. `closable`, `closeIcon` */
|
||||||
React.cloneElement(plainCloseIcon, ariaProps)
|
interface ClosableCollection {
|
||||||
) : (
|
closable?: ClosableType;
|
||||||
<span {...ariaProps}>{plainCloseIcon}</span>
|
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();
|
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', () => {
|
it('Should Table className & style works', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
|
@ -125,7 +125,7 @@ export type MenuConfig = ComponentStyleConfig & Pick<MenuProps, 'expandIcon'>;
|
|||||||
export type TourConfig = Pick<TourProps, 'closeIcon'>;
|
export type TourConfig = Pick<TourProps, 'closeIcon'>;
|
||||||
|
|
||||||
export type ModalConfig = ComponentStyleConfig &
|
export type ModalConfig = ComponentStyleConfig &
|
||||||
Pick<ModalProps, 'classNames' | 'styles' | 'closeIcon'>;
|
Pick<ModalProps, 'classNames' | 'styles' | 'closeIcon' | 'closable'>;
|
||||||
|
|
||||||
export type TabsConfig = ComponentStyleConfig &
|
export type TabsConfig = ComponentStyleConfig &
|
||||||
Pick<TabsProps, 'indicator' | 'indicatorSize' | 'moreIcon' | 'addIcon' | 'removeIcon'>;
|
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 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'>;
|
export type CardConfig = ComponentStyleConfig & Pick<CardProps, 'classNames' | 'styles'>;
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { DrawerProps as RCDrawerProps } from 'rc-drawer';
|
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';
|
import { ConfigContext } from '../config-provider';
|
||||||
|
|
||||||
export interface DrawerClassNames extends NonNullable<RCDrawerProps['classNames']> {
|
export interface DrawerClassNames extends NonNullable<RCDrawerProps['classNames']> {
|
||||||
@ -57,8 +58,6 @@ const DrawerPanel: React.FC<DrawerPanelProps> = (props) => {
|
|||||||
title,
|
title,
|
||||||
footer,
|
footer,
|
||||||
extra,
|
extra,
|
||||||
closeIcon,
|
|
||||||
closable,
|
|
||||||
onClose,
|
onClose,
|
||||||
headerStyle,
|
headerStyle,
|
||||||
bodyStyle,
|
bodyStyle,
|
||||||
@ -78,19 +77,14 @@ const DrawerPanel: React.FC<DrawerPanelProps> = (props) => {
|
|||||||
[onClose],
|
[onClose],
|
||||||
);
|
);
|
||||||
|
|
||||||
const mergedContextCloseIcon = React.useMemo(() => {
|
const [mergedClosable, mergedCloseIcon] = useClosable(
|
||||||
if (typeof drawerContext?.closable === 'object' && drawerContext.closable.closeIcon) {
|
pickClosable(props),
|
||||||
return drawerContext.closable.closeIcon;
|
pickClosable(drawerContext),
|
||||||
}
|
{
|
||||||
return drawerContext?.closeIcon;
|
closable: true,
|
||||||
}, [drawerContext?.closable, drawerContext?.closeIcon]);
|
closeIconRender: customCloseIconRender,
|
||||||
|
},
|
||||||
const [mergedClosable, mergedCloseIcon] = useClosable({
|
);
|
||||||
closable: closable ?? drawerContext?.closable,
|
|
||||||
closeIcon: typeof closeIcon !== 'undefined' ? closeIcon : mergedContextCloseIcon,
|
|
||||||
customCloseIconRender,
|
|
||||||
defaultClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const headerNode = React.useMemo<React.ReactNode>(() => {
|
const headerNode = React.useMemo<React.ReactNode>(() => {
|
||||||
if (!title && !mergedClosable) {
|
if (!title && !mergedClosable) {
|
||||||
|
@ -3,7 +3,7 @@ import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Dialog from 'rc-dialog';
|
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 { useZIndex } from '../_util/hooks/useZIndex';
|
||||||
import { getTransitionName } from '../_util/motion';
|
import { getTransitionName } from '../_util/motion';
|
||||||
import { canUseDocElement } from '../_util/styleChecker';
|
import { canUseDocElement } from '../_util/styleChecker';
|
||||||
@ -44,7 +44,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|||||||
getPopupContainer: getContextPopupContainer,
|
getPopupContainer: getContextPopupContainer,
|
||||||
getPrefixCls,
|
getPrefixCls,
|
||||||
direction,
|
direction,
|
||||||
modal,
|
modal: modalContext,
|
||||||
} = React.useContext(ConfigContext);
|
} = React.useContext(ConfigContext);
|
||||||
|
|
||||||
const handleCancel = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleCancel = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
@ -77,8 +77,6 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|||||||
wrapClassName,
|
wrapClassName,
|
||||||
centered,
|
centered,
|
||||||
getContainer,
|
getContainer,
|
||||||
closeIcon,
|
|
||||||
closable,
|
|
||||||
focusTriggerAfterClose = true,
|
focusTriggerAfterClose = true,
|
||||||
style,
|
style,
|
||||||
// Deprecated
|
// Deprecated
|
||||||
@ -106,13 +104,15 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|||||||
<Footer {...props} onOk={handleOk} onCancel={handleCancel} />
|
<Footer {...props} onOk={handleOk} onCancel={handleCancel} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const [mergedClosable, mergedCloseIcon] = useClosable({
|
const [mergedClosable, mergedCloseIcon] = useClosable(
|
||||||
closable,
|
pickClosable(props),
|
||||||
closeIcon: typeof closeIcon !== 'undefined' ? closeIcon : modal?.closeIcon,
|
pickClosable(modalContext),
|
||||||
customCloseIconRender: (icon) => renderCloseIcon(prefixCls, icon),
|
{
|
||||||
defaultCloseIcon: <CloseOutlined className={`${prefixCls}-close-icon`} />,
|
closable: true,
|
||||||
defaultClosable: true,
|
closeIcon: <CloseOutlined className={`${prefixCls}-close-icon`} />,
|
||||||
});
|
closeIconRender: (icon) => renderCloseIcon(prefixCls, icon),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// ============================ Refs ============================
|
// ============================ Refs ============================
|
||||||
// Select `ant-modal-content` by `panelRef`
|
// Select `ant-modal-content` by `panelRef`
|
||||||
@ -142,15 +142,15 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|||||||
focusTriggerAfterClose={focusTriggerAfterClose}
|
focusTriggerAfterClose={focusTriggerAfterClose}
|
||||||
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
|
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
|
||||||
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
|
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
|
||||||
className={classNames(hashId, className, modal?.className)}
|
className={classNames(hashId, className, modalContext?.className)}
|
||||||
style={{ ...modal?.style, ...style }}
|
style={{ ...modalContext?.style, ...style }}
|
||||||
classNames={{
|
classNames={{
|
||||||
...modal?.classNames,
|
...modalContext?.classNames,
|
||||||
...modalClassNames,
|
...modalClassNames,
|
||||||
wrapper: classNames(wrapClassNameExtended, modalClassNames?.wrapper),
|
wrapper: classNames(wrapClassNameExtended, modalClassNames?.wrapper),
|
||||||
}}
|
}}
|
||||||
styles={{
|
styles={{
|
||||||
...modal?.styles,
|
...modalContext?.styles,
|
||||||
...modalStyles,
|
...modalStyles,
|
||||||
}}
|
}}
|
||||||
panelRef={panelRef}
|
panelRef={panelRef}
|
||||||
|
@ -178,28 +178,25 @@ Array [
|
|||||||
>
|
>
|
||||||
Tag 2
|
Tag 2
|
||||||
<span
|
<span
|
||||||
class="ant-tag-close-icon"
|
aria-label="close-circle"
|
||||||
|
class="anticon anticon-close-circle ant-tag-close-icon"
|
||||||
|
role="img"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span
|
<svg
|
||||||
aria-label="close-circle"
|
aria-hidden="true"
|
||||||
class="anticon anticon-close-circle"
|
data-icon="close-circle"
|
||||||
role="img"
|
fill="currentColor"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
>
|
>
|
||||||
<svg
|
<path
|
||||||
aria-hidden="true"
|
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"
|
||||||
data-icon="close-circle"
|
/>
|
||||||
fill="currentColor"
|
</svg>
|
||||||
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>
|
|
||||||
</span>
|
</span>
|
||||||
</span>,
|
</span>,
|
||||||
]
|
]
|
||||||
@ -845,28 +842,25 @@ exports[`renders components/tag/demo/customize.tsx extend context correctly 1`]
|
|||||||
>
|
>
|
||||||
Tag2
|
Tag2
|
||||||
<span
|
<span
|
||||||
class="ant-tag-close-icon"
|
aria-label="close-circle"
|
||||||
|
class="anticon anticon-close-circle ant-tag-close-icon"
|
||||||
|
role="img"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span
|
<svg
|
||||||
aria-label="close-circle"
|
aria-hidden="true"
|
||||||
class="anticon anticon-close-circle"
|
data-icon="close-circle"
|
||||||
role="img"
|
fill="currentColor"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
>
|
>
|
||||||
<svg
|
<path
|
||||||
aria-hidden="true"
|
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"
|
||||||
data-icon="close-circle"
|
/>
|
||||||
fill="currentColor"
|
</svg>
|
||||||
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>
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -176,28 +176,25 @@ Array [
|
|||||||
>
|
>
|
||||||
Tag 2
|
Tag 2
|
||||||
<span
|
<span
|
||||||
class="ant-tag-close-icon"
|
aria-label="close-circle"
|
||||||
|
class="anticon anticon-close-circle ant-tag-close-icon"
|
||||||
|
role="img"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span
|
<svg
|
||||||
aria-label="close-circle"
|
aria-hidden="true"
|
||||||
class="anticon anticon-close-circle"
|
data-icon="close-circle"
|
||||||
role="img"
|
fill="currentColor"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
>
|
>
|
||||||
<svg
|
<path
|
||||||
aria-hidden="true"
|
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"
|
||||||
data-icon="close-circle"
|
/>
|
||||||
fill="currentColor"
|
</svg>
|
||||||
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>
|
|
||||||
</span>
|
</span>
|
||||||
</span>,
|
</span>,
|
||||||
]
|
]
|
||||||
@ -829,28 +826,25 @@ exports[`renders components/tag/demo/customize.tsx correctly 1`] = `
|
|||||||
>
|
>
|
||||||
Tag2
|
Tag2
|
||||||
<span
|
<span
|
||||||
class="ant-tag-close-icon"
|
aria-label="close-circle"
|
||||||
|
class="anticon anticon-close-circle ant-tag-close-icon"
|
||||||
|
role="img"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span
|
<svg
|
||||||
aria-label="close-circle"
|
aria-hidden="true"
|
||||||
class="anticon anticon-close-circle"
|
data-icon="close-circle"
|
||||||
role="img"
|
fill="currentColor"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
>
|
>
|
||||||
<svg
|
<path
|
||||||
aria-hidden="true"
|
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"
|
||||||
data-icon="close-circle"
|
/>
|
||||||
fill="currentColor"
|
</svg>
|
||||||
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>
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { CheckCircleOutlined } from '@ant-design/icons';
|
||||||
import { Simulate } from 'react-dom/test-utils';
|
import { Simulate } from 'react-dom/test-utils';
|
||||||
|
|
||||||
import { CheckCircleOutlined } from '@ant-design/icons';
|
|
||||||
import Tag from '..';
|
import Tag from '..';
|
||||||
import { resetWarned } from '../../_util/warning';
|
import { resetWarned } from '../../_util/warning';
|
||||||
|
|
||||||
import mountTest from '../../../tests/shared/mountTest';
|
import mountTest from '../../../tests/shared/mountTest';
|
||||||
import rtlTest from '../../../tests/shared/rtlTest';
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
import { act, fireEvent, render } from '../../../tests/utils';
|
import { act, fireEvent, render } from '../../../tests/utils';
|
||||||
@ -219,4 +218,11 @@ describe('Tag', () => {
|
|||||||
waitRaf();
|
waitRaf();
|
||||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
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 * as React from 'react';
|
||||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import omit from 'rc-util/lib/omit';
|
||||||
|
|
||||||
import type { PresetColorType, PresetStatusColorType } from '../_util/colors';
|
import type { PresetColorType, PresetStatusColorType } from '../_util/colors';
|
||||||
import { isPresetColor, isPresetStatusColor } 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 type { LiteralUnion } from '../_util/type';
|
||||||
import { devUseWarning } from '../_util/warning';
|
import { devUseWarning } from '../_util/warning';
|
||||||
import Wave from '../_util/wave';
|
import Wave from '../_util/wave';
|
||||||
@ -21,7 +22,7 @@ export interface TagProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|||||||
className?: string;
|
className?: string;
|
||||||
rootClassName?: string;
|
rootClassName?: string;
|
||||||
color?: LiteralUnion<PresetColorType | PresetStatusColorType>;
|
color?: LiteralUnion<PresetColorType | PresetStatusColorType>;
|
||||||
closable?: boolean;
|
closable?: boolean | ({ closeIcon?: React.ReactNode } & React.AriaAttributes);
|
||||||
/** Advised to use closeIcon instead. */
|
/** Advised to use closeIcon instead. */
|
||||||
closeIcon?: React.ReactNode;
|
closeIcon?: React.ReactNode;
|
||||||
/** @deprecated `visible` will be removed in next major version. */
|
/** @deprecated `visible` will be removed in next major version. */
|
||||||
@ -47,26 +48,27 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
|
|||||||
icon,
|
icon,
|
||||||
color,
|
color,
|
||||||
onClose,
|
onClose,
|
||||||
closeIcon,
|
|
||||||
closable,
|
|
||||||
bordered = true,
|
bordered = true,
|
||||||
|
visible: deprecatedVisible,
|
||||||
...props
|
...props
|
||||||
} = tagProps;
|
} = tagProps;
|
||||||
const { getPrefixCls, direction, tag } = React.useContext(ConfigContext);
|
const { getPrefixCls, direction, tag: tagContext } = React.useContext(ConfigContext);
|
||||||
const [visible, setVisible] = React.useState(true);
|
const [visible, setVisible] = React.useState(true);
|
||||||
|
|
||||||
|
const domProps = omit(props, ['closeIcon', 'closable']);
|
||||||
|
|
||||||
// Warning for deprecated usage
|
// Warning for deprecated usage
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
const warning = devUseWarning('Tag');
|
const warning = devUseWarning('Tag');
|
||||||
|
|
||||||
warning.deprecated(!('visible' in props), 'visible', 'visible && <Tag />');
|
warning.deprecated(!('visible' in tagProps), 'visible', 'visible && <Tag />');
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if ('visible' in props) {
|
if (deprecatedVisible !== undefined) {
|
||||||
setVisible(props.visible!);
|
setVisible(deprecatedVisible!);
|
||||||
}
|
}
|
||||||
}, [props.visible]);
|
}, [deprecatedVisible]);
|
||||||
|
|
||||||
const isPreset = isPresetColor(color);
|
const isPreset = isPresetColor(color);
|
||||||
const isStatus = isPresetStatusColor(color);
|
const isStatus = isPresetStatusColor(color);
|
||||||
@ -74,7 +76,7 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
|
|||||||
|
|
||||||
const tagStyle: React.CSSProperties = {
|
const tagStyle: React.CSSProperties = {
|
||||||
backgroundColor: color && !isInternalColor ? color : undefined,
|
backgroundColor: color && !isInternalColor ? color : undefined,
|
||||||
...tag?.style,
|
...tagContext?.style,
|
||||||
...style,
|
...style,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,7 +86,7 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
|
|||||||
|
|
||||||
const tagClassName = classNames(
|
const tagClassName = classNames(
|
||||||
prefixCls,
|
prefixCls,
|
||||||
tag?.className,
|
tagContext?.className,
|
||||||
{
|
{
|
||||||
[`${prefixCls}-${color}`]: isInternalColor,
|
[`${prefixCls}-${color}`]: isInternalColor,
|
||||||
[`${prefixCls}-has-color`]: color && !isInternalColor,
|
[`${prefixCls}-has-color`]: color && !isInternalColor,
|
||||||
@ -108,19 +110,22 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [, mergedCloseIcon] = useClosable({
|
const [, mergedCloseIcon] = useClosable(pickClosable(tagProps), pickClosable(tagContext), {
|
||||||
closable,
|
closable: false,
|
||||||
closeIcon: closeIcon ?? tag?.closeIcon,
|
closeIconRender: (iconNode: React.ReactNode) => {
|
||||||
customCloseIconRender: (iconNode: React.ReactNode) =>
|
const replacement = (
|
||||||
iconNode === null ? (
|
|
||||||
<CloseOutlined className={`${prefixCls}-close-icon`} onClick={handleCloseClick} />
|
|
||||||
) : (
|
|
||||||
<span className={`${prefixCls}-close-icon`} onClick={handleCloseClick}>
|
<span className={`${prefixCls}-close-icon`} onClick={handleCloseClick}>
|
||||||
{iconNode}
|
{iconNode}
|
||||||
</span>
|
</span>
|
||||||
),
|
);
|
||||||
defaultCloseIcon: null,
|
return replaceElement(iconNode, replacement, (originProps) => ({
|
||||||
defaultClosable: false,
|
onClick: (e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
originProps?.onClick?.(e);
|
||||||
|
handleCloseClick(e);
|
||||||
|
},
|
||||||
|
className: classNames(originProps?.className, `${prefixCls}-close-icon`),
|
||||||
|
}));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isNeedWave =
|
const isNeedWave =
|
||||||
@ -139,7 +144,7 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const tagNode: React.ReactNode = (
|
const tagNode: React.ReactNode = (
|
||||||
<span {...props} ref={ref} className={tagClassName} style={tagStyle}>
|
<span {...domProps} ref={ref} className={tagClassName} style={tagStyle}>
|
||||||
{kids}
|
{kids}
|
||||||
{mergedCloseIcon}
|
{mergedCloseIcon}
|
||||||
{isPreset && <PresetCmp key="preset" prefixCls={prefixCls} />}
|
{isPreset && <PresetCmp key="preset" prefixCls={prefixCls} />}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import useClosable from '../_util/hooks/useClosable';
|
import useClosable from '../_util/hooks/useClosable';
|
||||||
@ -31,17 +30,14 @@ const PurePanel: React.FC<PurePanelProps> = (props) => {
|
|||||||
|
|
||||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
||||||
|
|
||||||
const [mergedClosable, mergedCloseIcon] = useClosable({
|
const [mergedClosable, mergedCloseIcon] = useClosable({ closable, closeIcon }, null, {
|
||||||
closable,
|
closable: true,
|
||||||
closeIcon,
|
closeIconRender: (icon) =>
|
||||||
customCloseIconRender: (icon) =>
|
|
||||||
React.isValidElement(icon)
|
React.isValidElement(icon)
|
||||||
? cloneElement(icon, {
|
? cloneElement(icon, {
|
||||||
className: classNames(icon.props.className, `${prefixCls}-close-icon`),
|
className: classNames(icon.props.className, `${prefixCls}-close-icon`),
|
||||||
})
|
})
|
||||||
: icon,
|
: icon,
|
||||||
defaultCloseIcon: <CloseOutlined />,
|
|
||||||
defaultClosable: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return wrapCSSVar(
|
return wrapCSSVar(
|
||||||
|
Loading…
Reference in New Issue
Block a user