feat: ConfigProvider static function support iconPrefixCls (#30925)

* feat: Static iconPrefixCls

* feat: message & notification support iconPrefixCls

* docs: Update doc

* chore: Force update

* fix: ts def
This commit is contained in:
二货机器人 2021-06-09 15:36:59 +08:00 committed by GitHub
parent 827d34db1f
commit eb70f00513
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 139 additions and 98 deletions

View File

@ -61,7 +61,8 @@ Setting `Modal`、`Message`、`Notification` rootPrefixCls.
```jsx ```jsx
ConfigProvider.config({ ConfigProvider.config({
prefixCls: 'ant', prefixCls: 'ant', // 4.13.0+
iconPrefixCls: 'anticon', // 4.17.0+
}); });
``` ```

View File

@ -84,11 +84,19 @@ interface ProviderChildrenProps extends ConfigProviderProps {
} }
export const defaultPrefixCls = 'ant'; export const defaultPrefixCls = 'ant';
export const defaultIconPrefixCls = 'anticon';
let globalPrefixCls: string; let globalPrefixCls: string;
let globalIconPrefixCls: string;
const setGlobalConfig = (params: Pick<ConfigProviderProps, 'prefixCls'>) => { const setGlobalConfig = ({
if (params.prefixCls !== undefined) { prefixCls,
globalPrefixCls = params.prefixCls; iconPrefixCls,
}: Pick<ConfigProviderProps, 'prefixCls' | 'iconPrefixCls'>) => {
if (prefixCls !== undefined) {
globalPrefixCls = prefixCls;
}
if (iconPrefixCls !== undefined) {
globalIconPrefixCls = iconPrefixCls;
} }
}; };
@ -96,11 +104,16 @@ function getGlobalPrefixCls() {
return globalPrefixCls || defaultPrefixCls; return globalPrefixCls || defaultPrefixCls;
} }
function getGlobalIconPrefixCls() {
return globalIconPrefixCls || defaultIconPrefixCls;
}
export const globalConfig = () => ({ export const globalConfig = () => ({
getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => { getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => {
if (customizePrefixCls) return customizePrefixCls; if (customizePrefixCls) return customizePrefixCls;
return suffixCls ? `${getGlobalPrefixCls()}-${suffixCls}` : getGlobalPrefixCls(); return suffixCls ? `${getGlobalPrefixCls()}-${suffixCls}` : getGlobalPrefixCls();
}, },
getIconPrefixCls: getGlobalIconPrefixCls,
getRootPrefixCls: (rootPrefixCls?: string, customizePrefixCls?: string) => { getRootPrefixCls: (rootPrefixCls?: string, customizePrefixCls?: string) => {
// Customize rootPrefixCls is first priority // Customize rootPrefixCls is first priority
if (rootPrefixCls) { if (rootPrefixCls) {
@ -187,9 +200,10 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = props => {
}, },
); );
const memoIconContextValue = React.useMemo(() => ({ prefixCls: iconPrefixCls, csp }), [ const memoIconContextValue = React.useMemo(
iconPrefixCls, () => ({ prefixCls: iconPrefixCls, csp }),
]); [iconPrefixCls],
);
let childNode = children; let childNode = children;
// Additional Form provider // Additional Form provider

View File

@ -62,7 +62,8 @@ export default () => (
```jsx ```jsx
ConfigProvider.config({ ConfigProvider.config({
prefixCls: 'ant', prefixCls: 'ant', // 4.13.0+
iconPrefixCls: 'anticon', // 4.17.0+
}); });
``` ```

View File

@ -96,19 +96,20 @@ describe('message.config', () => {
}); });
it('should be able to global config rootPrefixCls', () => { it('should be able to global config rootPrefixCls', () => {
ConfigProvider.config({ prefixCls: 'prefix-test' }); ConfigProvider.config({ prefixCls: 'prefix-test', iconPrefixCls: 'bamboo' });
message.info('last'); message.info('last');
expect(document.querySelectorAll('.ant-message-notice').length).toBe(0); expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(0);
expect(document.querySelectorAll('.prefix-test-message-notice').length).toBe(1); expect(document.querySelectorAll('.prefix-test-message-notice')).toHaveLength(1);
ConfigProvider.config({ prefixCls: 'ant' }); expect(document.querySelectorAll('.bamboo-info-circle')).toHaveLength(1);
ConfigProvider.config({ prefixCls: 'ant', iconPrefixCls: null });
}); });
it('should be able to config prefixCls', () => { it('should be able to config prefixCls', () => {
message.config({ message.config({
prefixCls: 'prefix-test', prefixCls: 'prefix-test',
}); });
message.info('last'); message.info('last');
expect(document.querySelectorAll('.ant-message-notice').length).toBe(0); expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(0);
expect(document.querySelectorAll('.prefix-test-notice').length).toBe(1); expect(document.querySelectorAll('.prefix-test-notice')).toHaveLength(1);
message.config({ message.config({
prefixCls: '', // can be set to empty, ant default value is set in ConfigProvider prefixCls: '', // can be set to empty, ant default value is set in ConfigProvider
}); });
@ -119,7 +120,7 @@ describe('message.config', () => {
transitionName: '', transitionName: '',
}); });
message.info('last'); message.info('last');
expect(document.querySelectorAll('.ant-move-up-enter').length).toBe(0); expect(document.querySelectorAll('.ant-move-up-enter')).toHaveLength(0);
message.config({ message.config({
transitionName: 'ant-move-up', transitionName: 'ant-move-up',
}); });

View File

@ -11,7 +11,7 @@ import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled'; import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled';
import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled'; import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled';
import createUseMessage from './hooks/useMessage'; import createUseMessage from './hooks/useMessage';
import { globalConfig } from '../config-provider'; import ConfigProvider, { globalConfig } from '../config-provider';
type NoticeType = 'info' | 'success' | 'error' | 'warning' | 'loading'; type NoticeType = 'info' | 'success' | 'error' | 'warning' | 'loading';
@ -74,16 +74,18 @@ function getRCNotificationInstance(
callback: (info: { callback: (info: {
prefixCls: string; prefixCls: string;
rootPrefixCls: string; rootPrefixCls: string;
iconPrefixCls: string;
instance: RCNotificationInstance; instance: RCNotificationInstance;
}) => void, }) => void,
) { ) {
const { prefixCls: customizePrefixCls } = args; const { prefixCls: customizePrefixCls } = args;
const { getPrefixCls, getRootPrefixCls } = globalConfig(); const { getPrefixCls, getRootPrefixCls, getIconPrefixCls } = globalConfig();
const prefixCls = getPrefixCls('message', customizePrefixCls || localPrefixCls); const prefixCls = getPrefixCls('message', customizePrefixCls || localPrefixCls);
const rootPrefixCls = getRootPrefixCls(args.rootPrefixCls, prefixCls); const rootPrefixCls = getRootPrefixCls(args.rootPrefixCls, prefixCls);
const iconPrefixCls = getIconPrefixCls();
if (messageInstance) { if (messageInstance) {
callback({ prefixCls, rootPrefixCls, instance: messageInstance }); callback({ prefixCls, rootPrefixCls, iconPrefixCls, instance: messageInstance });
return; return;
} }
@ -97,7 +99,7 @@ function getRCNotificationInstance(
RCNotification.newInstance(instanceConfig, (instance: any) => { RCNotification.newInstance(instanceConfig, (instance: any) => {
if (messageInstance) { if (messageInstance) {
callback({ prefixCls, rootPrefixCls, instance: messageInstance }); callback({ prefixCls, rootPrefixCls, iconPrefixCls, instance: messageInstance });
return; return;
} }
messageInstance = instance; messageInstance = instance;
@ -106,7 +108,7 @@ function getRCNotificationInstance(
(messageInstance as any).config = instanceConfig; (messageInstance as any).config = instanceConfig;
} }
callback({ prefixCls, rootPrefixCls, instance }); callback({ prefixCls, rootPrefixCls, iconPrefixCls, instance });
}); });
} }
@ -139,7 +141,11 @@ export interface ArgsProps {
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void; onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
} }
function getRCNoticeProps(args: ArgsProps, prefixCls: string): NoticeContent { function getRCNoticeProps(
args: ArgsProps,
prefixCls: string,
iconPrefixCls?: string,
): NoticeContent {
const duration = args.duration !== undefined ? args.duration : defaultDuration; const duration = args.duration !== undefined ? args.duration : defaultDuration;
const IconComponent = typeToIcon[args.type]; const IconComponent = typeToIcon[args.type];
const messageClass = classNames(`${prefixCls}-custom-content`, { const messageClass = classNames(`${prefixCls}-custom-content`, {
@ -152,10 +158,12 @@ function getRCNoticeProps(args: ArgsProps, prefixCls: string): NoticeContent {
style: args.style || {}, style: args.style || {},
className: args.className, className: args.className,
content: ( content: (
<div className={messageClass}> <ConfigProvider iconPrefixCls={iconPrefixCls}>
{args.icon || (IconComponent && <IconComponent />)} <div className={messageClass}>
<span>{args.content}</span> {args.icon || (IconComponent && <IconComponent />)}
</div> <span>{args.content}</span>
</div>
</ConfigProvider>
), ),
onClose: args.onClose, onClose: args.onClose,
onClick: args.onClick, onClick: args.onClick,
@ -172,8 +180,10 @@ function notice(args: ArgsProps): MessageType {
return resolve(true); return resolve(true);
}; };
getRCNotificationInstance(args, ({ prefixCls, instance }) => { getRCNotificationInstance(args, ({ prefixCls, iconPrefixCls, instance }) => {
instance.notice(getRCNoticeProps({ ...args, key: target, onClose: callback }, prefixCls)); instance.notice(
getRCNoticeProps({ ...args, key: target, onClose: callback }, prefixCls, iconPrefixCls),
);
}); });
}); });
const result: any = () => { const result: any = () => {

View File

@ -11,6 +11,7 @@ interface ConfirmDialogProps extends ModalFuncProps {
close: (...args: any[]) => void; close: (...args: any[]) => void;
autoFocusButton?: null | 'ok' | 'cancel'; autoFocusButton?: null | 'ok' | 'cancel';
rootPrefixCls: string; rootPrefixCls: string;
iconPrefixCls?: string;
} }
const ConfirmDialog = (props: ConfirmDialogProps) => { const ConfirmDialog = (props: ConfirmDialogProps) => {
@ -33,6 +34,7 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
direction, direction,
prefixCls, prefixCls,
rootPrefixCls, rootPrefixCls,
iconPrefixCls,
bodyStyle, bodyStyle,
closable = false, closable = false,
closeIcon, closeIcon,
@ -78,33 +80,33 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
); );
return ( return (
<Dialog <ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls}>
prefixCls={prefixCls} <Dialog
className={classString} prefixCls={prefixCls}
wrapClassName={classNames({ [`${contentPrefixCls}-centered`]: !!props.centered })} className={classString}
onCancel={() => close({ triggerCancel: true })} wrapClassName={classNames({ [`${contentPrefixCls}-centered`]: !!props.centered })}
visible={visible} onCancel={() => close({ triggerCancel: true })}
title="" visible={visible}
footer="" title=""
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)} footer=""
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)} transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
mask={mask} maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
maskClosable={maskClosable} mask={mask}
maskStyle={maskStyle} maskClosable={maskClosable}
style={style} maskStyle={maskStyle}
width={width} style={style}
zIndex={zIndex} width={width}
afterClose={afterClose} zIndex={zIndex}
keyboard={keyboard} afterClose={afterClose}
centered={centered} keyboard={keyboard}
getContainer={getContainer} centered={centered}
closable={closable} getContainer={getContainer}
closeIcon={closeIcon} closable={closable}
modalRender={modalRender} closeIcon={closeIcon}
focusTriggerAfterClose={focusTriggerAfterClose} modalRender={modalRender}
> focusTriggerAfterClose={focusTriggerAfterClose}
<div className={`${contentPrefixCls}-body-wrapper`}> >
<ConfigProvider prefixCls={rootPrefixCls}> <div className={`${contentPrefixCls}-body-wrapper`}>
<div className={`${contentPrefixCls}-body`} style={bodyStyle}> <div className={`${contentPrefixCls}-body`} style={bodyStyle}>
{icon} {icon}
{props.title === undefined ? null : ( {props.title === undefined ? null : (
@ -112,22 +114,22 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
)} )}
<div className={`${contentPrefixCls}-content`}>{props.content}</div> <div className={`${contentPrefixCls}-content`}>{props.content}</div>
</div> </div>
</ConfigProvider> <div className={`${contentPrefixCls}-btns`}>
<div className={`${contentPrefixCls}-btns`}> {cancelButton}
{cancelButton} <ActionButton
<ActionButton type={okType}
type={okType} actionFn={onOk}
actionFn={onOk} close={close}
close={close} autoFocus={autoFocusButton === 'ok'}
autoFocus={autoFocusButton === 'ok'} buttonProps={okButtonProps}
buttonProps={okButtonProps} prefixCls={`${rootPrefixCls}-btn`}
prefixCls={`${rootPrefixCls}-btn`} >
> {okText}
{okText} </ActionButton>
</ActionButton> </div>
</div> </div>
</div> </Dialog>
</Dialog> </ConfigProvider>
); );
}; };

