diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 2e2f2fd3c8..0000000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-# Use the latest 2.1 version of CircleCI pipeline process engine.
-# See: https://circleci.com/docs/2.0/configuration-reference
-version: 2.1
-
-# Define a job to be invoked later in a workflow.
-# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
-jobs:
- test-argos-ci:
- docker:
- - image: cimg/node:21.1-browsers
- environment:
- NODE_OPTIONS: --openssl-legacy-provider
- steps:
- - checkout
- - run:
- name: Install node_modules
- command: yarn
- - run:
- name: Build dist file
- command: npm run dist:esbuild
- - run:
- name: Run image screenshot tests
- command: npm run test-image
- - run:
- name: Upload screenshots to Argos CI
- command: npm run argos
- # The resource_class feature allows configuring CPU and RAM resources for each job. Different resource classes are available for different executors. https://circleci.com/docs/2.0/configuration-reference/#resourceclass
- resource_class: large
-
-# Invoke jobs via workflows
-# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
-workflows:
- test-argos-ci-workflow:
- jobs:
- - test-argos-ci
diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json
index 96c2526e4c..16798ee955 100644
--- a/.codesandbox/ci.json
+++ b/.codesandbox/ci.json
@@ -1,4 +1,5 @@
{
+ "installCommand": "npm-install",
"sandboxes": ["antd-reproduction-template-forked-jyh2k9"],
"node": "18"
}
diff --git a/.dumi/global.css b/.dumi/global.css
index 8a14f9a69a..e56892774b 100644
--- a/.dumi/global.css
+++ b/.dumi/global.css
@@ -1,8 +1,10 @@
.demo-logo {
width: 120px;
+ min-width: 120px;
height: 32px;
background: rgba(255, 255, 255, 0.2);
border-radius: 6px;
+ margin-inline-end: 24px;
}
.demo-logo-vertical {
diff --git a/.dumi/hooks/useThemeAnimation.ts b/.dumi/hooks/useThemeAnimation.ts
index 34b72e7809..9e0f421633 100644
--- a/.dumi/hooks/useThemeAnimation.ts
+++ b/.dumi/hooks/useThemeAnimation.ts
@@ -68,6 +68,7 @@ const useThemeAnimation = () => {
if (!(event && typeof document.startViewTransition === 'function')) {
return;
}
+ const time = Date.now();
const x = event.clientX;
const y = event.clientY;
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
@@ -98,6 +99,7 @@ const useThemeAnimation = () => {
root.classList.add(isDark ? 'light' : 'dark');
})
.ready.then(() => {
+ console.log(`Theme transition finished in ${Date.now() - time}ms`);
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
diff --git a/.dumi/pages/index/components/BannerRecommends.tsx b/.dumi/pages/index/components/BannerRecommends.tsx
index 7a4dfb5612..961f7576e7 100644
--- a/.dumi/pages/index/components/BannerRecommends.tsx
+++ b/.dumi/pages/index/components/BannerRecommends.tsx
@@ -1,33 +1,41 @@
import * as React from 'react';
-import { createStyles, css, useTheme } from 'antd-style';
-import classNames from 'classnames';
import type { FC } from 'react';
import { useContext } from 'react';
-import { Typography, Skeleton, Carousel } from 'antd';
-import type { Extra, Icon } from './util';
-import SiteContext from '../../../theme/slots/SiteContext';
-import { getCarouselStyle, useSiteData } from './util';
-import useLocale from '../../../hooks/useLocale';
+import { Badge, Carousel, Skeleton, Typography } from 'antd';
+import { createStyles, useTheme } from 'antd-style';
+import classNames from 'classnames';
-const useStyle = createStyles(({ token }) => {
+import useLocale from '../../../hooks/useLocale';
+import SiteContext from '../../../theme/slots/SiteContext';
+import type { Extra, Icon } from './util';
+import { getCarouselStyle, useSiteData } from './util';
+
+const useStyle = createStyles(({ token, css, cx }) => {
const { carousel } = getCarouselStyle();
+ const itemBase = css`
+ display: flex;
+ flex: 1 1 0;
+ flex-direction: column;
+ align-items: stretch;
+ text-decoration: none;
+ background: ${token.colorBgContainer};
+ border: ${token.lineWidth}px solid ${token.colorBorderSecondary};
+ border-radius: ${token.borderRadiusLG}px;
+ transition: all ${token.motionDurationSlow};
+ padding-block: ${token.paddingMD}px;
+ padding-inline: ${token.paddingLG}px;
+ box-sizing: border-box;
+ `;
+
return {
- itemBase: css`
- display: flex;
- flex: 1 1 0;
- flex-direction: column;
- align-items: stretch;
- text-decoration: none;
- background: ${token.colorBgContainer};
- border: ${token.lineWidth}px solid ${token.colorBorderSecondary};
- border-radius: ${token.borderRadiusLG}px;
- transition: all ${token.motionDurationSlow};
- padding-block: ${token.paddingMD}px;
- padding-inline: ${token.paddingLG}px;
+ itemBase,
+ ribbon: css`
+ & > .${cx(itemBase)} {
+ height: 100%;
+ }
`,
cardItem: css`
- width: 33%;
&:hover {
box-shadow: ${token.boxShadowCard};
}
@@ -45,6 +53,9 @@ const useStyle = createStyles(({ token }) => {
column-gap: ${token.paddingMD * 2}px;
align-items: stretch;
text-align: start;
+ > * {
+ width: calc((100% - ${token.marginXXL * 2}px) / 3);
+ }
`,
carousel,
};
@@ -56,7 +67,7 @@ interface RecommendItemProps {
icons: Icon[];
className?: string;
}
-const RecommendItem = ({ extra, index, icons, className }: RecommendItemProps) => {
+const RecommendItem: React.FC = ({ extra, index, icons, className }) => {
const token = useTheme();
const { styles } = useStyle();
@@ -65,7 +76,7 @@ const RecommendItem = ({ extra, index, icons, className }: RecommendItemProps) =
}
const icon = icons.find((i) => i.name === extra.source);
- return (
+ const card = (
);
+
+ if (index === 0) {
+ return (
+
+ {card}
+
+ );
+ }
+
+ return card;
};
export const BannerRecommendsFallback: FC = () => {
@@ -93,8 +114,8 @@ export const BannerRecommendsFallback: FC = () => {
return isMobile ? (
- {list.map((extra, index) => (
-
+ {list.map((_, index) => (
+
))}
@@ -102,20 +123,26 @@ export const BannerRecommendsFallback: FC = () => {
) : (
{list.map((_, index) => (
-
+
+
+
))}
);
};
-export default function BannerRecommends() {
+const BannerRecommends: React.FC = () => {
const { styles } = useStyle();
const [, lang] = useLocale();
const { isMobile } = React.useContext(SiteContext);
const data = useSiteData();
const extras = data?.extras?.[lang];
- const icons = data?.icons;
- const first3 = extras.length === 0 ? Array(3).fill(null) : extras.slice(0, 3);
+ const icons = data?.icons || [];
+ const first3 = !extras || extras.length === 0 ? Array(3).fill(null) : extras.slice(0, 3);
+
+ if (!data) {
+ return
;
+ }
return (
@@ -147,4 +174,6 @@ export default function BannerRecommends() {
)}
);
-}
+};
+
+export default BannerRecommends;
diff --git a/.dumi/pages/index/components/PreviewBanner/ComponentsBlock.tsx b/.dumi/pages/index/components/PreviewBanner/ComponentsBlock.tsx
index 42dc50702d..e28b46c549 100644
--- a/.dumi/pages/index/components/PreviewBanner/ComponentsBlock.tsx
+++ b/.dumi/pages/index/components/PreviewBanner/ComponentsBlock.tsx
@@ -17,9 +17,9 @@ import {
Tooltip,
} from 'antd';
import { createStyles } from 'antd-style';
-import classNames from 'classnames';
import useLocale from '../../../../hooks/useLocale';
+import Tilt from './Tilt';
const { _InternalPanelDoNotUseOrYouWillBeFired: ModalPanel } = Modal;
const { _InternalPanelDoNotUseOrYouWillBeFired: InternalTooltip } = Tooltip;
@@ -72,16 +72,14 @@ const locales = {
const useStyle = createStyles(({ token, css }) => {
const gap = token.padding;
-
return {
holder: css`
width: 500px;
display: flex;
flex-direction: column;
row-gap: ${gap}px;
- opacity: 0.65;
+ opacity: 0.8;
`,
-
flex: css`
display: flex;
flex-wrap: nowrap;
@@ -105,25 +103,16 @@ const useStyle = createStyles(({ token, css }) => {
};
});
-export interface ComponentsBlockProps {
- className?: string;
- style?: React.CSSProperties;
-}
-
-const ComponentsBlock: React.FC
= (props) => {
- const { className, style } = props;
-
+const ComponentsBlock: React.FC = () => {
const [locale] = useLocale(locales);
const { styles } = useStyle();
return (
-
-
-
-
{/* Line */}
= (props) => {
26: '26°C',
37: '37°C',
100: {
- style: {
- color: '#f50',
- },
+ style: { color: '#f50' },
label: 100°C,
},
}}
defaultValue={[26, 37]}
/>
-
{/* Line */}
-
{/* Line */}
@@ -236,7 +198,6 @@ const ComponentsBlock: React.FC = (props) => {
checkedChildren={}
unCheckedChildren={}
/>
-
= (props) => {
/>
-
-
-
-
+
);
};
diff --git a/.dumi/pages/index/components/PreviewBanner/Tilt.tsx b/.dumi/pages/index/components/PreviewBanner/Tilt.tsx
new file mode 100644
index 0000000000..ec1213e03f
--- /dev/null
+++ b/.dumi/pages/index/components/PreviewBanner/Tilt.tsx
@@ -0,0 +1,34 @@
+import React, { useEffect, useRef } from 'react';
+import VanillaTilt from 'vanilla-tilt';
+import type { TiltOptions } from 'vanilla-tilt';
+
+interface TiltProps extends React.HTMLAttributes {
+ options?: TiltOptions;
+}
+
+// https://micku7zu.github.io/vanilla-tilt.js/index.html
+const defaultTiltOptions: TiltOptions = {
+ scale: 1.02,
+ max: 8,
+ speed: 1500,
+ glare: true,
+ 'max-glare': 0.8,
+};
+
+const Tilt: React.FC = ({ options, ...props }) => {
+ const node = useRef(null);
+ useEffect(() => {
+ if (node.current) {
+ VanillaTilt.init(node.current, {
+ ...defaultTiltOptions,
+ ...options,
+ });
+ }
+ return () => {
+ (node.current as any)?.vanillaTilt.destroy();
+ };
+ }, []);
+ return ;
+};
+
+export default Tilt;
diff --git a/.dumi/pages/index/components/PreviewBanner/index.tsx b/.dumi/pages/index/components/PreviewBanner/index.tsx
index 45991dfaba..cb26d82c1c 100644
--- a/.dumi/pages/index/components/PreviewBanner/index.tsx
+++ b/.dumi/pages/index/components/PreviewBanner/index.tsx
@@ -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,12 +35,14 @@ const useStyle = () => {
inset: 0;
backdrop-filter: blur(4px);
opacity: 1;
- transition: opacity 1s ease;
+ background-color: rgba(255, 255, 255, 0.2);
+ transition: all 1s ease;
+ pointer-events: none;
`);
return {
holder: css`
- height: 520px;
+ height: 640px;
display: flex;
flex-direction: column;
align-items: center;
@@ -94,6 +94,7 @@ const useStyle = () => {
child: css`
position: relative;
+ width: 100%;
z-index: 1;
`,
};
@@ -114,10 +115,8 @@ const PreviewBanner: React.FC = (props) => {
const { pathname, search } = useLocation();
const isZhCN = utils.isZhCN(pathname);
- const [componentsBlockStyle, mouseEvents] = useMouseTransform();
-
return (
-
+
{/* Image Left Top */}
= (props) => {
{/* Mobile not show the component preview */}
- {!isMobile && }
+ {isMobile ? null : (
+
+
+
+ )}
diff --git a/.dumi/pages/index/components/PreviewBanner/useMouseTransform.tsx b/.dumi/pages/index/components/PreviewBanner/useMouseTransform.tsx
deleted file mode 100644
index a6985067bb..0000000000
--- a/.dumi/pages/index/components/PreviewBanner/useMouseTransform.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import React, { startTransition } from 'react';
-import { ConfigProvider } from 'antd';
-
-const getTransformRotateStyle = (
- event: React.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({});
-
- const { direction } = React.useContext(ConfigProvider.ConfigContext);
-
- const isRTL = direction === 'rtl';
-
- const onMouseMove: React.MouseEventHandler = (event) => {
- const { currentTarget } = event;
- startTransition(() => {
- setComponentsBlockStyle((style) => ({
- ...style,
- transform: getTransformRotateStyle(event, currentTarget, multiple, isRTL),
- }));
- });
- };
-
- const onMouseEnter: React.MouseEventHandler = () => {
- startTransition(() => {
- setComponentsBlockStyle((style) => ({
- ...style,
- transition: `transform ${transitionDuration / 1000}s`,
- }));
- });
-
- setTimeout(() => {
- startTransition(() => {
- setComponentsBlockStyle((style) => ({
- ...style,
- transition: '',
- }));
- });
- }, transitionDuration);
- };
-
- const onMouseLeave: React.MouseEventHandler = () => {
- startTransition(() => {
- setComponentsBlockStyle((style) => ({
- ...style,
- transition: `transform ${transitionDuration / 1000}s`,
- transform: '',
- }));
- });
- };
-
- return [
- componentsBlockStyle,
- {
- onMouseMove,
- onMouseEnter,
- onMouseLeave,
- },
- ] as const;
-};
-
-export default useMouseTransform;
diff --git a/.dumi/pages/index/components/RecommendsOld.tsx b/.dumi/pages/index/components/RecommendsOld.tsx
deleted file mode 100644
index 5e6c00fa47..0000000000
--- a/.dumi/pages/index/components/RecommendsOld.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import * as React from 'react';
-import { createStyles, css, useTheme } from 'antd-style';
-import { Row, Col, Typography } from 'antd';
-import type { Recommendation } from './util';
-
-const useStyle = createStyles(({ token }) => ({
- card: css`
- height: 300px;
- background-size: 100% 100%;
- background-position: center;
- position: relative;
- overflow: hidden;
-
- &:before {
- position: absolute;
- background: linear-gradient(
- rgba(0, 0, 0, 0) 0%,
- rgba(0, 0, 0, 0.25) 40%,
- rgba(0, 0, 0, 0.65) 100%
- );
- opacity: 0.3;
- transition: all 0.5s;
- content: '';
- pointer-events: none;
- inset: 0;
- }
-
- &:hover {
- &:before {
- opacity: 1;
- }
-
- .intro {
- transform: translate3d(0, 0, 0);
-
- h4${token.antCls}-typography {
- padding-bottom: 0;
- }
- }
- }
-
- .intro {
- position: absolute;
- right: 0;
- bottom: 0;
- left: 0;
- transform: translate3d(0, 100%, 0);
- transition: all ${token.motionDurationSlow};
-
- ${token.antCls}-typography {
- margin: 0;
- color: #fff;
- font-weight: normal;
- text-shadow: 0 0 1px rgba(0, 0, 0, 0.5);
- transition: all ${token.motionDurationSlow};
- }
-
- h4${token.antCls}-typography {
- position: absolute;
- padding: 0 ${token.paddingMD}px ${token.paddingMD}px;
- transform: translate3d(0, -100%, 0);
- }
-
- div${token.antCls}-typography {
- padding: ${token.paddingXS}px ${token.paddingMD}px ${token.paddingLG}px;
- }
- }
- `,
-}));
-
-export interface RecommendsProps {
- recommendations?: Recommendation[];
-}
-
-export default function Recommends({ recommendations = [] }: RecommendsProps) {
- const token = useTheme();
- const { styles } = useStyle();
-
- return (
-
- {new Array(3).fill(null).map((_, index) => {
- const data = recommendations[index];
-
- return (
-
- {data ? (
-
-
- {data?.title}
- {data.description}
-
-
- ) : null}
-
- );
- })}
-
- );
-}
diff --git a/.dumi/pages/index/components/Theme/index.tsx b/.dumi/pages/index/components/Theme/index.tsx
index e528a1ad39..399b377b34 100644
--- a/.dumi/pages/index/components/Theme/index.tsx
+++ b/.dumi/pages/index/components/Theme/index.tsx
@@ -403,13 +403,12 @@ export default function Theme() {
...themeToken,
colorPrimary: colorPrimaryValue,
},
- hashed: true,
algorithm: algorithmFn,
components: {
Layout: isLight
? {
- colorBgHeader: 'transparent',
- colorBgBody: 'transparent',
+ headerBg: 'transparent',
+ bodyBg: 'transparent',
}
: {
// colorBgBody: 'transparent',
diff --git a/.dumi/pages/index/components/util.ts b/.dumi/pages/index/components/util.ts
index 295ff4bee7..a4cacaf55d 100644
--- a/.dumi/pages/index/components/util.ts
+++ b/.dumi/pages/index/components/util.ts
@@ -1,5 +1,6 @@
import { css } from 'antd-style';
-import useFetch from '../../../hooks/useFetch';
+import { useEffect, useState } from 'react';
+import fetch from 'cross-fetch';
export interface Author {
avatar: string;
@@ -80,8 +81,18 @@ export function preLoad(list: string[]) {
}
}
-export function useSiteData(): Partial {
- return useFetch('https://render.alipay.com/p/h5data/antd4-config_website-h5data.json');
+export function useSiteData(): Partial | undefined {
+ const [data, setData] = useState(undefined);
+
+ useEffect(() => {
+ fetch('https://render.alipay.com/p/h5data/antd4-config_website-h5data.json').then(
+ async (res) => {
+ setData(await res.json());
+ },
+ );
+ }, []);
+
+ return data;
}
export const getCarouselStyle = () => ({
diff --git a/.dumi/pages/index/index.tsx b/.dumi/pages/index/index.tsx
index 775df42f83..a528f4347e 100644
--- a/.dumi/pages/index/index.tsx
+++ b/.dumi/pages/index/index.tsx
@@ -4,7 +4,7 @@ import { createStyles, css } from 'antd-style';
import useDark from '../../hooks/useDark';
import useLocale from '../../hooks/useLocale';
-// import BannerRecommends, { BannerRecommendsFallback } from './components/BannerRecommends';
+import BannerRecommends from './components/BannerRecommends';
import PreviewBanner from './components/PreviewBanner';
import Group from './components/Group';
@@ -46,10 +46,7 @@ const Homepage: React.FC = () => {
return (
- {/* 文档很久没更新了,先藏起来 */}
- {/* }>
-
- */}
+
diff --git a/.dumi/theme/builtins/ComponentTokenTable/index.tsx b/.dumi/theme/builtins/ComponentTokenTable/index.tsx
index 7d64eb2d5a..45134cdca3 100644
--- a/.dumi/theme/builtins/ComponentTokenTable/index.tsx
+++ b/.dumi/theme/builtins/ComponentTokenTable/index.tsx
@@ -1,13 +1,26 @@
-import { RightOutlined, LinkOutlined, QuestionCircleOutlined } from '@ant-design/icons';
+import React, { useMemo, useState } from 'react';
+import { LinkOutlined, QuestionCircleOutlined, RightOutlined } from '@ant-design/icons';
+import { ConfigProvider, Popover, Table, Typography } from 'antd';
import { createStyles, css, useTheme } from 'antd-style';
import { getDesignToken } from 'antd-token-previewer';
-import React, { useMemo, useState } from 'react';
import tokenMeta from 'antd/es/version/token-meta.json';
import tokenData from 'antd/es/version/token.json';
-import { ConfigProvider, Table, Popover, Typography } from 'antd';
+
import useLocale from '../../../hooks/useLocale';
import { useColumns } from '../TokenTable';
+const compare = (token1: string, token2: string) => {
+ const hasColor1 = token1.toLowerCase().includes('color');
+ const hasColor2 = token2.toLowerCase().includes('color');
+ if (hasColor1 && !hasColor2) {
+ return -1;
+ }
+ if (!hasColor1 && hasColor2) {
+ return 1;
+ }
+ return token1 < token2 ? -1 : 1;
+};
+
const defaultToken = getDesignToken();
const locales = {
@@ -18,6 +31,8 @@ const locales = {
value: '默认值',
componentToken: '组件 Token',
globalToken: '全局 Token',
+ componentComment: '这里是你的组件 token',
+ globalComment: '这里是你的全局 token',
help: '如何定制?',
customizeTokenLink: '/docs/react/customize-theme-cn#修改主题变量',
customizeComponentTokenLink: '/docs/react/customize-theme-cn#修改组件变量',
@@ -29,6 +44,8 @@ const locales = {
value: 'Default Value',
componentToken: 'Component Token',
globalToken: 'Global Token',
+ componentComment: 'here is your component tokens',
+ globalComment: 'here is your global tokens',
help: 'How to use?',
customizeTokenLink: '/docs/react/customize-theme#customize-design-token',
customizeComponentTokenLink: 'docs/react/customize-theme#customize-component-token',
@@ -46,13 +63,13 @@ const useStyle = createStyles(() => ({
`,
arrowIcon: css`
font-size: 16px;
- margin-right: 8px;
+ margin-inline-end: 8px;
& svg {
transition: all 0.3s;
}
`,
help: css`
- margin-left: 8px;
+ margin-inline-start: 8px;
font-size: 12px;
font-weight: normal;
color: #999;
@@ -69,16 +86,14 @@ interface SubTokenTableProps {
helpLink: string;
tokens: string[];
component?: string;
+ comment?: {
+ componentComment?: string;
+ globalComment?: string;
+ };
}
-const SubTokenTable: React.FC
= ({
- defaultOpen,
- tokens,
- title,
- helpText,
- helpLink,
- component,
-}) => {
+const SubTokenTable: React.FC = (props) => {
+ const { defaultOpen, tokens, title, helpText, helpLink, component, comment } = props;
const [, lang] = useLocale(locales);
const token = useTheme();
const columns = useColumns();
@@ -92,24 +107,7 @@ const SubTokenTable: React.FC = ({
}
const data = tokens
- .sort(
- component
- ? undefined
- : (token1, token2) => {
- const hasColor1 = token1.toLowerCase().includes('color');
- const hasColor2 = token2.toLowerCase().includes('color');
-
- if (hasColor1 && !hasColor2) {
- return -1;
- }
-
- if (!hasColor1 && hasColor2) {
- return 1;
- }
-
- return token1 < token2 ? -1 : 1;
- },
- )
+ .sort(component ? undefined : compare)
.map((name) => {
const meta = component
? tokenMeta.components[component].find((item) => item.token === name)
@@ -133,7 +131,7 @@ const SubTokenTable: React.FC = ({
theme={{
components: {
${component}: {
- /* here is your component tokens */
+ /* ${comment?.componentComment} */
},
},
}}
@@ -143,7 +141,7 @@ const SubTokenTable: React.FC = ({
: `
@@ -161,16 +159,17 @@ const SubTokenTable: React.FC = ({
popupStyle={{ width: 400 }}
content={
+ {/* {code} */}
{code}
-
+
{helpText}
}
>
-
+
{helpText}
@@ -217,12 +216,16 @@ const ComponentTokenTable: React.FC = ({ component })
<>
{tokenMeta.components[component] && (
item.token)}
component={component}
- defaultOpen
+ comment={{
+ componentComment: locale.componentComment,
+ globalComment: locale.globalComment,
+ }}
/>
)}
= ({ component })
helpText={locale.help}
helpLink={locale.customizeComponentTokenLink}
tokens={mergedGlobalTokens}
+ comment={{
+ componentComment: locale.componentComment,
+ globalComment: locale.globalComment,
+ }}
/>
>
);
diff --git a/.dumi/theme/builtins/Container/index.tsx b/.dumi/theme/builtins/Container/index.tsx
new file mode 100644
index 0000000000..0d299bf903
--- /dev/null
+++ b/.dumi/theme/builtins/Container/index.tsx
@@ -0,0 +1,39 @@
+/**
+ * copied: https://github.com/arvinxx/dumi-theme-antd-style/tree/master/src/builtins/Container
+ */
+import * as React from 'react';
+import { Alert } from 'antd';
+import { type FC, type ReactNode } from 'react';
+import useStyles from './style';
+
+const Container: FC<{
+ type: 'info' | 'warning' | 'success' | 'error';
+ title?: string;
+ children: ReactNode;
+}> = ({ type, title, children }) => {
+ const { styles, cx } = useStyles();
+
+ return (
+
+ }
+ className={styles.alert}
+ />
+
+ );
+};
+
+export default Container;
diff --git a/.dumi/theme/builtins/Container/style.ts b/.dumi/theme/builtins/Container/style.ts
new file mode 100644
index 0000000000..eea3d77bd0
--- /dev/null
+++ b/.dumi/theme/builtins/Container/style.ts
@@ -0,0 +1,22 @@
+import { createStyles } from 'antd-style';
+
+const useStyles = createStyles(({ prefixCls, css }) => ({
+ container: css`
+ margin: 8px 0;
+ `,
+
+ alert: css`
+ .${prefixCls}-alert-message {
+ font-weight: bold;
+ }
+ `,
+
+ /* 使用 `&&` 加一点点权重 */
+ desc: css`
+ && p {
+ margin: 0;
+ }
+ `,
+}));
+
+export default useStyles;
diff --git a/.dumi/theme/builtins/DemoWrapper/index.tsx b/.dumi/theme/builtins/DemoWrapper/index.tsx
index 6cceeb3c51..5ce92c4570 100644
--- a/.dumi/theme/builtins/DemoWrapper/index.tsx
+++ b/.dumi/theme/builtins/DemoWrapper/index.tsx
@@ -1,15 +1,36 @@
import React, { useContext } from 'react';
import { DumiDemoGrid, FormattedMessage } from 'dumi';
-import { BugFilled, BugOutlined, CodeFilled, CodeOutlined } from '@ant-design/icons';
+import {
+ BugFilled,
+ BugOutlined,
+ CodeFilled,
+ CodeOutlined,
+ ExperimentFilled,
+ ExperimentOutlined,
+} from '@ant-design/icons';
import classNames from 'classnames';
-import { Tooltip } from 'antd';
+import { ConfigProvider, Tooltip } from 'antd';
import DemoContext from '../../slots/DemoContext';
import useLayoutState from '../../../hooks/useLayoutState';
+import useLocale from '../../../hooks/useLocale';
+
+const locales = {
+ cn: {
+ enableCssVar: '启用 CSS 变量',
+ disableCssVar: '禁用 CSS 变量',
+ },
+ en: {
+ enableCssVar: 'Enable CSS Var',
+ disableCssVar: 'Disable CSS Var',
+ },
+};
const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
const { showDebug, setShowDebug } = useContext(DemoContext);
+ const [locale] = useLocale(locales);
const [expandAll, setExpandAll] = useLayoutState(false);
+ const [enableCssVar, setEnableCssVar] = useLayoutState(true);
const expandTriggerClass = classNames('code-box-expand-trigger', {
'code-box-expand-trigger-active': expandAll,
@@ -23,29 +44,36 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
setExpandAll(!expandAll);
};
+ const handleCssVarToggle = () => {
+ setEnableCssVar((v) => !v);
+ };
+
const demos = React.useMemo(
() =>
- items.reduce((acc, item) => {
- const { previewerProps } = item;
- const { debug } = previewerProps;
+ items.reduce(
+ (acc, item) => {
+ const { previewerProps } = item;
+ const { debug } = previewerProps;
- if (debug && !showDebug) return acc;
+ if (debug && !showDebug) return acc;
- return acc.concat({
- ...item,
- previewerProps: {
- ...previewerProps,
- expand: expandAll,
- // always override debug property, because dumi will hide debug demo in production
- debug: false,
- /**
- * antd extra marker for the original debug
- * @see https://github.com/ant-design/ant-design/pull/40130#issuecomment-1380208762
- */
- originDebug: debug,
- },
- });
- }, [] as typeof items),
+ return acc.concat({
+ ...item,
+ previewerProps: {
+ ...previewerProps,
+ expand: expandAll,
+ // always override debug property, because dumi will hide debug demo in production
+ debug: false,
+ /**
+ * antd extra marker for the original debug
+ * @see https://github.com/ant-design/ant-design/pull/40130#issuecomment-1380208762
+ */
+ originDebug: debug,
+ },
+ });
+ },
+ [] as typeof items,
+ ),
[expandAll, showDebug],
);
@@ -74,8 +102,17 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
)}
+
+ {enableCssVar ? (
+
+ ) : (
+
+ )}
+
-
+
+
+
);
};
diff --git a/.dumi/theme/builtins/InstallDependencies/index.tsx b/.dumi/theme/builtins/InstallDependencies/index.tsx
index fd61fa091b..777241401f 100644
--- a/.dumi/theme/builtins/InstallDependencies/index.tsx
+++ b/.dumi/theme/builtins/InstallDependencies/index.tsx
@@ -1,7 +1,8 @@
-import SourceCode from 'dumi/theme-default/builtins/SourceCode';
import React from 'react';
-import type { TabsProps } from 'antd';
-import { Tabs } from 'antd';
+import { ConfigProvider, Tabs } from 'antd';
+import SourceCode from 'dumi/theme-default/builtins/SourceCode';
+import type { Tab } from 'rc-tabs/lib/interface';
+
import NpmLogo from './npm';
import PnpmLogo from './pnpm';
import YarnLogo from './yarn';
@@ -12,51 +13,34 @@ interface InstallProps {
pnpm?: string;
}
-const npmLabel = (
-
-
- npm
-
-);
-
-const pnpmLabel = (
-
-
- pnpm
-
-);
-
-const yarnLabel = (
-
-
- yarn
-
-);
-
const InstallDependencies: React.FC = (props) => {
const { npm, yarn, pnpm } = props;
- const items = React.useMemo(
- () =>
- [
- {
- key: 'npm',
- children: npm ? {npm} : null,
- label: npmLabel,
- },
- {
- key: 'yarn',
- children: yarn ? {yarn} : null,
- label: yarnLabel,
- },
- {
- key: 'pnpm',
- children: pnpm ? {pnpm} : null,
- label: pnpmLabel,
- },
- ].filter((item) => item.children),
- [npm, yarn, pnpm],
+ const items: Tab[] = [
+ {
+ key: 'npm',
+ label: 'npm',
+ children: npm ? {npm} : null,
+ icon: ,
+ },
+ {
+ key: 'yarn',
+ label: 'yarn',
+ children: yarn ? {yarn} : null,
+ icon: ,
+ },
+ {
+ key: 'pnpm',
+ label: 'pnpm',
+ children: pnpm ? {pnpm} : null,
+ icon: ,
+ },
+ ].filter((item) => item.children);
+
+ return (
+
+
+
);
- return ;
};
export default InstallDependencies;
diff --git a/.dumi/theme/builtins/InstallDependencies/npm.tsx b/.dumi/theme/builtins/InstallDependencies/npm.tsx
index ffd6755688..63937b0322 100644
--- a/.dumi/theme/builtins/InstallDependencies/npm.tsx
+++ b/.dumi/theme/builtins/InstallDependencies/npm.tsx
@@ -1,26 +1,39 @@
import React from 'react';
+import { createStyles, css } from 'antd-style';
+import classNames from 'classnames';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
+const useStyle = createStyles(() => ({
+ iconWrap: css`
+ display: inline-flex;
+ align-items: center;
+ line-height: 0;
+ text-align: center;
+ vertical-align: -0.125em;
+ `,
+}));
+
const NpmIcon: React.FC = (props) => {
const { className, style } = props;
+ const { styles } = useStyle();
return (
-
+
+
+
);
};
diff --git a/.dumi/theme/builtins/InstallDependencies/pnpm.tsx b/.dumi/theme/builtins/InstallDependencies/pnpm.tsx
index 1be5a1ce3a..67cb132e2e 100644
--- a/.dumi/theme/builtins/InstallDependencies/pnpm.tsx
+++ b/.dumi/theme/builtins/InstallDependencies/pnpm.tsx
@@ -1,28 +1,41 @@
import React from 'react';
+import { createStyles, css } from 'antd-style';
+import classNames from 'classnames';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
+const useStyle = createStyles(() => ({
+ iconWrap: css`
+ display: inline-flex;
+ align-items: center;
+ line-height: 0;
+ text-align: center;
+ vertical-align: -0.125em;
+ `,
+}));
+
const PnpmIcon: React.FC = (props) => {
const { className, style } = props;
+ const { styles } = useStyle();
return (
-
+
+
+
);
};
diff --git a/.dumi/theme/builtins/InstallDependencies/yarn.tsx b/.dumi/theme/builtins/InstallDependencies/yarn.tsx
index c79ac4eee7..7f73b97efd 100644
--- a/.dumi/theme/builtins/InstallDependencies/yarn.tsx
+++ b/.dumi/theme/builtins/InstallDependencies/yarn.tsx
@@ -1,27 +1,40 @@
import React from 'react';
+import { createStyles, css } from 'antd-style';
+import classNames from 'classnames';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
+const useStyle = createStyles(() => ({
+ iconWrap: css`
+ display: inline-flex;
+ align-items: center;
+ line-height: 0;
+ text-align: center;
+ vertical-align: -0.125em;
+ `,
+}));
+
const YarnIcon: React.FC = (props) => {
const { className, style } = props;
+ const { styles } = useStyle();
return (
-
+
+
+
);
};
diff --git a/.dumi/theme/builtins/Previewer/CodePreviewer.tsx b/.dumi/theme/builtins/Previewer/CodePreviewer.tsx
index e5516170cf..b9c0ec0896 100644
--- a/.dumi/theme/builtins/Previewer/CodePreviewer.tsx
+++ b/.dumi/theme/builtins/Previewer/CodePreviewer.tsx
@@ -255,7 +255,7 @@ const CodePreviewer: React.FC = (props) => {
'react@18/umd/react.development.js',
'react-dom@18/umd/react-dom.development.js',
'dayjs@1/dayjs.min.js',
- `antd@${pkg.version}/dist/antd-with-locales.js`,
+ `antd@${pkg.version}/dist/antd-with-locales.min.js`,
`@ant-design/icons/dist/index.umd.js`,
'react-router-dom/dist/umd/react-router-dom.production.min.js',
'react-router/dist/umd/react-router.production.min.js',
diff --git a/.dumi/theme/builtins/Previewer/index.tsx b/.dumi/theme/builtins/Previewer/index.tsx
index e36c556112..83a5b56a07 100644
--- a/.dumi/theme/builtins/Previewer/index.tsx
+++ b/.dumi/theme/builtins/Previewer/index.tsx
@@ -10,8 +10,9 @@ const Previewer = React.lazy(() => import('./Previewer'));
const useStyle = createStyles(({ css }) => ({
skeletonWrapper: css`
width: 100% !important;
- height: 500px;
+ height: 250px;
margin-bottom: 16px;
+ border-radius: 8px;
`,
}));
diff --git a/.dumi/theme/builtins/ResourceArticles/index.tsx b/.dumi/theme/builtins/ResourceArticles/index.tsx
index b6fbf635e9..d6bf4927f4 100644
--- a/.dumi/theme/builtins/ResourceArticles/index.tsx
+++ b/.dumi/theme/builtins/ResourceArticles/index.tsx
@@ -1,11 +1,10 @@
/* eslint-disable react/no-array-index-key */
import * as React from 'react';
-import { Suspense } from 'react';
import dayjs from 'dayjs';
import { FormattedMessage } from 'dumi';
import { createStyles } from 'antd-style';
import { Avatar, Divider, Empty, Skeleton, Tabs } from 'antd';
-import type { Article, Authors } from '../../../pages/index/components/util';
+import type { Article, Authors, SiteData } from '../../../pages/index/components/util';
import { useSiteData } from '../../../pages/index/components/util';
import useLocale from '../../../hooks/useLocale';
@@ -97,10 +96,11 @@ const ArticleList: React.FC = ({ name, data = [], authors = []
);
};
-const Articles: React.FC = () => {
+const Articles: React.FC<{ data: Partial }> = ({ data }) => {
const [, lang] = useLocale();
const isZhCN = lang === 'cn';
- const { articles = { cn: [], en: [] }, authors = [] } = useSiteData();
+
+ const { articles = { cn: [], en: [] }, authors = [] } = data;
// ========================== Data ==========================
const mergedData = React.useMemo(() => {
@@ -149,11 +149,13 @@ const Articles: React.FC = () => {
export default () => {
const { styles } = useStyle();
+ const data = useSiteData();
+
+ const articles = data ? : ;
+
return (
);
};
diff --git a/.dumi/theme/common/Color/ColorPaletteToolDark.tsx b/.dumi/theme/common/Color/ColorPaletteToolDark.tsx
index 59400cfae1..c7f3da669f 100644
--- a/.dumi/theme/common/Color/ColorPaletteToolDark.tsx
+++ b/.dumi/theme/common/Color/ColorPaletteToolDark.tsx
@@ -44,7 +44,7 @@ const ColorPaletteTool: React.FC = () => {
text += locale.saturation((s * 100).toFixed(2));
}
if (b * 100 < primaryMinBrightness) {
- text += locale.brightness((s * 100).toFixed(2));
+ text += locale.brightness((b * 100).toFixed(2));
}
}
return (
diff --git a/.dumi/theme/common/Loading.tsx b/.dumi/theme/common/Loading.tsx
index 912bd7cb37..cf1ff79613 100644
--- a/.dumi/theme/common/Loading.tsx
+++ b/.dumi/theme/common/Loading.tsx
@@ -11,10 +11,16 @@ const Loading: React.FC = () => {
pathname.startsWith('/changelog')
) {
return (
-
-
+
+
-
+
+
);
}
diff --git a/.dumi/theme/common/styles/Demo.tsx b/.dumi/theme/common/styles/Demo.tsx
index c988dc2999..9476e49a05 100644
--- a/.dumi/theme/common/styles/Demo.tsx
+++ b/.dumi/theme/common/styles/Demo.tsx
@@ -26,7 +26,7 @@ const GlobalDemoStyles: React.FC = () => {
margin: 0 0 16px;
background-color: ${token.colorBgContainer};
border: 1px solid ${token.colorSplit};
- border-radius: ${token.borderRadius}px;
+ border-radius: ${token.borderRadiusLG}px;
transition: all 0.2s;
.code-box-title {
@@ -39,7 +39,7 @@ const GlobalDemoStyles: React.FC = () => {
.code-box-demo {
background-color: ${token.colorBgContainer};
- border-radius: ${token.borderRadius}px ${token.borderRadius}px 0 0;
+ border-radius: ${token.borderRadiusLG}px ${token.borderRadiusLG}px 0 0;
> .demo {
overflow: auto;
}
@@ -85,10 +85,6 @@ const GlobalDemoStyles: React.FC = () => {
transition: background-color 0.4s;
margin-inline-start: 16px;
- ${antCls}-row-rtl & {
- border-radius: ${token.borderRadius}px 0 0 ${token.borderRadius}px;
- }
-
a,
a:hover {
color: ${token.colorText};
diff --git a/.dumi/theme/common/styles/Markdown.tsx b/.dumi/theme/common/styles/Markdown.tsx
index 0eef2359a9..56246e6160 100644
--- a/.dumi/theme/common/styles/Markdown.tsx
+++ b/.dumi/theme/common/styles/Markdown.tsx
@@ -199,28 +199,6 @@ const GlobalStyle: React.FC = () => {
font-size: 30px;
}
}
- .antd-site-snippet {
- .ant-tabs-tab {
- .snippet-label {
- display: flex;
- align-items: center;
- justify-content: center;
- svg {
- margin-inline-end: 8px;
- }
- }
- }
- .dumi-default-source-code {
- margin: 0 auto;
- background-color: ${token.siteMarkdownCodeBg};
- border-radius: ${token.borderRadius}px;
- > pre.prism-code {
- padding: 12px 20px;
- font-size: 13px;
- line-height: 2;
- }
- }
- }
.markdown table td > a:not(:last-child) {
margin-right: 0 !important;
diff --git a/.dumi/theme/layouts/GlobalLayout.tsx b/.dumi/theme/layouts/GlobalLayout.tsx
index 9a93813a2c..34f42003a5 100644
--- a/.dumi/theme/layouts/GlobalLayout.tsx
+++ b/.dumi/theme/layouts/GlobalLayout.tsx
@@ -4,7 +4,7 @@ import {
createCache,
extractStyle,
legacyNotSelectorLinter,
- logicalPropertiesLinter,
+ NaNLinter,
parentSelectorLinter,
StyleProvider,
} from '@ant-design/cssinjs';
@@ -45,7 +45,7 @@ const getAlgorithm = (themes: ThemeName[] = []) =>
}
return null;
})
- .filter((item) => item) as typeof antdTheme.darkAlgorithm[];
+ .filter((item) => item) as (typeof antdTheme.darkAlgorithm)[];
const GlobalLayout: React.FC = () => {
const outlet = useOutlet();
@@ -168,7 +168,7 @@ const GlobalLayout: React.FC = () => {
{
token: {
motion: !theme.includes('motion-off'),
},
+ cssVar: true,
}}
>
{content}
diff --git a/.dumi/theme/locales/en-US.json b/.dumi/theme/locales/en-US.json
index 6ca8a49d35..41dd070306 100644
--- a/.dumi/theme/locales/en-US.json
+++ b/.dumi/theme/locales/en-US.json
@@ -111,6 +111,8 @@
"app.footer.seeconf": "Experience Tech Conference",
"app.footer.xtech": "Ant Financial Experience Tech",
"app.footer.xtech.slogan": "Experience The Beauty",
+ "app.footer.galacean": "Galacean",
+ "app.footer.galacean.slogan": "Interactive Graphics Solution",
"app.docs.color.pick-primary": "Pick your primary color",
"app.docs.color.pick-background": "Pick your background color",
"app.docs.components.icon.search.placeholder": "Search icons here, click icon to copy code",
diff --git a/.dumi/theme/locales/zh-CN.json b/.dumi/theme/locales/zh-CN.json
index 2a37d63e3e..1cefada772 100644
--- a/.dumi/theme/locales/zh-CN.json
+++ b/.dumi/theme/locales/zh-CN.json
@@ -110,6 +110,8 @@
"app.footer.seeconf": "蚂蚁体验科技大会",
"app.footer.xtech": "蚂蚁体验科技",
"app.footer.xtech.slogan": "让用户体验美好",
+ "app.footer.galacean": "Galacean",
+ "app.footer.galacean.slogan": "互动图形解决方案",
"app.docs.color.pick-primary": "选择你的主色",
"app.docs.color.pick-background": "选择你的背景色",
"app.docs.components.icon.search.placeholder": "在此搜索图标,点击图标可复制代码",
diff --git a/.dumi/theme/plugin.ts b/.dumi/theme/plugin.ts
index 90a1490d0a..b6744cc65d 100644
--- a/.dumi/theme/plugin.ts
+++ b/.dumi/theme/plugin.ts
@@ -166,7 +166,7 @@ const RoutesPlugin = (api: IApi) => {
});
// Insert antd style to head
- const matchRegex = /;
};
@@ -156,6 +166,12 @@ If you are using the Pages Router in Next.js and using antd as your component li
1. Install `@ant-design/cssinjs`
+> Notes for developers
+>
+> Please note that when you install `@ant-design/cssinjs`, you must ensure that the version is consistent with the version of `@ant-design/cssinjs` in local `node_modules` of `antd`, otherwise, multiple React instances will appear, resulting in ctx being unable to be read correctly. (Tips: you can use `npm ls @ant-design/cssinjs` command to view the local version)
+>
+>
+
2. Rewrite `pages/_document.tsx`
diff --git a/docs/react/use-with-next.zh-CN.md b/docs/react/use-with-next.zh-CN.md
index bc7fc1df89..f218350904 100644
--- a/docs/react/use-with-next.zh-CN.md
+++ b/docs/react/use-with-next.zh-CN.md
@@ -58,6 +58,12 @@ export default Home;
1. 安装 `@ant-design/cssinjs`
+> 开发者注意事项:
+>
+> 请注意,安装 `@ant-design/cssinjs` 时必须确保版本号跟 `antd` 本地的 `node_modules` 中的 `@ant-design/cssinjs` 版本保持一致,否则会出现多个 React 实例,导致无法正确的读取 ctx。(Tips: 你可以通过 `npm ls @ant-design/cssinjs` 命令查看本地版本)
+>
+>
+
2. 创建 `lib/AntdRegistry.tsx`
@@ -67,16 +73,20 @@ export default Home;
import React from 'react';
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
-// 如果您使用的是 Next.js 14,请改用下面的导入。更多信息: https://github.com/ant-design/ant-design/issues/45567
-// import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs/lib';
import type Entity from '@ant-design/cssinjs/es/Cache';
import { useServerInsertedHTML } from 'next/navigation';
const StyledComponentsRegistry = ({ children }: React.PropsWithChildren) => {
const cache = React.useMemo(() => createCache(), []);
- useServerInsertedHTML(() => (
-
- ));
+ const isServerInserted = React.useRef(false);
+ useServerInsertedHTML(() => {
+ // 避免 css 重复插入
+ if (isServerInserted.current) {
+ return;
+ }
+ isServerInserted.current = true;
+ return ;
+ });
return {children};
};
@@ -156,6 +166,12 @@ export default HomePage;
1. 安装 `@ant-design/cssinjs`
+> 开发者注意事项:
+>
+> 请注意,安装 `@ant-design/cssinjs` 时必须确保版本号跟 `antd` 本地的 `node_modules` 中的 `@ant-design/cssinjs` 版本保持一致,否则会出现多个 React 实例,导致无法正确的读取 ctx。(Tips: 你可以通过 `npm ls @ant-design/cssinjs` 命令查看本地版本)
+>
+>
+
2. 改写 `pages/_document.tsx`
diff --git a/jest-puppeteer.config.js b/jest-puppeteer.config.js
index fb644512ed..ae97f22bd9 100644
--- a/jest-puppeteer.config.js
+++ b/jest-puppeteer.config.js
@@ -1,6 +1,7 @@
// jest-puppeteer.config.js
module.exports = {
launch: {
+ ignoreDefaultArgs: ['--disable-extensions'],
args: [
// Required for Docker version of Puppeteer
'--no-sandbox',
diff --git a/package.json b/package.json
index 58ab034e57..79fb486cd4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "antd",
- "version": "5.11.0",
+ "version": "5.12.5",
"description": "An enterprise-class UI design language and React components implementation",
"keywords": [
"ant",
@@ -44,7 +44,6 @@
],
"scripts": {
"api-collection": "antd-tools run api-collection",
- "argos": "tsx scripts/argos-upload.ts",
"authors": "tsx scripts/generate-authors.ts",
"biome:format": "biome format --write .",
"build": "npm run compile && NODE_OPTIONS='--max-old-space-size=4096' npm run dist",
@@ -75,7 +74,7 @@
"lint:deps": "antd-tools run deps-lint",
"lint:md": "remark . -f -q",
"lint:script": "npm run component-changelog && eslint . --ext .js,.jsx,.ts,.tsx --cache",
- "lint:style": "tsx scripts/check-cssinjs.ts",
+ "lint:style": "tsx scripts/check-cssinjs.tsx",
"pre-publish": "npm run test-all -- --skip-build && tsx ./scripts/pre-publish-notice.ts",
"prepare": "is-ci || husky install && dumi setup",
"prepublishOnly": "antd-tools run guard",
@@ -94,12 +93,14 @@
"pretest": "npm run version && npm run component-changelog",
"test": "jest --config .jest.js --no-cache",
"test-all": "sh -e ./scripts/test-all.sh",
- "test-image": "jest --config .jest.image.js --no-cache -i -u",
+ "test-image": "jest --config .jest.image.js --no-cache -i -u --forceExit",
"test-node": "npm run version && jest --config .jest.node.js --no-cache",
"test:update": "jest --config .jest.js --no-cache -u",
"token-meta": "tsx scripts/generate-token-meta.ts",
"tsc": "tsc --noEmit",
- "version": "tsx scripts/generate-version.ts"
+ "version": "tsx scripts/generate-version.ts",
+ "visual-regression": "tsx scripts/visual-regression/build.ts",
+ "npm-install": "npm install"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": "biome format --write",
@@ -113,50 +114,50 @@
],
"dependencies": {
"@ant-design/colors": "^7.0.0",
- "@ant-design/cssinjs": "^2.0.0-alpha.1",
+ "@ant-design/cssinjs": "^1.18.1",
"@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",
- "@rc-component/tour": "~1.10.0",
- "@rc-component/trigger": "^1.18.1",
+ "@rc-component/tour": "~1.12.0",
+ "@rc-component/trigger": "^1.18.2",
"classnames": "^2.3.2",
"copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.1",
"qrcode.react": "^3.1.0",
"rc-cascader": "~3.20.0",
"rc-checkbox": "~3.1.0",
- "rc-collapse": "~3.7.1",
+ "rc-collapse": "~3.7.2",
"rc-dialog": "~9.3.4",
"rc-drawer": "~6.5.2",
"rc-dropdown": "~4.1.0",
- "rc-field-form": "~1.40.0",
- "rc-image": "~7.3.2",
- "rc-input": "~1.3.5",
- "rc-input-number": "~8.4.0",
- "rc-mentions": "~2.9.1",
- "rc-menu": "~9.12.2",
+ "rc-field-form": "~1.41.0",
+ "rc-image": "~7.5.1",
+ "rc-input": "~1.4.2",
+ "rc-input-number": "~8.6.1",
+ "rc-mentions": "~2.10.1",
+ "rc-menu": "~9.12.4",
"rc-motion": "^2.9.0",
"rc-notification": "~5.3.0",
- "rc-pagination": "~3.7.0",
+ "rc-pagination": "~4.0.3",
"rc-picker": "~3.14.6",
"rc-progress": "~3.5.1",
"rc-rate": "~2.12.0",
"rc-resize-observer": "^1.4.0",
"rc-segmented": "~2.2.2",
"rc-select": "~14.10.0",
- "rc-slider": "~10.4.0",
+ "rc-slider": "~10.5.0",
"rc-steps": "~6.0.1",
"rc-switch": "~4.1.0",
- "rc-table": "~7.35.2",
- "rc-tabs": "~12.13.1",
- "rc-textarea": "~1.5.2",
- "rc-tooltip": "~6.1.2",
+ "rc-table": "~7.36.1",
+ "rc-tabs": "~12.15.0",
+ "rc-textarea": "~1.6.3",
+ "rc-tooltip": "~6.1.3",
"rc-tree": "~5.8.2",
"rc-tree-select": "~5.15.0",
- "rc-upload": "~4.3.5",
+ "rc-upload": "~4.3.6",
"rc-util": "^5.38.1",
"scroll-into-view-if-needed": "^3.1.0",
"throttle-debounce": "^5.0.0"
@@ -164,154 +165,169 @@
"devDependencies": {
"@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",
+ "@ant-design/tools": "^18.0.2",
+ "@antv/g6": "^4.8.23",
+ "@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",
+ "@madccc/duplicate-package-checker-webpack-plugin": "^1.0.0",
"@qixian.cs/github-contributors-list": "^1.1.0",
- "@size-limit/file": "^10.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",
- "@types/gtag.js": "^0.0.17",
- "@types/http-server": "^0.12.1",
- "@types/inquirer": "^9.0.3",
+ "@size-limit/file": "^11.0.0",
+ "@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/ali-oss": "^6.16.11",
+ "@types/fs-extra": "^11.0.4",
+ "@types/gtag.js": "^0.0.18",
+ "@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-environment-puppeteer": "^5.0.0",
- "@types/jest-image-snapshot": "^6.1.0",
- "@types/jquery": "^3.5.14",
- "@types/jsdom": "^21.1.2",
- "@types/lodash": "^4.14.139",
- "@types/node": "^20.0.0",
- "@types/nprogress": "^0.2.0",
- "@types/prismjs": "^1.26.0",
- "@types/progress": "^2.0.5",
- "@types/qs": "^6.9.7",
- "@types/react": "^18.2.33",
- "@types/react-copy-to-clipboard": "^5.0.0",
- "@types/react-dom": "^18.0.0",
- "@types/react-highlight-words": "^0.16.4",
- "@types/react-resizable": "^3.0.0",
- "@types/throttle-debounce": "^5.0.0",
- "@types/warning": "^3.0.0",
- "@typescript-eslint/eslint-plugin": "^6.5.0",
- "@typescript-eslint/parser": "^6.5.0",
- "antd-img-crop": "^4.9.0",
- "antd-style": "^3.5.0",
- "antd-token-previewer": "^2.0.4",
+ "@types/jest": "^29.5.10",
+ "@types/jest-axe": "^3.5.8",
+ "@types/jest-environment-puppeteer": "^5.0.6",
+ "@types/jest-image-snapshot": "^6.2.3",
+ "@types/jquery": "^3.5.29",
+ "@types/jsdom": "^21.1.6",
+ "@types/lodash": "^4.14.202",
+ "@types/node": "^20.10.0",
+ "@types/nprogress": "^0.2.3",
+ "@types/pixelmatch": "^5.2.6",
+ "@types/pngjs": "^6.0.4",
+ "@types/prismjs": "^1.26.3",
+ "@types/progress": "^2.0.7",
+ "@types/qs": "^6.9.10",
+ "@types/react": "^18.2.38",
+ "@types/react-copy-to-clipboard": "^5.0.7",
+ "@types/react-dom": "^18.2.17",
+ "@types/react-highlight-words": "^0.16.7",
+ "@types/react-resizable": "^3.0.7",
+ "@types/tar": "^6.1.10",
+ "@types/throttle-debounce": "^5.0.2",
+ "@types/warning": "^3.0.3",
+ "@typescript-eslint/eslint-plugin": "^6.12.0",
+ "@typescript-eslint/parser": "^6.12.0",
+ "ali-oss": "^6.18.1",
+ "antd-img-crop": "^4.17.0",
+ "antd-style": "^3.5.2",
+ "antd-token-previewer": "^2.0.5",
"chalk": "^4.0.0",
"cheerio": "1.0.0-rc.12",
"circular-dependency-plugin": "^5.2.2",
- "cross-env": "^7.0.0",
+ "cross-env": "^7.0.3",
"cross-fetch": "^4.0.0",
"crypto": "^1.0.1",
"dekko": "^0.2.1",
"dumi": "^2.3.0-alpha.9",
- "dumi-plugin-color-chunk": "^1.0.2",
- "duplicate-package-checker-webpack-plugin": "^3.0.0",
- "esbuild-loader": "^4.0.0",
- "eslint": "^8.40.0",
- "eslint-config-airbnb": "^19.0.0",
+ "dumi-plugin-color-chunk": "^1.0.4",
+ "esbuild-loader": "^4.0.2",
+ "eslint": "^8.54.0",
+ "eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^9.0.0",
- "eslint-import-resolver-typescript": "^3.5.2",
- "eslint-plugin-compat": "^4.1.1",
- "eslint-plugin-import": "^2.28.0",
- "eslint-plugin-jest": "^27.0.1",
- "eslint-plugin-jsx-a11y": "^6.2.1",
+ "eslint-import-resolver-typescript": "^3.6.1",
+ "eslint-plugin-compat": "^4.2.0",
+ "eslint-plugin-import": "^2.29.0",
+ "eslint-plugin-jest": "^27.6.0",
+ "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-lodash": "^7.4.0",
- "eslint-plugin-markdown": "^3.0.0",
- "eslint-plugin-react": "^7.31.8",
- "eslint-plugin-react-hooks": "^4.1.2",
- "eslint-plugin-unicorn": "^49.0.0",
- "fast-glob": "^3.2.11",
- "fetch-jsonp": "^1.1.3",
- "fs-extra": "^11.0.0",
- "gh-pages": "^6.0.0",
- "glob": "^10.3.7",
- "html2sketch": "^1.0.0",
- "http-server": "^14.0.0",
- "husky": "^8.0.1",
+ "eslint-plugin-markdown": "^3.0.1",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-unicorn": "^50.0.1",
+ "fast-glob": "^3.3.2",
+ "fetch-jsonp": "^1.3.0",
+ "fs-extra": "^11.1.1",
+ "gh-pages": "^6.1.0",
+ "glob": "^10.3.10",
+ "html2sketch": "^1.0.2",
+ "http-server": "^14.1.1",
+ "husky": "^8.0.3",
"identity-obj-proxy": "^3.0.0",
- "immer": "^10.0.1",
- "inquirer": "^9.2.11",
+ "immer": "^10.0.3",
+ "inquirer": "^9.2.12",
"is-ci": "^3.0.1",
"isomorphic-fetch": "^3.0.0",
- "jest": "^29.4.1",
+ "jest": "^29.7.0",
"jest-axe": "^8.0.0",
- "jest-canvas-mock": "^2.4.0",
- "jest-environment-jsdom": "^29.0.1",
- "jest-environment-node": "^29.0.0",
- "jest-image-snapshot": "^6.0.0",
- "jest-puppeteer": "^9.0.0",
- "jquery": "^3.4.1",
- "jsdom": "^22.0.0",
+ "jest-canvas-mock": "^2.5.2",
+ "jest-environment-jsdom": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-image-snapshot": "^6.2.0",
+ "jest-puppeteer": "^9.0.1",
+ "jquery": "^3.7.1",
+ "jsdom": "^23.0.0",
"jsonml-to-react-element": "^1.1.11",
"jsonml.js": "^0.1.0",
- "lint-staged": "^15.0.0",
+ "lint-staged": "^15.1.0",
"lodash": "^4.17.21",
- "lunar-typescript": "^1.6.9",
- "lz-string": "^1.4.4",
- "mockdate": "^3.0.0",
+ "lunar-typescript": "^1.6.13",
+ "lz-string": "^1.5.0",
+ "minimist": "^1.2.8",
+ "mockdate": "^3.0.5",
+ "node-fetch": "^3.3.2",
"node-notifier": "^10.0.1",
"nprogress": "^0.2.0",
- "open": "^9.0.0",
- "prettier": "^3.0.0",
- "prettier-plugin-jsdoc": "^1.0.1",
- "pretty-format": "^29.0.0",
+ "open": "^10.0.0",
+ "pixelmatch": "^5.3.0",
+ "pngjs": "^7.0.0",
+ "prettier": "^3.1.0",
+ "prettier-plugin-jsdoc": "^1.1.1",
+ "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",
- "react": "^18.0.0",
- "react-copy-to-clipboard": "^5.0.1",
- "react-countup": "^6.4.0",
- "react-dom": "^18.0.0",
- "react-draggable": "^4.4.3",
- "react-fast-marquee": "^1.2.1",
+ "rc-tween-one": "^3.0.6",
+ "rc-virtual-list": "^3.11.3",
+ "react": "^18.2.0",
+ "react-copy-to-clipboard": "^5.1.0",
+ "react-countup": "^6.5.0",
+ "react-dom": "^18.2.0",
+ "react-draggable": "^4.4.6",
+ "react-fast-marquee": "^1.6.2",
"react-highlight-words": "^0.20.0",
"react-infinite-scroll-component": "^6.1.0",
- "react-resizable": "^3.0.1",
- "react-router-dom": "^6.0.2",
- "react-sticky-box": "^2.0.0",
+ "react-resizable": "^3.0.5",
+ "react-router-dom": "^6.20.0",
+ "react-sticky-box": "^2.0.5",
"regenerator-runtime": "^0.14.0",
"remark": "^15.0.1",
"remark-cli": "^12.0.0",
- "remark-lint": "^9.0.0",
+ "remark-gfm": "^4.0.0",
+ "remark-html": "^16.0.1",
+ "remark-lint": "^9.1.2",
"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",
- "size-limit": "^10.0.0",
- "stylelint": "^15.1.0",
+ "remark-preset-lint-recommended": "^6.1.3",
+ "runes2": "^1.1.3",
+ "semver": "^7.5.4",
+ "sharp": "^0.33.0",
+ "simple-git": "^3.21.0",
+ "size-limit": "^11.0.0",
+ "stylelint": "^16.0.0",
"stylelint-config-rational-order": "^0.1.2",
- "stylelint-config-standard": "^34.0.0",
- "stylelint-prettier": "^4.0.0",
+ "stylelint-config-standard": "^35.0.0",
+ "stylelint-prettier": "^5.0.0",
"sylvanas": "^0.6.1",
- "terser": "^5.16.1",
- "tsx": "^3.12.8",
- "typedoc": "^0.25.0",
- "typescript": "~5.2.2",
- "vanilla-jsoneditor": "^0.18.0",
- "webpack-bundle-analyzer": "^4.1.0",
- "xhr-mock": "^2.4.1"
+ "tar": "^6.2.0",
+ "tar-fs": "^3.0.4",
+ "terser": "^5.24.0",
+ "tsx": "^4.6.0",
+ "typedoc": "^0.25.4",
+ "typescript": "~5.3.0",
+ "vanilla-jsoneditor": "^0.21.1",
+ "vanilla-tilt": "^1.8.1",
+ "webpack": "^5.89.0",
+ "webpack-bundle-analyzer": "^4.10.1",
+ "xhr-mock": "^2.5.1"
},
"peerDependencies": {
"react": ">=16.9.0",
@@ -325,15 +341,15 @@
"dumi": "^2.3.0-alpha.4"
}
},
- "packageManager": "npm@10.2.3",
+ "packageManager": "npm@10.2.5",
"size-limit": [
{
"path": "./dist/antd.min.js",
- "limit": "405 KiB"
+ "limit": "330 KiB"
},
{
"path": "./dist/antd-with-locales.min.js",
- "limit": "465 KiB"
+ "limit": "376 KiB"
}
],
"title": "Ant Design",
diff --git a/scripts/argos-upload.ts b/scripts/argos-upload.ts
deleted file mode 100644
index f65f82efb5..0000000000
--- a/scripts/argos-upload.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-// Thanks to material-ui ❤️
-// Create chunks for Argos: https://github.com/mui/material-ui/pull/23518
-// https://github.com/mui/material-ui/blob/af81aae3b292ed180e7652a665fad1be2b38a7b3/scripts/pushArgos.js
-import childProcess from 'child_process';
-import util from 'util';
-import argos from '@argos-ci/core';
-import glob from 'fast-glob';
-import lodashChunk from 'lodash/chunk';
-
-const execFileNode = util.promisify(childProcess.execFile);
-
-function execFile(command: string, args: string[]) {
- return execFileNode(command, args, {
- cwd: process.cwd(),
- env: process.env,
- encoding: 'utf-8',
- });
-}
-
-const screenshotsBase = 'imageSnapshots';
-const screenshotsChunks = `imageSnapshots-chunks`;
-const BATCH_SIZE = 128;
-
-async function cpToTemp(screenshot: string, target: string) {
- await execFile('mkdir', ['-p', target]);
- await execFile('cp', [screenshot, target]);
-}
-
-async function run() {
- const screenshots = await glob(`${screenshotsBase}/**/*`);
- const chunks = lodashChunk(screenshots, BATCH_SIZE);
-
- await Promise.all(
- chunks.map((chunk, chunkIndex) =>
- Promise.all(
- chunk.map>((screenshot) =>
- cpToTemp(screenshot, `${screenshotsChunks}/${chunkIndex}`),
- ),
- ),
- ),
- );
-
- // eslint-disable-next-line no-console -- pipe stdout
- console.log('Chunk Size:', chunks.length, '/', 'Total Snapshots:', screenshots.length);
-
- for (let i = 0; i < chunks.length; i += 1) {
- // eslint-disable-next-line no-await-in-loop
- const result = await argos.upload({
- root: `${screenshotsChunks}/${i}`,
- token: process.env.ARGOS_TOKEN,
- parallel: {
- total: chunks.length,
- nonce: process.env.ARGOS_PARALLEL_NONCE || process.env.CIRCLE_BUILD_NUM || '',
- },
- });
- // eslint-disable-next-line no-console -- pipe stdout
- console.log(i, '>', result);
- }
-}
-
-run().catch((error) => {
- // eslint-disable-next-line no-console
- console.error(error);
- process.exit(1);
-});
diff --git a/scripts/check-cssinjs.ts b/scripts/check-cssinjs.tsx
similarity index 62%
rename from scripts/check-cssinjs.ts
rename to scripts/check-cssinjs.tsx
index 3b4f0156bc..a68db05fe5 100644
--- a/scripts/check-cssinjs.ts
+++ b/scripts/check-cssinjs.tsx
@@ -1,13 +1,16 @@
/* eslint-disable no-console */
+import React from 'react';
import {
- StyleProvider,
legacyNotSelectorLinter,
logicalPropertiesLinter,
+ NaNLinter,
parentSelectorLinter,
+ StyleProvider,
} from '@ant-design/cssinjs';
import chalk from 'chalk';
-import React from 'react';
import ReactDOMServer from 'react-dom/server';
+
+import { ConfigProvider } from '../components';
import { generateCssinjs } from './generate-cssinjs';
console.log(chalk.green(`🔥 Checking CSS-in-JS...`));
@@ -23,20 +26,37 @@ console.error = (msg: any) => {
}
};
+async function checkCSSVar() {
+ await generateCssinjs({
+ key: 'check',
+ render(Component: any) {
+ ReactDOMServer.renderToString(
+
+
+
+
+ ,
+ );
+ },
+ });
+}
+
(async () => {
await generateCssinjs({
key: 'check',
render(Component: any) {
ReactDOMServer.renderToString(
- React.createElement(
- StyleProvider,
- { linters: [logicalPropertiesLinter, legacyNotSelectorLinter, parentSelectorLinter] },
- React.createElement(Component),
- ),
+
+
+ ,
);
},
});
+ await checkCSSVar();
+
if (errorCount > 0) {
console.log(chalk.red(`❌ CSS-in-JS check failed with ${errorCount} errors.`));
process.exit(1);
diff --git a/scripts/ci-mock-project-build.sh b/scripts/ci-mock-project-build.sh
index 6c559be160..d54b0f1986 100644
--- a/scripts/ci-mock-project-build.sh
+++ b/scripts/ci-mock-project-build.sh
@@ -3,14 +3,23 @@
# clean up
rm -rf ~tmpProj/
+# clean up `packageManager` since this will block yarn install
+echo "Cleaning up package.json..."
+sed -i '/packageManager/d' 'package.json' # linux no need for `''`
+sed -i '' '/packageManager/d' 'package.json' # mac need `''`
+
# clone project
+echo "Cloning project..."
git clone https://github.com/ant-design/ant-design-examples.git ~tmpProj --depth=1
# change directory
+echo "Changing directory..."
cd ~tmpProj/examples/with-nextjs-inline-style
# install dependencies
+echo "Installing dependencies..."
yarn
# build
+echo "Building..."
yarn run build
diff --git a/scripts/generate-cssinjs.ts b/scripts/generate-cssinjs.ts
index a9460717c9..4c993632eb 100644
--- a/scripts/generate-cssinjs.ts
+++ b/scripts/generate-cssinjs.ts
@@ -1,7 +1,7 @@
import url from 'node:url';
import path from 'path';
-import { globSync } from 'glob';
import React from 'react';
+import { globSync } from 'glob';
type StyleFn = (prefix?: string) => void;
diff --git a/scripts/post-script.ts b/scripts/post-script.ts
index bb60817585..5b33917b6f 100644
--- a/scripts/post-script.ts
+++ b/scripts/post-script.ts
@@ -48,6 +48,11 @@ const DEPRECIATED_VERSION = {
'5.8.0': ['https://github.com/ant-design/ant-design/issues/43943'],
'5.9.1': ['https://github.com/ant-design/ant-design/issues/44907'],
'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'],
+ '5.11.4': ['https://github.com/ant-design/ant-design/pull/46103'],
+ '5.12.3': ['https://github.com/ant-design/ant-design/issues/46525'],
} as const;
function matchDeprecated(v: string) {
diff --git a/scripts/test-all.sh b/scripts/test-all.sh
index 26fff26931..d82a864a8b 100755
--- a/scripts/test-all.sh
+++ b/scripts/test-all.sh
@@ -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,65 @@ 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-dist' "$@"; then
+ echo "[TEST ALL] dist-min test"
+ echo "[TEST ALL] dist-min test" > ~test-all.txt
+ LIB_DIR=dist-min npm test -- --bail
+else
+ echo "[TEST ALL] dist test...skip"
+fi
-echo "[TEST ALL] test lib"
-echo "[TEST ALL] test lib" > ~test-all.txt
-LIB_DIR=lib 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"
-echo "[TEST ALL] test" > ~test-all.txt
-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 node"
-echo "[TEST ALL] test node" > ~test-all.txt
-npm run test-node
+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
+
+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
diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json
new file mode 100644
index 0000000000..425dce052b
--- /dev/null
+++ b/scripts/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "ES2015",
+ "module": "commonjs",
+ "declaration": false,
+ "skipLibCheck": true,
+ "skipDefaultLibCheck": true,
+ "incremental": true,
+ "sourceMap": false,
+ "strict": false,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "resolveJsonModule": true,
+ "allowJs": true,
+ "noEmitOnError": false,
+ "importHelpers": false,
+ "typeRoots": ["../node_modules/@types"],
+ "types": ["node"]
+ },
+ "include": ["./"],
+ "exclude": ["**/node_modules"]
+}
diff --git a/scripts/visual-regression/build.ts b/scripts/visual-regression/build.ts
new file mode 100644
index 0000000000..8bbdb75b52
--- /dev/null
+++ b/scripts/visual-regression/build.ts
@@ -0,0 +1,374 @@
+/* eslint-disable compat/compat */
+/* eslint-disable no-console, no-await-in-loop, import/no-extraneous-dependencies, lodash/import-scope, no-restricted-syntax */
+import { assert } from 'console';
+import fs from 'fs';
+import os from 'os';
+import path from 'path';
+import { Readable } from 'stream';
+import { finished } from 'stream/promises';
+import chalk from 'chalk';
+import fse from 'fs-extra';
+import _ from 'lodash';
+import minimist from 'minimist';
+import pixelmatch from 'pixelmatch';
+import { PNG } from 'pngjs';
+import { remark } from 'remark';
+import remarkGfm from 'remark-gfm';
+import remarkHtml from 'remark-html';
+import sharp from 'sharp';
+import tar from 'tar';
+
+const ALI_OSS_BUCKET = 'antd-visual-diff';
+
+const isLocalEnv = process.env.LOCAL;
+
+const compareScreenshots = async (
+ baseImgPath: string,
+ currentImgPath: string,
+ diffImagePath: string,
+): Promise => {
+ const baseImgBuf = await sharp(baseImgPath).toBuffer();
+ const currentImgBuf = await sharp(currentImgPath).toBuffer();
+
+ const basePng = PNG.sync.read(baseImgBuf);
+ const targetWidth = basePng.width;
+ const targetHeight = basePng.height;
+
+ const comparePng = PNG.sync.read(
+ await sharp(currentImgBuf)
+ .resize({
+ width: targetWidth,
+ height: targetHeight,
+ fit: sharp.fit.contain,
+ background: { r: 255, g: 255, b: 255, alpha: 0 },
+ })
+ .png()
+ .toBuffer(),
+ );
+
+ const diffPng = new PNG({ width: targetWidth, height: targetHeight });
+
+ const mismatchedPixels = pixelmatch(
+ basePng.data,
+ comparePng.data,
+ diffPng.data,
+ targetWidth,
+ targetHeight,
+ { threshold: 0.1, diffMask: false },
+ );
+
+ // if mismatched then write diff image
+ if (mismatchedPixels) {
+ diffPng.pack().pipe(fs.createWriteStream(diffImagePath));
+ }
+
+ return mismatchedPixels / (targetWidth * targetHeight);
+};
+
+const readPngs = (dir: string) => fs.readdirSync(dir).filter((n) => n.endsWith('.png'));
+
+const prettyList = (list: string[]) => list.map((i) => ` * ${i}`).join('\n');
+
+const ossDomain = `https://${ALI_OSS_BUCKET}.oss-cn-shanghai.aliyuncs.com`;
+
+async function downloadFile(url: string, destPath: string) {
+ const response = await fetch(url);
+ if (!response.ok || response.status !== 200) {
+ throw new Error(`Download file failed: ${new URL(url).pathname}`);
+ }
+ // @ts-ignore
+ const body = Readable.fromWeb(response.body);
+ await finished(body.pipe(fs.createWriteStream(destPath)));
+}
+
+async function getBranchLatestRef(branchName: string) {
+ const baseImageRefUrl = `${ossDomain}/${branchName}/visual-regression-ref.txt`;
+ // get content from baseImageRefText
+ const res = await fetch(baseImageRefUrl);
+ const text = await res.text();
+ const ref = text.trim();
+ return ref;
+}
+
+async function downloadBaseSnapshots(ref: string, targetDir: string) {
+ // download imageSnapshotsUrl
+ const imageSnapshotsUrl = `${ossDomain}/${ref}/imageSnapshots.tar.gz`;
+ const targzPath = path.resolve(os.tmpdir(), `./${path.basename(targetDir)}.tar.gz`);
+ await downloadFile(imageSnapshotsUrl, targzPath);
+ // untar
+ return tar.x({
+ // remove top-level dir
+ strip: 1,
+ C: targetDir,
+ file: targzPath,
+ });
+}
+
+interface IBadCase {
+ type: 'removed' | 'changed';
+ filename: string;
+ /**
+ * 0 - 1
+ */
+ weight: number;
+}
+
+function md2Html(md: string) {
+ return remark().use(remarkGfm).use(remarkHtml).processSync(md).toString();
+}
+
+function parseArgs() {
+ // parse args from -- --pr-id=123 --base_ref=feature
+ const argv = minimist(process.argv.slice(2));
+ const prId = argv['pr-id'];
+ assert(prId, 'Missing --pr-id');
+ const baseRef = argv['base-ref'];
+ assert(baseRef, 'Missing --base-ref');
+ return {
+ prId,
+ baseRef,
+ };
+}
+
+function generateReport(
+ badCases: IBadCase[],
+ targetBranch: string,
+ targetRef: string,
+ prId: string,
+): [string, string] {
+ const publicPath = isLocalEnv ? path.resolve(__dirname, '../..') : `${ossDomain}/pr-${prId}`;
+
+ const passed = badCases.length === 0;
+
+ const commonHeader = `
+## Visual Regression Report for PR #${prId} ${passed ? 'Passed ✅' : 'Failed ❌'}
+> **Target branch:** ${targetBranch} (${targetRef})
+ `.trim();
+
+ if (passed) {
+ const mdStr = [
+ commonHeader,
+ '------------------------',
+ 'Congrats! No visual-regression diff found',
+ ].join('\n');
+
+ return [mdStr, md2Html(mdStr)];
+ }
+
+ const htmlReportLink = `${publicPath}/visualRegressionReport/report.html`;
+
+ const addonFullReportDesc = `\n\nCheck Full Report for details`;
+
+ let reportMdStr = `
+${commonHeader}
+> View Full Report \n
+------------------------
+| image name | expected | actual | diff |
+| --- | --- | --- | --- |
+ `.trim();
+ reportMdStr += '\n';
+
+ let fullVersionMd = reportMdStr;
+
+ let diffCount = 0;
+
+ for (const badCase of badCases) {
+ const { filename, type } = badCase;
+ let lineReportMdStr = '';
+ if (type === 'changed') {
+ lineReportMdStr += '| ';
+ lineReportMdStr += [
+ badCase.filename,
+ `![${targetBranch}: ${targetRef}](${publicPath}/visualRegressionReport/images/base/${filename})`,
+ `![current: pr-${prId}](${publicPath}/visualRegressionReport/images/current/${filename})`,
+ `![diff](${publicPath}/visualRegressionReport/images/diff/${filename})`,
+ ].join(' | ');
+ lineReportMdStr += ' |\n';
+ } else if (type === 'removed') {
+ lineReportMdStr += '| ';
+ lineReportMdStr += [
+ badCase.filename,
+ `![${targetBranch}: ${targetRef}](${publicPath}/visualRegressionReport/images/base/${filename})`,
+ `⛔️⛔️⛔️ Missing ⛔️⛔️⛔️`,
+ `🚨🚨🚨 Removed 🚨🚨🚨`,
+ ].join(' | ');
+ lineReportMdStr += ' |\n';
+ }
+
+ diffCount += 1;
+ if (diffCount <= 10) {
+ reportMdStr += lineReportMdStr;
+ }
+
+ fullVersionMd += lineReportMdStr;
+ }
+
+ reportMdStr += addonFullReportDesc;
+
+ // convert fullVersionMd to html
+ return [reportMdStr, md2Html(fullVersionMd)];
+}
+
+async function boot() {
+ const { prId, baseRef: targetBranch = 'master' } = parseArgs();
+
+ const baseImgSourceDir = path.resolve(__dirname, `../../imageSnapshots-${targetBranch}`);
+
+ /* --- prepare stage --- */
+ console.log(
+ chalk.green(
+ `Preparing image snapshots from latest \`${targetBranch}\` branch for pr \`${prId}\`\n`,
+ ),
+ );
+ await fse.ensureDir(baseImgSourceDir);
+
+ const targetCommitSha = await getBranchLatestRef(targetBranch);
+ assert(targetCommitSha, `Missing commit sha from ${targetBranch}`);
+
+ if (!isLocalEnv) {
+ await downloadBaseSnapshots(targetCommitSha, baseImgSourceDir);
+ } else if (!fse.existsSync(baseImgSourceDir)) {
+ console.log(
+ chalk.yellow(
+ `Please prepare image snapshots in folder \`$projectRoot/${path.basename(
+ baseImgSourceDir,
+ )}\` from latest \`${targetBranch}\` branch`,
+ ),
+ );
+ process.exit(1);
+ }
+
+ const currentImgSourceDir = path.resolve(__dirname, '../../imageSnapshots');
+
+ const reportDir = path.resolve(__dirname, '../../visualRegressionReport');
+ // save diff images(x3) to reportDir
+ const diffImgReportDir = path.resolve(reportDir, './images/diff');
+ const baseImgReportDir = path.resolve(reportDir, './images/base');
+ const currentImgReportDir = path.resolve(reportDir, './images/current');
+
+ await fse.ensureDir(diffImgReportDir);
+ await fse.ensureDir(baseImgReportDir);
+ await fse.ensureDir(currentImgReportDir);
+
+ console.log(chalk.blue('⛳ Checking image snapshots with branch %s'), targetBranch);
+ console.log('\n');
+
+ const baseImgFileList = readPngs(baseImgSourceDir);
+
+ /* --- compare stage --- */
+ const badCases: IBadCase[] = [];
+
+ // compare cssinjs and css-var png from pr
+ // to the same cssinjs png in `master` branch
+ const cssInJsImgNames = baseImgFileList
+ .filter((i) => !i.endsWith('.css-var.png'))
+ .map((n) => path.basename(n, path.extname(n)));
+
+ for (const basename of cssInJsImgNames) {
+ for (const extname of ['.png', '.css-var.png']) {
+ // baseImg always use cssinjs png
+ const baseImgName = `${basename}.png`;
+ const baseImgPath = path.join(baseImgSourceDir, baseImgName);
+
+ // currentImg use cssinjs png or css-var png
+ const compareImgName = basename + extname;
+ const currentImgPath = path.join(currentImgSourceDir, compareImgName);
+ const diffImgPath = path.join(diffImgReportDir, compareImgName);
+
+ const currentImgExists = await fse.exists(currentImgPath);
+ if (!currentImgExists) {
+ console.log(chalk.red(`⛔️ Missing image: ${compareImgName}\n`));
+ badCases.push({
+ type: 'removed',
+ filename: compareImgName,
+ weight: 1,
+ });
+ await fse.copy(baseImgPath, path.join(baseImgReportDir, compareImgName));
+ continue;
+ }
+
+ const mismatchedPxPercent = await compareScreenshots(
+ baseImgPath,
+ currentImgPath,
+ diffImgPath,
+ );
+
+ if (mismatchedPxPercent > 0) {
+ console.log(
+ 'Mismatched pixels for:',
+ chalk.yellow(compareImgName),
+ `${(mismatchedPxPercent * 100).toFixed(2)}%\n`,
+ );
+ // copy compare imgs(x2) to report dir
+ await fse.copy(baseImgPath, path.join(baseImgReportDir, compareImgName));
+ await fse.copy(currentImgPath, path.join(currentImgReportDir, compareImgName));
+
+ badCases.push({
+ type: 'changed',
+ filename: compareImgName,
+ weight: mismatchedPxPercent,
+ });
+ } else {
+ console.log('Passed for: %s\n', chalk.green(compareImgName));
+ }
+ }
+ }
+
+ /* --- generate report stage --- */
+ const jsonl = badCases.map((i) => JSON.stringify(i)).join('\n');
+ // write jsonl and markdown report to diffImgDir
+ await fse.writeFile(path.join(reportDir, './report.jsonl'), jsonl);
+ const [reportMdStr, reportHtmlStr] = generateReport(
+ badCases,
+ targetBranch,
+ targetCommitSha,
+ prId,
+ );
+ await fse.writeFile(path.join(reportDir, './report.md'), reportMdStr);
+ const htmlTemplate = await fse.readFile(path.join(__dirname, './report-template.html'), 'utf8');
+
+ await fse.writeFile(
+ path.join(reportDir, './report.html'),
+ htmlTemplate.replace('{{reportContent}}', reportHtmlStr),
+ 'utf-8',
+ );
+
+ await tar.c(
+ {
+ gzip: true,
+ // ignore top-level dir(e.g. visualRegressionReport) and zip all files in it
+ cwd: reportDir,
+ file: `${path.basename(reportDir)}.tar.gz`,
+ },
+ await fse.readdir(reportDir),
+ );
+
+ const currentImgFileList = readPngs(currentImgSourceDir);
+ /* --- text report stage --- */
+ console.log(
+ chalk.blue(`📊 Text report from pr #${prId} comparing to ${targetBranch}@${targetCommitSha}\n`),
+ );
+ // new images
+ const newImgs = _.difference(currentImgFileList, baseImgFileList);
+ if (newImgs.length) {
+ console.log(chalk.green(`🆕 ${newImgs.length} images added from this pr`));
+ console.log(chalk.green('🆕 Added images list:\n'));
+ console.log(prettyList(newImgs));
+ console.log('\n');
+ }
+
+ if (!badCases.length) {
+ console.log(chalk.green('🎉 All passed!'));
+ console.log('\n');
+ return;
+ }
+
+ const sortedBadCases = badCases.sort((a, b) => b.weight - a.weight);
+ console.log(chalk.red('⛔️ Failed cases:\n'));
+ console.log(prettyList(sortedBadCases.map((i) => `[${i.type}] ${i.filename}`)));
+ console.log('\n');
+ // let job failed
+ process.exit(1);
+}
+
+boot();
diff --git a/scripts/visual-regression/report-template.html b/scripts/visual-regression/report-template.html
new file mode 100644
index 0000000000..96c3f4aac7
--- /dev/null
+++ b/scripts/visual-regression/report-template.html
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+ Ant Design Visual Diff Report
+
+
+
+
+
+ {{reportContent}}
+
+
+
\ No newline at end of file
diff --git a/scripts/visual-regression/upload.js b/scripts/visual-regression/upload.js
new file mode 100644
index 0000000000..4b035efec6
--- /dev/null
+++ b/scripts/visual-regression/upload.js
@@ -0,0 +1,124 @@
+/* eslint-disable no-restricted-syntax, no-console */
+// Attention: use all node builtin modules except `ali-oss`
+// Must keep our ak/sk safe
+
+// eslint-disable-next-line import/no-extraneous-dependencies
+const OSS = require('ali-oss');
+const path = require('path');
+const fs = require('fs');
+const assert = require('assert');
+
+// node scripts/visual-regression/upload.js ./visualRegressionReport.tar.gz --ref=pr-id
+// node scripts/visual-regression/upload.js ./imageSnapshots.tar.gz --ref=master-commitId
+
+const args = process.argv.slice(2);
+if (args.length < 2) {
+ console.error('Usage: node scripts/visual-regression/upload.js --ref=');
+ process.exit(1);
+}
+
+const ALI_OSS_BUCKET = 'antd-visual-diff';
+
+/**
+ * Extract the tar file path and ref value from the cli arguments
+ * @param {string[]} cliArgs
+ */
+function parseArgs(cliArgs) {
+ const filepath = cliArgs[0];
+ let refValue = '';
+
+ for (let i = 1; i < cliArgs.length; i++) {
+ if (cliArgs[i].startsWith('--ref=')) {
+ refValue = cliArgs[i].substring(6);
+ break;
+ }
+ }
+
+ assert(filepath, 'filepath is required');
+ assert(refValue, 'refValue is required');
+
+ return [filepath, refValue];
+}
+
+async function walkDir(dirPath) {
+ const fileList = [];
+
+ const files = await fs.promises.readdir(dirPath);
+
+ for (const file of files) {
+ const filePath = path.join(dirPath, file);
+ const fileStat = fs.statSync(filePath);
+
+ if (fileStat.isDirectory()) {
+ // Recursively call this func for subdirs
+ // eslint-disable-next-line no-await-in-loop
+ fileList.push(...(await walkDir(filePath)));
+ } else {
+ fileList.push(filePath);
+ }
+ }
+
+ return fileList;
+}
+
+/**
+ *
+ * @param {import('ali-oss')} client
+ * @param {*} filePath
+ * @param {*} refValue
+ */
+async function uploadFile(client, filePath, refValue) {
+ const headers = {
+ // https://help.aliyun.com/zh/oss/user-guide/object-acl
+ 'x-oss-object-acl': 'public-read',
+ // https://help.aliyun.com/zh/oss/developer-reference/prevent-objects-from-being-overwritten-by-objects-that-have-the-same-names-3
+ 'x-oss-forbid-overwrite': 'false',
+ };
+
+ console.log('Uploading file: %s', filePath);
+ try {
+ const targetFilePath = path.relative(process.cwd(), filePath);
+ const r1 = await client.put(`${refValue}/${targetFilePath}`, filePath, { headers });
+ console.log('Uploading file successfully: %s', r1.name);
+ } catch (err) {
+ console.error('Uploading file failed: %s', err);
+ process.exit(1);
+ }
+}
+
+async function boot() {
+ const [filepath, refValue] = parseArgs(args);
+
+ const fileOrFolderName = filepath;
+ // check if exists
+ const filePath = path.resolve(process.cwd(), fileOrFolderName);
+
+ if (!fs.existsSync(filePath)) {
+ console.error('File not exists: %s', filePath);
+ process.exit(1);
+ }
+
+ const client = new OSS({
+ endpoint: 'oss-cn-shanghai.aliyuncs.com',
+ accessKeyId: process.env.ALI_OSS_AK_ID,
+ accessKeySecret: process.env.ALI_OSS_AK_SECRET,
+ bucket: ALI_OSS_BUCKET,
+ });
+
+ // if is a file then upload it directly
+ const stat = fs.statSync(filePath);
+ if (stat.isFile()) {
+ await uploadFile(client, filePath, refValue);
+ return;
+ }
+
+ if (stat.isDirectory()) {
+ const fileList = await walkDir(filePath);
+ for (const file of fileList) {
+ // eslint-disable-next-line no-await-in-loop
+ await uploadFile(client, file, refValue);
+ }
+ }
+}
+
+boot();
diff --git a/tests/index.test.ts b/tests/index.test.ts
index 5261565775..bbc286a7e8 100644
--- a/tests/index.test.ts
+++ b/tests/index.test.ts
@@ -1,13 +1,23 @@
+/* eslint-disable global-require */
import pkg from '../package.json';
const testDist = process.env.LIB_DIR === 'dist';
+const testDistMin = process.env.LIB_DIR === 'dist-min';
describe('antd dist files', () => {
// https://github.com/ant-design/ant-design/issues/1638
// https://github.com/ant-design/ant-design/issues/1968
it('exports modules correctly', () => {
- // eslint-disable-next-line global-require,import/no-unresolved
- const antd = testDist ? require('../dist/antd') : require('../components');
+ let antd;
+ if (testDist) {
+ // eslint-disable-next-line import/no-unresolved
+ antd = require('../dist/antd');
+ } else if (testDistMin) {
+ // eslint-disable-next-line import/no-unresolved
+ antd = require('../dist/antd.min');
+ } else {
+ antd = require('../components');
+ }
expect(Object.keys(antd)).toMatchSnapshot();
});
diff --git a/tests/setup.js b/tests/setup.ts
similarity index 51%
rename from tests/setup.js
rename to tests/setup.ts
index e5da82ca53..1977c6b1a8 100644
--- a/tests/setup.js
+++ b/tests/setup.ts
@@ -1,5 +1,9 @@
-/* eslint-disable no-console */
-const util = require('util');
+/* eslint-disable no-console, import/prefer-default-export */
+import util from 'util';
+import type { DOMWindow } from 'jsdom';
+
+// import { fillWindowEnv } from './utils';
+
const React = require('react');
// eslint-disable-next-line no-console
@@ -20,17 +24,21 @@ console.error = (...args) => {
}
};
-/* eslint-disable global-require */
-if (typeof window !== 'undefined') {
- global.window.resizeTo = (width, height) => {
- global.window.innerWidth = width || global.window.innerWidth;
- global.window.innerHeight = height || global.window.innerHeight;
- global.window.dispatchEvent(new Event('resize'));
+type Writeable = { -readonly [P in keyof T]: T[P] };
+
+// This function can not move to external file since jest setup not support
+export function fillWindowEnv(window: Window | DOMWindow) {
+ const win = window as Writeable & typeof globalThis;
+
+ win.resizeTo = (width, height) => {
+ win.innerWidth = width || win.innerWidth;
+ win.innerHeight = height || win.innerHeight;
+ win.dispatchEvent(new Event('resize'));
};
- global.window.scrollTo = () => {};
+ win.scrollTo = () => {};
// ref: https://github.com/ant-design/ant-design/issues/18774
- if (!window.matchMedia) {
- Object.defineProperty(global.window, 'matchMedia', {
+ if (!win.matchMedia) {
+ Object.defineProperty(win, 'matchMedia', {
writable: true,
configurable: true,
value: jest.fn((query) => ({
@@ -44,11 +52,19 @@ if (typeof window !== 'undefined') {
// Fix css-animation or rc-motion deps on these
// https://github.com/react-component/motion/blob/9c04ef1a210a4f3246c9becba6e33ea945e00669/src/util/motion.ts#L27-L35
// https://github.com/yiminghe/css-animation/blob/a5986d73fd7dfce75665337f39b91483d63a4c8c/src/Event.js#L44
- window.AnimationEvent = window.AnimationEvent || window.Event;
- window.TransitionEvent = window.TransitionEvent || window.Event;
+ win.AnimationEvent = win.AnimationEvent || win.Event;
+ win.TransitionEvent = win.TransitionEvent || win.Event;
// ref: https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
// ref: https://github.com/jsdom/jsdom/issues/2524
- Object.defineProperty(window, 'TextEncoder', { writable: true, value: util.TextEncoder });
- Object.defineProperty(window, 'TextDecoder', { writable: true, value: util.TextDecoder });
+ Object.defineProperty(win, 'TextEncoder', { writable: true, value: util.TextEncoder });
+ Object.defineProperty(win, 'TextDecoder', { writable: true, value: util.TextDecoder });
}
+
+/* eslint-disable global-require */
+if (typeof window !== 'undefined') {
+ fillWindowEnv(window);
+}
+
+global.requestAnimationFrame = global.requestAnimationFrame || global.setTimeout;
+global.cancelAnimationFrame = global.cancelAnimationFrame || global.clearTimeout;
diff --git a/tests/setupAfterEnv.ts b/tests/setupAfterEnv.ts
index 974bb9b650..0d71e44999 100644
--- a/tests/setupAfterEnv.ts
+++ b/tests/setupAfterEnv.ts
@@ -8,13 +8,11 @@ import { defaultConfig } from '../components/theme/internal';
defaultConfig.hashed = false;
if (process.env.LIB_DIR === 'dist') {
- jest.mock('../dist/antd', () => {
- const antd = jest.requireActual('../dist/antd');
- antd.theme.defaultConfig.hashed = false;
-
- return antd;
- });
+ jest.mock('antd', () => jest.requireActual('../dist/antd'));
+} else if (process.env.LIB_DIR === 'dist-min') {
+ jest.mock('antd', () => jest.requireActual('../dist/antd.min'));
} else if (process.env.LIB_DIR === 'es') {
+ jest.mock('antd', () => jest.requireActual('../es'));
jest.mock('../es/theme/internal', () => {
const esTheme = jest.requireActual('../es/theme/internal');
if (esTheme.defaultConfig) {
diff --git a/tests/shared/demoTest.tsx b/tests/shared/demoTest.tsx
index 538a4a3c7e..63bac11c88 100644
--- a/tests/shared/demoTest.tsx
+++ b/tests/shared/demoTest.tsx
@@ -11,6 +11,7 @@ import { render } from '../utils';
import { TriggerMockContext } from './demoTestContext';
import { excludeWarning, isSafeWarning } from './excludeWarning';
import rootPropsTest from './rootPropsTest';
+import { ConfigProvider } from 'antd';
export { rootPropsTest };
@@ -60,7 +61,11 @@ function baseText(doInject: boolean, component: string, options: Options = {}) {
}
// Inject cssinjs cache to avoid create element
- Demo = {Demo};
+ Demo = (
+
+ {Demo}
+
+ );
// Demo Test also include `dist` test which is already uglified.
// We need test this as SSR instead.
diff --git a/tests/shared/imageTest.tsx b/tests/shared/imageTest.tsx
index f1510d7a8f..c6dfd6b136 100644
--- a/tests/shared/imageTest.tsx
+++ b/tests/shared/imageTest.tsx
@@ -1,3 +1,4 @@
+import path from 'path';
import React from 'react';
// Reference: https://github.com/ant-design/ant-design/pull/24003#discussion_r427267386
// eslint-disable-next-line import/no-unresolved
@@ -5,10 +6,15 @@ import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import dayjs from 'dayjs';
import { globSync } from 'glob';
import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
+import { JSDOM } from 'jsdom';
import MockDate from 'mockdate';
import ReactDOMServer from 'react-dom/server';
import { App, ConfigProvider, theme } from '../../components';
+import { fillWindowEnv } from '../setup';
+import { render } from '../utils';
+
+jest.mock('../../components/grid/hooks/useBreakpoint', () => () => ({}));
const toMatchImageSnapshot = configureToMatchImageSnapshot({
customSnapshotsDir: `${process.cwd()}/imageSnapshots`,
@@ -26,11 +32,84 @@ const themes = {
interface ImageTestOptions {
onlyViewport?: boolean;
splitTheme?: boolean;
+ ssr?: boolean;
}
// eslint-disable-next-line jest/no-export
-export default function imageTest(component: React.ReactElement, options: ImageTestOptions) {
- function test(name: string, themedComponent: React.ReactElement) {
+export default function imageTest(
+ component: React.ReactElement,
+ identifier: string,
+ options: ImageTestOptions,
+) {
+ let doc: Document;
+ let container: HTMLDivElement;
+
+ beforeAll(() => {
+ const dom = new JSDOM('
', {
+ url: 'http://localhost/',
+ });
+ const win = dom.window;
+ doc = win.document;
+
+ (global as any).window = win;
+
+ // Fill env
+ const keys = [
+ ...Object.keys(win),
+ 'HTMLElement',
+ 'SVGElement',
+ 'ShadowRoot',
+ 'Element',
+ 'File',
+ 'Blob',
+ ].filter((key) => !(global as any)[key]);
+
+ keys.forEach((key) => {
+ (global as any)[key] = win[key];
+ });
+
+ // Fake Resize Observer
+ global.ResizeObserver = function FakeResizeObserver() {
+ return {
+ observe() {},
+ unobserve() {},
+ disconnect() {},
+ };
+ } as any;
+
+ // Fake promise not called
+ global.fetch = function mockFetch() {
+ return {
+ then() {
+ return this;
+ },
+ catch() {
+ return this;
+ },
+ finally() {
+ return this;
+ },
+ };
+ } as any;
+
+ // Fake matchMedia
+ win.matchMedia = () =>
+ ({
+ matches: false,
+ addListener: jest.fn(),
+ removeListener: jest.fn(),
+ }) as any;
+
+ // Fill window
+ fillWindowEnv(win);
+ });
+
+ beforeEach(() => {
+ doc.body.innerHTML = ``;
+ container = doc.querySelector('#root')!;
+ });
+
+ function test(name: string, suffix: string, themedComponent: React.ReactElement) {
it(name, async () => {
await jestPuppeteer.resetPage();
await page.setRequestInterception(true);
@@ -50,14 +129,30 @@ export default function imageTest(component: React.ReactElement, options: ImageT
const cache = createCache();
+ const emptyStyleHolder = doc.createElement('div');
+
const element = (
-
+
{themedComponent}
);
- const html = ReactDOMServer.renderToString(element);
- const styleStr = extractStyle(cache);
+ let html: string;
+ let styleStr: string;
+
+ if (options.ssr) {
+ html = ReactDOMServer.renderToString(element);
+ styleStr = extractStyle(cache);
+ } else {
+ const { unmount } = render(element, {
+ container,
+ });
+ html = container.innerHTML;
+ styleStr = extractStyle(cache);
+
+ // We should extract style before unmount
+ unmount();
+ }
await page.evaluate(
(innerHTML, ssrStyle) => {
@@ -80,7 +175,9 @@ export default function imageTest(component: React.ReactElement, options: ImageT
fullPage: !options.onlyViewport,
});
- expect(image).toMatchImageSnapshot();
+ expect(image).toMatchImageSnapshot({
+ customSnapshotIdentifier: `${identifier}${suffix}`,
+ });
MockDate.reset();
page.off('request', onRequestHandle);
@@ -91,14 +188,23 @@ export default function imageTest(component: React.ReactElement, options: ImageT
Object.entries(themes).forEach(([key, algorithm]) => {
test(
`component image screenshot should correct ${key}`,
+ `-${key}`,
{component}
,
);
+ test(
+ `[CSS Var] component image screenshot should correct ${key}`,
+ `-${key}.css-var`,
+
+ {component}
+
,
+ );
});
} else {
test(
`component image screenshot should correct`,
+ '',
<>
{Object.entries(themes).map(([key, algorithm]) => (
@@ -107,6 +213,17 @@ export default function imageTest(component: React.ReactElement, options: ImageT
))}
>,
);
+ test(
+ `[CSS Var] component image screenshot should correct`,
+ '.css-var',
+ <>
+ {Object.entries(themes).map(([key, algorithm]) => (
+
+ {component}
+
+ ))}
+ >,
+ );
}
}
@@ -114,6 +231,8 @@ type Options = {
skip?: boolean | string[];
onlyViewport?: boolean | string[];
splitTheme?: boolean | string[];
+ /** Use SSR render instead. Only used when the third part deps component */
+ ssr?: boolean;
};
// eslint-disable-next-line jest/no-export
@@ -133,7 +252,7 @@ export function imageDemoTest(component: string, options: Options = {}) {
if (typeof Demo === 'function') {
Demo =
;
}
- imageTest(Demo, {
+ imageTest(Demo, `${component}-${path.basename(file, '.tsx')}`, {
onlyViewport:
options.onlyViewport === true ||
(Array.isArray(options.onlyViewport) &&
@@ -141,6 +260,7 @@ export function imageDemoTest(component: string, options: Options = {}) {
splitTheme:
options.splitTheme === true ||
(Array.isArray(options.splitTheme) && options.splitTheme.some((c) => file.endsWith(c))),
+ ssr: options.ssr,
});
});
});
diff --git a/tests/shared/rootPropsTest.tsx b/tests/shared/rootPropsTest.tsx
index 1f4ee413df..b38ce248c4 100644
--- a/tests/shared/rootPropsTest.tsx
+++ b/tests/shared/rootPropsTest.tsx
@@ -1,5 +1,6 @@
/* eslint-disable global-require, import/no-dynamic-require, jest/no-export */
import React from 'react';
+
import ConfigProvider from '../../components/config-provider';
import { render, waitFakeTimer } from '../utils';
import { TriggerMockContext } from './demoTestContext';
@@ -20,14 +21,17 @@ function isSingleNode(node: any): node is Element {
}
export default function rootPropsTest(
- component: string,
+ component: string | string[],
customizeRender?: (
component: React.ComponentType
& Record,
props: any,
) => React.ReactNode,
options?: Options,
) {
- const Component = require(`../../components/${component}`).default as any;
+ const componentNames = Array.isArray(component) ? component : [component];
+ const [componentName, subComponentName] = componentNames;
+
+ const Component = require(`../../components/${componentName}`).default as any;
const name = options?.name ? `(${options.name})` : '';
describe(`RootProps${name}`, () => {
@@ -36,6 +40,7 @@ export default function rootPropsTest(
beforeEach(() => {
passed = false;
jest.useFakeTimers();
+ document.body.innerHTML = '';
});
afterEach(() => {
@@ -46,7 +51,7 @@ export default function rootPropsTest(
jest.useRealTimers();
});
- it('rootClassName', async () => {
+ it(['rootClassName', subComponentName].filter((v) => v).join(' '), async () => {
const rootClassName = 'TEST_ROOT_CLS';
if (options?.beforeRender) {
@@ -104,7 +109,7 @@ export default function rootPropsTest(
expect(childList.length).toBeGreaterThan(0);
if (options?.expectCount) {
- expect(childList.length).toBe(options.expectCount);
+ expect(childList).toHaveLength(options.expectCount);
}
childList.forEach((ele) => {
diff --git a/tests/utils.tsx b/tests/utils.tsx
index d3427d79e3..3355faf802 100644
--- a/tests/utils.tsx
+++ b/tests/utils.tsx
@@ -1,10 +1,10 @@
+import type { ReactElement } from 'react';
+import React, { createRef, StrictMode } from 'react';
import type { RenderOptions } from '@testing-library/react';
import { act, render } from '@testing-library/react';
import MockDate from 'mockdate';
import { _rs as onEsResize } from 'rc-resize-observer/es/utils/observerUtil';
import { _rs as onLibResize } from 'rc-resize-observer/lib/utils/observerUtil';
-import type { ReactElement } from 'react';
-import React, { StrictMode } from 'react';
export function assertsExist(item?: T): asserts item is T {
expect(item).not.toBeUndefined();
@@ -33,7 +33,7 @@ const customRender = (ui: ReactElement, options?: Omit
render(ui, { wrapper: StrictMode, ...options });
export function renderHook(func: () => T): { result: React.RefObject } {
- const result = React.createRef();
+ const result = createRef();
const Demo: React.FC = () => {
(result as any).current = func();
@@ -58,7 +58,7 @@ export { pureRender, customRender as render };
export const triggerResize = (target: Element) => {
const originGetBoundingClientRect = target.getBoundingClientRect;
- target.getBoundingClientRect = () => ({ width: 510, height: 903 } as DOMRect);
+ target.getBoundingClientRect = () => ({ width: 510, height: 903 }) as DOMRect;
act(() => {
onLibResize([{ target } as ResizeObserverEntry]);
diff --git a/webpack.config.js b/webpack.config.js
index 829a4f7251..278f2ce360 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -4,7 +4,8 @@ const getWebpackConfig = require('@ant-design/tools/lib/getWebpackConfig');
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 DuplicatePackageCheckerPlugin = require('@madccc/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