feat: ConfigProvider support classNames and styles for Empty (#52208)
Some checks are pending
Publish Any Commit / build (push) Waiting to run
🔀 Sync mirror to Gitee / mirror (push) Waiting to run
✅ test / lint (push) Waiting to run
✅ test / test-react-legacy (16, 1/2) (push) Waiting to run
✅ test / test-react-legacy (16, 2/2) (push) Waiting to run
✅ test / test-react-legacy (17, 1/2) (push) Waiting to run
✅ test / test-react-legacy (17, 2/2) (push) Waiting to run
✅ test / test-node (push) Waiting to run
✅ test / test-react-latest (dom, 1/2) (push) Waiting to run
✅ test / test-react-latest (dom, 2/2) (push) Waiting to run
✅ test / test-react-latest-dist (dist, 1/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist, 2/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist-min, 1/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist-min, 2/2) (push) Blocked by required conditions
✅ test / test-coverage (push) Blocked by required conditions
✅ test / build (push) Waiting to run
✅ test / test lib/es module (es, 1/2) (push) Waiting to run
✅ test / test lib/es module (es, 2/2) (push) Waiting to run
✅ test / test lib/es module (lib, 1/2) (push) Waiting to run
✅ test / test lib/es module (lib, 2/2) (push) Waiting to run
👁️ Visual Regression Persist Start / test image (push) Waiting to run

* feat: ConfigProvider support classNames and styles for Empty

* fix

* fix
This commit is contained in:
thinkasany 2025-01-03 14:48:16 +08:00 committed by GitHub
parent 32bc7fd85f
commit 9fcce8ffdc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 182 additions and 24 deletions

View File

@ -39,6 +39,7 @@ import type { PopoverProps } from '../popover';
import type { PopconfirmProps } from '../popconfirm'; import type { PopconfirmProps } from '../popconfirm';
import type { DescriptionsProps } from '../descriptions'; import type { DescriptionsProps } from '../descriptions';
import type { SliderProps } from '../slider'; import type { SliderProps } from '../slider';
import type { EmptyProps } from '../empty';
export const defaultPrefixCls = 'ant'; export const defaultPrefixCls = 'ant';
export const defaultIconPrefixCls = 'anticon'; export const defaultIconPrefixCls = 'anticon';
@ -141,6 +142,8 @@ export type TourConfig = Pick<TourProps, 'closeIcon'>;
export type DescriptionsConfig = ComponentStyleConfig & export type DescriptionsConfig = ComponentStyleConfig &
Pick<DescriptionsProps, 'classNames' | 'styles'>; Pick<DescriptionsProps, 'classNames' | 'styles'>;
export type EmptyConfig = ComponentStyleConfig & Pick<EmptyProps, 'classNames' | 'styles'>;
export type ModalConfig = ComponentStyleConfig & export type ModalConfig = ComponentStyleConfig &
Pick<ModalProps, 'classNames' | 'styles' | 'closeIcon' | 'closable'>; Pick<ModalProps, 'classNames' | 'styles' | 'closeIcon' | 'closable'>;
@ -293,7 +296,7 @@ export interface ConfigConsumerProps {
menu?: MenuConfig; menu?: MenuConfig;
checkbox?: ComponentStyleConfig; checkbox?: ComponentStyleConfig;
descriptions?: DescriptionsConfig; descriptions?: DescriptionsConfig;
empty?: ComponentStyleConfig; empty?: EmptyConfig;
badge?: BadgeConfig; badge?: BadgeConfig;
radio?: ComponentStyleConfig; radio?: ComponentStyleConfig;
rate?: ComponentStyleConfig; rate?: ComponentStyleConfig;

View File

@ -127,7 +127,7 @@ const {
| divider | Set Divider common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | divider | Set Divider common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| drawer | Set Drawer common props | { className?: string, style?: React.CSSProperties, classNames?: [DrawerProps\["classNames"\]](/components/drawer#api), styles?: [DrawerProps\["styles"\]](/components/drawer#api), closeIcon?: ReactNode } | - | 5.7.0, `classNames` and `styles`: 5.10.0, `closeIcon`: 5.14.0 | | drawer | Set Drawer common props | { className?: string, style?: React.CSSProperties, classNames?: [DrawerProps\["classNames"\]](/components/drawer#api), styles?: [DrawerProps\["styles"\]](/components/drawer#api), closeIcon?: ReactNode } | - | 5.7.0, `classNames` and `styles`: 5.10.0, `closeIcon`: 5.14.0 |
| dropdown | Set Dropdown common props | { className?: string, style?: React.CSSProperties } | - | 5.11.0 | | dropdown | Set Dropdown common props | { className?: string, style?: React.CSSProperties } | - | 5.11.0 |
| empty | Set Empty common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | empty | Set Empty common props | { className?: string, style?: React.CSSProperties, classNames?: [EmptyProps\["classNames"\]](/components/empty#api), styles?: [EmptyProps\["styles"\]](/components/empty#api) } | - | 5.7.0, `classNames` and `styles`: 5.23.0 |
| flex | Set Flex common props | { className?: string, style?: React.CSSProperties, vertical?: boolean } | - | 5.10.0 | | flex | Set Flex common props | { className?: string, style?: React.CSSProperties, vertical?: boolean } | - | 5.10.0 |
| floatButtonGroup | Set FloatButton.Group common props | { closeIcon?: React.ReactNode } | - | 5.16.0 | | floatButtonGroup | Set FloatButton.Group common props | { closeIcon?: React.ReactNode } | - | 5.16.0 |
| form | Set Form common props | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional`, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options) } | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0; className: 5.7.0; style: 5.7.0 | | form | Set Form common props | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional`, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options) } | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0; className: 5.7.0; style: 5.7.0 |

View File

@ -59,6 +59,7 @@ import type {
TreeSelectConfig, TreeSelectConfig,
Variant, Variant,
WaveConfig, WaveConfig,
EmptyConfig,
} from './context'; } from './context';
import { import {
ConfigConsumer, ConfigConsumer,
@ -211,7 +212,7 @@ export interface ConfigProviderProps {
floatButtonGroup?: FloatButtonGroupConfig; floatButtonGroup?: FloatButtonGroupConfig;
checkbox?: ComponentStyleConfig; checkbox?: ComponentStyleConfig;
descriptions?: ComponentStyleConfig; descriptions?: ComponentStyleConfig;
empty?: ComponentStyleConfig; empty?: EmptyConfig;
badge?: BadgeConfig; badge?: BadgeConfig;
radio?: ComponentStyleConfig; radio?: ComponentStyleConfig;
rate?: ComponentStyleConfig; rate?: ComponentStyleConfig;

View File

@ -129,7 +129,7 @@ const {
| divider | 设置 Divider 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | divider | 设置 Divider 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| drawer | 设置 Drawer 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [DrawerProps\["classNames"\]](/components/drawer-cn#api), styles?: [DrawerProps\["styles"\]](/components/drawer-cn#api), closeIcon?: ReactNode } | - | 5.7.0, `classNames``styles`: 5.10.0, `closeIcon`: 5.14.0 | | drawer | 设置 Drawer 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [DrawerProps\["classNames"\]](/components/drawer-cn#api), styles?: [DrawerProps\["styles"\]](/components/drawer-cn#api), closeIcon?: ReactNode } | - | 5.7.0, `classNames``styles`: 5.10.0, `closeIcon`: 5.14.0 |
| dropdown | 设置 Dropdown 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.11.0 | | dropdown | 设置 Dropdown 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.11.0 |
| empty | 设置 Empty 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | empty | 设置 Empty 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?:[EmptyProps\["classNames"\]](/components/empty-cn#api), styles?: [EmptyProps\["styles"\]](/components/empty-cn#api) } | - | 5.7.0, `classNames``styles`: 5.23.0 |
| flex | 设置 Flex 组件的通用属性 | { className?: string, style?: React.CSSProperties, vertical?: boolean } | - | 5.10.0 | | flex | 设置 Flex 组件的通用属性 | { className?: string, style?: React.CSSProperties, vertical?: boolean } | - | 5.10.0 |
| floatButtonGroup | 设置 FloatButton.Group 组件的通用属性 | { closeIcon?: React.ReactNode } | - | 5.16.0 | | floatButtonGroup | 设置 FloatButton.Group 组件的通用属性 | { closeIcon?: React.ReactNode } | - | 5.16.0 |
| form | 设置 Form 组件的通用属性 | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form-cn#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)} | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0; className: 5.7.0; style: 5.7.0 | | form | 设置 Form 组件的通用属性 | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form-cn#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)} | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0; className: 5.7.0; style: 5.7.0 |

View File

@ -12,7 +12,7 @@ describe('Empty', () => {
rtlTest(Empty); rtlTest(Empty);
it('image size should change', () => { it('image size should change', () => {
const { container } = render(<Empty imageStyle={{ height: 20 }} />); const { container } = render(<Empty styles={{ image: { height: 20 } }} />);
expect(container.querySelector<HTMLDivElement>('.ant-empty-image')?.style.height).toBe('20px'); expect(container.querySelector<HTMLDivElement>('.ant-empty-image')?.style.height).toBe('20px');
}); });
@ -45,4 +45,50 @@ describe('Empty', () => {
opacity: 0.65, opacity: 0.65,
}); });
}); });
it('should apply custom styles to Empty', () => {
const customClassNames = {
root: 'custom-root',
description: 'custom-description',
footer: 'custom-footer',
image: 'custom-image',
};
const customStyles = {
root: { color: 'red' },
description: { color: 'green' },
footer: { color: 'yellow' },
image: { backgroundColor: 'black' },
};
const { container } = render(
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
classNames={customClassNames}
styles={customStyles}
description={'Description'}
>
<div>Create Now</div>
</Empty>,
);
const emptyElement = container.querySelector('.ant-empty') as HTMLElement;
const emptyFooterElement = container.querySelector('.ant-empty-footer') as HTMLElement;
const emptyDescriptionElement = container.querySelector(
'.ant-empty-description',
) as HTMLElement;
const emptyImageElement = container.querySelector('.ant-empty-image') as HTMLElement;
// check classNames
expect(emptyElement.classList).toContain('custom-root');
expect(emptyFooterElement.classList).toContain('custom-footer');
expect(emptyDescriptionElement.classList).toContain('custom-description');
expect(emptyImageElement.classList).toContain('custom-image');
// check styles
expect(emptyElement.style.color).toBe('red');
expect(emptyDescriptionElement.style.color).toBe('green');
expect(emptyFooterElement.style.color).toBe('yellow');
expect(emptyImageElement.style.backgroundColor).toBe('black');
});
}); });

View File

@ -0,0 +1,48 @@
import React from 'react';
import { Button, Empty, Typography } from 'antd';
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
root: '根元素',
image: '图标元素',
description: '描述元素',
footer: '底部元素',
},
en: {
root: 'Root element',
image: 'Image element',
description: 'Description element',
footer: 'Footer element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
semantics={[
{ name: 'root', desc: locale.root, version: '5.23.0' },
{ name: 'image', desc: locale.image, version: '5.23.0' },
{ name: 'description', desc: locale.description, version: '5.23.0' },
{ name: 'footer', desc: locale.footer, version: '5.23.0' },
]}
>
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
styles={{ image: { height: 60 } }}
description={
<Typography.Text>
Customize <a href="#API">Description</a>
</Typography.Text>
}
>
<Button type="primary">Create Now</Button>
</Empty>
</SemanticPreview>
);
};
export default App;