View File

@ -1,5 +1,7 @@
import * as React from 'react';
import TestUtils, { act } from 'react-dom/test-utils'; import TestUtils, { act } from 'react-dom/test-utils';
import CSSMotion from 'rc-motion'; import CSSMotion from 'rc-motion';
import { SmileOutlined } from '@ant-design/icons';
import { genCSSMotion } from 'rc-motion/lib/CSSMotion'; import { genCSSMotion } from 'rc-motion/lib/CSSMotion';
import KeyCode from 'rc-util/lib/KeyCode'; import KeyCode from 'rc-util/lib/KeyCode';
import { resetWarned } from 'rc-util/lib/warning'; import { resetWarned } from 'rc-util/lib/warning';
@ -472,13 +474,14 @@ describe('Modal.confirm triggers callbacks correctly', () => {
it('should be able to global config rootPrefixCls', () => { it('should be able to global config rootPrefixCls', () => {
jest.useFakeTimers(); jest.useFakeTimers();
ConfigProvider.config({ prefixCls: 'my' }); ConfigProvider.config({ prefixCls: 'my', iconPrefixCls: 'bamboo' });
confirm({ title: 'title' }); confirm({ title: 'title', icon: <SmileOutlined /> });
jest.runAllTimers(); jest.runAllTimers();
expect(document.querySelectorAll('.ant-btn').length).toBe(0); expect(document.querySelectorAll('.ant-btn').length).toBe(0);
expect(document.querySelectorAll('.my-btn').length).toBe(2); expect(document.querySelectorAll('.my-btn').length).toBe(2);
expect(document.querySelectorAll('.bamboo-smile').length).toBe(1);
expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1); expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1);
ConfigProvider.config({ prefixCls: 'ant' }); ConfigProvider.config({ prefixCls: 'ant', iconPrefixCls: null });
jest.useRealTimers(); jest.useRealTimers();
}); });

