chore: merge master into feature

This commit is contained in:
lijianan 2023-11-23 03:09:24 +08:00
commit 17dff7c630
No known key found for this signature in database
GPG Key ID: 7595169217DDC1B5
50 changed files with 3263 additions and 2028 deletions

View File

@ -7,7 +7,7 @@ version: 2.1
jobs:
test-argos-ci:
docker:
- image: cimg/node:21.1-browsers
- image: cimg/node:21.2-browsers
environment:
NODE_OPTIONS: --openssl-legacy-provider
steps:

View File

@ -19,6 +19,7 @@ import {
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import Tilt from './Tilt';
import useLocale from '../../../../hooks/useLocale';
const { _InternalPanelDoNotUseOrYouWillBeFired: ModalPanel } = Modal;
@ -79,7 +80,7 @@ const useStyle = createStyles(({ token, css }) => {
display: flex;
flex-direction: column;
row-gap: ${gap}px;
opacity: 0.65;
opacity: 0.8;
`,
flex: css`
@ -117,7 +118,11 @@ const ComponentsBlock: React.FC<ComponentsBlockProps> = (props) => {
const { styles } = useStyle();
return (
<div className={classNames(className, styles.holder)} style={style}>
<Tilt
options={{ max: 20, glare: true, scale: 1 }}
className={classNames(className, styles.holder)}
style={style}
>
<ModalPanel title="Ant Design 5.0" width="100%">
{locale.text}
</ModalPanel>
@ -252,7 +257,7 @@ const ComponentsBlock: React.FC<ComponentsBlockProps> = (props) => {
<InternalTooltip title={locale.hello} placement="topLeft" className={styles.noMargin} />
<Alert message="Ant Design love you!" type="success" />
</div>
</Tilt>
);
};

View File

@ -0,0 +1,33 @@
import React, { useEffect, useRef } from 'react';
import VanillaTilt, { type TiltOptions } from 'vanilla-tilt';
interface TiltProps extends React.HTMLAttributes<HTMLDivElement> {
options?: TiltOptions;
}
// https://micku7zu.github.io/vanilla-tilt.js/index.html
const defaultTiltOptions = {
scale: 1.02,
max: 8,
speed: 1500,
glare: true,
'max-glare': 0.8,
};
const Tilt: React.FC<TiltProps> = ({ options, ...props }) => {
const node = useRef<HTMLDivElement>(null);
useEffect(() => {
if (node.current) {
VanillaTilt.init(node.current, {
...defaultTiltOptions,
...options,
});
}
return () => {
(node.current as any)?.vanillaTilt.destroy();
};
}, []);
return <div ref={node} {...props} />;
};
export default Tilt;

View File

@ -7,7 +7,6 @@ import useLocale from '../../../../hooks/useLocale';
import SiteContext from '../../../../theme/slots/SiteContext';
import * as utils from '../../../../theme/utils';
import { GroupMask } from '../Group';
import useMouseTransform from './useMouseTransform';
const ComponentsBlock = React.lazy(() => import('./ComponentsBlock'));
@ -28,7 +27,6 @@ const locales = {
const useStyle = () => {
const { direction } = React.useContext(ConfigProvider.ConfigContext);
const isRTL = direction === 'rtl';
return createStyles(({ token, css, cx }) => {
const textShadow = `0 0 3px ${token.colorBgContainer}`;
@ -37,7 +35,9 @@ const useStyle = () => {
inset: 0;
backdrop-filter: blur(4px);
opacity: 1;
transition: opacity 1s ease;
background: rgba(255, 255, 255, 0.2);
transition: all 1s ease;
pointer-events: none;
`);
return {
@ -114,10 +114,8 @@ const PreviewBanner: React.FC<PreviewBannerProps> = (props) => {
const { pathname, search } = useLocation();
const isZhCN = utils.isZhCN(pathname);
const [componentsBlockStyle, mouseEvents] = useMouseTransform();
return (
<GroupMask {...mouseEvents}>
<GroupMask>
{/* Image Left Top */}
<img
style={{ position: 'absolute', left: isMobile ? -120 : 0, top: 0, width: 240 }}
@ -134,7 +132,11 @@ const PreviewBanner: React.FC<PreviewBannerProps> = (props) => {
<div className={styles.holder}>
{/* Mobile not show the component preview */}
<Suspense fallback={null}>
{!isMobile && <ComponentsBlock className={styles.block} style={componentsBlockStyle} />}
{isMobile ? null : (
<div className={styles.block}>
<ComponentsBlock />
</div>
)}
</Suspense>
<div className={styles.mask} />

View File

@ -1,73 +0,0 @@
import React, { startTransition } from 'react';
import { ConfigProvider } from 'antd';
const getTransformRotateStyle = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
currentTarget: EventTarget & HTMLDivElement,
multiple: number,
isRTL: boolean,
): string => {
const box = currentTarget?.getBoundingClientRect();
const calcX = -(event.clientY - box.y - box.height / 2) / multiple;
const calcY = (event.clientX - box.x - box.width / 2) / multiple;
return isRTL
? `rotate3d(${24 + calcX}, ${83 + calcY}, -45, 57deg)`
: `rotate3d(${24 + calcX}, ${-83 + calcY}, 45, 57deg)`;
};
const useMouseTransform = ({ transitionDuration = 500, multiple = 36 } = {}) => {
const [componentsBlockStyle, setComponentsBlockStyle] = React.useState<React.CSSProperties>({});
const { direction } = React.useContext(ConfigProvider.ConfigContext);
const isRTL = direction === 'rtl';
const onMouseMove: React.MouseEventHandler<HTMLDivElement> = (event) => {
const { currentTarget } = event;
startTransition(() => {
setComponentsBlockStyle((style) => ({
...style,
transform: getTransformRotateStyle(event, currentTarget, multiple, isRTL),
}));
});
};
const onMouseEnter: React.MouseEventHandler<HTMLDivElement> = () => {
startTransition(() => {
setComponentsBlockStyle((style) => ({
...style,
transition: `transform ${transitionDuration / 1000}s`,
}));
});
setTimeout(() => {
startTransition(() => {
setComponentsBlockStyle((style) => ({
...style,
transition: '',
}));
});
}, transitionDuration);
};
const onMouseLeave: React.MouseEventHandler<HTMLDivElement> = () => {
startTransition(() => {
setComponentsBlockStyle((style) => ({
...style,
transition: `transform ${transitionDuration / 1000}s`,
transform: '',
}));
});
};
return [
componentsBlockStyle,
{
onMouseMove,
onMouseEnter,
onMouseLeave,
},
] as const;
};
export default useMouseTransform;

View File

@ -34,6 +34,7 @@ const locales = {
message: '',
shortMessage: '',
more: '',
link: '',
},
};

View File

@ -1,14 +1,14 @@
name: 🐦 Release to Tweet
on:
release:
types: [published]
create
permissions:
contents: read
jobs:
tweet:
if: github.event.ref_type == 'tag'
runs-on: ubuntu-latest
steps:
- name: Tweet

View File

@ -40,9 +40,17 @@ jobs:
echo "whitelisted=false" >> $GITHUB_OUTPUT
fi
- name: install dependencies
if: ${{ steps.check_user.outputs.whitelisted == 'true' }}
run: yarn
- name: cache package-lock.json
uses: actions/cache@v3
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts
- name: install
run: npm install
- name: Build dist file
id: build

View File

@ -16,6 +16,34 @@ tag: vVERSION
---
## 5.11.3
`2023-11-22`
- 🐞 Fix Modal static method create `zIndex` too high will cover other popup content. [#46012](https://github.com/ant-design/ant-design/pull/46012)
- Image
- 🆕 Image preview support mobile touch interactive. [#45989](https://github.com/ant-design/ant-design/pull/45989) [@JarvisArt](https://github.com/JarvisArt)
- 🐞 Fixed Image preview `z-index` conflict when in a nested pop-up. [#45979](https://github.com/ant-design/ant-design/pull/45979) [@kiner-tang](https://github.com/kiner-tang)
- 🐞 Fix Collapse header cursor style. [#45994](https://github.com/ant-design/ant-design/pull/45994)
- 🐞 Fix ColorPicker not support Form disabled config. [#45978](https://github.com/ant-design/ant-design/pull/45978) [@RedJue](https://github.com/RedJue)
- 🐞 Fix Typography.Text `ellipsis.tooltip` cannot open under Layout component. [#45962](https://github.com/ant-design/ant-design/pull/45962)
- 🐞 Remove Select native 🔍 icon from search input in Safari. [#46008](https://github.com/ant-design/ant-design/pull/46008)
- 💄 Remove Rate useless style.[#45927](https://github.com/ant-design/ant-design/pull/45927) [@JarvisArt](https://github.com/JarvisArt)
- 🛠 UMD `antd.js` will try to reuse global `@ant-design/cssinjs` first now. [#46009](https://github.com/ant-design/ant-design/pull/46009)
- 🌐 Improve `eu_ES` localization. [#45928](https://github.com/ant-design/ant-design/pull/45928) [@ionlizarazu](https://github.com/ionlizarazu)
## 5.11.2
`2023-11-17`
- 🆕 Table with `virtual` can now customize `components` except the `components.body`. [#45857](https://github.com/ant-design/ant-design/pull/45857)
- 🐞 Fix Button with href and disabled that could be focused. [#45910](https://github.com/ant-design/ant-design/pull/45910) [@MadCcc](https://github.com/MadCcc)
- 🐞 Fix `zIndex` logic problem that message and notification are covered when multiple Modal are opened. [#45911](https://github.com/ant-design/ant-design/pull/45911) [#45864](https://github.com/ant-design/ant-design/pull/45864) [@kiner-tang](https://github.com/kiner-tang)
- 💄 Fix QRCode `style.padding` is not working. [#45815](https://github.com/ant-design/ant-design/pull/45815)
- 💄 Optimize Carousel dots border radius style. [#45817](https://github.com/ant-design/ant-design/pull/45817)
- TypeScript
- 🤖 Optimize List `gutter` property type definition. [#45791](https://github.com/ant-design/ant-design/pull/45791) [@Asanio06](https://github.com/Asanio06)
## 5.11.1
`2023-11-09`

View File

@ -16,6 +16,34 @@ tag: vVERSION
---
## 5.11.3
`2023-11-22`
- 🐞 修复 Modal 静态方法创建 `zIndex` 过高会覆盖其他弹出内容的问题。[#46012](https://github.com/ant-design/ant-design/pull/46012)
- Image
- 🆕 Image 预览支持移动触摸事件交互。[#45989](https://github.com/ant-design/ant-design/pull/45989) [@JarvisArt](https://github.com/JarvisArt)
- 🐞 修复 Image 预览在嵌套弹框中 `z-index` 设置不正确的问题。[#45979](https://github.com/ant-design/ant-design/pull/45979) [@kiner-tang](https://github.com/kiner-tang)
- 🐞 修复 Collapse 可折叠区域鼠标 `hover` 样式问题。[#45994](https://github.com/ant-design/ant-design/pull/45994)
- 🐞 修复 ColorPicker 不支持 Form 组件的禁用问题。[#45978](https://github.com/ant-design/ant-design/pull/45978) [@RedJue](https://github.com/RedJue)
- 🐞 修复 Typography.Text `code` 在 Layout 下开启 `ellipsis` 时 tooltip 无效的问题。[#45962](https://github.com/ant-design/ant-design/pull/45962)
- 🐞 修复 Select 搜索框在 Safari 下显示多余的 🔍 图标。[#46008](https://github.com/ant-design/ant-design/pull/46008)
- 💄 删除 Rate 组件无用样式。 [#45927](https://github.com/ant-design/ant-design/pull/45927) [@JarvisArt](https://github.com/JarvisArt)
- 🛠 UMD 版本 `antd.js` 现在会优先使用全局的 `@ant-design/cssinjs` 依赖。[#46009](https://github.com/ant-design/ant-design/pull/46009)
- 🌐 补充 `eu_ES` 国际化内容。[#45928](https://github.com/ant-design/ant-design/pull/45928) [@ionlizarazu](https://github.com/ionlizarazu)
## 5.11.2
`2023-11-17`
- 🆕 放开 Table `virtual``components` 的限制,现在除了 `components.body` 都可以自定义。[#45857](https://github.com/ant-design/ant-design/pull/45857)
- 🐞 修复 Button 带有链接且禁用时可以被聚焦到的问题。[#45910](https://github.com/ant-design/ant-design/pull/45910) [@MadCcc](https://github.com/MadCcc)
- 🐞 修复 `zIndex` 逻辑,解决多层 Modal 打开时message 与 notification 被遮盖的问题。[#45911](https://github.com/ant-design/ant-design/pull/45911) [#45864](https://github.com/ant-design/ant-design/pull/45864) [@kiner-tang](https://github.com/kiner-tang)
- 💄 修复 QRCode 设置 `style.padding` 时无效的问题。[#45815](https://github.com/ant-design/ant-design/pull/45815)
- 💄 优化 Carousel 切换条圆角样式。[#45817](https://github.com/ant-design/ant-design/pull/45817)
- TypeScript
- 🤖 优化 List 属性 `gutter` 的类型定义。[#45791](https://github.com/ant-design/ant-design/pull/45791) [@Asanio06](https://github.com/Asanio06)
## 5.11.1
`2023-11-09`

19
alias/cssinjs.js Normal file
View File

@ -0,0 +1,19 @@
/* eslint-disable global-require, import/no-unresolved */
// This is a alias proxy, which will use global `@ant-design/cssinjs` first.
// Use local if global not found.
let cssinjs;
if (typeof window !== 'undefined' && window.antdCssinjs) {
// Use window UMD version
cssinjs = window.antdCssinjs;
} else if (typeof global !== 'undefined' && global.antdCssinjs) {
// Use global UMD version
cssinjs = global.antdCssinjs;
} else {
// Use local version.
// Use relative path since webpack will also replace module here.
cssinjs = require('../node_modules/@ant-design/cssinjs');
}
module.exports = cssinjs;

View File

@ -1,7 +1,7 @@
import type { PropsWithChildren } from 'react';
import React, { useEffect } from 'react';
import { render } from '@testing-library/react';
import type { MenuProps } from 'antd';
import type { ImageProps, MenuProps } from 'antd';
import {
AutoComplete,
Cascader,
@ -9,6 +9,7 @@ import {
DatePicker,
Drawer,
Dropdown,
Image,
Menu,
Modal,
Popconfirm,
@ -178,6 +179,15 @@ const consumerComponent: Record<ZIndexConsumer, React.FC<{ rootClassName: string
</>
),
Menu: (props) => <Menu {...props} items={items} defaultOpenKeys={['SubMenu']} />,
ImagePreview: ({ rootClassName }: ImageProps) => (
<Image
src="xxx"
preview={{
visible: true,
rootClassName: `${rootClassName} comp-item comp-ImagePreview`,
}}
/>
),
};
function getConsumerSelector(baseSelector: string, consumer: ZIndexConsumer): string {
@ -196,6 +206,8 @@ function getConsumerSelector(baseSelector: string, consumer: ZIndexConsumer): st
.join(',');
} else if (['Menu'].includes(consumer)) {
selector = `${baseSelector}.ant-menu-submenu-placement-rightTop`;
} else if (consumer === 'ImagePreview') {
selector = `${baseSelector}.comp-ImagePreview`;
}
return selector;
}
@ -337,7 +349,7 @@ describe('Test useZIndex hooks', () => {
const instance = Modal.confirm({
title: 'bamboo',
content: 'little',
content: <Select open />,
});
await waitFakeTimer();
@ -346,6 +358,10 @@ describe('Test useZIndex hooks', () => {
zIndex: '2000',
});
expect(document.querySelector('.ant-select-dropdown')).toHaveStyle({
zIndex: '2050',
});
instance.destroy();
await waitFakeTimer();

View File

@ -5,7 +5,7 @@ import zIndexContext from '../zindexContext';
export type ZIndexContainer = 'Modal' | 'Drawer' | 'Popover' | 'Popconfirm' | 'Tooltip' | 'Tour';
export type ZIndexConsumer = 'SelectLike' | 'Dropdown' | 'DatePicker' | 'Menu';
export type ZIndexConsumer = 'SelectLike' | 'Dropdown' | 'DatePicker' | 'Menu' | 'ImagePreview';
// Z-Index control range
// Container: 1000 + offset 100 (max base + 10 * offset = 2000)
@ -30,6 +30,7 @@ export const consumerBaseZIndexOffset: Record<ZIndexConsumer, number> = {
Dropdown: 50,
DatePicker: 50,
Menu: 50,
ImagePreview: 1,
};
function isContainerType(type: ZIndexContainer | ZIndexConsumer): type is ZIndexContainer {
@ -43,7 +44,13 @@ export function useZIndex(
const [, token] = useToken();
const parentZIndex = React.useContext(zIndexContext);
const isContainer = isContainerType(componentType);
if (customZIndex !== undefined) {
return [customZIndex, customZIndex];
}
let zIndex = parentZIndex ?? 0;
if (isContainer) {
zIndex +=
// Use preset token zIndex by default but not stack when has parent container

View File

@ -1085,7 +1085,6 @@ exports[`renders components/button/demo/disabled.tsx extend context correctly 1`
</a>
<a
class="ant-btn ant-btn-primary ant-btn-disabled"
href="https://ant.design/index-cn"
tabindex="-1"
>
<span>

View File

@ -990,7 +990,6 @@ exports[`renders components/button/demo/disabled.tsx correctly 1`] = `
</a>
<a
class="ant-btn ant-btn-primary ant-btn-disabled"
href="https://ant.design/index-cn"
tabindex="-1"
>
<span>

View File

@ -262,6 +262,7 @@ const InternalButton: React.ForwardRefRenderFunction<
className={classNames(classes, {
[`${prefixCls}-disabled`]: mergedDisabled,
})}
href={mergedDisabled ? undefined : linkButtonRestProps.href}
style={fullStyle}
onClick={handleClick}
ref={buttonRef as React.Ref<HTMLAnchorElement>}

View File

@ -49,12 +49,18 @@ export interface CardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 't
}
function getAction(actions: React.ReactNode[]): React.ReactNode[] {
return actions.map<React.ReactNode>((action, index) => (
// eslint-disable-next-line react/no-array-index-key
<li style={{ width: `${100 / actions.length}%` }} key={`action-${index}`}>
<span>{action}</span>
</li>
));
return actions.map<React.ReactNode>((action, index) => {
// Move this out since eslint not allow index key
// And eslint-disable makes conflict with rollup
// ref https://github.com/ant-design/ant-design/issues/46022
const key = `action-${index}`;
return (
<li style={{ width: `${100 / actions.length}%` }} key={key}>
<span>{action}</span>
</li>
);
});
}
const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => {

View File

@ -136,17 +136,8 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = (token) => {
},
},
[`${componentCls}-header-collapsible-only`]: {
cursor: 'default',
[`${componentCls}-header-text`]: {
flex: 'none',
cursor: 'pointer',
},
},
[`${componentCls}-icon-collapsible-only`]: {
cursor: 'default',
cursor: 'unset',
[`${componentCls}-expand-icon`]: {
cursor: 'pointer',

View File

@ -13,6 +13,7 @@ import { devUseWarning } from '../_util/warning';
import type { ConfigConsumerProps } from '../config-provider/context';
import { ConfigContext } from '../config-provider/context';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import DisabledContext from '../config-provider/DisabledContext';
import useSize from '../config-provider/hooks/useSize';
import type { SizeType } from '../config-provider/SizeContext';
import { FormItemInputContext, NoFormStyle } from '../form/context';
@ -106,7 +107,8 @@ const ColorPicker: CompoundedComponent = (props) => {
} = props;
const { getPrefixCls, direction, colorPicker } = useContext<ConfigConsumerProps>(ConfigContext);
const contextDisabled = useContext(DisabledContext);
const mergedDisabled = disabled ?? contextDisabled;
const [, token] = useToken();
const [colorValue, setColorValue] = useColorState(token.colorPrimary, {
@ -115,7 +117,7 @@ const ColorPicker: CompoundedComponent = (props) => {
});
const [popupOpen, setPopupOpen] = useMergedState(false, {
value: open,
postState: (openData) => !disabled && openData,
postState: (openData) => !mergedDisabled && openData,
onChange: onOpenChange,
});
const [formatValue, setFormatValue] = useMergedState(format, {
@ -223,7 +225,7 @@ const ColorPicker: CompoundedComponent = (props) => {
color: colorValue,
allowClear,
colorCleared,
disabled,
disabled: mergedDisabled,
disabledAlpha,
presets,
panelRender,
@ -241,7 +243,7 @@ const ColorPicker: CompoundedComponent = (props) => {
style={styles?.popup}
overlayInnerStyle={styles?.popupOverlayInner}
onOpenChange={(visible) => {
if (popupAllowCloseRef.current && !disabled) {
if (popupAllowCloseRef.current && !mergedDisabled) {
setPopupOpen(visible);
}
}}
@ -265,7 +267,7 @@ const ColorPicker: CompoundedComponent = (props) => {
style={mergedStyle}
color={value ? generateColor(value) : colorValue}
prefixCls={prefixCls}
disabled={disabled}
disabled={mergedDisabled}
colorCleared={colorCleared}
showText={showText}
format={formatValue}

View File

@ -56,6 +56,12 @@ The default locale is en-US, if you need to use other languages, recommend to us
If there are special needs (only modifying single component language), Please use the property: local. Example: [default](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json).
<!-- prettier-ignore -->
:::warning
When use with Nextjs App Router, make sure to add `'use client'` before import locale file of dayjs.
It's because all components of Ant Design only works in client, importing locale in RSC will not work.
:::
```jsx
import locale from 'antd/es/date-picker/locale/zh_CN';

View File

@ -57,6 +57,12 @@ demo:
如有特殊需求(仅修改单一组件的语言),请使用 locale 参数,参考:[默认配置](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json)。
<!-- prettier-ignore -->
:::warning
在搭配 Nextjs 的 App Router 使用时,注意在引入 dayjs 的 locale 文件时加上 `'use client'`
这是由于 Ant Design 的组件都是客户端组件,在 RSC 中引入 dayjs 的 locale 文件将不会在客户端生效。
:::
```jsx
import locale from 'antd/es/date-picker/locale/zh_CN';

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { DownOutlined } from '@ant-design/icons';
import type { MenuProps } from 'antd';
import type { DropdownProps, MenuProps } from 'antd';
import { Dropdown, Space } from 'antd';
const App: React.FC = () => {
@ -12,8 +12,10 @@ const App: React.FC = () => {
}
};
const handleOpenChange = (flag: boolean) => {
setOpen(flag);
const handleOpenChange: DropdownProps['onOpenChange'] = (nextOpen, info) => {
if (info.source === 'trigger' || nextOpen) {
setOpen(nextOpen);
}
};
const items: MenuProps['items'] = [

View File

@ -5608,6 +5608,415 @@ Array [
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-col-4 ant-form-item-label"
>
<label
class=""
title="ColorPicker"
>
ColorPicker
</label>
</div>
<div
class="ant-col ant-col-14 ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<div
class="ant-color-picker-trigger ant-color-picker-trigger-disabled"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background: rgb(22, 119, 255);"
/>
</div>
</div>
<div
class="ant-popover ant-zoom-big-appear ant-zoom-big-appear-prepare ant-zoom-big ant-color-picker ant-popover-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-popover-arrow"
style="position: absolute;"
/>
<div
class="ant-popover-content"
>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-inner-content"
>
<div
class="ant-color-picker-inner-content"
>
<div
class="ant-color-picker-panel"
>
<div
class="ant-color-picker-select"
>
<div
class="ant-color-picker-palette"
style="position: relative;"
>
<div
style="position: absolute; left: 0px; top: 0px; z-index: 1;"
>
<div
class="ant-color-picker-handler"
style="background-color: rgb(22, 119, 255);"
/>
</div>
<div
class="ant-color-picker-saturation"
style="background-color: rgb(0, 0, 0);"
/>
</div>
</div>
<div
class="ant-color-picker-slider-container"
>
<div
class="ant-color-picker-slider-group"
>
<div
class="ant-color-picker-slider ant-color-picker-slider-hue"
>
<div
class="ant-color-picker-palette"
style="position: relative;"
>
<div
style="position: absolute; left: 0px; top: 0px; z-index: 1;"
>
<div
class="ant-color-picker-handler ant-color-picker-handler-sm"
style="background-color: rgb(0, 0, 0);"
/>
</div>
<div
class="ant-color-picker-gradient"
style="position: absolute; inset: 0;"
/>
</div>
</div>
<div
class="ant-color-picker-slider ant-color-picker-slider-alpha"
>
<div
class="ant-color-picker-palette"
style="position: relative;"
>
<div
style="position: absolute; left: 0px; top: 0px; z-index: 1;"
>
<div
class="ant-color-picker-handler ant-color-picker-handler-sm"
style="background-color: rgb(22, 119, 255);"
/>
</div>
<div
class="ant-color-picker-gradient"
style="position: absolute; inset: 0;"
/>
</div>
</div>
</div>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background: rgb(22, 119, 255);"
/>
</div>
</div>
</div>
<div
class="ant-color-picker-input-container"
>
<div
class="ant-select ant-select-sm ant-select-borderless ant-color-picker-format-select ant-select-single ant-select-show-arrow ant-select-disabled"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
disabled=""
id="rc_select_TEST_OR_SSR"
readonly=""
role="combobox"
style="opacity: 0;"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-item"
title="HEX"
>
HEX
</span>
</div>
<div
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-select-dropdown-placement-bottomRight"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box; z-index: 1150; width: 68px;"
>
<div>
<div
id="rc_select_TEST_OR_SSR_list"
role="listbox"
style="height: 0px; width: 0px; overflow: hidden;"
>
<div
aria-label="HEX"
aria-selected="true"
id="rc_select_TEST_OR_SSR_list_0"
role="option"
>
hex
</div>
<div
aria-label="HSB"
aria-selected="false"
id="rc_select_TEST_OR_SSR_list_1"
role="option"
>
hsb
</div>
</div>
<div
class="rc-virtual-list"
style="position: relative;"
>
<div
class="rc-virtual-list-holder"
style="max-height: 256px; overflow-y: auto;"
>
<div>
<div
class="rc-virtual-list-holder-inner"
style="display: flex; flex-direction: column;"
>
<div
aria-selected="true"
class="ant-select-item ant-select-item-option ant-select-item-option-active ant-select-item-option-selected"
title="HEX"
>
<div
class="ant-select-item-option-content"
>
HEX
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
/>
</div>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option"
title="HSB"
>
<div
class="ant-select-item-option-content"
>
HSB
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
/>
</div>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option"
title="RGB"
>
<div
class="ant-select-item-option-content"
>
RGB
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select: none;"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-color-picker-input"
>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-disabled ant-color-picker-hex-input ant-input-affix-wrapper-sm"
>
<span
class="ant-input-prefix"
>
#
</span>
<input
class="ant-input ant-input-disabled ant-input-sm"
disabled=""
type="text"
value="1677ff"
/>
</span>
</div>
<div
class="ant-input-number ant-input-number-sm ant-color-picker-steppers ant-color-picker-alpha-input ant-input-number-disabled"
>
<div
class="ant-input-number-handler-wrap"
>
<span
aria-disabled="true"
aria-label="Increase Value"
class="ant-input-number-handler ant-input-number-handler-up ant-input-number-handler-up-disabled"
role="button"
unselectable="on"
>
<span
aria-label="up"
class="anticon anticon-up ant-input-number-handler-up-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</span>
<span
aria-disabled="false"
aria-label="Decrease Value"
class="ant-input-number-handler ant-input-number-handler-down"
role="button"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-input-number-handler-down-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-input-number-input-wrap"
>
<input
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="100"
autocomplete="off"
class="ant-input-number-input"
disabled=""
role="spinbutton"
step="1"
value="100%"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>,
]
`;

View File

@ -3024,6 +3024,48 @@ Array [
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-col-4 ant-form-item-label"
>
<label
class=""
title="ColorPicker"
>
ColorPicker
</label>
</div>
<div
class="ant-col ant-col-14 ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<div
class="ant-color-picker-trigger ant-color-picker-trigger-disabled"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22, 119, 255)"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>,
]
`;

View File

@ -1045,6 +1045,48 @@ exports[`Form form should support disabled 1`] = `
</div>
</div>
</div>
<div
class="ant-form-item"
>
<div
class="ant-row ant-form-item-row"
>
<div
class="ant-col ant-col-4 ant-form-item-label"
>
<label
class=""
title="ColorPicker"
>
ColorPicker
</label>
</div>
<div
class="ant-col ant-col-14 ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<div
class="ant-color-picker-trigger ant-color-picker-trigger-disabled"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background: rgb(22, 119, 255);"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
`;

View File

@ -1,9 +1,9 @@
import type { ChangeEventHandler } from 'react';
import React, { version as ReactVersion, useEffect, useRef, useState } from 'react';
import { AlertFilled } from '@ant-design/icons';
import type { ColProps } from 'antd/es/grid';
import classNames from 'classnames';
import scrollIntoView from 'scroll-into-view-if-needed';
import { AlertFilled } from '@ant-design/icons';
import type { FormInstance } from '..';
import Form from '..';
@ -14,6 +14,7 @@ import { act, fireEvent, pureRender, render, screen, waitFakeTimer } from '../..
import Button from '../../button';
import Cascader from '../../cascader';
import Checkbox from '../../checkbox';
import ColorPicker from '../../color-picker';
import ConfigProvider from '../../config-provider';
import DatePicker from '../../date-picker';
import Drawer from '../../drawer';
@ -1166,6 +1167,9 @@ describe('Form', () => {
<Form.Item label="Slider">
<Slider />
</Form.Item>
<Form.Item label="ColorPicker">
<ColorPicker />
</Form.Item>
</Form>
);
const { container } = render(<App />);
@ -1692,6 +1696,7 @@ describe('Form', () => {
{ label: 'female', value: 1 },
]}
/>,
<ColorPicker key="ColorPicker" disabled={disabled} />,
<InputNumber key="InputNumber" disabled={disabled} />,
<Input key="Input" disabled={disabled} />,
<Select key="Select" disabled={disabled} />,

View File

@ -1,9 +1,10 @@
import { PlusOutlined } from '@ant-design/icons';
import React, { useState } from 'react';
import { PlusOutlined } from '@ant-design/icons';
import {
Button,
Cascader,
Checkbox,
ColorPicker,
DatePicker,
Form,
Input,
@ -113,6 +114,9 @@ const FormDisabledDemo: React.FC = () => {
<Form.Item label="Slider">
<Slider />
</Form.Item>
<Form.Item label="ColorPicker">
<ColorPicker />
</Form.Item>
</Form>
</>
);

View File

@ -4,6 +4,7 @@ import classNames from 'classnames';
import RcImage from 'rc-image';
import type { ImageProps } from 'rc-image';
import { useZIndex } from '../_util/hooks/useZIndex';
import { getTransitionName } from '../_util/motion';
import { ConfigContext } from '../config-provider';
import defaultLocale from '../locale/en_US';
@ -46,6 +47,11 @@ const Image: CompositionImage<ImageProps> = (props) => {
const mergedClassName = classNames(className, hashId, image?.className);
const [zIndex] = useZIndex(
'ImagePreview',
typeof preview === 'object' ? preview.zIndex : undefined,
);
const mergedPreview = React.useMemo(() => {
if (preview === false) {
return preview;
@ -64,6 +70,7 @@ const Image: CompositionImage<ImageProps> = (props) => {
getContainer: getContainer || getContextPopupContainer,
transitionName: getTransitionName(rootPrefixCls, 'zoom', _preview.transitionName),
maskTransitionName: getTransitionName(rootPrefixCls, 'fade', _preview.maskTransitionName),
zIndex,
};
}, [preview, imageLocale]);

View File

@ -76900,7 +76900,7 @@ exports[`Locale Provider should display the text as eu 1`] = `
type="button"
>
<span>
Ezeztatu
Utzi
</span>
</button>
<button
@ -76965,7 +76965,7 @@ exports[`Locale Provider should display the text as eu 1`] = `
class="ant-transfer-list-header-selected"
>
0
Elementu
elementu
</span>
<span
class="ant-transfer-list-header-title"
@ -77199,7 +77199,7 @@ exports[`Locale Provider should display the text as eu 1`] = `
class="ant-transfer-list-header-selected"
>
0
Elementu
elementu
</span>
<span
class="ant-transfer-list-header-title"
@ -78486,7 +78486,7 @@ exports[`Locale Provider should display the text as eu 1`] = `
type="button"
>
<span>
Ezeztatu
Utzi
</span>
</button>
<button

View File

@ -19,36 +19,41 @@ const localeValues: Locale = {
Table: {
filterTitle: 'Iragazi menua',
filterConfirm: 'Onartu',
filterReset: 'Berrasi',
filterReset: 'Garbitu',
filterEmptyText: 'Iragazkirik gabe',
filterCheckall: 'Autatu dena',
filterCheckall: 'Hautatu dena',
filterSearchPlaceholder: 'Bilatu iragazkietan',
emptyText: 'Daturik gabe',
selectAll: 'Autatu dena',
selectAll: 'Hautatu dena',
selectInvert: 'Alderantzikatu hautaketa',
selectNone: 'Hustu dena',
selectionAll: 'Autatu datu guztiak',
selectionAll: 'Hautatu datu guztiak',
sortTitle: 'Ordenatu',
expand: 'Zabaldu ilera',
collapse: 'Ilera kolapsatu',
triggerDesc: 'Klik beheranzko ordenan ordenatzeko',
triggerAsc: 'Klik goranzko ordenan ordenatzeko',
expand: 'Zabaldu',
collapse: 'Itxi',
triggerDesc: 'Egin klik beheranzko ordenean ordenatzeko',
triggerAsc: 'Egin klik goranzko ordenean ordenatzeko',
cancelSort: 'Egin klik ordenamendua ezeztatzeko',
},
Tour: {
Next: 'Hurrengoa',
Previous: 'Aurrekoa',
Finish: 'Bukatu',
},
Modal: {
okText: 'Onartu',
cancelText: 'Ezeztatu',
cancelText: 'Utzi',
justOkText: 'Onartu',
},
Popconfirm: {
okText: 'Onartu',
cancelText: 'Ezeztatu',
cancelText: 'Utzi',
},
Transfer: {
titles: ['', ''],
searchPlaceholder: 'Bilatu hemen',
itemUnit: 'Elementu',
itemsUnit: 'Elementuak',
itemUnit: 'elementu',
itemsUnit: 'elementuak',
remove: 'Ezabatu',
selectCurrent: 'Hautatu uneko orria',
removeCurrent: 'Uneko orria ezabatu',
@ -57,9 +62,9 @@ const localeValues: Locale = {
selectInvert: 'Uneko orrialdea alderantzikatu',
},
Upload: {
uploading: 'Eguneratzen...',
removeFile: 'Fitxategi ezabatu',
uploadError: 'Errore bat gertate da fitxategia igotzerakoan',
uploading: 'Igotzen...',
removeFile: 'Fitxategia ezabatu',
uploadError: 'Errorea fitxategia igotzerakoan',
previewFile: 'Aurrebista',
downloadFile: 'Fitxategia deskargatu',
},
@ -81,13 +86,13 @@ const localeValues: Locale = {
Form: {
optional: '(aukerakoa)',
defaultValidateMessages: {
default: '${label} eremuaren balidazio errore',
default: '${label} eremuaren balidazio errorea',
required: 'Mesedez, sartu ${label}',
enum: '${label} [${enum}] -tako bat izan behar da',
whitespace: '${label} ezin da izan karaktere zuri bat',
date: {
format: '${label} dataren formatua baliogabea da',
parse: '${label} ezin da data batera deitu',
parse: '${label} ezin da data batera doitu',
invalid: '${label} data baliogabea da',
},
types: {
@ -106,19 +111,19 @@ const localeValues: Locale = {
hex: typeTemplate,
},
string: {
len: '${label} ${len} karaktere izan dehar ditu',
min: '${label} gutxienez ${min} karaktere izan behar ditu',
max: '${label} gehienez ${max} karaktere izan behar ditu',
range: '${label} ${min}-${max} karaktere artean izan behar ditu',
len: '${label} eremuak ${len} karaktere izan dehar ditu',
min: '${label} eremuak gutxienez ${min} karaktere izan behar ditu',
max: '${label} eremuak gehienez ${max} karaktere izan behar ditu',
range: '${label} eremuak ${min}-${max} karaktere artean izan behar ditu',
},
number: {
len: '${label} -ren balioa ${len} izan behar da',
min: '${label} -ren balio minimoa ${min} da',
max: '${label} -ren balio maximoa ${max} da',
range: '${label} -ren balioa ${min}-${max} -ren artean izan behar da',
len: '${label} eremuaren balioa ${len} izan behar da',
min: '${label} eremuaren balio minimoa ${min} da',
max: '${label} eremuaren balio maximoa ${max} da',
range: '${label} eremuaren balioa ${min}-${max} artekoa izan behar da',
},
array: {
len: '${len} ${label} izan behar du',
len: '${len} ${label} izan behar dira',
min: 'Gutxienez ${min} ${label}',
max: 'Gehienez ${max} ${label}',
range: '${label} kopuruak ${min}-${max} -ra bitartekoa izan behar du',
@ -129,7 +134,14 @@ const localeValues: Locale = {
},
},
Image: {
preview: 'Arruebista',
preview: 'Aurrebista',
},
QRCode: {
expired: 'QR kodea kadukatuta',
refresh: 'Freskatu',
},
ColorPicker: {
presetEmpty: 'Hustu',
},
};

View File

@ -17,7 +17,7 @@ import OkBtn from './components/ConfirmOkBtn';
import type { ModalContextProps } from './context';
import { ModalContextProvider } from './context';
import type { ModalFuncProps, ModalLocale } from './interface';
import Dialog from './Modal';
import Modal from './Modal';
import ConfirmCmp from './style/confirmCmp';
export interface ConfirmDialogProps extends ModalFuncProps {
@ -233,7 +233,7 @@ const ConfirmDialog: React.FC<ConfirmDialogProps> = (props) => {
// ========================= Render =========================
return (
<Dialog
<Modal
prefixCls={prefixCls}
className={classString}
wrapClassName={classNames(
@ -269,7 +269,7 @@ const ConfirmDialog: React.FC<ConfirmDialogProps> = (props) => {
focusTriggerAfterClose={focusTriggerAfterClose}
>
<ConfirmContent {...props} confirmPrefixCls={confirmPrefixCls} />
</Dialog>
</Modal>
);
};

View File

@ -670,31 +670,60 @@ exports[`renders components/modal/demo/modal-render.tsx extend context correctly
exports[`renders components/modal/demo/modal-render.tsx extend context correctly 2`] = `[]`;
exports[`renders components/modal/demo/nested.tsx extend context correctly 1`] = `
<button
aria-checked="false"
class="ant-switch"
role="switch"
style="position: relative; z-index: 0;"
type="button"
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
class="ant-space-item"
>
<span
class="ant-switch-inner-checked"
<button
aria-checked="false"
class="ant-switch"
role="switch"
style="position: relative; z-index: 0;"
type="button"
>
Open
</span>
<span
class="ant-switch-inner-unchecked"
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
>
<span
class="ant-switch-inner-checked"
>
Open
</span>
<span
class="ant-switch-inner-unchecked"
>
Close
</span>
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
Close
</span>
</span>
</button>
<span>
Static
</span>
</button>
</div>
<div
class="ant-space-item"
/>
<div
class="ant-space-item"
/>
<div
class="ant-space-item"
/>
</div>
`;
exports[`renders components/modal/demo/nested.tsx extend context correctly 2`] = `[]`;

View File

@ -640,31 +640,60 @@ exports[`renders components/modal/demo/modal-render.tsx correctly 1`] = `
`;
exports[`renders components/modal/demo/nested.tsx correctly 1`] = `
<button
aria-checked="false"
class="ant-switch"
role="switch"
style="position:relative;z-index:0"
type="button"
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
class="ant-space-item"
>
<span
class="ant-switch-inner-checked"
<button
aria-checked="false"
class="ant-switch"
role="switch"
style="position:relative;z-index:0"
type="button"
>
Open
</span>
<span
class="ant-switch-inner-unchecked"
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
>
<span
class="ant-switch-inner-checked"
>
Open
</span>
<span
class="ant-switch-inner-unchecked"
>
Close
</span>
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
Close
</span>
</span>
</button>
<span>
Static
</span>
</button>
</div>
<div
class="ant-space-item"
/>
<div
class="ant-space-item"
/>
<div
class="ant-space-item"
/>
</div>
`;
exports[`renders components/modal/demo/position.tsx correctly 1`] = `

View File

@ -18,14 +18,21 @@ const Demo: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const onShowStatic = () => {
Modal.confirm({
content: <Select open value="1" options={options} />,
});
};
return (
<>
<Space>
<Switch
style={{ position: 'relative', zIndex: isModalOpen ? 4000 : 0 }}
checkedChildren="Open"
unCheckedChildren="Close"
onChange={(open) => setIsModalOpen(open)}
/>
<Button onClick={onShowStatic}>Static</Button>
<Modal
title="Basic Modal"
open={isModalOpen}
@ -123,7 +130,7 @@ const Demo: React.FC = () => {
{messageHolder}
{notificationHolder}
</>
</Space>
);
};

View File

@ -103,7 +103,8 @@ The items listed above are all functions, expecting a settings object as paramet
| cancelText | Text of the Cancel button with Modal.confirm | string | `Cancel` | |
| centered | Centered Modal | boolean | false | |
| className | The className of container | string | - | |
| closeIcon | Custom close icon. 5.7.0: close button will be hidden when setting to `null` or `false` | boolean \| ReactNode | &lt;CloseOutlined /> | |
| closable | Whether a close (x) button is visible on top right of the confirm dialog or not | boolean | false | 4.9.0 |
| closeIcon | Custom close icon | ReactNode | undefined | 4.9.0 |
| content | Content | ReactNode | - | |
| footer | Footer content, set as `footer: null` when you don't need default buttons | (params:[footerRenderParams](/components/modal-cn#footerrenderparams))=> React.ReactNode \| React.ReactNode | - | 5.9.0 |
| getContainer | Return the mount node for Modal | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |

View File

@ -104,7 +104,8 @@ demo:
| cancelText | 设置 Modal.confirm 取消按钮文字 | string | `取消` | |
| centered | 垂直居中展示 Modal | boolean | false | |
| className | 容器类名 | string | - | |
| closeIcon | 自定义关闭图标。5.7.0:设置为 `null``false` 时隐藏关闭按钮 | boolean \| ReactNode | &lt;CloseOutlined /> | |
| closable | 是否显示右上角的关闭按钮 | boolean | false | 4.9.0 |
| closeIcon | 自定义关闭图标 | ReactNode | undefined | 4.9.0 |
| content | 内容 | ReactNode | - | |
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer: null` | (params:[footerRenderParams](/components/modal-cn#footerrenderparams))=> React.ReactNode \| React.ReactNode | - | 5.9.0 |
| getContainer | 指定 Modal 挂载的 HTML 节点false 为挂载在当前 dom | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,17 @@
import React from 'react';
import { Rate } from 'antd';
import { Space, Rate } from 'antd';
const App: React.FC = () => (
<>
<Rate defaultValue={3} />
<span style={{ fontSize: 14, marginLeft: 8 }}>allowClear: true</span>
<Space>
<Rate defaultValue={3} />
<span>allowClear: true</span>
</Space>
<br />
<Rate allowClear={false} defaultValue={3} />
<span style={{ fontSize: 14, marginLeft: 8 }}>allowClear: false</span>
<Space>
<Rate allowClear={false} defaultValue={3} />
<span>allowClear: false</span>
</Space>
</>
);

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Rate } from 'antd';
import { Space, Rate } from 'antd';
const desc = ['terrible', 'bad', 'normal', 'good', 'wonderful'];
@ -7,10 +7,10 @@ const App: React.FC = () => {
const [value, setValue] = useState(3);
return (
<span>
<Space>
<Rate tooltips={desc} onChange={setValue} value={value} />
{value ? <span style={{ fontSize: 14, marginLeft: 8 }}>{desc[value - 1]}</span> : ''}
</span>
{value ? <span>{desc[value - 1]}</span> : ''}
</Space>
);
};

View File

@ -121,14 +121,6 @@ const genRateStyle: GenerateStyle<RateToken> = (token) => {
// star styles
...genRateStarStyle(token),
// text styles
// TODO: Remove in v6
[`+ ${componentCls}-text`]: {
display: 'inline-block',
marginInlineStart: token.marginXS,
fontSize: token.fontSize,
},
// rtl styles
...genRateRtlStyle(token),
},

View File

@ -35,6 +35,7 @@ function genSizeStyle(token: SelectToken, suffix?: string): CSSObject {
'&-input': {
width: '100%',
WebkitAppearance: 'textfield',
},
},

View File

@ -261,6 +261,8 @@ export const getEllipsisStyles = (): CSSObject => ({
overflow: 'hidden',
textOverflow: 'ellipsis',
verticalAlign: 'bottom',
// https://github.com/ant-design/ant-design/issues/45953
boxSizing: 'content-box',
},
},

View File

@ -264,7 +264,6 @@
"Eugene Molokov",
"Eusen",
"Evan Charlton",
"EvanOne文一",
"Evgeny Kuznetsov",
"Eward Song",
"FJHou",
@ -300,6 +299,7 @@
"Geoffrey Bell",
"George Ciobanu",
"George Gray",
"George H",
"Germini",
"Gerson Garrido",
"Gherciu Gheorghe",
@ -368,6 +368,8 @@
"ImJoeHs",
"Infinity",
"Inian",
"Ion Lizarazu",
"Isaac Batista",
"Israel kusayev",
"Italo",
"Ivan",
@ -400,6 +402,7 @@
"Jaroslav Bereza",
"Jarret Moses",
"Jarvis1010",
"JarvisArt",
"Jase Owens",
"Jase Pellerin",
"Jason",
@ -448,6 +451,7 @@
"Johnny Lim",
"Johnny-young",
"Johnsen",
"Jonasz",
"Jonatas Walker",
"Jonathan Gabaut",
"Jonathan Lee",
@ -467,6 +471,7 @@
"Julia Passynkova",
"Julien Confetti",
"Jun Wooram",
"Jung Min O",
"JuniorTour",
"Junwoo Ji",
"Junyu Zhan",
@ -499,6 +504,7 @@
"Kian",
"Kieren",
"Kiho · Cham",
"Kilig",
"Kim, Harim",
"Kimmo Saari",
"KingxBeta",
@ -531,6 +537,7 @@
"LeoYang",
"Leon Koole",
"Leon Shi",
"Leslie Wu",
"Lewis",
"Li C. Pan",
"Li Chao",
@ -594,6 +601,7 @@
"Matt Wilkinson",
"Max",
"Maximilian Meyer",
"Mayowa Sogbein",
"Md_ZubairAhmed",
"Mehdi Salem Naraghi",
"MeiLin",
@ -658,6 +666,7 @@
"Neil",
"Nekron",
"Neo Tan",
"Nerohero",
"Neto Braghetto",
"Neverland",
"Nghiệp",
@ -680,6 +689,7 @@
"Noel Kim (김민혁)",
"Nokecy",
"OAwan",
"Oceansdeep7",
"Olaniyi Philip Ojeyinka",
"Oleg Kuzava",
"Oleksandr Kovalchuk",
@ -840,6 +850,7 @@
"Sukka",
"Sumit Vekariya",
"Sunny Luo",
"Sven",
"Sven Efftinge",
"Svyatoslav",
"SyMind",
@ -851,6 +862,7 @@
"Tannmay S Gupta",
"Tao",
"Tao Zhang",
"Tao Zhou",
"Taucher Christoph",
"Taylor Sabell",
"Ted Shin",
@ -889,11 +901,13 @@
"Varun Dey",
"Varun Sharma",
"Vemund Santi",
"ViPro (京京)",
"Vic",
"Victor",
"Victor Repkow",
"Victor Ye",
"Vijay Thirugnanam",
"Vincent",
"Vincent Zhang",
"Vineet Srivastav",
"Viorel Cojocaru",
@ -902,8 +916,10 @@
"Vitaly Budovski",
"ViviaRui",
"Vlad Vovk",
"Vladimir Yamshikov",
"Vu Hoang Minh",
"Vyacheslav Kamenev",
"Vyacheslav Sedykh",
"Walter Barbagallo",
"Wang Jun",
"Wang Riwu",
@ -995,10 +1011,12 @@
"Zester Quinn Albano",
"Zhang Zhi",
"Zhanghao",
"Zhaolin Liang",
"Zheeeng",
"ZhiHao Li",
"Zhiqiang Gong",
"Zhongjan",
"Zhou Bill",
"Zhou Fang",
"ZhouZhen",
"Zhuo Chen",
@ -1062,6 +1080,7 @@
"chen wen jun",
"chen-jingjie",
"chencheng (云谦)",
"cheng87126",
"chenlei",
"chenlong",
"chensw",
@ -1081,6 +1100,7 @@
"cooljser",
"corneyl",
"csr632",
"cwg",
"daczczcz1",
"dainli",
"dally_G",
@ -1113,6 +1133,7 @@
"eruca",
"ezpub",
"fairyland",
"faruxue",
"feeng",
"feng zhi hao",
"fengmk2",
@ -1205,6 +1226,7 @@
"keng",
"kenve",
"kermolaev",
"killa",
"kily zhou",
"kiner-tang(文辉)",
"klouskingsley",
@ -1220,6 +1242,7 @@
"leadream",
"lehug",
"leijingdao",
"leixd1994",
"lewis liu",
"lexlexa",
"lgmcolin",
@ -1386,6 +1409,7 @@
"wangxing",
"wangxingkang",
"wangxueliang",
"wangzhihao",
"wanli",
"warmhug",
"weited",
@ -1490,6 +1514,7 @@
"二手掉包工程师",
"二货爱吃白萝卜",
"云剪者",
"云泥",
"付引",
"何乐",
"何志勇",
@ -1534,6 +1559,7 @@
"期贤",
"未觉雨声",
"朮厃",
"李勇",
"李瀚",
"李环冀",
"杨兴洲",

View File

@ -0,0 +1,204 @@
---
title: Ant Design meets CSS Variables
date: 2023-11-21
author: MadCcc
---
## Pain of Ant Design 5.0
Ant Design allows customization of theme tokens through ConfigProvider, supporting nested themes. Nested theme tokens inherit modifications made in the parent theme. From this perspective, antd's theme capabilities have reached their peak in the 5.0 version.
However, the purpose of this article is not to praise antd 5.0 again; that has already been done when it was released. Since the release of version 5.0, almost a year has passed, during which the community has raised various questions and demands. These issues and directions for improvement are the pain points faced by antd.
### CSS Output Size
One significant impact is on users in SSR (Server-Side Rendering) scenarios.
While antd switched to cssinjs, it implemented the ability to selectively import CSS without relying on babel-plugin-import for automatic tree-shaking. However, compared to component libraries like MUI or Mantine, which started using cssinjs from the beginning, antd's adoption of cssinjs can be considered a partial departure. One key difference is that **antd's cssinjs does not follow changes in component props**.
In libraries like MUI or Mantine 6.x, the generated CSS for components with different props configurations is actually different. These styles are placed within a hash class. For example, named classNames in MUI, such as `xxx-focused`, are essentially empty shells, allowing users to customize them easily. The benefit is a significant reduction in the size of the style file. If a page only uses the outlined variant of a Button, there won't be any styles for the filled variant in the final output.
![image](https://github.com/ant-design/ant-design/assets/27722486/e3a1fbe4-2cba-487b-9e3d-06233dda40b4)
In antd 5.0, whenever a component is used, antd automatically includes all styles related to that component—whether they are used or not. This approach has two reasons:
1. Antd did not change the organization of styles from version 4.x to 5.x; it still combines classes to achieve different style effects.
2. To reduce the frequency of dynamically generating styles, antd has implemented a caching strategy at the component level. The same component will only insert styles once, reducing the performance cost of cssinjs during CSS serialization.
It is evident that the advantages of traditional cssinjs and antd are also each other's disadvantages. Antd's css output size becomes significantly large, which is particularly noticeable in SSR scenarios where inline style tags are required.
### Theme Switching
When switching between light and dark themes in cssinjs component libraries, two common issues arise:
1. There is a delay when switching themes.
2. When a static site is switched to a dark theme and then refreshed, it always reverts from the light theme to the dark theme.
Antd has encountered these two issues, and they stem from the nature of runtime-generated styles using cssinjs. The delay is due to the need for a new round of CSS serialization when switching themes. The inability to seamlessly refresh from a static site to a dark theme is because static sites cannot retain the styles of the switched theme.
These issues are non-existent in theme systems based on CSS variables. Examples of such pages include [react.dev](https://react.dev). The principle is simple:
1. Modifying CSS variables does not require re-serialization of CSS, eliminating this performance cost.
2. CSS variables can be injected before page rendering using a script under the body, blocking rendering and avoiding unnecessary style rendering.
## How to Break Through?
Looking at the case of Mantine 7.0, it seems that a theme system/style engine based on CSS variables has a better user experience. However, for cssinjs, there are reasons not to abandon its flexible theme capabilities. In other words, there is a desire to have both the small size and fast switching features of CSS variables, while retaining cssinjs's theme nesting and ability to have multiple themes coexist.
Naturally, the question arises: can we combine cssinjs with CSS variables? After all, they don't seem to be completely mutually exclusive.
## Ant Design and CSS Variables
Antd has collaborated with CSS variables before, as seen in the 4.x era, where there was a set of CSS files based on CSS variables. In version 4.x, antd's theme was mainly implemented using less variables, assigning less variables as CSS variable names, and then assigning values to these CSS variables elsewhere. This was the capability of antd's 4.x CSS variable theme.
The theme capability of antd 5.0 is actually an evolution from 4.x, still using a set of theme tokens for customization. The current theme system's processing flow involves calculating a unique hash variable based on the values of theme tokens to ensure isolation between themes:
![image](https://github.com/ant-design/ant-design/assets/27722486/29ccf0be-3b8e-4ab2-a01d-24dff391ea98)
> The :where selector does not increase the overall specificity, making it suitable for theme isolation.
### Mapping CSS Variables
Naturally, we thought of mapping all tokens to CSS variables. In this [RFC](https://github.com/ant-design/ant-design/discussions/44654), all tokens are mapped to CSS variables, and CSS variables are used to fill the values of the tokens with. As a result, the hash calculation for theme isolation becomes fixed because the values of each token, now represented as CSS variables, will not change. Now, we have a stable HTML, and switching themes only requires replacing the corresponding CSS variables without going through the lengthy cssinjs serialization process.
![image](https://github.com/ant-design/ant-design/assets/27722486/ef9eeb23-b231-45f9-b27d-9ea9b21dc098)
In this approach, the insertion of CSS variables is excluded from the lifecycle of the entire theme. Antd only cares about the replacement between tokens and CSS variables. As long as antd applies CSS variables to various parts of component styles, we can build themes based on CSS variables on top of this.
As shown in the figure, the control of CSS variables for the entire theme is placed under the `:root` selector. This implies that we can modify these CSS variables at any time, either at compile time or in the browser environment. However, placing CSS variables under :root also means that this will be a theme affecting the entire document, and we cannot make adjustments to a specific part of the theme.
Now the question becomes: Can we make CSS variables work locally?
### CSS Variables Isolation
The answer is yes. Recall the hash introduced in 5.0. It plays a crucial role in solving this problem.
By constraining CSS variables within a hash class selector, we can make these CSS variables only effective for components under that theme. At the same time, we can use the provided theme context to dynamically generate CSS variables based on the current theme. We directly convert the values of current theme tokens into CSS variables, combining them with the current hash value to obtain a complete set of styles.
![image](https://github.com/ant-design/ant-design/assets/27722486/7f01d46c-63be-4a81-b576-bfbbfb9a2988)
It appears perfect, utilizing the existing theme features to achieve the isolation of CSS variables between themes. However, up to this point, there is a significant flaw in this approach, as mentioned earlier: to ensure the stability of HTML, when calculating the hash, we actually use the mapped token values to CSS variables, similar to var(--color-primary); and these values do not change because we do not intentionally modify the mapping between CSS variables and tokens. This results in a **fixed hash value**.
Consider the scenario of nesting:
```tsx
<ConfigProvider theme={{ token: { colorPrimary: 'blue' } }}>
<Button>Button 1</Button>
<ConfigProvider theme={{ token: { colorPrimary: 'green' } }}>
<Button>Button 2</Button>
</ConfigProvider>
</ConfigProvider>
```
In the current theme system of Ant Design 5.0, the hash values corresponding to these two buttons are different. As a result, their styles do not affect each other, illustrating the role of hash in theme isolation.
However, in the CSS variable solution, the modification of these two tokens does not actually affect the calculation of the hash. Consequently, theme isolation breaks down, and both `colorPrimary` values end up under the same hash, leading to mutual overlap. To maintain theme isolation, we require different hash values for Button 1 and Button 2, creating a clear contradiction and a new issue.
Let's reconsider the original intention of adopting CSS variables: to achieve faster theme switching and reduce the performance overhead of theme changes. It can be observed that the emphasis is on **switching**.
For nested or parallel themes, their focus is not on "switching" but on "isolation." Therefore, they require distinct and stable hash values, and in most scenarios, their HTML remains stable.
For theme switching, the emphasis is on "switching". What we expect is to achieve fast and high-performance theme switching using CSS variables under a fixed hash (i.e., stable HTML). This does not conflict with theme isolation. Therefore, we still need different hashes to achieve CSS variable isolation. On top of this, different themes corresponding to various hash values can be generated based on user preferences for CSS variable styles.
It might sound a bit abstract, so let's use code to explain the desired outcome. Taking the example of nested themes mentioned earlier, here's what we want the generated CSS file to include:
```css
:where(.css-hash1).ant-btn {
background-color: var(--color-primary);
}
:where(.css-hash2).ant-btn {
background-color: var(--color-primary);
}
.css-hash1 {
--color-primary: blue;
}
.css-hash2 {
--color-primary: green;
}
```
The result is as expected, combining CSS variables with hashes. But in fact, we should adjust our thinking slightly and return the hash calculation to the original value of the token, or directly use CSS variables to calculate the hash:
![image](https://github.com/ant-design/ant-design/assets/27722486/e4c2a109-34d6-4fe7-a3e4-03ed532775b8)
<a name="HAyMz"></a>
### CSS Variables Switching
So far, we have integrated theme isolation with CSS variables using hash, meeting our ambitious goals. However, there is still an issue with hash. It is dynamically calculated, and users cannot know the hash value in advance, making it impossible to directly manipulate CSS variables using JavaScript.
But there is always a solution. Besides directly using JavaScript to modify CSS variables, we can also utilize CSS selectors to switch CSS variables in different scenarios. This requires us to generate CSS variables for different themes in advance:
```css
/* Using custom classes as selectors */
.light .css-hash {
--color-text: rgba(0, 0, 0, 0.85);
}
.dark .css-hash {
--color-text: rgba(255, 255, 255, 0.88);
}
```
At this point, we only need to switch the class on the outermost DOM element wrapping the component to easily achieve theme switching. This outer DOM element can be `html`, `body`, or a specific element in the application. It depends on users.
The problems always seem to be interconnected. We mentioned that the hash is normally calculated based on tokens, but when it comes to dynamic switching, we want the hash to remain constant. So, for the two sets of themes mentioned earlier, their hashes should be consistent, even though their corresponding CSS variables are different. Therefore, we need to make some adjustments to the hash calculation method.
![image](https://github.com/ant-design/ant-design/assets/27722486/43639237-e286-4a06-85c2-f2e93d82087a)
In fact, it can be observed that after applying CSS variables, what we have been doing is turning the 'dynamic' into 'static', making every effort to avoid dynamically modifying tokens using JavaScript. This is crucial as it would trigger JavaScript computation, thereby slowing down webpage performance. Conversely, we leverage the static compilation capability of CSS variables to gradually narrow down the scope of things that would change at runtime. Ultimately, it becomes a matter of efficiently switching themes by modifying only a class name or a DOM attribute.
Finally, don't forget that tokens can be passed through the context. In the case of nested themes, the current tokens inherit values from the context to override them. As described earlier, we need to provide multiple tokens in the theme, each with a different theme name. If multiple themes are used in nested themes at different levels, the computational complexity is actually multiplied. For example:
![image](https://github.com/ant-design/ant-design/assets/27722486/c5493235-68af-495c-b5cd-7e1730c38898)
This can become a potential burden.
### Dynamic Themes with CSS Variables
Consider the following scenario: users can freely modify the theme color and text font size on the webpage to suit their preferences. This scenario does not align with the research discussed earlier because our previous study was based on a complete set of known themes (such as a dark theme). When users can freely modify the value of a variable, we cannot pre-build the theme; instead, we must rely on runtime capabilities for modifications. This situation can be referred to as a true 'dynamic theme'.
As mentioned earlier, there are two obstacles to implementing dynamic themes based on CSS variables:
1. Changes in hash values will cause components and the DOM to re-render.
2. Users cannot know the hash value in advance.
There are two solutions for these two points, respectively.
#### Random Hash
For the first issue, the apparent problem to solve is the dynamic hash value caused by token changes. The existence of hash values serves two purposes: style isolation and caching. In the context of dynamic CSS themes, we can discard the caching feature since we have replaced all tokens with CSS variables. The styles themselves do not change; each token change only affects the inserted CSS variables. At this point, the performance impact of serializing CSS has been significantly reduced. Therefore, we can use random hashes to ensure style isolation.
![image](https://github.com/ant-design/ant-design/assets/27722486/c7128e5e-6330-4dd4-939f-312e11f88af3)
At this point, we can see that users can simply modify tokens in the ConfigProvider, just like before, to use dynamic themes without any noticeable changes in usage. An additional point is that we eliminate the performance cost of calculating hashes here. However, as a trade-off, we might generate two sets of CSS variables that are identical, yet their hashes are different. The impact of this depends on the user's specific usage patterns.
#### Custom Hash
The above solution can actually address most of the issues in various scenarios. However, let's revisit a problem mentioned from the beginning: the FOUC issue. In a statically compiled web page, all theme changes implemented through React lifecycle methods cannot be reflected in the user's browser immediately. We must provide users with the ability to modify the theme before the browser renders the page.
The implementation of this capability essentially involves allowing users to replace CSS variables in scripts. As mentioned earlier, one method is to directly replace the class or attribute on the HTML in the script to apply statically compiled CSS variables, which is not suitable for dynamic themes. Therefore, users need to directly modify the values of CSS variables in the script, introducing the problem of 'needing to know the hash value in advance.' If the hash value is completely random or calculated based on tokens, users cannot use this hash value outside the React lifecycle, or it is challenging to save this hash value for use in scripts outside the React lifecycle.
In other words, as long as the user knows the hash value, is that sufficient? Therefore, we allow users to customize the hash value, and users do not need to worry about the loss of style isolation due to custom reasons—because we can easily detect if users are using the same hash value in the application. In this way, users only need to override hashed CSS variables in the format of Ant Design (antd)—we can provide a factory function to help users generate CSS variable styles.
![image](https://github.com/ant-design/ant-design/assets/27722486/257c5811-bd67-48b3-8ea3-29a428d96bc8)
### Farewell Hash
After ceasing the active calculation of the hash, two questions arise:
1. Is it still a hash?
2. Do we still need to add hash to the styles?
The first question is somewhat philosophical. From a historical perspective, it is still a hash. However, since it is no longer necessary to compute it, it is simply a pure random value or a user-defined string.
The second question is crucial. Now that all tokens have been replaced, styles under different themes no longer have any differences, and the isolating role played by the hash is no longer significant. We still add the hash class to the DOM as the scope of the theme, which directly affects where the components derive their CSS variable values. However, the styles themselves do not care about these, so we can optimize further:
![image](https://github.com/ant-design/ant-design/assets/27722486/f49c5e57-f17e-4725-b850-43708e9a6235)
Styles can now exist independently! This means that different themes can share the same set of styles—there is no longer a need to generate these styles repeatedly.
However, we still need to consider micro-application scenarios. Although hash values are isolated between different versions of antd, styles lose their scope without a hash. Without hash, different versions of antd can contaminate each other. Therefore, we will still provide the ability to apply hash to the overall styles of the application—this is the application-level hash. Unlike the theme hash, the entire application can still reuse the same set of styles.

View File

@ -0,0 +1,208 @@
---
title: 当 Ant Design 遇上 CSS 变量
date: 2023-11-21
author: MadCcc
---
## Ant Design 5.0 的痛点
antd 可以通过 ConfigProvider 对主题 token 进行定制,同样支持嵌套主题,并且嵌套的主题 token 会继承上一层已经修改的部分。从这一点上来讲antd 的主题能力已经达到了 5 个大版本以来的巅峰。
当然我写这篇文章并不是为了把 antd 5.0 再拉出来夸一遍的,这件事在 5.0 发布的时候已经做过了。自 5.0 发布至今已经过去了快一年,其间也充斥着社区的各种质疑与需求,这些也都是 antd 目前面临的痛点与优化方向。
### CSS 产物体积
这一点的主要影响的为 SSR 场景用户。
antd 其实切换到 cssinjs 的时候已经默认实现了按需引入 css 的能力,不需要依靠 `babel-plugin-import` 就能够实现自动的 tree-shaking。但是与 MUI 或者 Mantine 这些一开始就已经使用 cssinjs 的组件库相比antd 使用 cssinjs 其实算是半路出家,也因此有一些历史债务是无法去除的,在 cssinjs 的使用方面也与其他组件库不同:**antd 的 cssinjs 并不跟随组件本身的 props 改变**。
在 MUI 与 Mantine 6.x 中,配置了不同 props 的组件所生成的 css 其实是不一样的,而这些样式都会放在一个 hash class 里面。是的没错MUI 的那些具名的 className 比如 `xxx-focused` 都是空壳,是方便用户进行魔改的。这样做的好处是会大大减少样式文件的大小,比如一个页面只使用了 outlined 变体的 Button那么最终的样式里就不会存在 filled 变体的样式。
![image](https://github.com/ant-design/ant-design/assets/27722486/e3a1fbe4-2cba-487b-9e3d-06233dda40b4)
而在 antd 5.0 中只要使用了某个组件antd 就会自动引入有关这个组件的所有样式——不管有没有使用过。这样做有两个原因:
1. antd 从 4.x 到 5.x 进行改造时并没有改变样式的组织方式,任然是和 4.x 一样通过 class 的组合来实现不同的样式效果。
2. 减少动态生成样式的次数。我们以组件为维度制定了缓存策略,同一个组件只会插入一次样式,这样会减少 cssinjs 在序列化 css 时的性能损耗。
可以看出传统 cssinjs 和 antd 各自的优点其实反过来也是对方的缺点。antd 的 css 产物大小因此变得非常庞大,这一点在 SSR 场景下需要内联 style 标签时就显得格外碍眼。
### 主题切换
在 cssinjs 的组件库中切换亮暗色主题时我们通常会发现两个问题:
1. 切换主题时有卡顿感
2. 静态站点切换到暗色后刷新,总是会从亮色再变回暗色
这两个问题其实 antd 也中招了,其原因归结起来其实还是由于 cssinjs 运行时生成样式的特性。前者卡顿是因为需要进行一轮新的 css 序列化;后者不能无缝刷新是因为静态站点不能保留切换后主题的样式。
这两个问题在 CSS 变量的主题系统下就不存在,典型页面就是 [react.dev](https://react.dev)。原理其实也很简单:
1. 修改 CSS 变量不需要重新序列化 css省去了这一性能消耗
2. 在页面渲染之前 CSS 变量就可以进行注入,即通过 body 下的 script 来阻塞渲染,从而避免了渲染不必要的样式。
## 如何破局?
结合 Mantine 7.0 的案例来看,似乎以 CSS 变量为基础的主题系统/样式引擎拥有更好的用户体验。但是对于 cssinjs 我们也有不能割舍的理由——我们并不希望像 Mantine 那样舍弃更加灵活的主题能力。也就是说我们希望拥有 CSS 变量体积小、切换快的特性,又希望保留 cssinjs 的主题嵌套、多主题并存的能力。
那么我们自然而然的就想到能不能将 cssinjs 与 CSS 变量结合到一起?毕竟他们看起来并不是完全互斥的。
## Ant Design 与 CSS 变量
antd 不是第一次与 CSS 变量合作了,早在 4.x 时代就已经有了一套基于 CSS 变量的 CSS 文件。4.x 中 antd 的主题主要通过 less 变量来实现,将 less 变量赋值为 CSS 变量名,然后在其他地方给这些 CSS 变量赋值,这就是 antd 4.x CSS 变量主题的能力。
antd 5.0 的主题能力其实由 4.x 的进化而来,同样拥有一套主题 token 来进行主题定制。这是目前的主题系统的处理过程,其中我们会基于主题 Token 的值来计算一个唯一的 hash 变量,从而保证主题之间相互隔离:
![image](https://github.com/ant-design/ant-design/assets/27722486/29ccf0be-3b8e-4ab2-a01d-24dff391ea98)
> `:where`选择器不会提高整体权重,所以很适合用来做主题隔离
### CSS 变量映射
所以很自然地,我们也想到了将所有 token 用 CSS 变量填充的方案,于是这篇 RFC 便诞生了:[[RFC] CSS variable theme of antd](https://github.com/ant-design/ant-design/discussions/44654)
在这篇 RFC 中,我们引入了一个设想:将所有 token 都映射到 CSS 变量,并用 CSS 变量来填充 token 的值。如此一来 antd 对于主题的 hash 计算就会固定,因为每个 token 的值都不会再变了。这时我们就会得到一份稳定的 HTML想要切换主题时只需要替换对应的 CSS 变量,而不需要经历冗长的 cssinjs 序列化过程。
![image](https://github.com/ant-design/ant-design/assets/27722486/ef9eeb23-b231-45f9-b27d-9ea9b21dc098)
现在我们融合 cssinjs 和 CSS 变量的方案初具雏形了。这个方案中,其实 CSS 变量插入的环节被排除在了整个主题的生命周期之外,因为我们只关心 token 和 CSS 变量之间的相互替换。只要 antd 将 CSS 变量运用到了组件样式的各个角落,我们自然就可以在此之上进行基于 CSS 变量的主题构筑。
如上图所示,控制整个主题的 CSS 变量最终被放在了 `:root`选择器下,这也就意味着我们可以在任何时机修改这一份 CSS 变量:可以是编译时,也可以在浏览器环境中。但是另一方面,将 CSS 变量放置在 `:root` 下也意味着这将会是一份影响范围是整个 document 的主题,我们无法进行局部主题的调整。
那么现在问题就变成了:我们能否让 CSS 变量局部生效?
### CSS 变量隔离
答案是可以的。还记得我们在 5.0 引入的 hash 吗?这就是解决这个问题的关键。
通过将 CSS 变量约束在 hash 的类选择器下,我们就可以让这批 CSS 变量只对该主题下的组件生效。同时我们也可以利用上下文中所提供的主题来实时地生产 CSS 变量:把当前主题配置的 token 值直接转化为 CSS 变量,组合上当前的 hash 值就得到了一份完整的样式。
![image](https://github.com/ant-design/ant-design/assets/27722486/7f01d46c-63be-4a81-b576-bfbbfb9a2988)
看起来很完美,利用现有的主题特性实现了 CSS 变量在主题之间隔离的功能。但是实际上到目前为止这个方案有一个巨大的缺陷,而这在上文中也提到过:为了保证 HTML 的稳定,计算 hash 时我们实际上是使用的映射到 CSS 变量后的 token 值,也就是类似 `var(--color-primary)` 的值;而这些值是不会变的,因为我们不会去刻意修改 CSS 变量与 token 间的映射关系,**所以这也就会导致 hash 是一个固定值。**
考虑嵌套的场景:
```tsx
<ConfigProvider theme={{ token: { colorPrimary: 'blue' } }}>
<Button>Button 1</Button>
<ConfigProvider theme={{ token: { colorPrimary: 'green' } }}>
<Button>Button 2</Button>
</ConfigProvider>
</ConfigProvider>
```
在 antd 5.0 当前的主题系统下,这两个 Button 所对应的 hash 值是不一样的,所以他们的样式互不影响,这就是 hash 在主题隔离的作用。
但是在 CSS 变量的方案下,这两处 token 修改并不会实际影响 hash 的计算,所以会导致主题隔离失效,两个 `colorPrimary`会被放置在相同的 hash 下,导致相互覆盖。在保证主题隔离的前提下,我们需要 Button 1 和 Button 2 的 hash 值不一样,但这又明显是矛盾的,产生了一个新的问题。
回过头来思考一下我们采用 CSS 变量的初衷:**实现更快的主题切换,降低主题切换的性能消耗。**可以发现重点其实在“切换”上。
对于嵌套主题或者平行主题来说,他们的侧重点并不是“切换”而是“隔离”,所以需要各异且稳定的 hash在大多数场景下他们的 HTML 也是稳定的。
对于切换主题来说,重点就来到了“切换”。我们所期望的是在固定 hash即稳定的 HTML的情况下利用 CSS 变量实现快速、高性能的主题切换,这一点与主题隔离并不冲突,所以我们仍然需要不同的 hash 来 CSS 变量隔离的局面。在此之上,再根据用户的需求生产各个 hash 下不同的主题对应的 CSS 变量样式。
这说起来有些抽象,我们来用代码解释一下想要的效果。同样是上面举出的嵌套主题的场景,我们最终希望得到的 CSS 文件应该包含这些:
```css
:where(.css-hash1).ant-btn {
background-color: var(--color-primary);
}
:where(.css-hash2).ant-btn {
background-color: var(--color-primary);
}
.css-hash1 {
--color-primary: blue;
}
.css-hash2 {
--color-primary: green;
}
```
这样的产物是符合预期的,它将 CSS 变量与 hash 结合到了一起。但实际上这样我们应该稍微调整一下我们的思路,将 hash 计算回归到 token 原本的值上,或者直接利用 CSS 变量来计算 hash
![image](https://github.com/ant-design/ant-design/assets/27722486/e4c2a109-34d6-4fe7-a3e4-03ed532775b8)
<a name="HAyMz"></a>
### CSS 变量切换
到目前为止,我们通过 hash 将主题隔离与 CSS 变量融合到了一起,满足了一下我们既要又要的野心。但是 hash 其实还有一个问题——它是动态计算的,用户无法提前得知 hash 值到底是什么,也就无法实现直接用 js 操作修改 CSS 变量。
但是办法总是有的。我们除了直接利用 js 修改 CSS 变量之外,还可以利用 CSS 选择器来实现不同场景下 CSS 变量的切换,这就要求我们提前把不同主题的 CSS 变量同时生成好:
```css
/* 利用自定义的主题 class 作为嵌套选择器 */
.light .css-hash {
--color-text: rgba(0, 0, 0, 0.85);
}
.dark .css-hash {
--color-text: rgba(255, 255, 255, 0.88);
}
```
此时我们只需要切换包裹在组件外层的 dom 上的 class就可以轻易实现主题的切换。这个外层 dom 可以是 html可以是 body也可以是应用中的某一层元素 —— 这由用户自由决定。
问题总是一环套一环的。我们之前提到 hash 还是正常通过 token 计算的,但是涉及到动态切换时我们又希望 hash 是不变的。所以想上文中列出的两套主题,他们的 hash 应该是一致的,尽管他们对应的 CSS 变量并不相同。于是我们需要在 hash 的计算方法上做一些文章。
![image](https://github.com/ant-design/ant-design/assets/27722486/43639237-e286-4a06-85c2-f2e93d82087a)
实际上可以发现我们在套用 CSS 变量之后,我们一直在做的一件事情就是把“动态”变为“静态”,极力去避免使用 js 动态修改 token 的情况发生,因为这一定会唤起 js 的计算逻辑从而拖慢网页性能。相对的,我们利用 CSS 变量可提前编译的静态能力,将运行时会产生变化的东西一步步缩小范围,最终变成只需要修改一个类名或者 dom 属性就可以做到高性能的主题切换。
最后别忘了 token 是可以通过 context 进行传递的,在嵌套主题时,当前的 token 会继承来自上下文的 token 值进行覆盖。根据上文中的描述,我们需要在主题中提供复数的 token他们分别拥有不同的主题名。如果在嵌套的主题中多层使用了多主题那么其计算量其实是乘算的。举个例子
![image](https://github.com/ant-design/ant-design/assets/27722486/c5493235-68af-495c-b5cd-7e1730c38898)
这会成为一个潜在的负担。
### CSS 变量动态主题
考虑这样的场景:用户可以在网页中任意修改主题色以及文字字号,以符合自己的喜好。这个场景与上文所研究的就并不适配了,原因是我们之前的研究建立在有一整套已知的主题(如暗色主题)上,但用户可以随意修改某个变量值的时候我们就不能够提前对主题进行构建,只能够依赖运行时的能力进行修改。这种场景可以称为是真正的“动态主题”。
如同上文所说,实现基于 CSS 变量的动态主题有两个阻碍:
1. hash 值变化会导致组件和 dom 重新渲染;
2. 用户无法提前得知 hash 值
而针对这两点分别有两种解决方案。
#### 随机 hash
对于前者,我们需要解决的问题明显是 token 变化带来的动态 hash 值。hash 值存在的原因有两个,样式隔离和缓存。样式隔离需要的是每个主题对应不一样的 hash缓存需要的是每个主题对应唯一的 hash。在 CSS 动态主题的场景下,我们可以抛弃缓存这一特性,因为我们已经将所有的 token 替换为了 CSS 变量,样式本身并不会改变,每次改变 token 只会改变插入的 CSS 变量,这时**序列化 CSS 带来的性能消耗已经被大幅减小了**。因此我们完全可以采用随机 hash 来保证样式隔离。
![image](https://github.com/ant-design/ant-design/assets/27722486/c7128e5e-6330-4dd4-939f-312e11f88af3)
这时我们可以发现用户想要使用动态主题时只要像之前一样在 ConfigProvider 里修改 token 就可以了,不会产生任何使用上的变化。而额外的一点就是,这里我们省去了计算 hash 带来的性能损耗,但相对的我们可能会生产出一模一样的两套 CSS 变量而他们的 hash 并不一致。这一点具体会带来正面还是负面的影响还得具体看用户的使用方法。
#### 自定义 hash
上述的方案其实已经可以解决大部分场景下的问题但我们回过头看还有一个从一开始就提到的问题FOUC 问题。在一个已经静态编译的网页上,所有利用 react 生命周期实现的主题变化都不能在第一时间反应到用户的浏览器上。我们必须提供给用户能够在浏览器渲染页面前,就能够修改主题的能力。
这种能力的实现方式其实就是能够让用户在脚本中替换 CSS 变量。我们在上面提到了一种方法是在脚本中直接替换 html 上的 class 或者属性来套用已经静态编译完成的 CSS 变量,这对于动态主题并不适用。所以用户就需要在脚本中直接修改 CSS 变量的值,所以就引入了“需要提前得知 hash 值”这个问题,因为如果 hash 值完全随机或者根据 token 计算,用户就无法在 react 生命周期之外使用这个 hash 值;或者说很难把这个 hash 值存下来,供在 react 生命周期之外的 script 使用。
那么换句话讲,只要用户知道 hash 值是不是就可以了呢?
所以我们允许用户 diy hash 值,用户也不需要担心因为自定义的原因导致样式隔离失效——我们很容易就可以检测出用户在应用中使用了相同的 hash 值。如此一来用户只需要按照 antd 的格式来覆盖 hashed CSS 变量就可以了——我们可以提供一个工厂函数来帮助用户生产 CSS 变量样式。
![image](https://github.com/ant-design/ant-design/assets/27722486/257c5811-bd67-48b3-8ea3-29a428d96bc8)
### 再见了 hash
不再主动计算 hash 后,我们心中冒出了两个问题:
1. hash 它还是 hash 吗?
2. 我们还需要在样式上添加 hash 吗?
第一个问题其实有点哲学,从发展历程来讲,它就是 hash。但是他已经不再需要计算了所以就是一个纯粹的随机值或者用户自定义的字符串。
第二个问题很重要。在所有 token 都被替换的现在不同主题下的样式已经不会再有任何区别了hash 起到的隔离作用也不再重要。我们仍然会在 dom 上添加 hash class 作为主题的 scope它会直接影响组件所采用的 CSS 变量源于何处。但是样式并不关心这些,所以我们再进行一次优化:
![image](https://github.com/ant-design/ant-design/assets/27722486/f49c5e57-f17e-4725-b850-43708e9a6235)
样式居然可以单独存在了!这意味着不同的主题可以共用同一份样式——我们不再需要重复生成这些样式了。
当然还需要考虑微应用场景,不同版本的 antd 之间虽然 hash 是隔离的,但是样式失去了 hash 之后也就失去了作用域,不同版本的 antd 会相互污染,所以我们仍会提供对应用整体的样式打上 hash 的能力——这就是应用级别的 hash与主题 hash 不同,整个应用仍然可以复用同一份样式。

View File

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "5.11.1",
"version": "5.11.3",
"description": "An enterprise-class UI design language and React components implementation",
"keywords": [
"ant",
@ -113,10 +113,10 @@
],
"dependencies": {
"@ant-design/colors": "^7.0.0",
"@ant-design/cssinjs": "^1.18.0-alpha.2",
"@ant-design/cssinjs": "^1.18.0-alpha.5",
"@ant-design/icons": "^5.2.6",
"@ant-design/react-slick": "~1.0.2",
"@babel/runtime": "^7.18.3",
"@babel/runtime": "^7.23.4",
"@ctrl/tinycolor": "^3.6.1",
"@rc-component/color-picker": "~1.4.1",
"@rc-component/mutate-observer": "^1.1.0",
@ -133,7 +133,7 @@
"rc-drawer": "~6.5.2",
"rc-dropdown": "~4.1.0",
"rc-field-form": "~1.40.0",
"rc-image": "~7.3.2",
"rc-image": "~7.5.0",
"rc-input": "~1.3.6",
"rc-input-number": "~8.4.0",
"rc-mentions": "~2.9.1",
@ -165,32 +165,32 @@
"@ant-design/compatible": "^5.1.2",
"@ant-design/happy-work-theme": "^1.0.0",
"@ant-design/tools": "^17.3.2",
"@antv/g6": "^4.8.13",
"@argos-ci/core": "^1.0.0",
"@babel/eslint-plugin": "^7.19.1",
"@biomejs/biome": "^1.0.0",
"@codesandbox/sandpack-react": "^2.9.0",
"@dnd-kit/core": "^6.0.7",
"@antv/g6": "^4.8.23",
"@argos-ci/core": "^1.2.1",
"@babel/eslint-plugin": "^7.22.10",
"@biomejs/biome": "^1.3.3",
"@codesandbox/sandpack-react": "^2.10.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.1",
"@emotion/react": "^11.10.4",
"@emotion/server": "^11.4.0",
"@ianvs/prettier-plugin-sort-imports": "^4.1.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",
"@ianvs/prettier-plugin-sort-imports": "^4.1.1",
"@qixian.cs/github-contributors-list": "^1.1.0",
"@size-limit/file": "^11.0.0",
"@stackblitz/sdk": "^1.3.0",
"@testing-library/dom": "^9.0.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.2",
"@types/fs-extra": "^11.0.1",
"@stackblitz/sdk": "^1.9.0",
"@testing-library/dom": "^9.3.3",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
"@types/fs-extra": "^11.0.4",
"@types/gtag.js": "^0.0.18",
"@types/http-server": "^0.12.1",
"@types/inquirer": "^9.0.3",
"@types/http-server": "^0.12.4",
"@types/inquirer": "^9.0.7",
"@types/isomorphic-fetch": "^0.0.39",
"@types/jest": "^29.0.0",
"@types/jest-axe": "^3.5.3",
"@types/jest": "^29.5.10",
"@types/jest-axe": "^3.5.8",
"@types/jest-environment-puppeteer": "^5.0.0",
"@types/jest-image-snapshot": "^6.1.0",
"@types/jquery": "^3.5.14",
@ -274,8 +274,8 @@
"pretty-format": "^29.7.0",
"prismjs": "^1.29.0",
"progress": "^2.0.3",
"puppeteer": "^21.1.1",
"qs": "^6.10.1",
"puppeteer": "^21.5.2",
"qs": "^6.11.2",
"rc-footer": "^0.6.8",
"rc-tween-one": "^3.0.3",
"rc-virtual-list": "^3.4.11",
@ -297,21 +297,22 @@
"remark-lint-no-undefined-references": "^4.2.1",
"remark-preset-lint-recommended": "^6.0.0",
"runes2": "^1.1.2",
"semver": "^7.3.5",
"simple-git": "^3.0.0",
"semver": "^7.5.4",
"simple-git": "^3.21.0",
"size-limit": "^11.0.0",
"stylelint": "^15.1.0",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^34.0.0",
"stylelint-prettier": "^4.0.0",
"stylelint-prettier": "^4.0.2",
"sylvanas": "^0.6.1",
"terser": "^5.16.1",
"tsx": "^4.1.0",
"typedoc": "^0.25.0",
"terser": "^5.24.0",
"tsx": "^4.3.0",
"typedoc": "^0.25.3",
"typescript": "~5.2.2",
"vanilla-jsoneditor": "^0.18.0",
"webpack-bundle-analyzer": "^4.1.0",
"xhr-mock": "^2.4.1"
"vanilla-tilt": "^1.8.1",
"webpack-bundle-analyzer": "^4.10.1",
"xhr-mock": "^2.5.1"
},
"peerDependencies": {
"react": ">=16.9.0",
@ -325,7 +326,7 @@
"dumi": "^2.3.0-alpha.4"
}
},
"packageManager": "npm@10.2.3",
"packageManager": "npm@10.2.4",
"size-limit": [
{
"path": "./dist/antd.min.js",

View File

@ -50,6 +50,7 @@ const DEPRECIATED_VERSION = {
'5.10.0': ['https://github.com/ant-design/ant-design/issues/45289'],
'5.11.0': ['https://github.com/ant-design/ant-design/issues/45742'],
'5.11.1': ['https://github.com/ant-design/ant-design/issues/45883'],
'5.11.2': ['https://github.com/ant-design/ant-design/issues/46005'],
} as const;
function matchDeprecated(v: string) {

View File

@ -1,18 +1,49 @@
#!/bin/sh
#!/bin/bash
echo "[TEST ALL] test changelog"
echo "[TEST ALL] test changelog" > ~test-all.txt
tsx ./scripts/check-version-md.ts
# Full skip argms
# npm run test-all -- --skip-changelog --skip-commit --skip-lint --skip-build --skip-dekko --skip-dist --skip-es --skip-lib --skip-test --skip-node
echo "[TEST ALL] check-commit"
echo "[TEST ALL] check-commit" > ~test-all.txt
npm run check-commit
# Check exist argument
has_arg() {
local term="$1"
local start=0
echo "[TEST ALL] lint"
echo "[TEST ALL] lint" > ~test-all.txt
npm run lint
for arg in "$@"; do
if [ $start -gt 0 ] && [ "$arg" == "$term" ]; then
return 0 # Return 0 if argument exist
fi
if [ "$1" != "--skip-build" ]; then
start=$((start+1))
done
return 1 # Return 1 if argument not exist
}
if ! has_arg '--skip-changelog' "$@"; then
echo "[TEST ALL] test changelog"
echo "[TEST ALL] test changelog" > ~test-all.txt
tsx ./scripts/check-version-md.ts
else
echo "[TEST ALL] test changelog...skip"
fi
if ! has_arg '--skip-commit' "$@"; then
echo "[TEST ALL] check-commit"
echo "[TEST ALL] check-commit" > ~test-all.txt
npm run check-commit
else
echo "[TEST ALL] check-commit...skip"
fi
if ! has_arg '--skip-lint' "$@"; then
echo "[TEST ALL] lint"
echo "[TEST ALL] lint" > ~test-all.txt
npm run lint
else
echo "[TEST ALL] lint...skip"
fi
if ! has_arg '--skip-build' "$@"; then
echo "[TEST ALL] dist"
echo "[TEST ALL] dist" > ~test-all.txt
npm run dist
@ -21,32 +52,57 @@ if [ "$1" != "--skip-build" ]; then
echo "[TEST ALL] compile" > ~test-all.txt
npm run compile
else
echo "Skip build..."
echo "[TEST ALL] build...skip"
fi
echo "[TEST ALL] dekko dist"
echo "[TEST ALL] dekko dist" > ~test-all.txt
node ./tests/dekko/dist.test.js
if ! has_arg '--skip-dekko' "$@"; then
echo "[TEST ALL] dekko dist"
echo "[TEST ALL] dekko dist" > ~test-all.txt
node ./tests/dekko/dist.test.js
echo "[TEST ALL] dist test"
echo "[TEST ALL] dist test" > ~test-all.txt
LIB_DIR=dist npm test
echo "[TEST ALL] dekko lib"
echo "[TEST ALL] dekko lib" > ~test-all.txt
node ./tests/dekko/lib.test.js
else
echo "[TEST ALL] dekko test...skip"
fi
echo "[TEST ALL] dekko lib"
echo "[TEST ALL] dekko lib" > ~test-all.txt
if ! has_arg '--skip-dist' "$@"; then
echo "[TEST ALL] dist test"
echo "[TEST ALL] dist test" > ~test-all.txt
LIB_DIR=dist npm test -- --bail
else
echo "[TEST ALL] dist test...skip"
fi
echo "[TEST ALL] test es"
echo "[TEST ALL] test es" > ~test-all.txt
LIB_DIR=es npm test
if ! has_arg '--skip-es' "$@"; then
echo "[TEST ALL] test es"
echo "[TEST ALL] test es" > ~test-all.txt
LIB_DIR=es npm test -- --bail
else
echo "[TEST ALL] test es...skip"
fi
echo "[TEST ALL] test lib"
echo "[TEST ALL] test lib" > ~test-all.txt
LIB_DIR=lib npm test
if ! has_arg '--skip-lib' "$@"; then
echo "[TEST ALL] test lib"
echo "[TEST ALL] test lib" > ~test-all.txt
LIB_DIR=lib npm test -- --bail
else
echo "[TEST ALL] test lib...skip"
fi
echo "[TEST ALL] test"
echo "[TEST ALL] test" > ~test-all.txt
npm test
if ! has_arg '--skip-test' "$@"; then
echo "[TEST ALL] test"
echo "[TEST ALL] test" > ~test-all.txt
npm test -- --bail
else
echo "[TEST ALL] test...skip"
fi
echo "[TEST ALL] test node"
echo "[TEST ALL] test node" > ~test-all.txt
npm run test-node
if ! has_arg '--skip-node' "$@"; then
echo "[TEST ALL] test node"
echo "[TEST ALL] test node" > ~test-all.txt
npm run test-node -- --bail
else
echo "[TEST ALL] test node...skip"
fi

View File

@ -5,6 +5,7 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const { EsbuildPlugin } = require('esbuild-loader');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
const path = require('path');
function addLocales(webpackConfig) {
let packageName = 'antd-with-locales';
@ -24,6 +25,13 @@ function externalDayjs(config) {
};
}
function externalCssinjs(config) {
config.resolve = config.resolve || {};
config.resolve.alias = config.resolve.alias || {};
config.resolve.alias['@ant-design/cssinjs'] = path.resolve(__dirname, 'alias/cssinjs');
}
let webpackConfig = getWebpackConfig(false);
// Used for `size-limit` ci which only need to check min files
@ -37,6 +45,8 @@ if (process.env.RUN_ENV === 'PRODUCTION') {
webpackConfig.forEach((config) => {
addLocales(config);
externalDayjs(config);
externalCssinjs(config);
// Reduce non-minified dist files size
config.optimization.usedExports = true;
// use esbuild