View File

@ -4,7 +4,7 @@ import { Button, Empty, Typography } from 'antd';
const App: React.FC = () => ( const App: React.FC = () => (
<Empty <Empty
image="https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg" image="https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg"
imageStyle={{ height: 60 }} styles={{ image: { height: 60 } }}
description={ description={
<Typography.Text> <Typography.Text>
Customize <a href="#API">Description</a> Customize <a href="#API">Description</a>

View File

@ -64,6 +64,10 @@ Common props ref[Common props](/docs/react/common-props)
} }
</style> </style>
## Semantic DOM
<code src="./demo/_semantic.tsx" simplify="true"></code>
## Design Token ## Design Token
<ComponentTokenTable component="Empty"></ComponentTokenTable> <ComponentTokenTable component="Empty"></ComponentTokenTable>

View File

@ -1,6 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { devUseWarning } from '../_util/warning';
import { ConfigContext } from '../config-provider'; import { ConfigContext } from '../config-provider';
import { useLocale } from '../locale'; import { useLocale } from '../locale';
import DefaultEmptyImg from './empty'; import DefaultEmptyImg from './empty';
@ -14,16 +15,19 @@ export interface TransferLocale {
description: string; description: string;
} }
export type SemanticName = 'root' | 'image' | 'description' | 'footer';
export interface EmptyProps { export interface EmptyProps {
prefixCls?: string; prefixCls?: string;
className?: string; className?: string;
rootClassName?: string; rootClassName?: string;
style?: React.CSSProperties; style?: React.CSSProperties;
/** @since 3.16.0 */ /** @deprecated Please use `styles={{ image: {} }}` instead */
imageStyle?: React.CSSProperties; imageStyle?: React.CSSProperties;
image?: React.ReactNode; image?: React.ReactNode;
description?: React.ReactNode; description?: React.ReactNode;
children?: React.ReactNode; children?: React.ReactNode;
classNames?: Partial<Record<SemanticName, string>>;
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
} }
type CompoundedComponent = React.FC<EmptyProps> & { type CompoundedComponent = React.FC<EmptyProps> & {
@ -31,17 +35,20 @@ type CompoundedComponent = React.FC<EmptyProps> & {
PRESENTED_IMAGE_SIMPLE: React.ReactNode; PRESENTED_IMAGE_SIMPLE: React.ReactNode;
}; };
const Empty: CompoundedComponent = ({ const Empty: CompoundedComponent = (props) => {
className, const {
rootClassName, className,
prefixCls: customizePrefixCls, rootClassName,
image = defaultEmptyImg, prefixCls: customizePrefixCls,
description, image = defaultEmptyImg,
children, description,
imageStyle, children,
style, imageStyle,
...restProps style,
}) => { classNames: emptyClassNames,
styles,
...restProps
} = props;
const { getPrefixCls, direction, empty } = React.useContext(ConfigContext); const { getPrefixCls, direction, empty } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('empty', customizePrefixCls); const prefixCls = getPrefixCls('empty', customizePrefixCls);
@ -60,6 +67,15 @@ const Empty: CompoundedComponent = ({
imageNode = image; imageNode = image;
} }
// ============================= Warning ==============================
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Empty');
[['imageStyle', 'styles: { image: {} }']].forEach(([deprecatedName, newName]) => {
warning.deprecated(!(deprecatedName in props), deprecatedName, newName);
});
}
return wrapCSSVar( return wrapCSSVar(
<div <div
className={classNames( className={classNames(
@ -73,15 +89,49 @@ const Empty: CompoundedComponent = ({
}, },
className, className,
rootClassName, rootClassName,
empty?.classNames?.root,
emptyClassNames?.root,
)} )}
style={{ ...empty?.style, ...style }} style={{ ...empty?.styles?.root, ...empty?.style, ...styles?.root, ...style }}
{...restProps} {...restProps}
> >
<div className={`${prefixCls}-image`} style={imageStyle}> <div
className={classNames(
`${prefixCls}-image`,
empty?.classNames?.image,
emptyClassNames?.image,
)}
style={{ ...imageStyle, ...empty?.styles?.image, ...styles?.image }}
>
{imageNode} {imageNode}
</div> </div>
{des && <div className={`${prefixCls}-description`}>{des}</div>} {des && (
{children && <div className={`${prefixCls}-footer`}>{children}</div>} <div
className={classNames(
`${prefixCls}-description`,
empty?.classNames?.description,
emptyClassNames?.description,
)}
style={{ ...empty?.styles?.description, ...styles?.description }}
>
{des}
</div>
)}
{children && (
<div
className={classNames(
`${prefixCls}-footer`,
empty?.classNames?.footer,
emptyClassNames?.footer,
)}
style={{
...empty?.styles?.footer,
...styles?.footer,
}}
>
{children}
</div>
)}
</div>, </div>,
); );
}; };

View File

@ -65,6 +65,10 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*obM7S5lIxeMAAA
} }
</style> </style>
## Semantic DOM
<code src="./demo/_semantic.tsx" simplify="true"></code>
## 主题变量Design Token ## 主题变量Design Token
<ComponentTokenTable component="Empty"></ComponentTokenTable> <ComponentTokenTable component="Empty"></ComponentTokenTable>

View File

@ -381,8 +381,10 @@ const FilterDropdown = <RecordType extends AnyObject = AnyObject>(
<Empty <Empty
image={Empty.PRESENTED_IMAGE_SIMPLE} image={Empty.PRESENTED_IMAGE_SIMPLE}
description={locale.filterEmptyText} description={locale.filterEmptyText}
imageStyle={{ styles={{
height: 24, image: {
height: 24,
},
}} }}
style={{ style={{
margin: 0, margin: 0,