View File

@ -18,9 +18,7 @@ function getRootPrefixCls() {
type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps); type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps);
export type ModalFunc = ( export type ModalFunc = (props: ModalFuncProps) => {
props: ModalFuncProps,
) => {
destroy: () => void; destroy: () => void;
update: (configUpdate: ConfigUpdate) => void; update: (configUpdate: ConfigUpdate) => void;
}; };
@ -60,16 +58,18 @@ export default function confirm(config: ModalFuncProps) {
*/ */
setTimeout(() => { setTimeout(() => {
const runtimeLocale = getConfirmLocale(); const runtimeLocale = getConfirmLocale();
const { getPrefixCls } = globalConfig(); const { getPrefixCls, getIconPrefixCls } = globalConfig();
// because Modal.config  set rootPrefixCls, which is different from other components // because Modal.config  set rootPrefixCls, which is different from other components
const rootPrefixCls = getPrefixCls(undefined, getRootPrefixCls()); const rootPrefixCls = getPrefixCls(undefined, getRootPrefixCls());
const prefixCls = customizePrefixCls || `${rootPrefixCls}-modal`; const prefixCls = customizePrefixCls || `${rootPrefixCls}-modal`;
const iconPrefixCls = getIconPrefixCls();
ReactDOM.render( ReactDOM.render(
<ConfirmDialog <ConfirmDialog
{...props} {...props}
prefixCls={prefixCls} prefixCls={prefixCls}
rootPrefixCls={rootPrefixCls} rootPrefixCls={rootPrefixCls}
iconPrefixCls={iconPrefixCls}
okText={okText || (props.okCancel ? runtimeLocale.okText : runtimeLocale.justOkText)} okText={okText || (props.okCancel ? runtimeLocale.okText : runtimeLocale.justOkText)}
cancelText={cancelText || runtimeLocale.cancelText} cancelText={cancelText || runtimeLocale.cancelText}
/>, />,

View File

@ -102,11 +102,12 @@ describe('notification', () => {
}); });
it('should be able to global config rootPrefixCls', () => { it('should be able to global config rootPrefixCls', () => {
ConfigProvider.config({ prefixCls: 'prefix-test' }); ConfigProvider.config({ prefixCls: 'prefix-test', iconPrefixCls: 'bamboo' });
notification.open({ message: 'Notification Title', duration: 0 }); notification.success({ message: 'Notification Title', duration: 0 });
expect(document.querySelectorAll('.ant-notification-notice').length).toBe(0); expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(0);
expect(document.querySelectorAll('.prefix-test-notification-notice').length).toBe(1); expect(document.querySelectorAll('.prefix-test-notification-notice')).toHaveLength(1);
ConfigProvider.config({ prefixCls: 'ant' }); expect(document.querySelectorAll('.bamboo-check-circle')).toHaveLength(1);
ConfigProvider.config({ prefixCls: 'ant', iconPrefixCls: null });
}); });
it('should be able to config prefixCls', () => { it('should be able to config prefixCls', () => {
@ -117,8 +118,8 @@ describe('notification', () => {
message: 'Notification Title', message: 'Notification Title',
duration: 0, duration: 0,
}); });
expect(document.querySelectorAll('.ant-notification-notice').length).toBe(0); expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(0);
expect(document.querySelectorAll('.prefix-test-notice').length).toBe(1); expect(document.querySelectorAll('.prefix-test-notice')).toHaveLength(1);
notification.config({ notification.config({
prefixCls: '', prefixCls: '',
}); });

View File

@ -8,7 +8,7 @@ import CloseCircleOutlined from '@ant-design/icons/CloseCircleOutlined';
import ExclamationCircleOutlined from '@ant-design/icons/ExclamationCircleOutlined'; import ExclamationCircleOutlined from '@ant-design/icons/ExclamationCircleOutlined';
import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined'; import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined';
import createUseNotification from './hooks/useNotification'; import createUseNotification from './hooks/useNotification';
import { globalConfig } from '../config-provider'; import ConfigProvider, { globalConfig } from '../config-provider';
export type NotificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; export type NotificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
@ -108,7 +108,11 @@ function getPlacementStyle(
function getNotificationInstance( function getNotificationInstance(
args: ArgsProps, args: ArgsProps,
callback: (info: { prefixCls: string; instance: RCNotificationInstance }) => void, callback: (info: {
prefixCls: string;
iconPrefixCls: string;
instance: RCNotificationInstance;
}) => void,
) { ) {
const { const {
placement = defaultPlacement, placement = defaultPlacement,
@ -118,14 +122,15 @@ function getNotificationInstance(
closeIcon = defaultCloseIcon, closeIcon = defaultCloseIcon,
prefixCls: customizePrefixCls, prefixCls: customizePrefixCls,
} = args; } = args;
const { getPrefixCls } = globalConfig(); const { getPrefixCls, getIconPrefixCls } = globalConfig();
const prefixCls = getPrefixCls('notification', customizePrefixCls || defaultPrefixCls); const prefixCls = getPrefixCls('notification', customizePrefixCls || defaultPrefixCls);
const iconPrefixCls = getIconPrefixCls();
const cacheKey = `${prefixCls}-${placement}`; const cacheKey = `${prefixCls}-${placement}`;
const cacheInstance = notificationInstance[cacheKey]; const cacheInstance = notificationInstance[cacheKey];
if (cacheInstance) { if (cacheInstance) {
Promise.resolve(cacheInstance).then(instance => { Promise.resolve(cacheInstance).then(instance => {
callback({ prefixCls: `${prefixCls}-notice`, instance }); callback({ prefixCls: `${prefixCls}-notice`, iconPrefixCls, instance });
}); });
return; return;
@ -154,6 +159,7 @@ function getNotificationInstance(
resolve(notification); resolve(notification);
callback({ callback({
prefixCls: `${prefixCls}-notice`, prefixCls: `${prefixCls}-notice`,
iconPrefixCls,
instance: notification, instance: notification,
}); });
}, },
@ -188,7 +194,7 @@ export interface ArgsProps {
closeIcon?: React.ReactNode; closeIcon?: React.ReactNode;
} }
function getRCNoticeProps(args: ArgsProps, prefixCls: string) { function getRCNoticeProps(args: ArgsProps, prefixCls: string, iconPrefixCls?: string) {
const { const {
duration: durationArg, duration: durationArg,
icon, icon,
@ -221,15 +227,17 @@ function getRCNoticeProps(args: ArgsProps, prefixCls: string) {
return { return {
content: ( content: (
<div className={iconNode ? `${prefixCls}-with-icon` : ''} role="alert"> <ConfigProvider iconPrefixCls={iconPrefixCls}>
{iconNode} <div className={iconNode ? `${prefixCls}-with-icon` : ''} role="alert">
<div className={`${prefixCls}-message`}> {iconNode}
{autoMarginTag} <div className={`${prefixCls}-message`}>
{message} {autoMarginTag}
{message}
</div>
<div className={`${prefixCls}-description`}>{description}</div>
{btn ? <span className={`${prefixCls}-btn`}>{btn}</span> : null}
</div> </div>
<div className={`${prefixCls}-description`}>{description}</div> </ConfigProvider>
{btn ? <span className={`${prefixCls}-btn`}>{btn}</span> : null}
</div>
), ),
duration, duration,
closable: true, closable: true,
@ -244,8 +252,8 @@ function getRCNoticeProps(args: ArgsProps, prefixCls: string) {
} }
function notice(args: ArgsProps) { function notice(args: ArgsProps) {
getNotificationInstance(args, ({ prefixCls, instance }) => { getNotificationInstance(args, ({ prefixCls, iconPrefixCls, instance }) => {
instance.notice(getRCNoticeProps(args, prefixCls)); instance.notice(getRCNoticeProps(args, prefixCls, iconPrefixCls));
}); });
} }