chore: auto merge branches (#46406)

chore: merge master into feature
This commit is contained in:
github-actions[bot] 2023-12-12 11:28:30 +00:00 committed by GitHub
commit 7c21018f4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 1573 additions and 611 deletions

View File

@ -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 {

View File

@ -1,8 +1,8 @@
import SourceCode from 'dumi/theme-default/builtins/SourceCode';
import React from 'react';
import type { TabsProps } from 'antd';
import { ConfigProvider, Tabs } from 'antd';
import { createStyles, css } from 'antd-style';
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';
@ -13,68 +13,31 @@ interface InstallProps {
pnpm?: string;
}
const useStyle = createStyles(() => ({
packageManager: css`
display: flex;
align-items: center;
justify-content: center;
svg {
margin-inline-end: 8px;
}
`,
}));
const InstallDependencies: React.FC<InstallProps> = (props) => {
const { npm, yarn, pnpm } = props;
const { styles } = useStyle();
const items = React.useMemo<TabsProps['items']>(
() =>
[
{
key: 'npm',
children: npm ? <SourceCode lang="bash">{npm}</SourceCode> : null,
label: (
<div className={styles.packageManager}>
<NpmLogo />
<span>npm</span>
</div>
),
},
{
key: 'yarn',
children: yarn ? <SourceCode lang="bash">{yarn}</SourceCode> : null,
label: (
<div className={styles.packageManager}>
<YarnLogo />
<span>yarn</span>
</div>
),
},
{
key: 'pnpm',
children: pnpm ? <SourceCode lang="bash">{pnpm}</SourceCode> : null,
label: (
<div className={styles.packageManager}>
<PnpmLogo />
<span>pnpm</span>
</div>
),
},
].filter((item) => item.children),
[npm, yarn, pnpm],
);
const items: Tab[] = [
{
key: 'npm',
label: 'npm',
children: npm ? <SourceCode lang="bash">{npm}</SourceCode> : null,
icon: <NpmLogo />,
},
{
key: 'yarn',
label: 'yarn',
children: yarn ? <SourceCode lang="bash">{yarn}</SourceCode> : null,
icon: <YarnLogo />,
},
{
key: 'pnpm',
label: 'pnpm',
children: pnpm ? <SourceCode lang="bash">{pnpm}</SourceCode> : null,
icon: <PnpmLogo />,
},
].filter((item) => item.children);
return (
<ConfigProvider
theme={{
components: {
Tabs: {
horizontalMargin: '0',
},
},
}}
>
<ConfigProvider theme={{ components: { Tabs: { horizontalMargin: '0' } } }}>
<Tabs className="markdown" size="small" defaultActiveKey="npm" items={items} />
</ConfigProvider>
);

View File

@ -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<IconProps> = (props) => {
const { className, style } = props;
const { styles } = useStyle();
return (
<svg
className={className}
style={style}
fill="#E53E3E"
focusable="false"
height="1em"
stroke="#E53E3E"
strokeWidth="0"
viewBox="0 0 16 16"
width="1em"
>
<path d="M0 0v16h16v-16h-16zM13 13h-2v-8h-3v8h-5v-10h10v10z" />
</svg>
<span className={classNames(styles.iconWrap, className)} style={style}>
<svg
fill="#E53E3E"
focusable="false"
height="1em"
stroke="#E53E3E"
strokeWidth="0"
viewBox="0 0 16 16"
width="1em"
>
<path d="M0 0v16h16v-16h-16zM13 13h-2v-8h-3v8h-5v-10h10v10z" />
</svg>
</span>
);
};

View File

@ -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<IconProps> = (props) => {
const { className, style } = props;
const { styles } = useStyle();
return (
<svg
className={className}
style={style}
aria-hidden="true"
fill="#F69220"
focusable="false"
height="1em"
role="img"
stroke="#F69220"
strokeWidth="0"
viewBox="0 0 24 24"
width="1em"
>
<path d="M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z" />
</svg>
<span className={classNames(styles.iconWrap, className)} style={style}>
<svg
aria-hidden="true"
fill="#F69220"
focusable="false"
height="1em"
role="img"
stroke="#F69220"
strokeWidth="0"
viewBox="0 0 24 24"
width="1em"
>
<path d="M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z" />
</svg>
</span>
);
};

View File

@ -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<IconProps> = (props) => {
const { className, style } = props;
const { styles } = useStyle();
return (
<svg
className={className}
style={style}
aria-hidden="true"
fill="#2C8EBB"
focusable="false"
height="1em"
stroke="#2C8EBB"
strokeWidth="0"
viewBox="0 0 496 512"
width="1em"
>
<path d="M393.9 345.2c-39 9.3-48.4 32.1-104 47.4 0 0-2.7 4-10.4 5.8-13.4 3.3-63.9 6-68.5 6.1-12.4.1-19.9-3.2-22-8.2-6.4-15.3 9.2-22 9.2-22-8.1-5-9-9.9-9.8-8.1-2.4 5.8-3.6 20.1-10.1 26.5-8.8 8.9-25.5 5.9-35.3.8-10.8-5.7.8-19.2.8-19.2s-5.8 3.4-10.5-3.6c-6-9.3-17.1-37.3 11.5-62-1.3-10.1-4.6-53.7 40.6-85.6 0 0-20.6-22.8-12.9-43.3 5-13.4 7-13.3 8.6-13.9 5.7-2.2 11.3-4.6 15.4-9.1 20.6-22.2 46.8-18 46.8-18s12.4-37.8 23.9-30.4c3.5 2.3 16.3 30.6 16.3 30.6s13.6-7.9 15.1-5c8.2 16 9.2 46.5 5.6 65.1-6.1 30.6-21.4 47.1-27.6 57.5-1.4 2.4 16.5 10 27.8 41.3 10.4 28.6 1.1 52.7 2.8 55.3.8 1.4 13.7.8 36.4-13.2 12.8-7.9 28.1-16.9 45.4-17 16.7-.5 17.6 19.2 4.9 22.2zM496 256c0 136.9-111.1 248-248 248S0 392.9 0 256 111.1 8 248 8s248 111.1 248 248zm-79.3 75.2c-1.7-13.6-13.2-23-28-22.8-22 .3-40.5 11.7-52.8 19.2-4.8 3-8.9 5.2-12.4 6.8 3.1-44.5-22.5-73.1-28.7-79.4 7.8-11.3 18.4-27.8 23.4-53.2 4.3-21.7 3-55.5-6.9-74.5-1.6-3.1-7.4-11.2-21-7.4-9.7-20-13-22.1-15.6-23.8-1.1-.7-23.6-16.4-41.4 28-12.2.9-31.3 5.3-47.5 22.8-2 2.2-5.9 3.8-10.1 5.4h.1c-8.4 3-12.3 9.9-16.9 22.3-6.5 17.4.2 34.6 6.8 45.7-17.8 15.9-37 39.8-35.7 82.5-34 36-11.8 73-5.6 79.6-1.6 11.1 3.7 19.4 12 23.8 12.6 6.7 30.3 9.6 43.9 2.8 4.9 5.2 13.8 10.1 30 10.1 6.8 0 58-2.9 72.6-6.5 6.8-1.6 11.5-4.5 14.6-7.1 9.8-3.1 36.8-12.3 62.2-28.7 18-11.7 24.2-14.2 37.6-17.4 12.9-3.2 21-15.1 19.4-28.2z" />
</svg>
<span className={classNames(styles.iconWrap, className)} style={style}>
<svg
aria-hidden="true"
fill="#2C8EBB"
focusable="false"
height="1em"
stroke="#2C8EBB"
strokeWidth="0"
viewBox="0 0 496 512"
width="1em"
>
<path d="M393.9 345.2c-39 9.3-48.4 32.1-104 47.4 0 0-2.7 4-10.4 5.8-13.4 3.3-63.9 6-68.5 6.1-12.4.1-19.9-3.2-22-8.2-6.4-15.3 9.2-22 9.2-22-8.1-5-9-9.9-9.8-8.1-2.4 5.8-3.6 20.1-10.1 26.5-8.8 8.9-25.5 5.9-35.3.8-10.8-5.7.8-19.2.8-19.2s-5.8 3.4-10.5-3.6c-6-9.3-17.1-37.3 11.5-62-1.3-10.1-4.6-53.7 40.6-85.6 0 0-20.6-22.8-12.9-43.3 5-13.4 7-13.3 8.6-13.9 5.7-2.2 11.3-4.6 15.4-9.1 20.6-22.2 46.8-18 46.8-18s12.4-37.8 23.9-30.4c3.5 2.3 16.3 30.6 16.3 30.6s13.6-7.9 15.1-5c8.2 16 9.2 46.5 5.6 65.1-6.1 30.6-21.4 47.1-27.6 57.5-1.4 2.4 16.5 10 27.8 41.3 10.4 28.6 1.1 52.7 2.8 55.3.8 1.4 13.7.8 36.4-13.2 12.8-7.9 28.1-16.9 45.4-17 16.7-.5 17.6 19.2 4.9 22.2zM496 256c0 136.9-111.1 248-248 248S0 392.9 0 256 111.1 8 248 8s248 111.1 248 248zm-79.3 75.2c-1.7-13.6-13.2-23-28-22.8-22 .3-40.5 11.7-52.8 19.2-4.8 3-8.9 5.2-12.4 6.8 3.1-44.5-22.5-73.1-28.7-79.4 7.8-11.3 18.4-27.8 23.4-53.2 4.3-21.7 3-55.5-6.9-74.5-1.6-3.1-7.4-11.2-21-7.4-9.7-20-13-22.1-15.6-23.8-1.1-.7-23.6-16.4-41.4 28-12.2.9-31.3 5.3-47.5 22.8-2 2.2-5.9 3.8-10.1 5.4h.1c-8.4 3-12.3 9.9-16.9 22.3-6.5 17.4.2 34.6 6.8 45.7-17.8 15.9-37 39.8-35.7 82.5-34 36-11.8 73-5.6 79.6-1.6 11.1 3.7 19.4 12 23.8 12.6 6.7 30.3 9.6 43.9 2.8 4.9 5.2 13.8 10.1 30 10.1 6.8 0 58-2.9 72.6-6.5 6.8-1.6 11.5-4.5 14.6-7.1 9.8-3.1 36.8-12.3 62.2-28.7 18-11.7 24.2-14.2 37.6-17.4 12.9-3.2 21-15.1 19.4-28.2z" />
</svg>
</span>
);
};

View File

@ -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 {

View File

@ -1,12 +1,12 @@
import type { FC, ReactNode } from 'react';
import React from 'react';
import { CodeOutlined, SkinOutlined } from '@ant-design/icons';
import { Tabs } from 'antd';
import { useRouteMeta } from 'dumi';
import type { IContentTabsProps } from 'dumi/theme-default/slots/ContentTabs';
import type { TabsProps } from 'rc-tabs';
import { Tabs } from 'antd';
const titleMap: Record<string, string> = {
const titleMap: Record<string, ReactNode> = {
design: '设计',
};
@ -23,24 +23,17 @@ const ContentTabs: FC<IContentTabsProps> = ({ tabs, tabKey, onChange }) => {
const items: TabsProps['items'] = [
{
label: (
<span>
<CodeOutlined />
</span>
),
key: 'development',
label: '开发',
icon: <CodeOutlined />,
},
];
tabs?.forEach((tab) => {
items.push({
label: (
<span>
{iconMap[tab.key]}
{titleMap[tab.key]}
</span>
),
key: tab.key,
label: titleMap[tab.key],
icon: iconMap[tab.key],
});
});
@ -48,7 +41,7 @@ const ContentTabs: FC<IContentTabsProps> = ({ tabs, tabKey, onChange }) => {
<Tabs
items={items}
activeKey={tabKey || 'development'}
onChange={(key) => onChange(tabs.find((tab) => tab.key === key))}
onChange={(key) => onChange(tabs?.find((tab) => tab.key === key))}
style={{ margin: '32px 0 -16px' }}
/>
);

View File

@ -0,0 +1,111 @@
# Each PR will visual-regression diff that help to check code is work as expect.
name: 👀 Visual Regression Diff Build
on:
pull_request:
branches: [master, feature]
types: [opened, synchronize, reopened]
# Cancel prev CI if new commit come
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Prepare node modules. Reuse cache if available
setup:
name: prepare node_modules
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: cache package-lock.json
uses: actions/cache@v3
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts
- name: hack for single file
run: |
if [ ! -d "package-temp-dir" ]; then
mkdir package-temp-dir
fi
cp package-lock.json package-temp-dir
- name: cache node_modules
id: node_modules_cache_id
uses: actions/cache@v3
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: install
if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
run: npm ci
visual-diff-report:
name: visual-diff report
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 18
- name: restore cache from package-lock.json
uses: actions/cache@v3
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v3
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: generate image snapshots
id: test-image
run: |
node node_modules/puppeteer/install.mjs
npm run version
npm run test-image
env:
NODE_OPTIONS: "--max_old_space_size=4096"
# Execute visual regression diff task and zip then
# output as visualRegressionReport.tar.gz
- name: visual regression diff
run: |
npm run visual-regression -- --pr-id=${{ github.event.number }}
# Upload report in `visualRegressionReport`
- name: upload report artifact
uses: actions/upload-artifact@v3
with:
name: visual-regression-report
path: visualRegressionReport.tar.gz
# Upload git ref for next workflow `visual-regression-diff-finish` use
- name: Save persist key
if: ${{ always() }}
# should be pr id
run: echo ${{ github.event.number }} > ./visual-regression-pr-id.txt
- name: Upload persist key
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: visual-regression-diff-ref
path: ./visual-regression-pr-id.txt

View File

@ -0,0 +1,131 @@
# Each PR will visual-regression diff that help to check code is work as expect.
name: 👀 Visual Regression Diff Finish
on:
workflow_run:
workflows: ["👀 Visual Regression Diff Build"]
types:
- completed
permissions:
contents: read
jobs:
upstream-workflow-summary:
name: upstream workflow summary
runs-on: ubuntu-latest
outputs:
jobs: ${{ steps.visual_diff_build_job_status.outputs.result }}
build-success: ${{ steps.visual_diff_build_job_status.outputs.build-success }}
build-failure: ${{ steps.visual_diff_build_job_status.outputs.build-failure }}
steps:
- name: summary jobs status
uses: actions/github-script@v6
id: visual_diff_build_job_status
with:
script: |
const response = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
});
// { [name]: [conclusion] }, e.g. { 'test image': 'success' }
const jobs = (response.data?.jobs ?? []).reduce((acc, job) => {
if(job?.status === 'completed' && 'name' in job && 'conclusion' in job) {
acc[job.name] = job.conclusion;
}
return acc;
}, {});
const total = Object.keys(jobs).length;
if(total === 0) core.setFailed('no jobs found');
// the name here must be the same as `jobs.xxx.{name}`
console.log('visual-diff report job status: %s', jobs['visual-diff report']);
// set output
core.setOutput('build-success', jobs['visual-diff report'] === 'success');
core.setOutput('build-failure', jobs['visual-diff report'] === 'failure');
return jobs;
download-visual-regression-report:
name: download visual-regression report
permissions:
actions: read # for dawidd6/action-download-artifact to query and download artifacts
issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
runs-on: ubuntu-latest
needs: [upstream-workflow-summary]
steps:
- name: checkout
uses: actions/checkout@v4
# We need get persist-index first
- name: download image snapshot artifact
uses: dawidd6/action-download-artifact@v2
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
name: visual-regression-diff-ref
# Save PR id to output
- name: save PR id
id: pr
run: echo "id=$(<visual-regression-pr-id.txt)" >> $GITHUB_OUTPUT
# Download report artifact
- name: download report artifact
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
uses: dawidd6/action-download-artifact@v2
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
name: visual-regression-report
# unzip report and then upload them to oss
- name: upload visual-regression report
id: report
env:
ALI_OSS_AK_ID: ${{ secrets.ALI_OSS_AK_ID }}
ALI_OSS_AK_SECRET: ${{ secrets.ALI_OSS_AK_SECRET }}
run: |
mkdir ./visualRegressionReport
tar -xzvf visualRegressionReport.tar.gz -C ./visualRegressionReport
echo "✅ Uncompress Finished"
rm package.json
npm i ali-oss --no-save
echo "✅ Install `ali-oss` Finished"
echo "🤖 Uploading"
node scripts/visual-regression/upload.js ./visualRegressionReport --ref=pr-${{ steps.pr.outputs.id }}
echo "✅ Uploaded"
delimiter="$(openssl rand -hex 8)"
echo "content<<${delimiter}" >> "${GITHUB_OUTPUT}"
echo "$(<visualRegressionReport/report.md)" >> "${GITHUB_OUTPUT}"
echo "${delimiter}" >> "${GITHUB_OUTPUT}"
- name: success comment
uses: actions-cool/maintain-one-comment@v3
if: ${{ steps.report.outcome == 'success' }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
${{ steps.report.outputs.content }}
<!-- VISUAL_DIFF_REGRESSION_HOOK -->
body-include: '<!-- VISUAL_DIFF_REGRESSION_HOOK -->'
number: ${{ steps.pr.outputs.id }}
- name: failed comment
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-failure) || steps.report.outcome == 'failure' || failure() }}
uses: actions-cool/maintain-one-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
## Visual-Regression Diff Failed
<!-- VISUAL_DIFF_REGRESSION_HOOK -->
body-include: '<!-- VISUAL_DIFF_REGRESSION_HOOK -->'
number: ${{ steps.pr.outputs.id }}

View File

@ -0,0 +1,32 @@
# When `visual-regression-diff-build` start. Leave a message on the PR
#
# 🚨🚨🚨 Important 🚨🚨🚨
# Never do any `checkout` or `npm install` action!
# `pull_request_target` will enable PR to access the secrets!
name: 👀 Visual Regression Diff Start
on:
pull_request:
branches: [master, feature]
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
visual-regression-diff-start:
permissions:
issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
name: start visual-regression diff
runs-on: ubuntu-latest
steps:
- name: update status comment
uses: actions-cool/maintain-one-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
## Visual-Regression Diff Building...
<!-- VISUAL_DIFF_REGRESSION_HOOK -->
body-include: '<!-- VISUAL_DIFF_REGRESSION_HOOK -->'

View File

@ -24,7 +24,6 @@ jobs:
uses: actions/github-script@v6
id: persist_start_job_status
with:
# todo: split it out as github actions
script: |
const response = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
@ -43,7 +42,9 @@ jobs:
const total = Object.keys(jobs).length;
if(total === 0) core.setFailed('no jobs found');
// the name here must be the same as `jobs.xxx.{name}` in preview-build.yml
// the name here must be the same as `jobs.xxx.{name}`
console.log('visual-diff report job status: %s', jobs['test image']);
// set output
core.setOutput('build-success', jobs['test image'] === 'success');
core.setOutput('build-failure', jobs['test image'] === 'failure');
@ -69,7 +70,7 @@ jobs:
# Save visual-regression ref to output
- name: Extra Visual Regression Ref
id: visuall-regression
id: visual_regression
run: echo "id=$(<visual-regression-ref.txt)" >> $GITHUB_OUTPUT
- name: Download Visual-Regression Artifact
@ -88,13 +89,13 @@ jobs:
env:
ALI_OSS_AK_ID: ${{ secrets.ALI_OSS_AK_ID }}
ALI_OSS_AK_SECRET: ${{ secrets.ALI_OSS_AK_SECRET }}
ALI_OSS_BUCKET: ${{ secrets.ALI_OSS_BUCKET }}
run: |
rm package.json
npm i ali-oss --no-save
echo "✅ Install `ali-oss` Finished"
echo "🤖 Uploading"
node scripts/visual-regression-upload.js ./imageSnapshots.tar.gz --ref=${{ github.sha }}
node scripts/visual-regression-upload.js ./visual-regression-ref.txt --ref=${{ github.ref_name }}
node scripts/visual-regression/upload.js ./imageSnapshots.tar.gz --ref=${{ github.sha }}
node scripts/visual-regression/upload.js ./visual-regression-ref.txt --ref=${{ github.ref_name }}
echo "✅ Uploaded"

1
.gitignore vendored
View File

@ -63,6 +63,7 @@ __image_snapshots__/
/jest-stare
/imageSnapshots*
/imageDiffSnapshots
/visualRegressionReport*
.devcontainer*
.husky/prepare-commit-msg

View File

@ -16,6 +16,20 @@ tag: vVERSION
---
## 5.12.2
`2023-12-11`
- 🐞 MISC: Fix `useId` error when webpack build with React 17. [#46261](https://github.com/ant-design/ant-design/pull/46261)
- Pagination
- 🐞 Fix Pagination throws error in legacy browsers. [react-component/pagination#545](https://github.com/react-component/pagination/pull/545)
- 🐞 Fix Pagination `current` not working in `simple` mode. [react-component/pagination#546](https://github.com/react-component/pagination/pull/546)
- 🐞 Fix Table filter dropdown lost background color in CSS variables mode. [#46314](https://github.com/ant-design/ant-design/pull/46314)
- 🐞 Prevent interaction when Spin component enable `fullscreen` prop. [#46303](https://github.com/ant-design/ant-design/pull/46303) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 Fix Form `hideRequiredMark` prop's priority compared with ConfigProvider `form` prop. [#46299](https://github.com/ant-design/ant-design/pull/46299) [@linhf123](https://github.com/linhf123)
- TypeScript
- 🤖 Fix Descriptions `id` type. [#46367](https://github.com/ant-design/ant-design/pull/46367) [@RSS1102](https://github.com/RSS1102)
## 5.12.1
`2023-12-04`

View File

@ -16,6 +16,20 @@ tag: vVERSION
---
## 5.12.2
`2023-12-11`
- 🐞 MISC: 修复 React 17 以下使用 webpack 构建时报错 `useId` 找不到的问题。[#46261](https://github.com/ant-design/ant-design/pull/46261)
- Pagination
- 🐞 修复 Pagination 在低版本浏览器上报错的问题。[react-component/pagination#545](https://github.com/react-component/pagination/pull/545)
- 🐞 修复 Pagination `simple` 模式下 `current` 受控选中分页不生效的问题。[react-component/pagination#546](https://github.com/react-component/pagination/pull/546)
- 🐞 修复 Table 筛选菜单在 CSS 变量模式下丢失背景色的问题。[#46314](https://github.com/ant-design/ant-design/pull/46314)
- 🐞 优化 Spin 交互,全屏状态时禁止用户触发鼠标事件。[#46303](https://github.com/ant-design/ant-design/pull/46303) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 修复 Form `hideRequiredMark` 属性的优先级低于 ConfigProvider 的 form 配置的问题。[#46299](https://github.com/ant-design/ant-design/pull/46299) [@linhf123](https://github.com/linhf123)
- TypeScript
- 🤖 修复 Descriptions TS 定义不支持 `id` 属性的问题。[#46367](https://github.com/ant-design/ant-design/pull/46367) [@RSS1102](https://github.com/RSS1102)
## 5.12.1
`2023-12-04`

View File

@ -1,20 +1,29 @@
<p align="center">
<a href="https://ant.design">
<img width="200" src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg">
</a>
</p>
<div align="center"><a name="readme-top"></a>
<h1 align="center">Ant Design</h1>
<img height="180" src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg">
<div align="center">
<h1>Ant Design</h1>
一套企业级 UI 设计语言和 React 组件库。
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
[![CI status][github-action-image]][github-action-url]
[![codecov][codecov-image]][codecov-url]
[![NPM version][npm-image]][npm-url]
[![NPM downloads][download-image]][download-url]
[![][bundlephobia-image]][bundlephobia-url] [![][bundlesize-js-image]][unpkg-js-url] [![FOSSA Status][fossa-image]][fossa-url]
[![][bundlephobia-image]][bundlephobia-url]
[![][bundlesize-js-image]][unpkg-js-url]
[![FOSSA Status][fossa-image]][fossa-url]
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
[![Follow Twitter][twitter-image]][twitter-url]
[![Renovate status][renovate-image]][renovate-dashboard-url]
[![][issues-helper-image]][issues-helper-url]
[![dumi][dumi-image]][dumi-url]
[![Issues need help][help-wanted-image]][help-wanted-url]
[Changelog](./CHANGELOG-zh-CN.md) · [Report Bug][github-issues-url] · [Request Feature][github-issues-url] · [English](./README.md) · 中文
![](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png)
[npm-image]: http://img.shields.io/npm/v/antd.svg?style=flat-square
[npm-url]: http://npmjs.org/package/antd
@ -34,19 +43,29 @@
[unpkg-js-url]: https://unpkg.com/browse/antd/dist/antd.min.js
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/antd?style=flat-square
[bundlephobia-url]: https://bundlephobia.com/package/antd
[issues-helper-image]: https://img.shields.io/badge/using-issues--helper-orange?style=flat-square
[issues-helper-url]: https://github.com/actions-cool/issues-helper
[issues-helper-image]: https://img.shields.io/badge/using-actions--cool-blue?style=flat-square
[issues-helper-url]: https://github.com/actions-cool
[renovate-image]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg?style=flat-square
[renovate-dashboard-url]: https://github.com/ant-design/ant-design/issues/32498
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
[dumi-url]: https://github.com/umijs/dumi
[github-issues-url]: https://new-issue.ant.design
<!-- Copy-paste in your Readme.md file -->
<a href="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats?repo_id=34526884" target="_blank" style="display: block" align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats/thumbnail.png?repo_id=34526884&image_size=auto&color_scheme=dark" width="655" height="auto">
<img alt="Performance Stats of ant-design/ant-design - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats/thumbnail.png?repo_id=34526884&image_size=auto&color_scheme=light" width="655" height="auto">
</picture>
</a>
<!-- Made with [OSS Insight](https://ossinsight.io/) -->
</div>
[![](https://user-images.githubusercontent.com/507615/209472919-6f7e8561-be8c-4b0b-9976-eb3c692aa20a.png)](https://ant.design)
[English](./README.md) | 中文
## ✨ 特性
- 🌈 提炼自企业级中后台产品的交互语言和视觉风格。
@ -152,6 +171,19 @@ $ npm start
## 🤝 参与共建 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
ome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
<!-- Copy-paste in your Readme.md file -->
<a href="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors?repo_id=34526884&limit=30" target="_blank" style="display: block" align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=34526884&limit=30&image_size=auto&color_scheme=dark" width="655" height="auto">
<img alt="Active Contributors of ant-design/ant-design - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=34526884&limit=30&image_size=auto&color_scheme=light" width="655" height="auto">
</picture>
</a>
<!-- Made with [OSS Insight](https://ossinsight.io/) -->
请参考[贡献指南](https://ant.design/docs/react/contributing-cn).
> 强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) 和 [《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/%7Esgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393),更好的问题更容易获得帮助。

View File

@ -1,20 +1,29 @@
<p align="center">
<a href="https://ant.design">
<img width="200" src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg">
</a>
</p>
<div align="center"><a name="readme-top"></a>
<h1 align="center">Ant Design</h1>
<img height="180" src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg">
<div align="center">
<h1>Ant Design</h1>
An enterprise-class UI design language and React UI library.
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
[![CI status][github-action-image]][github-action-url]
[![codecov][codecov-image]][codecov-url]
[![NPM version][npm-image]][npm-url]
[![NPM downloads][download-image]][download-url]
[![][bundlephobia-image]][bundlephobia-url] [![][bundlesize-js-image]][unpkg-js-url] [![FOSSA Status][fossa-image]][fossa-url]
[![][bundlephobia-image]][bundlephobia-url]
[![][bundlesize-js-image]][unpkg-js-url]
[![FOSSA Status][fossa-image]][fossa-url]
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
[![Follow Twitter][twitter-image]][twitter-url]
[![Renovate status][renovate-image]][renovate-dashboard-url]
[![][issues-helper-image]][issues-helper-url]
[![dumi][dumi-image]][dumi-url]
[![Issues need help][help-wanted-image]][help-wanted-url]
[更新日志](./CHANGELOG.en-US.md) · [报告问题][github-issues-url] · [特性需求][github-issues-url] · English | [中文](./README-zh_CN.md)
![](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png)
[npm-image]: http://img.shields.io/npm/v/antd.svg?style=flat-square
[npm-url]: http://npmjs.org/package/antd
@ -40,6 +49,7 @@ An enterprise-class UI design language and React UI library.
[renovate-dashboard-url]: https://github.com/ant-design/ant-design/issues/32498
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
[dumi-url]: https://github.com/umijs/dumi
[github-issues-url]: https://new-issue.ant.design
<!-- Copy-paste in your Readme.md file -->
@ -56,8 +66,6 @@ An enterprise-class UI design language and React UI library.
[![](https://user-images.githubusercontent.com/507615/209472919-6f7e8561-be8c-4b0b-9976-eb3c692aa20a.png)](https://ant.design)
English | [中文](./README-zh_CN.md)
## ✨ Features
- 🌈 Enterprise-class UI designed for web applications.

View File

@ -1 +0,0 @@
# antd-image-snapshots

View File

@ -1,22 +1,15 @@
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import * as React from 'react';
import ConfigProvider, { ConfigContext } from '../config-provider';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
export function withPureRenderTheme(Component: any) {
return function PureRenderThemeComponent(props: any) {
return (
<ConfigProvider
theme={{
token: {
motion: false,
zIndexPopupBase: 0,
},
}}
>
<Component {...props} />
</ConfigProvider>
);
};
import ConfigProvider, { ConfigContext } from '../config-provider';
import type { AnyObject } from './type';
export function withPureRenderTheme<T extends AnyObject = AnyObject>(Component: React.FC<T>) {
return (props: T) => (
<ConfigProvider theme={{ token: { motion: false, zIndexPopupBase: 0 } }}>
<Component {...props} />
</ConfigProvider>
);
}
export interface BaseProps {
@ -25,15 +18,15 @@ export interface BaseProps {
}
/* istanbul ignore next */
export default function genPurePanel<ComponentProps extends BaseProps>(
const genPurePanel = <ComponentProps extends BaseProps = BaseProps>(
Component: any,
defaultPrefixCls?: string,
getDropdownCls?: null | ((prefixCls: string) => string),
postProps?: (props: ComponentProps) => ComponentProps,
) {
type WrapProps = Omit<ComponentProps, 'open' | 'visible'> & { open?: boolean };
) => {
type WrapProps = ComponentProps & AnyObject;
function PurePanel(props: WrapProps) {
const PurePanel: React.FC<WrapProps> = (props) => {
const { prefixCls: customizePrefixCls, style } = props;
const holderRef = React.useRef<HTMLDivElement>(null);
@ -88,22 +81,21 @@ export default function genPurePanel<ComponentProps extends BaseProps>(
};
if (postProps) {
mergedProps = postProps(mergedProps as ComponentProps);
mergedProps = postProps(mergedProps);
}
const mergedStyle: React.CSSProperties = {
paddingBottom: popupHeight,
position: 'relative',
minWidth: popupWidth,
};
return (
<div
ref={holderRef}
style={{
paddingBottom: popupHeight,
position: 'relative',
minWidth: popupWidth,
}}
>
<div ref={holderRef} style={mergedStyle}>
<Component {...mergedProps} />
</div>
);
}
};
return withPureRenderTheme(PurePanel);
}
return withPureRenderTheme<AnyObject>(PurePanel);
};
export default genPurePanel;

View File

@ -1,7 +1,7 @@
type RecordType = Record<string, any>;
import type { AnyObject } from './type';
function extendsObject<T extends RecordType>(...list: T[]) {
const result: RecordType = { ...list[0] };
const extendsObject = <T extends AnyObject = AnyObject>(...list: T[]) => {
const result: AnyObject = { ...list[0] };
for (let i = 1; i < list.length; i++) {
const obj = list[i];
@ -16,6 +16,6 @@ function extendsObject<T extends RecordType>(...list: T[]) {
}
return result;
}
};
export default extendsObject;

View File

@ -9,9 +9,5 @@ export const getRenderPropValue = (
return null;
}
if (typeof propValue === 'function') {
return propValue();
}
return propValue;
return typeof propValue === 'function' ? propValue() : propValue;
};

View File

@ -53,7 +53,7 @@ Common props ref[Common props](/docs/react/common-props)
| --- | --- | --- | --- | --- |
| itemRender | Custom item renderer | (route, params, routes, paths) => ReactNode | - | |
| params | Routing parameters | object | - | |
| items | The routing stack information of router | [items\[\]](#ItemType) | - | 5.3.0 |
| items | The routing stack information of router | [ItemType\[\]](#ItemType) | - | 5.3.0 |
| separator | Custom separator | ReactNode | `/` | |
### ItemType

View File

@ -1,16 +1,19 @@
import type { TriggerProps } from '@rc-component/trigger';
import dayjs from 'dayjs';
import 'dayjs/locale/mk'; // to test local in 'prop locale should works' test case
import React from 'react';
import { CloseCircleFilled } from '@ant-design/icons';
import userEvent from '@testing-library/user-event';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import MockDate from 'mockdate';
import dayJsGenerateConfig from 'rc-picker/lib/generate/dayjs';
import React from 'react';
import userEvent from '@testing-library/user-event';
import { CloseCircleFilled } from '@ant-design/icons';
import DatePicker from '..';
import { resetWarned } from '../../_util/warning';
import focusTest from '../../../tests/shared/focusTest';
import { fireEvent, render, screen, waitFor } from '../../../tests/utils';
import { resetWarned } from '../../_util/warning';
import type { PickerLocale } from '../generatePicker';
import { closeCircleByRole, expectCloseCircle } from './utils';
@ -124,6 +127,55 @@ describe('DatePicker', () => {
).toBe(60);
});
it('showTime={{ showMinute: true, showSecond: true }}', () => {
const { container } = render(
<DatePicker
defaultValue={dayjs()}
showTime={{ showMinute: true, showSecond: true }}
format="YYYY-MM-DD"
open
/>,
);
expect(container.querySelectorAll('.ant-picker-time-panel-column').length).toBe(2);
expect(
container
.querySelectorAll('.ant-picker-time-panel-column')?.[0]
.querySelectorAll('.ant-picker-time-panel-cell').length,
).toBe(60);
expect(
container
.querySelectorAll('.ant-picker-time-panel-column')?.[1]
.querySelectorAll('.ant-picker-time-panel-cell').length,
).toBe(60);
});
it('showTime={{ showHour: true, showMinute: true, showSecond: true }}', () => {
const { container } = render(
<DatePicker
defaultValue={dayjs()}
showTime={{ showHour: true, showMinute: true, showSecond: true }}
format="YYYY-MM-DD"
open
/>,
);
expect(container.querySelectorAll('.ant-picker-time-panel-column').length).toBe(3);
expect(
container
.querySelectorAll('.ant-picker-time-panel-column')?.[0]
.querySelectorAll('.ant-picker-time-panel-cell').length,
).toBe(24);
expect(
container
.querySelectorAll('.ant-picker-time-panel-column')?.[1]
.querySelectorAll('.ant-picker-time-panel-cell').length,
).toBe(60);
expect(
container
.querySelectorAll('.ant-picker-time-panel-column')?.[2]
.querySelectorAll('.ant-picker-time-panel-cell').length,
).toBe(60);
});
it('showTime={{ showHour: true, showSecond: true }}', () => {
const { container } = render(
<DatePicker
@ -146,27 +198,69 @@ describe('DatePicker', () => {
).toBe(60);
});
it('showTime={{ showMinute: true, showSecond: true }}', () => {
it('showTime={{ showSecond: true }}', () => {
const { container } = render(
<DatePicker
defaultValue={dayjs()}
showTime={{ showMinute: true, showSecond: true }}
showTime={{ showSecond: true }}
format="YYYY-MM-DD"
open
/>,
);
expect(container.querySelectorAll('.ant-picker-time-panel-column').length).toBe(2);
expect(container.querySelectorAll('.ant-picker-time-panel-column').length).toBe(1);
expect(
container
.querySelectorAll('.ant-picker-time-panel-column')?.[0]
.querySelectorAll('.ant-picker-time-panel-cell').length,
).toBe(60);
});
it('showTime={{ showMinute: true }}', () => {
const { container } = render(
<DatePicker
defaultValue={dayjs()}
showTime={{ showMinute: true }}
format="YYYY-MM-DD"
open
/>,
);
expect(container.querySelectorAll('.ant-picker-time-panel-column').length).toBe(1);
expect(
container
.querySelectorAll('.ant-picker-time-panel-column')?.[1]
.querySelectorAll('.ant-picker-time-panel-column')?.[0]
.querySelectorAll('.ant-picker-time-panel-cell').length,
).toBe(60);
});
it('showTime={{ showHour: true }}', () => {
const { container } = render(
<DatePicker
defaultValue={dayjs()}
showTime={{ showHour: true }}
format="YYYY-MM-DD"
open
/>,
);
expect(container.querySelectorAll('.ant-picker-time-panel-column').length).toBe(1);
expect(
container
.querySelectorAll('.ant-picker-time-panel-column')?.[0]
.querySelectorAll('.ant-picker-time-panel-cell').length,
).toBe(24);
});
it('showTime={{ }} (no true args)', () => {
const { container } = render(
<DatePicker
defaultValue={dayjs()}
showTime={{ }}
format="YYYY-MM-DD"
open
/>,
);
expect(container.querySelectorAll('.ant-picker-time-panel-column').length).toBe(0);
});
it('showTime should work correctly when format is custom function', () => {
const { container } = render(
<DatePicker

View File

@ -56,6 +56,65 @@ describe('RangePicker', () => {
expect(container.firstChild).toMatchSnapshot();
});
it('the left selection is before the right selection', () => {
let rangePickerValue: dayjs.Dayjs[] = [];
const Test: React.FC = () => {
const [value, setValue] = useState<RangeValue<dayjs.Dayjs>>(null);
return (
<RangePicker
value={value}
mode={['month', 'month']}
onPanelChange={(v) => {
setValue(v);
rangePickerValue = v as dayjs.Dayjs[];
}}
/>
);
};
const wrapper = render(<Test />);
openPicker(wrapper);
selectCell(wrapper, 'Feb');
openPicker(wrapper, 1);
selectCell(wrapper, 'May');
closePicker(wrapper, 1);
const [start, end] = rangePickerValue;
expect(start.isBefore(end, 'date')).toBeTruthy();
});
it('the left selection is after the right selection, no selection made', () => {
let rangePickerValue: dayjs.Dayjs[] = [];
const Test: React.FC = () => {
const [value, setValue] = useState<RangeValue<dayjs.Dayjs>>(null);
return (
<RangePicker
value={value}
mode={['month', 'month']}
onPanelChange={(v) => {
setValue(v);
rangePickerValue = v as dayjs.Dayjs[];
}}
/>
);
};
const wrapper = render(<Test />);
openPicker(wrapper);
selectCell(wrapper, 'May');
openPicker(wrapper, 1);
selectCell(wrapper, 'Feb');
closePicker(wrapper, 1);
const [start, end] = rangePickerValue;
expect(start).not.toBeNull();
expect(end).toBeNull();
});
// https://github.com/ant-design/ant-design/issues/13302
describe('in "month" mode, when the left and right panels select the same month', () => {
it('the cell status is correct', () => {

View File

@ -1,7 +1,8 @@
import React from 'react';
import BehaviorMap from '../../../.dumi/theme/common/BehaviorMap';
const BehaviorPattern = () => (
const BehaviorPattern: React.FC = () => (
<BehaviorMap
data={{
id: '200000004',

View File

@ -1,9 +1,9 @@
import type { FC } from 'react';
import React from 'react';
import type { Dayjs } from 'dayjs';
import { DatePicker } from 'antd';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
import { DatePicker } from 'antd';
import type { Dayjs } from 'dayjs';
const { _InternalPanelDoNotUseOrYouWillBeFired: PureDatePicker } = DatePicker;
@ -48,9 +48,9 @@ const useStyle = createStyles(({ token }) => ({
}
`,
minus: css`
color: #52C41A80;
color: #52c41a80;
.ant-picker-cell-in-view & {
color: #52C41A;
color: #52c41a;
}
.ant-picker-cell-selected & {
color: #fff;

View File

@ -1,18 +1,17 @@
import React from 'react';
import dayjs from 'dayjs';
import { DatePicker } from 'antd';
import type { TimeRangePickerProps } from 'antd';
import dayjs from 'dayjs';
const { _InternalRangePanelDoNotUseOrYouWillBeFired: PureRangePicker } = DatePicker;
const App: React.FC = () => (
<PureRangePicker
presets={[
{ label: 'Last 7 Days', value: [dayjs().add(-7, 'd'), dayjs()] },
{ label: 'Last 14 Days', value: [dayjs().add(-14, 'd'), dayjs()] },
{ label: 'Last 30 Days', value: [dayjs().add(-30, 'd'), dayjs()] },
{ label: 'Last 90 Days', value: [dayjs().add(-90, 'd'), dayjs()] },
]}
/>
);
const rangePresets: TimeRangePickerProps['presets'] = [
{ label: 'Last 7 Days', value: [dayjs().add(-7, 'd'), dayjs()] },
{ label: 'Last 14 Days', value: [dayjs().add(-14, 'd'), dayjs()] },
{ label: 'Last 30 Days', value: [dayjs().add(-30, 'd'), dayjs()] },
{ label: 'Last 90 Days', value: [dayjs().add(-90, 'd'), dayjs()] },
];
const App: React.FC = () => <PureRangePicker presets={rangePresets} />;
export default App;

View File

@ -58,8 +58,7 @@ If there are special needs (only modifying single component language), Please us
<!-- prettier-ignore -->
:::warning
When use with Nextjs App Router, make sure to add `'use client'` before import locale file of dayjs.
It's because all components of Ant Design only works in client, importing locale in RSC will not work.
When use with Nextjs App Router, make sure to add `'use client'` before import locale file of dayjs. It's because all components of Ant Design only works in client, importing locale in RSC will not work.
:::
```jsx

View File

@ -59,8 +59,7 @@ demo:
<!-- prettier-ignore -->
:::warning
在搭配 Nextjs 的 App Router 使用时,注意在引入 dayjs 的 locale 文件时加上 `'use client'`
这是由于 Ant Design 的组件都是客户端组件,在 RSC 中引入 dayjs 的 locale 文件将不会在客户端生效。
在搭配 Nextjs 的 App Router 使用时,注意在引入 dayjs 的 locale 文件时加上 `'use client'`。这是由于 Ant Design 的组件都是客户端组件,在 RSC 中引入 dayjs 的 locale 文件将不会在客户端生效。
:::
```jsx

View File

@ -188,6 +188,16 @@ describe('Descriptions', () => {
expect(wrapper.container.firstChild).toMatchSnapshot();
});
it('Descriptions support id', () => {
const wrapper = render(
<Descriptions id="descriptions">
<Descriptions.Item>Cloud Database</Descriptions.Item>
</Descriptions>,
);
const descriptionItemsElement = wrapper.container.querySelector('#descriptions');
expect(descriptionItemsElement).not.toBeNull();
});
it('keep key', () => {
render(
<Descriptions>

View File

@ -47,6 +47,7 @@ export interface DescriptionsProps {
labelStyle?: React.CSSProperties;
contentStyle?: React.CSSProperties;
items?: DescriptionsItemType[];
id?: string;
}
const Descriptions: React.FC<DescriptionsProps> & CompoundedComponent = (props) => {

View File

@ -297,7 +297,7 @@ exports[`renders components/flex/demo/combination.tsx extend context correctly 1
target="_blank"
>
<span>
Get Start
Get Started
</span>
</a>
</div>

View File

@ -293,7 +293,7 @@ exports[`renders components/flex/demo/combination.tsx correctly 1`] = `
target="_blank"
>
<span>
Get Start
Get Started
</span>
</a>
</div>

View File

@ -23,7 +23,7 @@ const App: React.FC = () => (
antd is an enterprise-class UI design language and React UI library.
</Typography.Title>
<Button type="primary" href="https://ant.design" target="_blank">
Get Start
Get Started
</Button>
</Flex>
</Flex>

View File

@ -115,4 +115,9 @@ export default genStyleHooks(
];
},
prepareComponentToken,
{
// Flex component don't apply extra font style
// https://github.com/ant-design/ant-design/issues/46403
resetStyle: false,
},
);

View File

@ -91,7 +91,7 @@ const InternalForm: React.ForwardRefRenderFunction<FormInstance, FormProps> = (p
if (hideRequiredMark) {
return false;
}
if (contextForm && contextForm.requiredMark !== undefined) {
return contextForm.requiredMark;
}

View File

@ -2,124 +2,47 @@
exports[`renders components/layout/demo/basic.tsx extend context correctly 1`] = `
<div
class="ant-space ant-space-vertical"
style="row-gap: 48px; width: 100%;"
class="ant-flex ant-flex-wrap-wrap ant-flex-gap-middle"
>
<div
class="ant-space-item"
class="ant-layout"
style="border-radius: 8px; overflow: hidden; width: calc(50% - 8px); max-width: calc(50% - 8px);"
>
<div
class="ant-layout"
<header
class="ant-layout-header"
style="text-align: center; color: rgb(255, 255, 255); height: 64px; padding-inline: 48px; line-height: 64px; background-color: rgb(64, 150, 255);"
>
<header
class="ant-layout-header"
style="text-align: center; color: rgb(255, 255, 255); height: 64px; padding-inline: 50px; line-height: 64px; background-color: rgb(125, 188, 234);"
>
Header
</header>
<main
class="ant-layout-content"
style="text-align: center; min-height: 120px; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(16, 142, 233);"
>
Content
</main>
<footer
class="ant-layout-footer"
style="text-align: center; color: rgb(255, 255, 255); background-color: rgb(125, 188, 234);"
>
Footer
</footer>
</div>
Header
</header>
<main
class="ant-layout-content"
style="text-align: center; min-height: 120px; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(9, 88, 217);"
>
Content
</main>
<footer
class="ant-layout-footer"
style="text-align: center; color: rgb(255, 255, 255); background-color: rgb(64, 150, 255);"
>
Footer
</footer>
</div>
<div
class="ant-space-item"
class="ant-layout"
style="border-radius: 8px; overflow: hidden; width: calc(50% - 8px); max-width: calc(50% - 8px);"
>
<div
class="ant-layout"
<header
class="ant-layout-header"
style="text-align: center; color: rgb(255, 255, 255); height: 64px; padding-inline: 48px; line-height: 64px; background-color: rgb(64, 150, 255);"
>
<header
class="ant-layout-header"
style="text-align: center; color: rgb(255, 255, 255); height: 64px; padding-inline: 50px; line-height: 64px; background-color: rgb(125, 188, 234);"
>
Header
</header>
<div
class="ant-layout ant-layout-has-sider"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="text-align: center; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(59, 160, 233); flex: 0 0 200px; max-width: 200px; min-width: 200px; width: 200px;"
>
<div
class="ant-layout-sider-children"
>
Sider
</div>
</aside>
<main
class="ant-layout-content"
style="text-align: center; min-height: 120px; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(16, 142, 233);"
>
Content
</main>
</div>
<footer
class="ant-layout-footer"
style="text-align: center; color: rgb(255, 255, 255); background-color: rgb(125, 188, 234);"
>
Footer
</footer>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-layout"
>
<header
class="ant-layout-header"
style="text-align: center; color: rgb(255, 255, 255); height: 64px; padding-inline: 50px; line-height: 64px; background-color: rgb(125, 188, 234);"
>
Header
</header>
<div
class="ant-layout ant-layout-has-sider"
>
<main
class="ant-layout-content"
style="text-align: center; min-height: 120px; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(16, 142, 233);"
>
Content
</main>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="text-align: center; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(59, 160, 233); flex: 0 0 200px; max-width: 200px; min-width: 200px; width: 200px;"
>
<div
class="ant-layout-sider-children"
>
Sider
</div>
</aside>
</div>
<footer
class="ant-layout-footer"
style="text-align: center; color: rgb(255, 255, 255); background-color: rgb(125, 188, 234);"
>
Footer
</footer>
</div>
</div>
<div
class="ant-space-item"
>
Header
</header>
<div
class="ant-layout ant-layout-has-sider"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="text-align: center; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(59, 160, 233); flex: 0 0 200px; max-width: 200px; min-width: 200px; width: 200px;"
style="text-align: center; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(22, 119, 255); flex: 0 0 25%; max-width: 25%; min-width: 25%; width: 25%;"
>
<div
class="ant-layout-sider-children"
@ -127,28 +50,92 @@ exports[`renders components/layout/demo/basic.tsx extend context correctly 1`] =
Sider
</div>
</aside>
<div
class="ant-layout"
<main
class="ant-layout-content"
style="text-align: center; min-height: 120px; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(9, 88, 217);"
>
<header
class="ant-layout-header"
style="text-align: center; color: rgb(255, 255, 255); height: 64px; padding-inline: 50px; line-height: 64px; background-color: rgb(125, 188, 234);"
Content
</main>
</div>
<footer
class="ant-layout-footer"
style="text-align: center; color: rgb(255, 255, 255); background-color: rgb(64, 150, 255);"
>
Footer
</footer>
</div>
<div
class="ant-layout"
style="border-radius: 8px; overflow: hidden; width: calc(50% - 8px); max-width: calc(50% - 8px);"
>
<header
class="ant-layout-header"
style="text-align: center; color: rgb(255, 255, 255); height: 64px; padding-inline: 48px; line-height: 64px; background-color: rgb(64, 150, 255);"
>
Header
</header>
<div
class="ant-layout ant-layout-has-sider"
>
<main
class="ant-layout-content"
style="text-align: center; min-height: 120px; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(9, 88, 217);"
>
Content
</main>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="text-align: center; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(22, 119, 255); flex: 0 0 25%; max-width: 25%; min-width: 25%; width: 25%;"
>
<div
class="ant-layout-sider-children"
>
Header
</header>
<main
class="ant-layout-content"
style="text-align: center; min-height: 120px; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(16, 142, 233);"
>
Content
</main>
<footer
class="ant-layout-footer"
style="text-align: center; color: rgb(255, 255, 255); background-color: rgb(125, 188, 234);"
>
Footer
</footer>
Sider
</div>
</aside>
</div>
<footer
class="ant-layout-footer"
style="text-align: center; color: rgb(255, 255, 255); background-color: rgb(64, 150, 255);"
>
Footer
</footer>
</div>
<div
class="ant-layout ant-layout-has-sider"
style="border-radius: 8px; overflow: hidden; width: calc(50% - 8px); max-width: calc(50% - 8px);"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="text-align: center; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(22, 119, 255); flex: 0 0 25%; max-width: 25%; min-width: 25%; width: 25%;"
>
<div
class="ant-layout-sider-children"
>
Sider
</div>
</aside>
<div
class="ant-layout"
>
<header
class="ant-layout-header"
style="text-align: center; color: rgb(255, 255, 255); height: 64px; padding-inline: 48px; line-height: 64px; background-color: rgb(64, 150, 255);"
>
Header
</header>
<main
class="ant-layout-content"
style="text-align: center; min-height: 120px; line-height: 120px; color: rgb(255, 255, 255); background-color: rgb(9, 88, 217);"
>
Content
</main>
<footer
class="ant-layout-footer"
style="text-align: center; color: rgb(255, 255, 255); background-color: rgb(64, 150, 255);"
>
Footer
</footer>
</div>
</div>
</div>
@ -1100,7 +1087,7 @@ exports[`renders components/layout/demo/component-token.tsx extend context corre
</nav>
<main
class="ant-layout-content"
style="padding: 24px; margin: 0px; min-height: 280px; background: rgb(255, 255, 255);"
style="padding: 24px; margin: 0px; min-height: 280px; background: rgb(245, 245, 245); border-radius: 8px;"
>
Content
</main>
@ -1380,7 +1367,7 @@ exports[`renders components/layout/demo/custom-trigger.tsx extend context correc
</header>
<main
class="ant-layout-content"
style="margin: 24px 16px; padding: 24px; min-height: 280px; background: rgb(255, 255, 255);"
style="margin: 24px 16px; padding: 24px; min-height: 280px; background: rgb(255, 255, 255); border-radius: 8px;"
>
Content
</main>
@ -1405,6 +1392,7 @@ exports[`renders components/layout/demo/fixed.tsx extend context correctly 1`] =
class="ant-menu-overflow ant-menu ant-menu-root ant-menu-horizontal ant-menu-dark"
data-menu-list="true"
role="menu"
style="flex: 1; min-width: 0;"
tabindex="0"
>
<li
@ -1604,8 +1592,8 @@ exports[`renders components/layout/demo/fixed.tsx extend context correctly 1`] =
</div>
</header>
<main
class="ant-layout-content site-layout"
style="padding: 0px 50px;"
class="ant-layout-content"
style="padding: 0px 48px;"
>
<nav
class="ant-breadcrumb"
@ -1648,7 +1636,7 @@ exports[`renders components/layout/demo/fixed.tsx extend context correctly 1`] =
</ol>
</nav>
<div
style="padding: 24px; min-height: 380px; background: rgb(255, 255, 255);"
style="padding: 24px; min-height: 380px; background: rgb(255, 255, 255); border-radius: 8px;"
>
Content
</div>
@ -2225,7 +2213,7 @@ exports[`renders components/layout/demo/fixed-sider.tsx extend context correctly
</div>
</aside>
<div
class="ant-layout site-layout"
class="ant-layout"
style="margin-left: 200px;"
>
<header
@ -2237,7 +2225,7 @@ exports[`renders components/layout/demo/fixed-sider.tsx extend context correctly
style="margin: 24px 16px 0px; overflow: initial;"
>
<div
style="padding: 24px; text-align: center; background: rgb(255, 255, 255);"
style="padding: 24px; text-align: center; background: rgb(255, 255, 255); border-radius: 8px;"
>
<p>
long content
@ -2799,7 +2787,7 @@ exports[`renders components/layout/demo/responsive.tsx extend context correctly
style="margin: 24px 16px 0px;"
>
<div
style="padding: 24px; min-height: 360px; background: rgb(255, 255, 255);"
style="padding: 24px; min-height: 360px; background: rgb(255, 255, 255); border-radius: 8px;"
>
content
</div>
@ -3451,7 +3439,7 @@ exports[`renders components/layout/demo/side.tsx extend context correctly 1`] =
</ol>
</nav>
<div
style="padding: 24px; min-height: 360px; background: rgb(255, 255, 255);"
style="padding: 24px; min-height: 360px; background: rgb(255, 255, 255); border-radius: 8px;"
>
Bill is a cat.
</div>
@ -3474,7 +3462,7 @@ exports[`renders components/layout/demo/side.tsx extend context correctly 2`] =
exports[`renders components/layout/demo/top.tsx extend context correctly 1`] = `
<div
class="ant-layout layout"
class="ant-layout"
>
<header
class="ant-layout-header"
@ -3487,6 +3475,7 @@ exports[`renders components/layout/demo/top.tsx extend context correctly 1`] = `
class="ant-menu-overflow ant-menu ant-menu-root ant-menu-horizontal ant-menu-dark"
data-menu-list="true"
role="menu"
style="flex: 1; min-width: 0;"
tabindex="0"
>
<li
@ -4239,7 +4228,7 @@ exports[`renders components/layout/demo/top.tsx extend context correctly 1`] = `
</header>
<main
class="ant-layout-content"
style="padding: 0px 50px;"
style="padding: 0px 48px;"
>
<nav
class="ant-breadcrumb"
@ -4282,8 +4271,7 @@ exports[`renders components/layout/demo/top.tsx extend context correctly 1`] = `
</ol>
</nav>
<div
class="site-layout-content"
style="background: rgb(255, 255, 255);"
style="background: rgb(255, 255, 255); min-height: 280px; padding: 24px; border-radius: 8px;"
>
Content
</div>
@ -4318,6 +4306,7 @@ exports[`renders components/layout/demo/top-side.tsx extend context correctly 1`
class="ant-menu-overflow ant-menu ant-menu-root ant-menu-horizontal ant-menu-dark"
data-menu-list="true"
role="menu"
style="flex: 1; min-width: 0;"
tabindex="0"
>
<li
@ -4518,7 +4507,7 @@ exports[`renders components/layout/demo/top-side.tsx extend context correctly 1`
</header>
<main
class="ant-layout-content"
style="padding: 0px 50px;"
style="padding: 0px 48px;"
>
<nav
class="ant-breadcrumb"
@ -4562,7 +4551,7 @@ exports[`renders components/layout/demo/top-side.tsx extend context correctly 1`
</nav>
<div
class="ant-layout ant-layout-has-sider"
style="padding: 24px 0px; background: rgb(255, 255, 255);"
style="padding: 24px 0px; background: rgb(255, 255, 255); border-radius: 8px;"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
@ -5480,6 +5469,7 @@ exports[`renders components/layout/demo/top-side-2.tsx extend context correctly
class="ant-menu-overflow ant-menu ant-menu-root ant-menu-horizontal ant-menu-dark"
data-menu-list="true"
role="menu"
style="flex: 1; min-width: 0;"
tabindex="0"
>
<li
@ -6605,7 +6595,7 @@ exports[`renders components/layout/demo/top-side-2.tsx extend context correctly
</nav>
<main
class="ant-layout-content"
style="padding: 24px; margin: 0px; min-height: 280px; background: rgb(255, 255, 255);"
style="padding: 24px; margin: 0px; min-height: 280px; background: rgb(255, 255, 255); border-radius: 8px;"
>
Content
</main>

View File

@ -2,124 +2,47 @@
exports[`renders components/layout/demo/basic.tsx correctly 1`] = `
<div
class="ant-space ant-space-vertical"
style="row-gap:48px;width:100%"
class="ant-flex ant-flex-wrap-wrap ant-flex-gap-middle"
>
<div
class="ant-space-item"
class="ant-layout"
style="border-radius:8px;overflow:hidden;width:calc(50% - 8px);max-width:calc(50% - 8px)"
>
<div
class="ant-layout"
<header
class="ant-layout-header"
style="text-align:center;color:#fff;height:64px;padding-inline:48px;line-height:64px;background-color:#4096ff"
>
<header
class="ant-layout-header"
style="text-align:center;color:#fff;height:64px;padding-inline:50px;line-height:64px;background-color:#7dbcea"
>
Header
</header>
<main
class="ant-layout-content"
style="text-align:center;min-height:120px;line-height:120px;color:#fff;background-color:#108ee9"
>
Content
</main>
<footer
class="ant-layout-footer"
style="text-align:center;color:#fff;background-color:#7dbcea"
>
Footer
</footer>
</div>
Header
</header>
<main
class="ant-layout-content"
style="text-align:center;min-height:120px;line-height:120px;color:#fff;background-color:#0958d9"
>
Content
</main>
<footer
class="ant-layout-footer"
style="text-align:center;color:#fff;background-color:#4096ff"
>
Footer
</footer>
</div>
<div
class="ant-space-item"
class="ant-layout"
style="border-radius:8px;overflow:hidden;width:calc(50% - 8px);max-width:calc(50% - 8px)"
>
<div
class="ant-layout"
<header
class="ant-layout-header"
style="text-align:center;color:#fff;height:64px;padding-inline:48px;line-height:64px;background-color:#4096ff"
>
<header
class="ant-layout-header"
style="text-align:center;color:#fff;height:64px;padding-inline:50px;line-height:64px;background-color:#7dbcea"
>
Header
</header>
<div
class="ant-layout ant-layout-has-sider"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="text-align:center;line-height:120px;color:#fff;background-color:#3ba0e9;flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
<div
class="ant-layout-sider-children"
>
Sider
</div>
</aside>
<main
class="ant-layout-content"
style="text-align:center;min-height:120px;line-height:120px;color:#fff;background-color:#108ee9"
>
Content
</main>
</div>
<footer
class="ant-layout-footer"
style="text-align:center;color:#fff;background-color:#7dbcea"
>
Footer
</footer>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-layout"
>
<header
class="ant-layout-header"
style="text-align:center;color:#fff;height:64px;padding-inline:50px;line-height:64px;background-color:#7dbcea"
>
Header
</header>
<div
class="ant-layout ant-layout-has-sider"
>
<main
class="ant-layout-content"
style="text-align:center;min-height:120px;line-height:120px;color:#fff;background-color:#108ee9"
>
Content
</main>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="text-align:center;line-height:120px;color:#fff;background-color:#3ba0e9;flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
<div
class="ant-layout-sider-children"
>
Sider
</div>
</aside>
</div>
<footer
class="ant-layout-footer"
style="text-align:center;color:#fff;background-color:#7dbcea"
>
Footer
</footer>
</div>
</div>
<div
class="ant-space-item"
>
Header
</header>
<div
class="ant-layout ant-layout-has-sider"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="text-align:center;line-height:120px;color:#fff;background-color:#3ba0e9;flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
style="text-align:center;line-height:120px;color:#fff;background-color:#1677ff;flex:0 0 25%;max-width:25%;min-width:25%;width:25%"
>
<div
class="ant-layout-sider-children"
@ -127,28 +50,92 @@ exports[`renders components/layout/demo/basic.tsx correctly 1`] = `
Sider
</div>
</aside>
<div
class="ant-layout"
<main
class="ant-layout-content"
style="text-align:center;min-height:120px;line-height:120px;color:#fff;background-color:#0958d9"
>
<header
class="ant-layout-header"
style="text-align:center;color:#fff;height:64px;padding-inline:50px;line-height:64px;background-color:#7dbcea"
Content
</main>
</div>
<footer
class="ant-layout-footer"
style="text-align:center;color:#fff;background-color:#4096ff"
>
Footer
</footer>
</div>
<div
class="ant-layout"
style="border-radius:8px;overflow:hidden;width:calc(50% - 8px);max-width:calc(50% - 8px)"
>
<header
class="ant-layout-header"
style="text-align:center;color:#fff;height:64px;padding-inline:48px;line-height:64px;background-color:#4096ff"
>
Header
</header>
<div
class="ant-layout ant-layout-has-sider"
>
<main
class="ant-layout-content"
style="text-align:center;min-height:120px;line-height:120px;color:#fff;background-color:#0958d9"
>
Content
</main>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="text-align:center;line-height:120px;color:#fff;background-color:#1677ff;flex:0 0 25%;max-width:25%;min-width:25%;width:25%"
>
<div
class="ant-layout-sider-children"
>
Header
</header>
<main
class="ant-layout-content"
style="text-align:center;min-height:120px;line-height:120px;color:#fff;background-color:#108ee9"
>
Content
</main>
<footer
class="ant-layout-footer"
style="text-align:center;color:#fff;background-color:#7dbcea"
>
Footer
</footer>
Sider
</div>
</aside>
</div>
<footer
class="ant-layout-footer"
style="text-align:center;color:#fff;background-color:#4096ff"
>
Footer
</footer>
</div>
<div
class="ant-layout ant-layout-has-sider"
style="border-radius:8px;overflow:hidden;width:calc(50% - 8px);max-width:calc(50% - 8px)"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="text-align:center;line-height:120px;color:#fff;background-color:#1677ff;flex:0 0 25%;max-width:25%;min-width:25%;width:25%"
>
<div
class="ant-layout-sider-children"
>
Sider
</div>
</aside>
<div
class="ant-layout"
>
<header
class="ant-layout-header"
style="text-align:center;color:#fff;height:64px;padding-inline:48px;line-height:64px;background-color:#4096ff"
>
Header
</header>
<main
class="ant-layout-content"
style="text-align:center;min-height:120px;line-height:120px;color:#fff;background-color:#0958d9"
>
Content
</main>
<footer
class="ant-layout-footer"
style="text-align:center;color:#fff;background-color:#4096ff"
>
Footer
</footer>
</div>
</div>
</div>
@ -418,7 +405,7 @@ exports[`renders components/layout/demo/component-token.tsx correctly 1`] = `
</nav>
<main
class="ant-layout-content"
style="padding:24px;margin:0;min-height:280px;background:#ffffff"
style="padding:24px;margin:0;min-height:280px;background:#f5f5f5;border-radius:8px"
>
Content
</main>
@ -586,7 +573,7 @@ exports[`renders components/layout/demo/custom-trigger.tsx correctly 1`] = `
</header>
<main
class="ant-layout-content"
style="margin:24px 16px;padding:24px;min-height:280px;background:#ffffff"
style="margin:24px 16px;padding:24px;min-height:280px;background:#ffffff;border-radius:8px"
>
Content
</main>
@ -609,6 +596,7 @@ exports[`renders components/layout/demo/fixed.tsx correctly 1`] = `
class="ant-menu-overflow ant-menu ant-menu-root ant-menu-horizontal ant-menu-dark"
data-menu-list="true"
role="menu"
style="flex:1;min-width:0"
tabindex="0"
>
<li
@ -691,8 +679,8 @@ exports[`renders components/layout/demo/fixed.tsx correctly 1`] = `
/>
</header>
<main
class="ant-layout-content site-layout"
style="padding:0 50px"
class="ant-layout-content"
style="padding:0 48px"
>
<nav
class="ant-breadcrumb"
@ -735,7 +723,7 @@ exports[`renders components/layout/demo/fixed.tsx correctly 1`] = `
</ol>
</nav>
<div
style="padding:24px;min-height:380px;background:#ffffff"
style="padding:24px;min-height:380px;background:#ffffff;border-radius:8px"
>
Content
</div>
@ -1025,7 +1013,7 @@ exports[`renders components/layout/demo/fixed-sider.tsx correctly 1`] = `
</div>
</aside>
<div
class="ant-layout site-layout"
class="ant-layout"
style="margin-left:200px"
>
<header
@ -1037,7 +1025,7 @@ exports[`renders components/layout/demo/fixed-sider.tsx correctly 1`] = `
style="margin:24px 16px 0;overflow:initial"
>
<div
style="padding:24px;text-align:center;background:#ffffff"
style="padding:24px;text-align:center;background:#ffffff;border-radius:8px"
>
<p>
long content
@ -1417,7 +1405,7 @@ exports[`renders components/layout/demo/responsive.tsx correctly 1`] = `
style="margin:24px 16px 0"
>
<div
style="padding:24px;min-height:360px;background:#ffffff"
style="padding:24px;min-height:360px;background:#ffffff;border-radius:8px"
>
content
</div>
@ -1698,7 +1686,7 @@ exports[`renders components/layout/demo/side.tsx correctly 1`] = `
</ol>
</nav>
<div
style="padding:24px;min-height:360px;background:#ffffff"
style="padding:24px;min-height:360px;background:#ffffff;border-radius:8px"
>
Bill is a cat.
</div>
@ -1715,7 +1703,7 @@ exports[`renders components/layout/demo/side.tsx correctly 1`] = `
exports[`renders components/layout/demo/top.tsx correctly 1`] = `
<div
class="ant-layout layout"
class="ant-layout"
>
<header
class="ant-layout-header"
@ -1728,6 +1716,7 @@ exports[`renders components/layout/demo/top.tsx correctly 1`] = `
class="ant-menu-overflow ant-menu ant-menu-root ant-menu-horizontal ant-menu-dark"
data-menu-list="true"
role="menu"
style="flex:1;min-width:0"
tabindex="0"
>
<li
@ -1955,7 +1944,7 @@ exports[`renders components/layout/demo/top.tsx correctly 1`] = `
</header>
<main
class="ant-layout-content"
style="padding:0 50px"
style="padding:0 48px"
>
<nav
class="ant-breadcrumb"
@ -1998,8 +1987,7 @@ exports[`renders components/layout/demo/top.tsx correctly 1`] = `
</ol>
</nav>
<div
class="site-layout-content"
style="background:#ffffff"
style="background:#ffffff;min-height:280px;padding:24px;border-radius:8px"
>
Content
</div>
@ -2028,6 +2016,7 @@ exports[`renders components/layout/demo/top-side.tsx correctly 1`] = `
class="ant-menu-overflow ant-menu ant-menu-root ant-menu-horizontal ant-menu-dark"
data-menu-list="true"
role="menu"
style="flex:1;min-width:0"
tabindex="0"
>
<li
@ -2111,7 +2100,7 @@ exports[`renders components/layout/demo/top-side.tsx correctly 1`] = `
</header>
<main
class="ant-layout-content"
style="padding:0 50px"
style="padding:0 48px"
>
<nav
class="ant-breadcrumb"
@ -2155,7 +2144,7 @@ exports[`renders components/layout/demo/top-side.tsx correctly 1`] = `
</nav>
<div
class="ant-layout ant-layout-has-sider"
style="padding:24px 0;background:#ffffff"
style="padding:24px 0;background:#ffffff;border-radius:8px"
>
<aside
class="ant-layout-sider ant-layout-sider-dark"
@ -2387,6 +2376,7 @@ exports[`renders components/layout/demo/top-side-2.tsx correctly 1`] = `
class="ant-menu-overflow ant-menu ant-menu-root ant-menu-horizontal ant-menu-dark"
data-menu-list="true"
role="menu"
style="flex:1;min-width:0"
tabindex="0"
>
<li
@ -2715,7 +2705,7 @@ exports[`renders components/layout/demo/top-side-2.tsx correctly 1`] = `
</nav>
<main
class="ant-layout-content"
style="padding:24px;margin:0;min-height:280px;background:#ffffff"
style="padding:24px;margin:0;min-height:280px;background:#ffffff;border-radius:8px"
>
Content
</main>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Layout, Space } from 'antd';
import { Layout, Flex } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
@ -7,9 +7,9 @@ const headerStyle: React.CSSProperties = {
textAlign: 'center',
color: '#fff',
height: 64,
paddingInline: 50,
paddingInline: 48,
lineHeight: '64px',
backgroundColor: '#7dbcea',
backgroundColor: '#4096ff',
};
const contentStyle: React.CSSProperties = {
@ -17,54 +17,70 @@ const contentStyle: React.CSSProperties = {
minHeight: 120,
lineHeight: '120px',
color: '#fff',
backgroundColor: '#108ee9',
backgroundColor: '#0958d9',
};
const siderStyle: React.CSSProperties = {
textAlign: 'center',
lineHeight: '120px',
color: '#fff',
backgroundColor: '#3ba0e9',
backgroundColor: '#1677ff',
};
const footerStyle: React.CSSProperties = {
textAlign: 'center',
color: '#fff',
backgroundColor: '#7dbcea',
backgroundColor: '#4096ff',
};
const layoutStyle = {
borderRadius: 8,
overflow: 'hidden',
width: 'calc(50% - 8px)',
maxWidth: 'calc(50% - 8px)',
};
const App: React.FC = () => (
<Space direction="vertical" style={{ width: '100%' }} size={[0, 48]}>
<Layout>
<Flex gap="middle" wrap="wrap">
<Layout style={layoutStyle}>
<Header style={headerStyle}>Header</Header>
<Content style={contentStyle}>Content</Content>
<Footer style={footerStyle}>Footer</Footer>
</Layout>
<Layout>
<Layout style={layoutStyle}>
<Header style={headerStyle}>Header</Header>
<Layout hasSider>
<Sider style={siderStyle}>Sider</Sider>
<Layout>
<Sider width="25%" style={siderStyle}>
Sider
</Sider>
<Content style={contentStyle}>Content</Content>
</Layout>
<Footer style={footerStyle}>Footer</Footer>
</Layout>
<Layout>
<Layout style={layoutStyle}>
<Header style={headerStyle}>Header</Header>
<Layout hasSider>
<Layout>
<Content style={contentStyle}>Content</Content>
<Sider style={siderStyle}>Sider</Sider>
<Sider width="25%" style={siderStyle}>
Sider
</Sider>
</Layout>
<Footer style={footerStyle}>Footer</Footer>
</Layout>
<Layout>
<Sider style={siderStyle}>Sider</Sider>
<Layout style={layoutStyle}>
<Sider width="25%" style={siderStyle}>
Sider
</Sider>
<Layout>
<Header style={headerStyle}>Header</Header>
<Content style={contentStyle}>Content</Content>
<Footer style={footerStyle}>Footer</Footer>
</Layout>
</Layout>
</Space>
</Flex>
);
export default App;

View File

@ -27,7 +27,7 @@ const items2: MenuProps['items'] = [UserOutlined, LaptopOutlined, NotificationOu
const App: React.FC = () => {
const {
token: { colorBgContainer },
token: { colorBgContainer, colorBgLayout, borderRadiusLG },
} = theme.useToken();
return (
@ -39,8 +39,8 @@ const App: React.FC = () => {
headerBg: '#1677ff',
headerHeight: 64,
headerPadding: `0 24px`,
headerColor: '#fff',
siderBg: '#fff',
headerColor: colorBgContainer,
siderBg: colorBgContainer,
},
},
}}
@ -71,7 +71,8 @@ const App: React.FC = () => {
padding: 24,
margin: 0,
minHeight: 280,
background: colorBgContainer,
background: colorBgLayout,
borderRadius: borderRadiusLG,
}}
>
Content

View File

@ -67,7 +67,7 @@ const items: MenuProps['items'] = [
const App: React.FC = () => {
const [collapsed, setCollapsed] = useState(true);
const {
token: { colorBgContainer },
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
@ -101,6 +101,7 @@ const App: React.FC = () => {
padding: 24,
minHeight: 280,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
Content

View File

@ -13,7 +13,7 @@ const { Header, Sider, Content } = Layout;
const App: React.FC = () => {
const [collapsed, setCollapsed] = useState(false);
const {
token: { colorBgContainer },
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
@ -62,6 +62,7 @@ const App: React.FC = () => {
padding: 24,
minHeight: 280,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
Content

View File

@ -31,7 +31,7 @@ const items: MenuProps['items'] = [
const App: React.FC = () => {
const {
token: { colorBgContainer },
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
@ -49,10 +49,17 @@ const App: React.FC = () => {
<div className="demo-logo-vertical" />
<Menu theme="dark" mode="inline" defaultSelectedKeys={['4']} items={items} />
</Sider>
<Layout className="site-layout" style={{ marginLeft: 200 }}>
<Layout style={{ marginLeft: 200 }}>
<Header style={{ padding: 0, background: colorBgContainer }} />
<Content style={{ margin: '24px 16px 0', overflow: 'initial' }}>
<div style={{ padding: 24, textAlign: 'center', background: colorBgContainer }}>
<div
style={{
padding: 24,
textAlign: 'center',
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
<p>long content</p>
{
// indicates very long content

View File

@ -3,9 +3,14 @@ import { Breadcrumb, Layout, Menu, theme } from 'antd';
const { Header, Content, Footer } = Layout;
const items = new Array(3).fill(null).map((_, index) => ({
key: String(index + 1),
label: `nav ${index + 1}`,
}));
const App: React.FC = () => {
const {
token: { colorBgContainer },
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
@ -25,19 +30,26 @@ const App: React.FC = () => {
theme="dark"
mode="horizontal"
defaultSelectedKeys={['2']}
items={new Array(3).fill(null).map((_, index) => ({
key: String(index + 1),
label: `nav ${index + 1}`,
}))}
items={items}
style={{ flex: 1, minWidth: 0 }}
/>
</Header>
<Content className="site-layout" style={{ padding: '0 50px' }}>
<Content style={{ padding: '0 48px' }}>
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item>Home</Breadcrumb.Item>
<Breadcrumb.Item>List</Breadcrumb.Item>
<Breadcrumb.Item>App</Breadcrumb.Item>
</Breadcrumb>
<div style={{ padding: 24, minHeight: 380, background: colorBgContainer }}>Content</div>
<div
style={{
padding: 24,
minHeight: 380,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
Content
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>Ant Design ©2023 Created by Ant UED</Footer>
</Layout>

View File

@ -4,9 +4,17 @@ import { Layout, Menu, theme } from 'antd';
const { Header, Content, Footer, Sider } = Layout;
const items = [UserOutlined, VideoCameraOutlined, UploadOutlined, UserOutlined].map(
(icon, index) => ({
key: String(index + 1),
icon: React.createElement(icon),
label: `nav ${index + 1}`,
}),
);
const App: React.FC = () => {
const {
token: { colorBgContainer },
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
@ -22,23 +30,21 @@ const App: React.FC = () => {
}}
>
<div className="demo-logo-vertical" />
<Menu
theme="dark"
mode="inline"
defaultSelectedKeys={['4']}
items={[UserOutlined, VideoCameraOutlined, UploadOutlined, UserOutlined].map(
(icon, index) => ({
key: String(index + 1),
icon: React.createElement(icon),
label: `nav ${index + 1}`,
}),
)}
/>
<Menu theme="dark" mode="inline" defaultSelectedKeys={['4']} items={items} />
</Sider>
<Layout>
<Header style={{ padding: 0, background: colorBgContainer }} />
<Content style={{ margin: '24px 16px 0' }}>
<div style={{ padding: 24, minHeight: 360, background: colorBgContainer }}>content</div>
<div
style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
content
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>Ant Design ©2023 Created by Ant UED</Footer>
</Layout>

View File

@ -42,7 +42,7 @@ const items: MenuItem[] = [
const App: React.FC = () => {
const [collapsed, setCollapsed] = useState(false);
const {
token: { colorBgContainer },
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
@ -58,7 +58,14 @@ const App: React.FC = () => {
<Breadcrumb.Item>User</Breadcrumb.Item>
<Breadcrumb.Item>Bill</Breadcrumb.Item>
</Breadcrumb>
<div style={{ padding: 24, minHeight: 360, background: colorBgContainer }}>
<div
style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
Bill is a cat.
</div>
</Content>

View File

@ -32,14 +32,20 @@ const items2: MenuProps['items'] = [UserOutlined, LaptopOutlined, NotificationOu
const App: React.FC = () => {
const {
token: { colorBgContainer },
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
<Layout>
<Header style={{ display: 'flex', alignItems: 'center' }}>
<div className="demo-logo" />
<Menu theme="dark" mode="horizontal" defaultSelectedKeys={['2']} items={items1} />
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['2']}
items={items1}
style={{ flex: 1, minWidth: 0 }}
/>
</Header>
<Layout>
<Sider width={200} style={{ background: colorBgContainer }}>
@ -63,6 +69,7 @@ const App: React.FC = () => {
margin: 0,
minHeight: 280,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
Content

View File

@ -32,22 +32,30 @@ const items2: MenuProps['items'] = [UserOutlined, LaptopOutlined, NotificationOu
const App: React.FC = () => {
const {
token: { colorBgContainer },
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
<Layout>
<Header style={{ display: 'flex', alignItems: 'center' }}>
<div className="demo-logo" />
<Menu theme="dark" mode="horizontal" defaultSelectedKeys={['2']} items={items1} />
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['2']}
items={items1}
style={{ flex: 1, minWidth: 0 }}
/>
</Header>
<Content style={{ padding: '0 50px' }}>
<Content style={{ padding: '0 48px' }}>
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item>Home</Breadcrumb.Item>
<Breadcrumb.Item>List</Breadcrumb.Item>
<Breadcrumb.Item>App</Breadcrumb.Item>
</Breadcrumb>
<Layout style={{ padding: '24px 0', background: colorBgContainer }}>
<Layout
style={{ padding: '24px 0', background: colorBgContainer, borderRadius: borderRadiusLG }}
>
<Sider style={{ background: colorBgContainer }} width={200}>
<Menu
mode="inline"

View File

@ -3,35 +3,42 @@ import { Breadcrumb, Layout, Menu, theme } from 'antd';
const { Header, Content, Footer } = Layout;
const items = new Array(15).fill(null).map((_, index) => ({
key: index + 1,
label: `nav ${index + 1}`,
}));
const App: React.FC = () => {
const {
token: { colorBgContainer },
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
<Layout className="layout">
<Layout>
<Header style={{ display: 'flex', alignItems: 'center' }}>
<div className="demo-logo" />
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['2']}
items={new Array(15).fill(null).map((_, index) => {
const key = index + 1;
return {
key,
label: `nav ${key}`,
};
})}
items={items}
style={{ flex: 1, minWidth: 0 }}
/>
</Header>
<Content style={{ padding: '0 50px' }}>
<Content style={{ padding: '0 48px' }}>
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item>Home</Breadcrumb.Item>
<Breadcrumb.Item>List</Breadcrumb.Item>
<Breadcrumb.Item>App</Breadcrumb.Item>
</Breadcrumb>
<div className="site-layout-content" style={{ background: colorBgContainer }}>
<div
style={{
background: colorBgContainer,
minHeight: 280,
padding: 24,
borderRadius: borderRadiusLG,
}}
>
Content
</div>
</Content>

View File

@ -73,4 +73,10 @@ describe('Spin', () => {
errSpy.mockRestore();
});
it('right style when fullscreen', () => {
const { container } = render(<Spin fullscreen spinning />);
const element = container.querySelector<HTMLDivElement>('.ant-spin.ant-spin-fullscreen');
expect(element).not.toHaveStyle({ pointerEvents: 'none' });
});
});

View File

@ -2787,9 +2787,7 @@ exports[`renders components/tabs/demo/icon.tsx correctly 1`] = `
</span>
</span>
<span>
Tab
<!-- -->
1
Tab 1
</span>
</div>
</div>
@ -2827,9 +2825,7 @@ exports[`renders components/tabs/demo/icon.tsx correctly 1`] = `
</span>
</span>
<span>
Tab
<!-- -->
2
Tab 2
</span>
</div>
</div>

View File

@ -8,8 +8,8 @@ const App: React.FC = () => (
items={[AppleOutlined, AndroidOutlined].map((Icon, i) => {
const id = String(i + 1);
return {
label: <span>Tab {id}</span>,
key: id,
label: `Tab ${id}`,
children: `Tab ${id}`,
icon: <Icon />,
};

View File

@ -60,7 +60,6 @@ const App: React.FC = () => {
<Option value="card">Child - card</Option>
<Option value="editable-card">Parent - card edit</Option>
</Select>
<Tabs
defaultActiveKey="1"
tabPosition={parentPos}
@ -77,7 +76,6 @@ const App: React.FC = () => {
style={{ height: 300 }}
items={new Array(20).fill(null).map((_, index) => {
const key = String(index);
return {
label: `Tab ${key}`,
key,

View File

@ -25,9 +25,7 @@ To enable CSS variable mode, use the `cssVar` configuration in the `theme` prope
<!-- prettier-ignore -->
:::warning
CSS variable mode requires a unique key for each theme to ensure style isolation.
In React 18, we use `useId` to generate unique keys by default, so you don't have to worry about this issue in React 18.
But in React 17 or 16, you need to manually set a unique key for each theme, otherwise the themes will be mixed up.
CSS variable mode requires a unique key for each theme to ensure style isolation. In React 18, we use `useId` to generate unique keys by default, so you don't have to worry about this issue in React 18. But in React 17 or 16, you need to manually set a unique key for each theme, otherwise the themes will be mixed up.
:::
```tsx

View File

@ -25,8 +25,7 @@ CSS 变量模式为 Ant Design 的样式能力带来了两个重要的提升:
<!-- prettier-ignore -->
:::warning
CSS 变量模式需要为每一个主题设置独特的 key 来保证样式隔离,在 React 18 中我们使用了 `useId` 来生成唯一的 key所以在 React 18 中,你可以不用关心这个问题。
但是在 React 17 或者 16 中,你需要手动为每一个主题设置一个唯一的 key否则会导致主题混乱。
CSS 变量模式需要为每一个主题设置独特的 key 来保证样式隔离,在 React 18 中我们使用了 `useId` 来生成唯一的 key所以在 React 18 中,你可以不用关心这个问题。但是在 React 17 或者 16 中,你需要手动为每一个主题设置一个唯一的 key否则会导致主题混乱。
:::
```tsx

View File

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "5.12.1",
"version": "5.12.2",
"description": "An enterprise-class UI design language and React components implementation",
"keywords": [
"ant",
@ -99,6 +99,7 @@
"token-meta": "tsx scripts/generate-token-meta.ts",
"tsc": "tsc --noEmit",
"version": "tsx scripts/generate-version.ts",
"visual-regression": "tsx scripts/visual-regression/build.ts",
"npm-install": "npm install"
},
"lint-staged": {
@ -113,7 +114,7 @@
],
"dependencies": {
"@ant-design/colors": "^7.0.0",
"@ant-design/cssinjs": "^1.18.0",
"@ant-design/cssinjs": "^1.18.1",
"@ant-design/icons": "^5.2.6",
"@ant-design/react-slick": "~1.0.2",
"@babel/runtime": "^7.23.4",
@ -140,7 +141,7 @@
"rc-menu": "~9.12.4",
"rc-motion": "^2.9.0",
"rc-notification": "~5.3.0",
"rc-pagination": "~4.0.1",
"rc-pagination": "~4.0.3",
"rc-picker": "~3.14.6",
"rc-progress": "~3.5.1",
"rc-rate": "~2.12.0",
@ -199,6 +200,8 @@
"@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",
@ -207,6 +210,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",
@ -266,10 +270,14 @@
"lodash": "^4.17.21",
"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.1.0",
"pixelmatch": "^5.3.0",
"pngjs": "^7.0.0",
"prettier": "^3.1.0",
"prettier-plugin-jsdoc": "^1.1.1",
"pretty-format": "^29.7.0",
@ -294,23 +302,28 @@
"regenerator-runtime": "^0.14.0",
"remark": "^15.0.1",
"remark-cli": "^12.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.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": "^15.11.0",
"stylelint": "^16.0.0",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^34.0.0",
"stylelint-prettier": "^4.1.0",
"stylelint-config-standard": "^35.0.0",
"stylelint-prettier": "^5.0.0",
"sylvanas": "^0.6.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.19.0",
"vanilla-jsoneditor": "^0.20.0",
"vanilla-tilt": "^1.8.1",
"webpack": "^5.89.0",
"webpack-bundle-analyzer": "^4.10.1",
@ -328,7 +341,7 @@
"dumi": "^2.3.0-alpha.4"
}
},
"packageManager": "npm@10.2.4",
"packageManager": "npm@10.2.5",
"size-limit": [
{
"path": "./dist/antd.min.js",

22
scripts/tsconfig.json Normal file
View File

@ -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"]
}

View File

@ -0,0 +1,308 @@
/* eslint-disable compat/compat */
/* eslint-disable no-console, no-await-in-loop, import/no-extraneous-dependencies, lodash/import-scope, no-restricted-syntax */
import path from 'path';
import fs from 'fs';
import os from 'os';
import { Readable } from 'stream';
import { finished } from 'stream/promises';
import { remark } from 'remark';
import remarkHtml from 'remark-html';
import remarkGfm from 'remark-gfm';
import minimist from 'minimist';
import tar from 'tar';
import fse from 'fs-extra';
import chalk from 'chalk';
import _ from 'lodash';
import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';
import sharp from 'sharp';
import { assert } from 'console';
const ALI_OSS_BUCKET = 'antd-visual-diff';
const compareScreenshots = async (
baseImgPath: string,
currentImgPath: string,
diffImagePath: string,
): Promise<number> => {
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)) * 100;
};
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;
}
function md2Html(md: string) {
return remark().use(remarkGfm).use(remarkHtml).processSync(md).toString();
}
function generateReport(
badCases: IBadCase[],
targetBranch: string,
targetRef: string,
): [string, string] {
// parse args from -- --pr-id=123
const argv = minimist(process.argv.slice(2));
const prId = argv['pr-id'];
assert(prId, 'Missing --pr-id');
const publicPath = `${ossDomain}/pr-${prId}`;
const commonHeader = `
## Visual Regression Report for PR #${prId}
> **Target branch:** ${targetBranch} (${targetRef})
`.trim();
if (badCases.length === 0) {
const mdStr = [
commonHeader,
'------------------------',
'Congrats! No visual-regression diff found',
].join('\n');
return [mdStr, md2Html(mdStr)];
}
const htmlReportLink = `${publicPath}/visualRegressionReport/report.html`;
const addonFullReportDesc = `\n\nToo many visual-regression diffs found, please check [Full Report](${htmlReportLink}) for details`;
// github action pr comment has limit of 65536 4-byte unicode characters
const limit = 65536 - addonFullReportDesc.length;
let reportMdStr = `
${commonHeader}
> [View Full Report](${htmlReportLink})\n
------------------------
| image name | expected | actual | diff |
| --- | --- | --- | --- |
`.trim();
reportMdStr += '\n';
let fullVersionMd = reportMdStr;
let addonFullReportDescAdded = false;
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';
}
if (lineReportMdStr) {
if (reportMdStr.length + lineReportMdStr.length < limit) {
reportMdStr += lineReportMdStr;
} else if (!addonFullReportDescAdded) {
reportMdStr += addonFullReportDesc;
addonFullReportDescAdded = true;
}
fullVersionMd += lineReportMdStr;
}
}
// convert fullVersionMd to html
return [reportMdStr, md2Html(fullVersionMd)];
}
async function boot() {
console.log(chalk.green('Preparing image snapshots from latest `master` branch\n'));
const baseImgSourceDir = path.resolve(__dirname, '../../imageSnapshots-master');
await fse.ensureDir(baseImgSourceDir);
const targetBranch = 'master';
const targetRef = await getBranchLatestRef(targetBranch);
assert(targetRef, `Missing ref from ${targetBranch}`);
await downloadBaseSnapshots(targetRef, baseImgSourceDir);
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);
const currentImgFileList = readPngs(currentImgSourceDir);
const deletedImgs = _.difference(baseImgFileList, currentImgFileList);
if (deletedImgs.length) {
console.log(chalk.red('⛔️ Missing images compare to master:\n'), prettyList(deletedImgs));
console.log('\n');
}
// ignore new images
const newImgs = _.difference(currentImgFileList, baseImgFileList);
if (newImgs.length) {
console.log(chalk.green('🆕 Added images:\n'), prettyList(newImgs));
console.log('\n');
}
const badCases: IBadCase[] = [];
for (const file of baseImgFileList) {
const baseImgPath = path.join(baseImgSourceDir, file);
const currentImgPath = path.join(currentImgSourceDir, file);
const diffImgPath = path.join(diffImgReportDir, file);
const currentImgExists = await fse.exists(currentImgPath);
if (!currentImgExists) {
console.log(chalk.red(`⛔️ Missing image: ${file}\n`));
badCases.push({
type: 'removed',
filename: file,
});
await fse.copy(baseImgPath, path.join(baseImgReportDir, file));
continue;
}
const mismatchedPxPercent = await compareScreenshots(baseImgPath, currentImgPath, diffImgPath);
if (mismatchedPxPercent > 0) {
console.log(
'Mismatched pixels for:',
chalk.yellow(file),
`${mismatchedPxPercent.toFixed(2)}%\n`,
);
// copy compare imgs(x2) to report dir
await fse.copy(baseImgPath, path.join(baseImgReportDir, file));
await fse.copy(currentImgPath, path.join(currentImgReportDir, file));
badCases.push({
type: 'changed',
filename: file,
});
} else {
console.log('Passed for: %s\n', chalk.green(file));
}
}
if (badCases.length) {
console.log(chalk.red('⛔️ Failed cases:\n'), prettyList(badCases.map((i) => i.filename)));
console.log('\n');
}
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, targetRef);
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),
);
}
boot();

View File

@ -0,0 +1,74 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link href="https://unpkg.com/antd@latest/dist/reset.css" rel="stylesheet" />
<style>
body {
padding-top: 16px;
padding-bottom: 16px;
}
/* Table Styles */
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
/* Hover Effect */
tr:hover {
background-color: #e5e5e5;
}
/* Responsive Table */
@media screen and (max-width: 600px) and (min-width: 1px) {
table {
border: 0;
}
th,
td {
display: block;
padding: 6px;
border: none;
}
th {
text-align: left;
background-color: transparent;
}
tr:nth-child(even) {
background-color: transparent;
}
tr:nth-child(odd) {
background-color: transparent;
}
tr:hover {
background-color: transparent;
}
}
</style>
</head>
<body>
{{reportContent}}
</body>
</html>

View File

@ -6,17 +6,19 @@
const OSS = require('ali-oss');
const path = require('path');
const fs = require('fs');
const { assert } = require('console');
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
// 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 <tarFilePath> --ref=<refValue>');
console.error('Usage: node scripts/visual-regression/upload.js <tarFilePath> --ref=<refValue>');
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
@ -99,7 +101,7 @@ async function boot() {
endpoint: 'oss-cn-shanghai.aliyuncs.com',
accessKeyId: process.env.ALI_OSS_AK_ID,
accessKeySecret: process.env.ALI_OSS_AK_SECRET,
bucket: process.env.ALI_OSS_BUCKET,
bucket: ALI_OSS_BUCKET,
});
// if is a file then upload it directly

View File

@ -35,7 +35,7 @@ export default function imageTest(
identifier: string,
options: ImageTestOptions,
) {
function test(name: string, themedComponent: React.ReactElement) {
function test(name: string, suffix: string, themedComponent: React.ReactElement) {
it(name, async () => {
await jestPuppeteer.resetPage();
await page.setRequestInterception(true);
@ -86,7 +86,7 @@ export default function imageTest(
});
expect(image).toMatchImageSnapshot({
customSnapshotIdentifier: `${identifier}-${name.replace(/\s/g, '-')}`,
customSnapshotIdentifier: `${identifier}${suffix}`,
});
MockDate.reset();
@ -98,12 +98,14 @@ export default function imageTest(
Object.entries(themes).forEach(([key, algorithm]) => {
test(
`component image screenshot should correct ${key}`,
`-${key}`,
<div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}>
<ConfigProvider theme={{ algorithm }}>{component}</ConfigProvider>
</div>,
);
test(
`component image screenshot should correct ${key}.css-var`,
`[CSS Var] component image screenshot should correct ${key}`,
`-${key}.css-var`,
<div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}>
<div>CSS Var</div>
<ConfigProvider theme={{ algorithm, cssVar: true }}>{component}</ConfigProvider>
@ -113,6 +115,7 @@ export default function imageTest(
} else {
test(
`component image screenshot should correct`,
'',
<>
{Object.entries(themes).map(([key, algorithm]) => (
<div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}>
@ -122,7 +125,8 @@ export default function imageTest(
</>,
);
test(
`component image screenshot should correct.css-var`,
`[CSS Var] component image screenshot should correct`,
'.css-var',
<>
<div>CSS Var</div>
{Object.entries(themes).map(([key, algorithm]) => (

File diff suppressed because one or more lines are too long