Compare commits

...

23 Commits

Author SHA1 Message Date
afc163
e1dcf2fc74 Merge branch 'master' into copilot/fix-ci-issues
Signed-off-by: afc163 <afc163@gmail.com>
2026-02-06 13:39:03 +08:00
github-actions[bot]
8a16c49f5c chore: upgrade deps (#56877)
Co-authored-by: afc163 <507615+afc163@users.noreply.github.com>
Co-authored-by: thinkasany <480968828@qq.com>
2026-02-06 13:36:50 +08:00
二货爱吃白萝卜
bbb0683ab6 fix(button): add theme-aware preset color hover/active tokens (#56872)
* fix(button): add theme-aware preset color hover/active tokens

Add ${colorKey}Hover and ${colorKey}Active tokens that swap values based on dark/light mode for improved contrast and user experience.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: add preset color hover/active tokens for consistent button interaction

- Add xxxHover and xxxActive tokens for preset colors in genColorMapToken
- Override these tokens in dark mode to swap hover/active values
- Update ButtonToken type to include PresetColorHoverActiveMap
- Update button variant styles to use new hover/active tokens
- Fix #56656: button hover/active state inconsistency in dark mode

* test: add @csstools to compileModules

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: support .mjs files in Jest configuration

- Updated .jest.js transform pattern to include .mjs files
- Simplified .jest.node.js transform patterns
- Added jest-mjs-transformer.js for babel-jest mjs handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: remove unused jest-mjs-transformer.js

- Removed jest-mjs-transformer.js as it's no longer used
- .mjs files are now handled by the updated transform patterns in Jest configs

* test: add .mjs support to .jest.image.js

- Updated .jest.image.js transform pattern to include .mjs files
- fixes image test failures due to ES module parsing errors

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:10:41 +08:00
copilot-swe-agent[bot]
b8f06104ca fix: add mjs file extension support to node test config
Co-authored-by: afc163 <507615+afc163@users.noreply.github.com>
2026-02-06 02:39:17 +00:00
copilot-swe-agent[bot]
c5425a0812 fix: add @csstools to Jest transform whitelist to fix CI parse error
Co-authored-by: afc163 <507615+afc163@users.noreply.github.com>
2026-02-05 15:55:16 +00:00
copilot-swe-agent[bot]
e981eff147 Initial plan 2026-02-05 15:53:15 +00:00
二货爱吃白萝卜
99eb877828 test(modal): add mouseDown event before click in test (#56874)
* test(modal): add mouseDown event before click in test

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(modal): add mouseDown event before click in confirm test

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(modal): improve confirm test event firing and timer management

- Add mouseDown event before click for mask interaction
- Use fireEvent consistently instead of direct click()
- Properly setup and cleanup fake timers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 22:13:14 +08:00
二货爱吃白萝卜
9673185fbf chore: lock antd-img-crop to ~4.27.0 (#56869)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 15:20:44 +08:00
Wanpan
d2d124d305 fix(upload): add height to picture card style (#56864) 2026-02-05 09:55:48 +08:00
二货爱吃白萝卜
6e91b7f8af docs: Use direct dumi code demo instead of sandpack (#56862)
* docs(replace): replace sandpack demos with code blocks in customize-theme.en-US.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: update demo paths in theme customization docs

* docs: add first example demo and update documentation

* docs(demo): add Radio component to disable-motion example

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(demo): initialize timerRef with null for proper typing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 20:37:02 +08:00
Tarek BAZINE
d442e3e1a6 fix: Update reference URL in font specification (#56859)
* fix: Update reference URL in font specification

Signed-off-by: Tarek BAZINE <et_bazine@esi.dz>

* Update docs/spec/font.en-US.md

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: 二货爱吃白萝卜 <smith3816@gmail.com>

* Update references in font specification document

Signed-off-by: 二货爱吃白萝卜 <smith3816@gmail.com>

---------

Signed-off-by: Tarek BAZINE <et_bazine@esi.dz>
Signed-off-by: 二货爱吃白萝卜 <smith3816@gmail.com>
Co-authored-by: 二货爱吃白萝卜 <smith3816@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-04 17:46:35 +08:00
lijianan
1a789f1e5b refactor(cssinjs): simplify transition style generation (#56826)
* chore: code optimization

* update
2026-02-04 14:54:08 +08:00
dependabot[bot]
45d8f9a2d4 chore: bump @eslint-react/eslint-plugin from 2.9.3 to 2.9.4 (#56850)
Bumps [@eslint-react/eslint-plugin](https://github.com/Rel1cx/eslint-react/tree/HEAD/packages/plugins/eslint-plugin) from 2.9.3 to 2.9.4.
- [Release notes](https://github.com/Rel1cx/eslint-react/releases)
- [Changelog](https://github.com/Rel1cx/eslint-react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Rel1cx/eslint-react/commits/v2.9.4/packages/plugins/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@eslint-react/eslint-plugin"
  dependency-version: 2.9.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-04 09:11:56 +08:00
高艳兵
3810d1dc6a fix(button): make tokens take effect (#56842)
* refactor(button): rework component-token demo

* fix(button): linkHoverBg token not work

* fix(button): enable ghostBg for outlined and dashed ghost

* fix(button): make defaultBgDisabled take effect

* fix(button): apply paddingBlock tokens to vertical padding

* fix(button): apply contentLineHeight tokens to button line-height

* fix: token.defaultBgDisabled

* revert(button): drop contentLineHeight and paddingBlock changes

* chore(button): remove unused tokens from component-token demo
2026-02-03 14:18:27 +08:00
dependabot[bot]
ef32250465 chore: bump @eslint-react/eslint-plugin from 2.8.4 to 2.9.3 (#56843)
Bumps [@eslint-react/eslint-plugin](https://github.com/Rel1cx/eslint-react/tree/HEAD/packages/plugins/eslint-plugin) from 2.8.4 to 2.9.3.
- [Release notes](https://github.com/Rel1cx/eslint-react/releases)
- [Changelog](https://github.com/Rel1cx/eslint-react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Rel1cx/eslint-react/commits/v2.9.3/packages/plugins/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@eslint-react/eslint-plugin"
  dependency-version: 2.9.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: thinkasany <480968828@qq.com>
2026-02-03 11:25:31 +08:00
thinkasany
d71e9ffb0d chore: rm overrides deps (#56840)
* test

* test

* fix

* turn off ESLINT_CACHE

* rm
2026-02-03 10:55:29 +08:00
lijianan
955afb14f4 chore: update date (#56837)
* chore: update date

* update

* Update package.json

Signed-off-by: thinkasany <480968828@qq.com>

---------

Signed-off-by: thinkasany <480968828@qq.com>
Co-authored-by: thinkasany <480968828@qq.com>
2026-02-02 23:05:22 +08:00
thinkasany
443abf2ebe chore: ci failed (#56838)
Co-authored-by: 遇见同学 <1875694521@qq.com>
2026-02-02 23:04:44 +08:00
thinkasany
1931606b97 chore: bump jsdom@28 (#56835)
* chore: bump jsdom@28

* Update tests/setup.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: thinkasany <480968828@qq.com>

---------

Signed-off-by: thinkasany <480968828@qq.com>
Signed-off-by: meet-student <1875694521@qq.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-02 22:54:16 +08:00
thinkasany
ceddbda113 chore: lock @eslint-react/eslint-plugin 2.8.4 (#56836)
* chore: lock @eslint-react/eslint-plugin 2.9.1

* update

* fix

---------

Co-authored-by: 遇见同学 <1875694521@qq.com>
2026-02-02 22:38:07 +08:00
huangkevin-apr
41a5b2fe9e chore(a11y): add title element to search bar icon for screen readers (#56521)
* fix(a11y): add title element to search bar icon for screen readers

* Revert "fix(a10y): add title element to search bar icon for screen readers"

This reverts commit 9caf0bbc4e.

* fix: the svg elements has no accessible name

---------

Co-authored-by: thinkasany <480968828@qq.com>
2026-02-02 21:35:51 +08:00
二货爱吃白萝卜
08f23516a0 fix: antd umd build (#56830)
* chore: debug of ci

* chore: debug of ci

* chore: clean up

* use ut

---------

Co-authored-by: thinkasany <480968828@qq.com>
2026-02-02 21:04:19 +08:00
dependabot[bot]
5a0f141ddc chore: bump dawidd6/action-download-artifact from 13 to 14 (#56834)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 13 to 14.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](https://github.com/dawidd6/action-download-artifact/compare/v13...v14)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-version: '14'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 20:52:33 +08:00
65 changed files with 805 additions and 676 deletions

View File

@@ -63,7 +63,7 @@ jobs:
steps:
# We need get PR id first
- name: download pr artifact
uses: dawidd6/action-download-artifact@v13
uses: dawidd6/action-download-artifact@v14
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
@@ -83,7 +83,7 @@ jobs:
# Download site artifact
- name: download site artifact
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
uses: dawidd6/action-download-artifact@v13
uses: dawidd6/action-download-artifact@v14
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}

View File

@@ -17,12 +17,12 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
- uses: utooland/setup-utoo@v1
- name: size-limit
uses: ant-design/size-limit-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
package_manager: bun
package_manager: ut
build_script: dist
env:
NODE_OPTIONS: --max_old_space_size=4096

View File

@@ -70,7 +70,7 @@ jobs:
# We need get persist-index first
- name: download image snapshot artifact
uses: dawidd6/action-download-artifact@v13
uses: dawidd6/action-download-artifact@v14
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
@@ -92,7 +92,7 @@ jobs:
- name: download report artifact
id: download_report
if: ${{ needs.upstream-workflow-summary.outputs.build-status == 'success' || needs.upstream-workflow-summary.outputs.build-status == 'failure' }}
uses: dawidd6/action-download-artifact@v13
uses: dawidd6/action-download-artifact@v14
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}

View File

@@ -67,7 +67,7 @@ jobs:
# We need get persist key first
- name: Download Visual Regression Ref
uses: dawidd6/action-download-artifact@v13
uses: dawidd6/action-download-artifact@v14
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
@@ -81,7 +81,7 @@ jobs:
- name: Download Visual-Regression Artifact
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
uses: dawidd6/action-download-artifact@v13
uses: dawidd6/action-download-artifact@v14
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}

View File

@@ -1,13 +1,11 @@
const { moduleNameMapper, transformIgnorePatterns } = require('./.jest');
// jest config for image snapshots
module.exports = {
setupFiles: ['./tests/setup.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'md'],
moduleNameMapper,
transform: {
'\\.tsx?$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
'\\.js$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
'^.+\\.(ts|tsx|js|mjs)$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
'\\.md$': './node_modules/@ant-design/tools/lib/jest/demoPreprocessor',
'\\.(jpg|png|gif|svg)$': './node_modules/@ant-design/tools/lib/jest/imagePreprocessor',
},

View File

@@ -6,11 +6,13 @@ const compileModules = [
'countup.js',
'.pnpm',
'@asamuzakjp/css-color',
'@csstools',
'@rc-component',
// jsdom 27+ depends on ESM parse5, need transform
'parse5',
'@exodus',
'jsdom',
'@csstools',
];
// cnpm use `_` as prefix
@@ -62,7 +64,7 @@ module.exports = {
],
transform: {
'\\.tsx?$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
'\\.(m?)js$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
'\\.(m?)js(m)?$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
'\\.md$': './node_modules/@ant-design/tools/lib/jest/demoPreprocessor',
'\\.(jpg|png|gif|svg)$': './node_modules/@ant-design/tools/lib/jest/imagePreprocessor',
},

View File

@@ -1,14 +1,12 @@
const { moduleNameMapper, transformIgnorePatterns } = require('./.jest');
// jest config for server render environment
module.exports = {
setupFiles: ['./tests/setup.ts'],
setupFilesAfterEnv: ['./tests/setupAfterEnv.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'md'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'mjs', 'md'],
moduleNameMapper,
transform: {
'\\.tsx?$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
'\\.js$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
'^.+\\.(ts|tsx|js|mjs)$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
'\\.md$': './node_modules/@ant-design/tools/lib/jest/demoPreprocessor',
'\\.(jpg|png|gif|svg)$': './node_modules/@ant-design/tools/lib/jest/imagePreprocessor',
},

View File

@@ -17,7 +17,7 @@ tag: vVERSION
## 6.2.3
`2026-02-01`
`2026-02-02`
- Button
- 🐞 Fix Button `defaultBg`, `defaultColor`, `defaultHoverColor` and `defaultActiveColor` tokens not taking effect. [#56238](https://github.com/ant-design/ant-design/pull/56238) [@ug-hero](https://github.com/ug-hero)

View File

@@ -8,7 +8,14 @@ export interface ComponentToken {}
export interface WaveToken extends FullToken<'Wave'> {}
const genWaveStyle: GenerateStyle<WaveToken> = (token) => {
const { componentCls, colorPrimary, antCls } = token;
const {
componentCls,
colorPrimary,
motionDurationSlow,
motionEaseInOut,
motionEaseOutCirc,
antCls,
} = token;
const [, varRef] = genCssVar(antCls, 'wave');
return {
[componentCls]: {
@@ -22,20 +29,18 @@ const genWaveStyle: GenerateStyle<WaveToken> = (token) => {
// =================== Motion ===================
'&.wave-motion-appear': {
transition: [
`box-shadow 0.4s ${token.motionEaseOutCirc}`,
`opacity 2s ${token.motionEaseOutCirc}`,
].join(','),
transition: [`box-shadow 0.4s`, `opacity 2s`]
.map((prop) => `${prop} ${motionEaseOutCirc}`)
.join(','),
'&-active': {
boxShadow: `0 0 0 6px currentcolor`,
opacity: 0,
},
'&.wave-quick': {
transition: [
`box-shadow ${token.motionDurationSlow} ${token.motionEaseInOut}`,
`opacity ${token.motionDurationSlow} ${token.motionEaseInOut}`,
].join(','),
transition: [`box-shadow`, `opacity`]
.map((prop) => `${prop} ${motionDurationSlow} ${motionEaseInOut}`)
.join(','),
},
},
},

View File

@@ -98,9 +98,9 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
[`&${componentCls}-motion-leave`]: {
overflow: 'hidden',
opacity: 1,
transition: `max-height ${duration} ${motionEaseInOutCirc}, opacity ${duration} ${motionEaseInOutCirc},
padding-top ${duration} ${motionEaseInOutCirc}, padding-bottom ${duration} ${motionEaseInOutCirc},
margin-bottom ${duration} ${motionEaseInOutCirc}`,
transition: [`max-height`, `opacity`, `padding-top`, `padding-bottom`, `margin-bottom`]
.map((prop) => `${prop} ${duration} ${motionEaseInOutCirc}`)
.join(', '),
},
[`&${componentCls}-motion-leave-active`]: {

View File

@@ -170,7 +170,9 @@ const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = (token) => {
[`${componentCls}-ink`]: {
position: 'absolute',
bottom: 0,
transition: `left ${motionDurationSlow} ease-in-out, width ${motionDurationSlow} ease-in-out`,
transition: [`left`, `width`]
.map((prop) => `${prop} ${motionDurationSlow} ease-in-out`)
.join(', '),
height: lineWidthBold,
backgroundColor: colorPrimary,
},

View File

@@ -1,8 +1,222 @@
import React from 'react';
import { SearchOutlined } from '@ant-design/icons';
import { Button, ConfigProvider, Flex } from 'antd';
const App: React.FC = () => (
<Flex gap="small" vertical>
<div>Component Token</div>
<ConfigProvider
theme={{
components: {
Button: {
fontWeight: '900',
contentFontSizeSM: 20,
contentFontSize: 30,
contentFontSizeLG: 40,
paddingInlineSM: 20,
paddingInline: 30,
paddingInlineLG: 40,
},
},
}}
>
<Flex gap="small" align="center">
<Button size="small">Small</Button>
<Button>Default</Button>
<Button size="large">Large</Button>
</Flex>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
dangerColor: 'green',
dangerShadow: 'yellow',
},
},
}}
>
<Flex gap="small" align="center">
<Button danger>Default</Button>
<Button danger type="primary">
Primary
</Button>
<Button danger type="dashed">
Dashed
</Button>
<Button danger type="text">
Text
</Button>
<Button danger type="link">
Link
</Button>
</Flex>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
dashedBgDisabled: 'red',
},
},
}}
>
<Button type="dashed" disabled>
Dashed Disabled
</Button>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
defaultColor: 'red',
defaultBg: 'blue',
defaultBorderColor: 'green',
defaultShadow: 'yellow',
defaultBgDisabled: 'pink',
defaultHoverColor: 'brown',
defaultHoverBg: 'orange',
defaultHoverBorderColor: 'purple',
defaultActiveColor: 'fuchsia',
defaultActiveBg: 'aqua',
defaultActiveBorderColor: 'lime',
},
},
}}
>
<Flex gap="small" align="start">
<Button>Default</Button>
<Button type="dashed">Dashed</Button>
<Button type="text">Text</Button>
<Button disabled>Disabled</Button>
</Flex>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
ghostBg: 'red',
defaultGhostColor: 'yellow',
defaultGhostBorderColor: 'green',
},
},
}}
>
<Flex gap="small" align="start">
<Button ghost>Default</Button>
<Button ghost type="dashed">
Dashed
</Button>
<Button ghost type="text">
Text
</Button>
<Button ghost type="link">
Link
</Button>
<Button ghost type="primary">
Primary
</Button>
</Flex>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
iconGap: 40,
},
},
}}
>
<Button icon={<SearchOutlined />}>icon gap 40</Button>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
linkHoverBg: 'red',
},
},
}}
>
<Flex gap="small" align="center">
<Button type="link" href="https://ant.design">
Link
</Button>
<Button disabled type="link" href="https://ant.design">
Link(disabled)
</Button>
</Flex>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
onlyIconSizeSM: 20,
onlyIconSize: 30,
onlyIconSizeLG: 40,
},
},
}}
>
<Flex gap="small" align="center">
<Button size="small" icon={<SearchOutlined />} />
<Button icon={<SearchOutlined />} />
<Button size="large" icon={<SearchOutlined />} />
</Flex>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
primaryColor: 'red',
primaryShadow: 'yellow',
},
},
}}
>
<Button type="primary">Primary</Button>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
solidTextColor: 'red',
},
},
}}
>
<Button variant="solid" color="default">
Solid
</Button>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
textTextColor: 'red',
textHoverBg: 'yellow',
textTextHoverColor: 'blue',
textTextActiveColor: 'green',
},
},
}}
>
<Flex gap="small" align="start">
<Button type="text">Text</Button>
<Button variant="text" color="default">
Default Text
</Button>
<Button variant="text" color="primary">
Primary Text
</Button>
<Button variant="text" color="danger">
Danger Text
</Button>
</Flex>
</ConfigProvider>
<div>Global Token</div>
<ConfigProvider
theme={{
components: {
@@ -85,47 +299,6 @@ const App: React.FC = () => (
</Button>
</Flex>
</ConfigProvider>
<ConfigProvider
theme={{
components: { Button: { paddingInline: 100, paddingInlineLG: 150, paddingInlineSM: 50 } },
}}
>
<Flex gap="small" vertical align="start">
<Button>Default Button</Button>
<Button shape="round">Default Button</Button>
<Button size="large">Default Button</Button>
<Button shape="round" size="large">
Default Button
</Button>
<Button size="small">Default Button</Button>
<Button shape="round" size="small">
Default Button
</Button>
</Flex>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Button: {
defaultBg: 'red',
defaultColor: 'blue',
defaultHoverColor: 'green',
defaultActiveColor: 'yellow',
textTextColor: 'purple',
textTextHoverColor: 'orange',
textTextActiveColor: 'pink',
textHoverBg: 'brown',
},
},
}}
>
<Flex gap="small" align="start">
<Button>Default Button</Button>
<Button type="dashed">Dashed Button</Button>
<Button type="text">Text Button</Button>
</Flex>
</ConfigProvider>
</Flex>
);

View File

@@ -4,9 +4,9 @@ import { unit } from '@ant-design/cssinjs';
import { AggregationColor } from '../../color-picker/color';
import { isBright } from '../../color-picker/components/ColorPresets';
import { PresetColors } from '../../theme/interface';
import type { FullToken, GenStyleFn, GetDefaultToken, PresetColorKey } from '../../theme/internal';
import { getLineHeight, mergeToken } from '../../theme/internal';
import { PresetColors } from '../../theme/interface';
import getAlphaColor from '../../theme/util/getAlphaColor';
/** Component only token. Which will handle additional calculation of alias token */
@@ -154,16 +154,19 @@ export interface ComponentToken {
/**
* @desc 按钮纵向内间距
* @descEN Vertical padding of button
* @deprecated not used
*/
paddingBlock: CSSProperties['paddingBlock'];
/**
* @desc 大号按钮纵向内间距
* @descEN Vertical padding of large button
* @deprecated not used
*/
paddingBlockLG: CSSProperties['paddingBlock'];
/**
* @desc 小号按钮纵向内间距
* @descEN Vertical padding of small button
* @deprecated not used
*/
paddingBlockSM: CSSProperties['paddingBlock'];
/**
@@ -209,16 +212,19 @@ export interface ComponentToken {
/**
* @desc 按钮内容字体行高
* @descEN Line height of button content
* @deprecated not used
*/
contentLineHeight: number;
/**
* @desc 大号按钮内容字体行高
* @descEN Line height of large button content
* @deprecated not used
*/
contentLineHeightLG: number;
/**
* @desc 小号按钮内容字体行高
* @descEN Line height of small button content
* @deprecated not used
*/
contentLineHeightSM: number;
/**
@@ -241,6 +247,10 @@ type ShadowColorMap = {
[Key in `${PresetColorKey}ShadowColor`]: string;
};
type PresetColorHoverActiveMap = {
[Key in `${PresetColorKey}Hover` | `${PresetColorKey}Active`]: string;
};
type GroupToken = {
/**
* @desc 按钮组边框颜色
@@ -251,7 +261,11 @@ type GroupToken = {
groupBorderColor: string;
};
export interface ButtonToken extends FullToken<'Button'>, ShadowColorMap, GroupToken {
export interface ButtonToken
extends FullToken<'Button'>,
ShadowColorMap,
PresetColorHoverActiveMap,
GroupToken {
/**
* @desc 按钮横向内边距
* @descEN Horizontal padding of button

View File

@@ -165,6 +165,7 @@ const genVariantStyle: GenerateStyle<ButtonToken> = (token) => {
[varName('color-base')]: token.colorLink,
[varName('color-hover')]: token.colorLinkHover,
[varName('color-active')]: token.colorLinkActive,
[varName('bg-color-hover')]: token.linkHoverBg,
},
// ======================== Compatible ========================
@@ -223,6 +224,10 @@ const genVariantStyle: GenerateStyle<ButtonToken> = (token) => {
[varName('text-color-active')]: token.defaultActiveColor,
[varName('shadow')]: token.defaultShadow,
[`&${componentCls}-variant-outlined`]: {
[varName('bg-color-disabled')]: token.defaultBgDisabled,
},
[`&${componentCls}-variant-solid`]: {
[varName('text-color')]: token.solidTextColor,
[varName('text-color-hover')]: varRef('text-color'),
@@ -239,7 +244,7 @@ const genVariantStyle: GenerateStyle<ButtonToken> = (token) => {
[varName('text-color-hover')]: token.defaultHoverColor,
[varName('text-color-active')]: token.defaultActiveColor,
[varName('bg-color-container')]: token.defaultBg,
[varName('bg-color-hover')]: token.defaultHoverBg,
[varName('bg-color-hover')]: token.defaultHoverBg,
[varName('bg-color-active')]: token.defaultActiveBg,
},
@@ -263,10 +268,10 @@ const genVariantStyle: GenerateStyle<ButtonToken> = (token) => {
PresetColors.map((colorKey) => {
const darkColor = token[`${colorKey}6`];
const lightColor = token[`${colorKey}1`];
const hoverColor = token[`${colorKey}5`];
const hoverColor = token[`${colorKey}Hover`];
const lightHoverColor = token[`${colorKey}2`];
const lightActiveColor = token[`${colorKey}3`];
const activeColor = token[`${colorKey}7`];
const activeColor = token[`${colorKey}Active`];
const shadowColor = token[`${colorKey}ShadowColor`];
@@ -303,8 +308,15 @@ const genVariantStyle: GenerateStyle<ButtonToken> = (token) => {
{
// Ghost
[`&${componentCls}-background-ghost`]: {
[varName('bg-color')]: 'transparent',
[varName('bg-color')]: token.ghostBg,
[varName('bg-color-hover')]: token.ghostBg,
[varName('bg-color-active')]: token.ghostBg,
[varName('shadow')]: 'none',
[`&${componentCls}-variant-outlined, &${componentCls}-variant-dashed`]: {
[varName('bg-color-hover')]: token.ghostBg,
[varName('bg-color-active')]: token.ghostBg,
},
},
},
],

View File

@@ -315,6 +315,7 @@ const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => {
boxShadowTertiary,
bodyPadding,
extraColor,
motionDurationMid,
} = token;
return {
@@ -379,7 +380,9 @@ const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => {
[`${componentCls}-hoverable`]: {
cursor: 'pointer',
transition: `box-shadow ${token.motionDurationMid}, border-color ${token.motionDurationMid}`,
transition: [`box-shadow`, `border-color`]
.map((prop) => `${prop} ${motionDurationMid}`)
.join(', '),
'&:hover': {
borderColor: 'transparent',

View File

@@ -101,7 +101,9 @@ const genPickerStyle: GenerateStyle<PickerToken> = (token) => {
alignItems: 'center',
lineHeight: 1,
borderRadius,
transition: `border ${motionDurationMid}, box-shadow ${motionDurationMid}, background ${motionDurationMid}`,
transition: [`border`, `box-shadow`, `background-color`]
.map((prop) => `${prop} ${motionDurationMid}`)
.join(', '),
[`${componentCls}-prefix`]: {
flex: '0 0 auto',

View File

@@ -116,7 +116,9 @@ export const genOverflowStyle = (
marginBlock: INTERNAL_FIXED_ITEM_MARGIN,
borderRadius: borderRadiusSM,
cursor: 'default',
transition: `font-size ${motionDurationSlow}, line-height ${motionDurationSlow}, height ${motionDurationSlow}`,
transition: [`font-size`, `line-height`, `height`]
.map((prop) => `${prop} ${motionDurationSlow}`)
.join(', '),
marginInlineEnd: token.calc(INTERNAL_FIXED_ITEM_MARGIN).mul(2).equal(),
paddingInlineStart: paddingXS,
paddingInlineEnd: token.calc(paddingXS).div(2).equal(),

View File

@@ -2,7 +2,7 @@ import type { FormToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
const genFormValidateMotionStyle: GenerateStyle<FormToken> = (token) => {
const { componentCls } = token;
const { componentCls, motionDurationFast, motionEaseInOut } = token;
const helpCls = `${componentCls}-show-help`;
const helpItemCls = `${componentCls}-show-help-item`;
@@ -10,7 +10,7 @@ const genFormValidateMotionStyle: GenerateStyle<FormToken> = (token) => {
return {
[helpCls]: {
// Explain holder
transition: `opacity ${token.motionDurationFast} ${token.motionEaseInOut}`,
transition: `opacity ${motionDurationFast} ${motionEaseInOut}`,
'&-appear, &-enter': {
opacity: 0,
@@ -31,9 +31,9 @@ const genFormValidateMotionStyle: GenerateStyle<FormToken> = (token) => {
// Explain
[helpItemCls]: {
overflow: 'hidden',
transition: `height ${token.motionDurationFast} ${token.motionEaseInOut},
opacity ${token.motionDurationFast} ${token.motionEaseInOut},
transform ${token.motionDurationFast} ${token.motionEaseInOut} !important`,
transition: `${['height', 'opacity', 'transform']
.map((prop) => `${prop} ${motionDurationFast} ${motionEaseInOut}`)
.join(', ')} !important`,
[`&${helpItemCls}-appear, &${helpItemCls}-enter`]: {
transform: `translateY(-5px)`,

View File

@@ -43,10 +43,9 @@ const getHorizontalStyle: GenerateStyle<MenuToken> = (token) => {
},
[`${componentCls}-item, ${componentCls}-submenu-title`]: {
transition: [
`border-color ${motionDurationSlow}`,
`background-color ${motionDurationSlow}`,
].join(','),
transition: [`border-color`, `background-color`]
.map((prop) => `${prop} ${motionDurationSlow}`)
.join(','),
},
// ===================== Sub Menu =====================

View File

@@ -516,12 +516,9 @@ const genSubMenuArrowStyle = (token: MenuToken): CSSObject => {
height: token.calc(menuArrowSize).mul(0.15).equal(),
backgroundColor: 'currentcolor',
borderRadius,
transition: [
`background-color ${motionDurationSlow} ${motionEaseInOut}`,
`transform ${motionDurationSlow} ${motionEaseInOut}`,
`top ${motionDurationSlow} ${motionEaseInOut}`,
`color ${motionDurationSlow} ${motionEaseInOut}`,
].join(','),
transition: [`background-color`, `transform`, `top`, `color`]
.map((prop) => `${prop} ${motionDurationSlow} ${motionEaseInOut}`)
.join(','),
content: '""',
},
@@ -619,10 +616,9 @@ const getBaseStyle: GenerateStyle<MenuToken> = (token) => {
},
[`&-horizontal ${componentCls}-submenu`]: {
transition: [
`border-color ${motionDurationSlow} ${motionEaseInOut}`,
`background-color ${motionDurationSlow} ${motionEaseInOut}`,
].join(','),
transition: [`border-color`, `background-color`]
.map((prop) => `${prop} ${motionDurationSlow} ${motionEaseInOut}`)
.join(','),
},
[`${componentCls}-submenu, ${componentCls}-submenu-inline`]: {
@@ -635,10 +631,9 @@ const getBaseStyle: GenerateStyle<MenuToken> = (token) => {
[`${componentCls}-submenu ${componentCls}-sub`]: {
cursor: 'initial',
transition: [
`background-color ${motionDurationSlow} ${motionEaseInOut}`,
`padding ${motionDurationSlow} ${motionEaseInOut}`,
].join(','),
transition: [`background-color`, `padding`]
.map((prop) => `${prop} ${motionDurationSlow} ${motionEaseInOut}`)
.join(','),
},
[`${componentCls}-title-content`]: {

View File

@@ -239,10 +239,9 @@ const getThemeStyle = (token: MenuToken, themeSuffix: string): CSSInterpolation
borderInlineEnd: `${unit(activeBarWidth)} solid ${itemSelectedColor}`,
transform: 'scaleY(0.0001)',
opacity: 0,
transition: [
`transform ${motionDurationMid} ${motionEaseOut}`,
`opacity ${motionDurationMid} ${motionEaseOut}`,
].join(','),
transition: [`transform`, `opacity`]
.map((prop) => `${prop} ${motionDurationMid} ${motionEaseOut}`)
.join(','),
content: '""',
},
@@ -258,10 +257,9 @@ const getThemeStyle = (token: MenuToken, themeSuffix: string): CSSInterpolation
'&::after': {
transform: 'scaleY(1)',
opacity: 1,
transition: [
`transform ${motionDurationMid} ${motionEaseInOut}`,
`opacity ${motionDurationMid} ${motionEaseInOut}`,
].join(','),
transition: [`transform`, `opacity`]
.map((prop) => `${prop} ${motionDurationMid} ${motionEaseInOut}`)
.join(','),
},
},
},

View File

@@ -678,6 +678,8 @@ describe('Modal.confirm triggers callbacks correctly', () => {
describe('the callback close should be a method when onCancel has a close parameter', () => {
(['confirm', 'info', 'success', 'warning', 'error'] as const).forEach((type) => {
it(`click the close icon to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]?.({
@@ -688,17 +690,21 @@ describe('Modal.confirm triggers callbacks correctly', () => {
await waitFakeTimer();
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
$$('.ant-modal-close')[0].click();
fireEvent.click($$('.ant-modal-close')[0]);
await waitFakeTimer();
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
expect(mock).toHaveBeenCalledWith(expect.any(Function));
jest.useRealTimers();
});
});
(['confirm', 'info', 'success', 'warning', 'error'] as const).forEach((type) => {
it(`press ESC to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]?.({
@@ -709,17 +715,21 @@ describe('Modal.confirm triggers callbacks correctly', () => {
await waitFakeTimer();
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
fireEvent.keyDown(window, { key: 'Escape' });
fireEvent.keyDown($$(`.ant-modal-confirm-${type}`)[0], { key: 'Escape' });
await waitFakeTimer(0);
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
expect(mock).toHaveBeenCalledWith(expect.any(Function));
jest.useRealTimers();
});
});
(['confirm', 'info', 'success', 'warning', 'error'] as const).forEach((type) => {
it(`click the mask to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]?.({
@@ -732,17 +742,22 @@ describe('Modal.confirm triggers callbacks correctly', () => {
expect($$('.ant-modal-mask')).toHaveLength(1);
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
$$('.ant-modal-wrap')[0].click();
fireEvent.mouseDown($$('.ant-modal-wrap')[0]);
fireEvent.click($$('.ant-modal-wrap')[0]);
await waitFakeTimer();
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
expect(mock).toHaveBeenCalledWith(expect.any(Function));
jest.useRealTimers();
});
});
});
it('confirm modal click Cancel button close callback is a function', async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal.confirm({
@@ -751,13 +766,17 @@ describe('Modal.confirm triggers callbacks correctly', () => {
await waitFakeTimer();
$$('.ant-modal-confirm-btns > .ant-btn')[0].click();
fireEvent.click($$('.ant-modal-confirm-btns > .ant-btn')[0]);
await waitFakeTimer();
expect(mock).toHaveBeenCalledWith(expect.any(Function));
jest.useRealTimers();
});
it('close can close modal when onCancel has a close parameter', async () => {
jest.useFakeTimers();
Modal.confirm({
onCancel: (close) => close(),
});
@@ -766,10 +785,12 @@ describe('Modal.confirm triggers callbacks correctly', () => {
expect($$('.ant-modal-confirm-confirm')).toHaveLength(1);
$$('.ant-modal-confirm-btns > .ant-btn')[0].click();
fireEvent.click($$('.ant-modal-confirm-btns > .ant-btn')[0]);
await waitFakeTimer();
expect($$('.ant-modal-confirm-confirm')).toHaveLength(0);
jest.useRealTimers();
});
// https://github.com/ant-design/ant-design/issues/37461

View File

@@ -186,6 +186,7 @@ describe('Modal.hook', () => {
expect(cancelCount).toEqual(1); // click cancel btn, trigger onCancel
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
fireEvent.mouseDown(document.body.querySelectorAll('.ant-modal-wrap')[0]);
fireEvent.click(document.body.querySelectorAll('.ant-modal-wrap')[0]);
expect(cancelCount).toEqual(2); // click modal wrapper, trigger onCancel
});
@@ -322,6 +323,7 @@ describe('Modal.hook', () => {
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
// Click mask to close
fireEvent.mouseDown(document.body.querySelectorAll('.ant-modal-wrap')[0]);
fireEvent.click(document.body.querySelectorAll('.ant-modal-wrap')[0]);
await waitFakeTimer();

View File

@@ -184,7 +184,7 @@ export const genModalMaskStyle: GenerateStyle<TokenWithCommonCls<AliasToken>> =
};
const genModalStyle: GenerateStyle<ModalToken> = (token) => {
const { componentCls } = token;
const { componentCls, motionDurationMid } = token;
return [
// ======================== Root =========================
@@ -284,8 +284,9 @@ const genModalStyle: GenerateStyle<ModalToken> = (token) => {
border: 0,
outline: 0,
cursor: 'pointer',
transition: `color ${token.motionDurationMid}, background-color ${token.motionDurationMid}`,
transition: ['color', 'background-color']
.map((prop) => `${prop} ${motionDurationMid}`)
.join(', '),
'&-x': {
display: 'flex',
fontSize: token.fontSizeLG,

View File

@@ -136,6 +136,7 @@ export const genNoticeStyle = (token: NotificationToken): CSSObject => {
colorErrorBg,
colorInfoBg,
colorWarningBg,
motionDurationMid,
} = token;
const noticeCls = `${componentCls}-notice`;
@@ -223,7 +224,10 @@ export const genNoticeStyle = (token: NotificationToken): CSSObject => {
width: token.notificationCloseButtonSize,
height: token.notificationCloseButtonSize,
borderRadius: token.borderRadiusSM,
transition: `background-color ${token.motionDurationMid}, color ${token.motionDurationMid}`,
transition: ['color', 'background-color']
.map((prop) => `${prop} ${motionDurationMid}`)
.join(', '),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',

View File

@@ -388,11 +388,9 @@ const getRadioButtonStyle: GenerateStyle<RadioToken> = (token) => {
borderBlockStartWidth: calc(lineWidth).add(0.02).equal(),
borderInlineEndWidth: lineWidth,
cursor: 'pointer',
transition: [
`color ${motionDurationMid}`,
`background-color ${motionDurationMid}`,
`box-shadow ${motionDurationMid}`,
].join(','),
transition: [`color`, `background-color`, `box-shadow`]
.map((prop) => `${prop} ${motionDurationMid}`)
.join(','),
a: {
color: buttonColor,

View File

@@ -78,7 +78,7 @@ const segmentedTextEllipsisCss: CSSObject = {
// ============================== Styles ==============================
const genSegmentedStyle: GenerateStyle<SegmentedToken> = (token: SegmentedToken) => {
const { componentCls } = token;
const { componentCls, motionDurationSlow, motionEaseInOut } = token;
const labelHeight = token
.calc(token.controlHeight)
@@ -262,8 +262,10 @@ const genSegmentedStyle: GenerateStyle<SegmentedToken> = (token: SegmentedToken)
// transition effect when `appear-active`
[`${componentCls}-thumb-motion-appear-active`]: {
transition: `transform ${token.motionDurationSlow} ${token.motionEaseInOut}, width ${token.motionDurationSlow} ${token.motionEaseInOut}`,
willChange: 'transform, width',
transition: [`transform`, `width`]
.map((prop) => `${prop} ${motionDurationSlow} ${motionEaseInOut}`)
.join(', '),
},
[`&${componentCls}-shape-round`]: {

View File

@@ -13,7 +13,7 @@ export type { ComponentToken };
// =============================== Base ===============================
const genBaseStyle: GenerateStyle<SelectToken> = (token) => {
const { antCls, componentCls, inputPaddingHorizontalBase } = token;
const { antCls, componentCls, motionDurationMid, inputPaddingHorizontalBase } = token;
const hoverShowClearStyle: CSSObject = {
[`${componentCls}-clear`]: {
@@ -66,7 +66,9 @@ const genBaseStyle: GenerateStyle<SelectToken> = (token) => {
textTransform: 'none',
cursor: 'pointer',
opacity: 0,
transition: `color ${token.motionDurationMid} ease, opacity ${token.motionDurationSlow} ease`,
transition: ['color', 'opacity']
.map((prop) => `${prop} ${motionDurationMid} ease`)
.join(', '),
textRendering: 'auto',
// https://github.com/ant-design/ant-design/issues/54205
// Force GPU compositing on Safari to prevent flickering on opacity/transform transitions

View File

@@ -41,7 +41,7 @@ const antRotate = new Keyframes('antRotate', {
});
const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject => {
const { componentCls, calc } = token;
const { componentCls, motionDurationSlow, calc } = token;
return {
[componentCls]: {
...resetComponent(token),
@@ -52,7 +52,7 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
textAlign: 'center',
verticalAlign: 'middle',
opacity: 0,
transition: `transform ${token.motionDurationSlow} ${token.motionEaseInOutCirc}`,
transition: `transform ${motionDurationSlow} ${token.motionEaseInOutCirc}`,
'&-spinning': {
position: 'relative',
@@ -148,7 +148,7 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
[`${componentCls}-container`]: {
position: 'relative',
transition: `opacity ${token.motionDurationSlow}`,
transition: `opacity ${motionDurationSlow}`,
'&::after': {
position: 'absolute',
@@ -161,7 +161,7 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
height: '100%',
background: token.colorBgContainer,
opacity: 0,
transition: `all ${token.motionDurationSlow}`,
transition: `all ${motionDurationSlow}`,
content: '""',
pointerEvents: 'none',
},
@@ -193,7 +193,9 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
height: '1em',
fontSize: token.dotSize,
display: 'inline-block',
transition: `transform ${token.motionDurationSlow} ease, opacity ${token.motionDurationSlow} ease`,
transition: [`transform`, `opacity`]
.map((prop) => `${prop} ${motionDurationSlow} ease`)
.join(', '),
transformOrigin: '50% 50%',
lineHeight: 1,
color: token.colorPrimary,
@@ -272,7 +274,7 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
'&-circle': {
strokeLinecap: 'round',
transition: ['stroke-dashoffset', 'stroke-dasharray', 'stroke', 'stroke-width', 'opacity']
.map((item) => `${item} ${token.motionDurationSlow} ease`)
.map((item) => `${item} ${motionDurationSlow} ease`)
.join(','),
fillOpacity: 0,
stroke: 'currentcolor',

View File

@@ -135,7 +135,7 @@ export const genCommonStyle = (
export const genFocusOutline = (token: AliasToken, offset?: number): CSSObject => ({
outline: `${unit(token.lineWidthFocus)} solid ${token.colorPrimaryBorder}`,
outlineOffset: offset ?? 1,
transition: 'outline-offset 0s, outline 0s',
transition: [`outline-offset`, `outline`].map((prop) => `${prop} 0s`).join(', '),
});
export const genFocusStyle = (token: AliasToken, offset?: number): CSSObject => ({

View File

@@ -1,23 +1,26 @@
import type { AliasToken, GenerateStyle, TokenWithCommonCls } from '../../theme/internal';
const genCollapseMotion: GenerateStyle<TokenWithCommonCls<AliasToken>> = (token) => ({
[token.componentCls]: {
// For common/openAnimation
[`${token.antCls}-motion-collapse-legacy`]: {
overflow: 'hidden',
'&-active': {
transition: `height ${token.motionDurationMid} ${token.motionEaseInOut},
opacity ${token.motionDurationMid} ${token.motionEaseInOut} !important`,
const genCollapseMotion: GenerateStyle<TokenWithCommonCls<AliasToken>> = (token) => {
const { componentCls, antCls, motionDurationMid, motionEaseInOut } = token;
return {
[componentCls]: {
// For common/openAnimation
[`${antCls}-motion-collapse-legacy`]: {
overflow: 'hidden',
'&-active': {
transition: `${['height', 'opacity']
.map((prop) => `${prop} ${motionDurationMid} ${motionEaseInOut}`)
.join(', ')} !important`,
},
},
[`${antCls}-motion-collapse`]: {
overflow: 'hidden',
transition: `${['height', 'opacity']
.map((prop) => `${prop} ${motionDurationMid} ${motionEaseInOut}`)
.join(', ')} !important`,
},
},
[`${token.antCls}-motion-collapse`]: {
overflow: 'hidden',
transition: `height ${token.motionDurationMid} ${token.motionEaseInOut},
opacity ${token.motionDurationMid} ${token.motionEaseInOut} !important`,
},
},
});
};
};
export default genCollapseMotion;

View File

@@ -250,6 +250,7 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
innerMinMargin,
innerMaxMargin,
handleSize,
switchDuration,
calc,
} = token;
const switchInnerCls = `${componentCls}-inner`;
@@ -266,15 +267,19 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
height: '100%',
paddingInlineStart: innerMaxMargin,
paddingInlineEnd: innerMinMargin,
transition: `padding-inline-start ${token.switchDuration} ease-in-out, padding-inline-end ${token.switchDuration} ease-in-out`,
transition: [`padding-inline-start`, `padding-inline-end`]
.map((prop) => `${prop} ${switchDuration} ease-in-out`)
.join(', '),
[`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: {
display: 'block',
color: token.colorTextLightSolid,
fontSize: token.fontSizeSM,
transition: `margin-inline-start ${token.switchDuration} ease-in-out, margin-inline-end ${token.switchDuration} ease-in-out`,
pointerEvents: 'none',
minHeight: trackHeight,
transition: [`margin-inline-start`, `margin-inline-end`]
.map((prop) => `${prop} ${switchDuration} ease-in-out`)
.join(', '),
},
[`${switchInnerCls}-checked`]: {

View File

@@ -345,8 +345,10 @@ const genTableStyle: GenerateStyle<TableToken, CSSObject> = (token) => {
[`${componentCls}-tbody`]: {
'> tr': {
'> th, > td': {
transition: `background-color ${motionDurationMid}, border-color ${motionDurationMid}`,
borderBottom: tableBorder,
transition: [`background-color`, `border-color`]
.map((prop) => `${prop} ${motionDurationMid}`)
.join(', '),
// ========================= Nest Table ===========================
[`

View File

@@ -369,6 +369,7 @@ const genPositionStyle: GenerateStyle<TabsToken> = (token: TabsToken): CSSObject
horizontalMargin,
verticalItemPadding,
verticalItemMargin,
motionDurationSlow,
calc,
} = token;
return {
@@ -397,8 +398,9 @@ const genPositionStyle: GenerateStyle<TabsToken> = (token: TabsToken): CSSObject
height: token.lineWidthBold,
'&-animated': {
transition: `width ${token.motionDurationSlow}, left ${token.motionDurationSlow},
right ${token.motionDurationSlow}`,
transition: ['width', 'left', 'right']
.map((prop) => `${prop} ${motionDurationSlow}`)
.join(', '),
},
},
@@ -524,7 +526,7 @@ const genPositionStyle: GenerateStyle<TabsToken> = (token: TabsToken): CSSObject
width: token.lineWidthBold,
'&-animated': {
transition: `height ${token.motionDurationSlow}, top ${token.motionDurationSlow}`,
transition: ['height', 'top'].map((prop) => `${prop} ${motionDurationSlow}`).join(', '),
},
},

View File

@@ -2,6 +2,7 @@ import { generate } from '@ant-design/colors';
import type { DerivativeFunc } from '@ant-design/cssinjs';
import type { MapToken, PresetColorType, SeedToken } from '../../interface';
import { PresetColors } from '../../interface/presetColors';
import defaultAlgorithm from '../default';
import { defaultPresetColors } from '../seed';
import genColorMapToken from '../shared/genColorMapToken';
@@ -29,6 +30,19 @@ const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
generateNeutralColorPalettes,
});
const presetColorHoverActiveTokens = PresetColors.reduce<Record<string, string>>(
(prev, colorKey) => {
const colorBase = token[colorKey as keyof PresetColorType];
if (colorBase) {
const colorPalette = generateColorPalettes(colorBase);
prev[`${colorKey}Hover`] = colorPalette[7];
prev[`${colorKey}Active`] = colorPalette[5];
}
return prev;
},
{},
);
return {
...mergedMapToken,
@@ -38,6 +52,8 @@ const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
// Colors
...colorMapToken,
...presetColorHoverActiveTokens,
// Customize selected item background color
// https://github.com/ant-design/ant-design/issues/30524#issuecomment-871961867
colorPrimaryBg: colorMapToken.colorPrimaryBorder,

View File

@@ -1,6 +1,7 @@
import { FastColor } from '@ant-design/fast-color';
import type { ColorMapToken, SeedToken } from '../../interface';
import { PresetColors } from '../../interface/presetColors';
import type { GenerateColorMap, GenerateNeutralColorMap } from '../ColorMap';
interface PaletteGenerators {
@@ -37,6 +38,16 @@ export default function genColorMapToken(
.mix(new FastColor(errorColors[3]), 50)
.toHexString();
const presetColorTokens: Record<string, string> = {};
PresetColors.forEach((colorKey) => {
const colorBase = seed[colorKey];
if (colorBase) {
const colorPalette = generateColorPalettes(colorBase);
presetColorTokens[`${colorKey}Hover`] = colorPalette[5];
presetColorTokens[`${colorKey}Active`] = colorPalette[7];
}
});
return {
...neutralColors,
@@ -101,6 +112,8 @@ export default function genColorMapToken(
colorLink: linkColors[6],
colorLinkActive: linkColors[7],
...presetColorTokens,
colorBgMask: new FastColor('#000').setA(0.45).toRgbString(),
colorWhite: '#fff',
};

View File

@@ -67,6 +67,7 @@ const genBaseStyle: GenerateStyle<TourToken> = (token) => {
motionDurationSlow,
antCls,
primaryPrevBtnBg,
motionDurationMid,
} = token;
const [varName, varRef] = genCssVar(antCls, 'tooltip');
@@ -117,7 +118,11 @@ const genBaseStyle: GenerateStyle<TourToken> = (token) => {
width: closeBtnSize,
height: closeBtnSize,
borderRadius: token.borderRadiusSM,
transition: `background-color ${token.motionDurationMid}, color ${token.motionDurationMid}`,
transition: ['color', 'background-color']
.map((prop) => `${prop} ${motionDurationMid}`)
.join(', '),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',

View File

@@ -116,6 +116,7 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
treeNodePadding,
titleHeight,
indentSize,
motionDurationMid,
nodeSelectedBg,
nodeHoverBg,
colorTextQuaternary,
@@ -352,7 +353,13 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
background: 'transparent',
borderRadius: token.borderRadius,
cursor: 'pointer',
transition: `all ${token.motionDurationMid}, border 0s, line-height 0s, box-shadow 0s`,
transition: [
`all ${motionDurationMid}`,
'border 0s',
'line-height 0s',
'box-shadow 0s',
].join(', '),
...getDropIndicatorStyle(prefixCls, token),
'&:hover': {

View File

@@ -5,7 +5,7 @@ import { clearFix, textEllipsis } from '../../style';
import type { GenerateStyle } from '../../theme/internal';
const genListStyle: GenerateStyle<UploadToken> = (token) => {
const { componentCls, iconCls, fontSize, lineHeight, calc } = token;
const { componentCls, iconCls, fontSize, lineHeight, motionDurationSlow, calc } = token;
const itemCls = `${componentCls}-list-item`;
const actionsCls = `${itemCls}-actions`;
const actionCls = `${itemCls}-action`;
@@ -23,7 +23,7 @@ const genListStyle: GenerateStyle<UploadToken> = (token) => {
fontSize,
display: 'flex',
alignItems: 'center',
transition: `background-color ${token.motionDurationSlow}`,
transition: `background-color ${motionDurationSlow}`,
borderRadius: token.borderRadiusSM,
'&:hover': {
@@ -35,7 +35,7 @@ const genListStyle: GenerateStyle<UploadToken> = (token) => {
padding: `0 ${unit(token.paddingXS)}`,
lineHeight,
flex: 'auto',
transition: `all ${token.motionDurationSlow}`,
transition: `all ${motionDurationSlow}`,
},
[actionsCls]: {
@@ -47,7 +47,7 @@ const genListStyle: GenerateStyle<UploadToken> = (token) => {
[iconCls]: {
color: token.actionsColor,
transition: `all ${token.motionDurationSlow}`,
transition: `all ${motionDurationSlow}`,
},
[`
@@ -100,7 +100,9 @@ const genListStyle: GenerateStyle<UploadToken> = (token) => {
},
[`${componentCls}-list-item-container`]: {
transition: `opacity ${token.motionDurationSlow}, height ${token.motionDurationSlow}`,
transition: ['opacity', 'height']
.map((prop) => `${prop} ${motionDurationSlow}`)
.join(', '),
// For smooth removing animation
'&::before': {

View File

@@ -141,6 +141,8 @@ const genPictureCardStyle: GenerateStyle<UploadToken> = (token) => {
[`${listCls}${listCls}-picture-card, ${listCls}${listCls}-picture-circle`]: {
display: 'flex',
flexWrap: 'wrap',
height: uploadPictureCardSize,
'@supports not (gap: 1px)': {
'& > *': {
marginBlockEnd: token.marginXS,

View File

@@ -31,36 +31,8 @@ When you need context information (such as the content configured by ConfigProvi
By modifying `token` property of `theme`, we can modify Design Token globally. Some tokens will affect other tokens. We call these tokens Seed Token.
```sandpack
const sandpackConfig = {
autorun: true,
};
import { Button, ConfigProvider, Space } from 'antd';
import React from 'react';
const App: React.FC = () => (
<ConfigProvider
theme={{
token: {
// Seed Token
colorPrimary: '#00b96b',
borderRadius: 2,
// Alias Token
colorBgContainer: '#f6ffed',
},
}}
>
<Space>
<Button type="primary">Primary</Button>
<Button>Default</Button>
</Space>
</ConfigProvider>
);
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/modify-theme-token.tsx">Customize Design Token</code>
### Use Preset Algorithms
@@ -72,33 +44,8 @@ Themes with different styles can be quickly generated by modifying `algorithm`.
You can switch algorithms by modifying the `algorithm` property of `theme` in ConfigProvider.
```sandpack
const sandpackConfig = {
dark: true,
};
import React from 'react';
import { Button, ConfigProvider, Input, Space, theme } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
// 1. Use dark algorithm
algorithm: theme.darkAlgorithm,
// 2. Combine dark algorithm and compact algorithm
// algorithm: [theme.darkAlgorithm, theme.compactAlgorithm],
}}
>
<Space>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
);
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/preset-algorithm.tsx">Use Preset Algorithms</code>
### Customize Component Token
@@ -112,99 +59,15 @@ By default, all component tokens can only override global token and will not be
In version `>= 5.8.0`, component tokens support the `algorithm` property, which can be used to enable algorithm or pass in other algorithms.
:::
```sandpack
import React from 'react';
import { ConfigProvider, Button, Space, Input, Divider } from 'antd';
const App: React.FC = () => (
<>
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: '#00b96b',
algorithm: true, // Enable algorithm
},
Input: {
colorPrimary: '#eb2f96',
algorithm: true, // Enable algorithm
}
},
}}
>
<Space>
<div style={{ fontSize: 14 }}>Enable algorithm: </div>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
<Divider />
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: '#00b96b',
},
Input: {
colorPrimary: '#eb2f96',
}
},
}}
>
<Space>
<div style={{ fontSize: 14 }}>Disable algorithm: </div>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
</>
);
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/component-token.tsx">Customize Component Token</code>
### Disable Motion
antd has built-in interaction animations to make enterprise-level pages more detailed. In some extreme scenarios, it may affect the performance of page interaction. If you need to turn off the animation, try setting `motion` of `token` to `false`:
```sandpack
import React from 'react';
import { Checkbox, Col, ConfigProvider, Flex, Radio, Row, Switch } from 'antd';
const App: React.FC = () => {
const [checked, setChecked] = React.useState<boolean>(false);
const timerRef = React.useRef<ReturnType<typeof setInterval>>();
React.useEffect(() => {
timerRef.current = setInterval(() => {
setChecked((prev) => !prev);
}, 500);
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
const nodes = (
<Flex gap="small">
<Checkbox checked={checked}>Checkbox</Checkbox>
<Radio checked={checked}>Radio</Radio>
<Switch checked={checked} />
</Flex>
);
return (
<Row gutter={[24, 24]}>
<Col span={24}>{nodes}</Col>
<Col span={24}>
<ConfigProvider theme={{ token: { motion: false } }}>{nodes}</ConfigProvider>
</Col>
</Row>
);
};
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/disable-motion.tsx">Disable Motion</code>
## Advanced
@@ -239,100 +102,22 @@ fs.writeFileSync('/path/to/somewhere', cssText);
In v5, dynamically switching themes is very simple for users, you can dynamically switch themes at any time through the `theme` property of `ConfigProvider` without any additional configuration.
```sandpack
import { Button, ConfigProvider, Space, Input, ColorPicker, Divider } from 'antd';
import React from 'react';
const App: React.FC = () => {
const [primary, setPrimary] = React.useState('#1677ff');
return (
<>
<ColorPicker showText value={primary} onChange={(color) => setPrimary(color.toHexString())} />
<Divider />
<ConfigProvider
theme={{
token: {
colorPrimary: primary,
},
}}
>
<Space>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
</>
);
}
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/dynamic-theme.tsx">Switch Themes Dynamically</code>
### Nested Theme
By nesting `ConfigProvider` you can apply local theme to some parts of your page. Design Tokens that have not been changed in the child theme will inherit the parent theme.
```sandpack
import React from 'react';
import { Button, ConfigProvider, Space } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
token: {
colorPrimary: '#1677ff',
},
}}
>
<Space>
<Button type="primary">Theme 1</Button>
<ConfigProvider
theme={{
token: {
colorPrimary: '#00b96b',
},
}}
>
<Button type="primary">Theme 2</Button>
</ConfigProvider>
</Space>
</ConfigProvider>
);
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/local-theme.tsx">Nested Theme</code>
### Consume Design Token
If you want to consume the Design Token under the current theme, we provide `useToken` hook to get Design Token.
```sandpack
import React from 'react';
import { Button, theme } from 'antd';
const { useToken } = theme;
const App: React.FC = () => {
const { token } = useToken();
return (
<div
style={{
backgroundColor: token.colorPrimaryBg,
padding: token.padding,
borderRadius: token.borderRadius,
color: token.colorPrimaryText,
fontSize: token.fontSize,
}}
>
Consume Design Token
</div>
);
};
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/use-token.tsx">Consume Design Token</code>
### Static consume (e.g. less)

View File

@@ -31,36 +31,8 @@ Ant Design 设计规范和技术上支持灵活的样式定制,以满足业务
通过 `theme` 中的 `token` 属性,可以修改一些主题变量。部分主题变量会引起其他主题变量的变化,我们把这些主题变量称为 Seed Token。
```sandpack
const sandpackConfig = {
autorun: true,
};
import { Button, ConfigProvider, Space } from 'antd';
import React from 'react';
const App: React.FC = () => (
<ConfigProvider
theme={{
token: {
// Seed Token影响范围大
colorPrimary: '#00b96b',
borderRadius: 2,
// 派生变量,影响范围小
colorBgContainer: '#f6ffed',
},
}}
>
<Space>
<Button type="primary">Primary</Button>
<Button>Default</Button>
</Space>
</ConfigProvider>
);
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/modify-theme-token.tsx">修改主题变量</code>
### 使用预设算法
@@ -72,33 +44,8 @@ export default App;
你可以通过 `theme` 中的 `algorithm` 属性来切换算法,并且支持配置多种算法,将会依次生效。
```sandpack
const sandpackConfig = {
dark: true,
};
import React from 'react';
import { Button, ConfigProvider, Input, Space, theme } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
// 1. 单独使用暗色算法
algorithm: theme.darkAlgorithm,
// 2. 组合使用暗色算法与紧凑算法
// algorithm: [theme.darkAlgorithm, theme.compactAlgorithm],
}}
>
<Space>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
);
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/preset-algorithm.tsx">使用预设算法</code>
### 修改组件变量
@@ -112,99 +59,15 @@ export default App;
`>= 5.8.0` 版本中,组件变量支持传入 `algorithm` 属性,可以开启派生计算或者传入其他算法。
:::
```sandpack
import React from 'react';
import { ConfigProvider, Button, Space, Input, Divider } from 'antd';
const App: React.FC = () => (
<>
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: '#00b96b',
algorithm: true, // 启用算法
},
Input: {
colorPrimary: '#eb2f96',
algorithm: true, // 启用算法
}
},
}}
>
<Space>
<div style={{ fontSize: 14 }}>开启算法:</div>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
<Divider />
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: '#00b96b',
},
Input: {
colorPrimary: '#eb2f96',
}
},
}}
>
<Space>
<div style={{ fontSize: 14 }}>禁用算法:</div>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
</>
);
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/component-token.tsx">修改组件变量</code>
### 禁用动画
antd 默认内置了一些组件交互动效让企业级页面更加富有细节,在一些极端场景可能会影响页面交互性能,如需关闭动画可以 `token` 中的 `motion` 修改为 `false`
```sandpack
import React from 'react';
import { Checkbox, Col, ConfigProvider, Flex, Radio, Row, Switch } from 'antd';
const App: React.FC = () => {
const [checked, setChecked] = React.useState<boolean>(false);
const timerRef = React.useRef<ReturnType<typeof setInterval>>();
React.useEffect(() => {
timerRef.current = setInterval(() => {
setChecked((prev) => !prev);
}, 500);
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
const nodes = (
<Flex gap="small">
<Checkbox checked={checked}>Checkbox</Checkbox>
<Radio checked={checked}>Radio</Radio>
<Switch checked={checked} />
</Flex>
);
return (
<Row gutter={[24, 24]}>
<Col span={24}>{nodes}</Col>
<Col span={24}>
<ConfigProvider theme={{ token: { motion: false } }}>{nodes}</ConfigProvider>
</Col>
</Row>
);
};
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/disable-motion.tsx">禁用动画</code>
## 进阶使用
@@ -239,100 +102,22 @@ fs.writeFileSync('/path/to/somewhere', cssText);
在 v5 中,动态切换主题对用户来说是非常简单的,你可以在任何时候通过 `ConfigProvider``theme` 属性来动态切换主题,而不需要任何额外配置。
```sandpack
import { Button, ConfigProvider, Space, Input, ColorPicker, Divider } from 'antd';
import React from 'react';
const App: React.FC = () => {
const [primary, setPrimary] = React.useState('#1677ff');
return (
<>
<ColorPicker showText value={primary} onChange={(color) => setPrimary(color.toHexString())} />
<Divider />
<ConfigProvider
theme={{
token: {
colorPrimary: primary,
},
}}
>
<Space>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
</>
);
}
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/dynamic-theme.tsx">动态切换</code>
### 局部主题(嵌套主题)
可以嵌套使用 `ConfigProvider` 来实现局部主题的更换。在子主题中未被改变的 Design Token 将会继承父主题。
```sandpack
import React from 'react';
import { Button, ConfigProvider, Space } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
token: {
colorPrimary: '#1677ff',
},
}}
>
<Space>
<Button type="primary">Theme 1</Button>
<ConfigProvider
theme={{
token: {
colorPrimary: '#00b96b',
},
}}
>
<Button type="primary">Theme 2</Button>
</ConfigProvider>
</Space>
</ConfigProvider>
);
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/local-theme.tsx">局部主题</code>
### 使用 Design Token
如果你希望使用当前主题下的 Design Token我们提供了 `useToken` 这个 hook 来获取 Design Token。
```sandpack
import React from 'react';
import { Button, theme } from 'antd';
const { useToken } = theme;
const App: React.FC = () => {
const { token } = useToken();
return (
<div
style={{
backgroundColor: token.colorPrimaryBg,
padding: token.padding,
borderRadius: token.borderRadius,
color: token.colorPrimaryText,
fontSize: token.fontSize,
}}
>
使用 Design Token
</div>
);
};
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/use-token.tsx">使用 Design Token</code>
### 静态消费(如 less

View File

@@ -0,0 +1,7 @@
## zh-CN
除了整体的 Design Token各个组件也会开放自己的 Component Token 来实现针对组件的样式定制能力,不同的组件之间不会相互影响。同样地,也可以通过这种方式来覆盖组件的其他 Design Token。在 `>= 5.8.0` 版本中,组件变量支持传入 `algorithm` 属性,可以开启派生计算或者传入其他算法。
## en-US
In addition to the overall Design Token, each component also exposes its own Component Token to enable component-specific style customization capabilities, without mutual influence between different components. Similarly, you can also override other Design Tokens consumed by the component through this method. In version `>= 5.8.0`, the component token supports passing the `algorithm` property to enable derivative calculation or pass in other algorithms.

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { Button, ConfigProvider, Divider, Input, Space } from 'antd';
const App: React.FC = () => (
<>
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: '#00b96b',
algorithm: true, // Enable algorithm
},
Input: {
colorPrimary: '#eb2f96',
algorithm: true, // Enable algorithm
},
},
}}
>
<Space>
<div style={{ fontSize: 14 }}>Algorithm Enabled:</div>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
<Divider />
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: '#00b96b',
},
Input: {
colorPrimary: '#eb2f96',
},
},
}}
>
<Space>
<div style={{ fontSize: 14 }}>Algorithm Disabled:</div>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
</>
);
export default App;

View File

@@ -0,0 +1,7 @@
## zh-CN
antd 默认内置了一些组件交互动效让企业级页面更加富有细节,在一些极端场景可能会影响页面交互性能,如需关闭动画可以 `token` 中的 `motion` 修改为 `false`
## en-US
Ant Design includes some built-in component interaction animations to make enterprise pages more detailed. In some extreme scenarios, this may affect page interaction performance. If you need to turn off animations, you can set `motion` in `token` to `false`.

View File

@@ -0,0 +1,37 @@
import React, { useEffect, useRef, useState } from 'react';
import { Checkbox, Col, ConfigProvider, Flex, Radio, Row, Switch } from 'antd';
const App: React.FC = () => {
const [checked, setChecked] = useState<boolean>(false);
const timerRef = useRef<ReturnType<typeof setInterval>>(null);
useEffect(() => {
timerRef.current = setInterval(() => {
setChecked((prev) => !prev);
}, 500);
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
const nodes = (
<Flex gap="small">
<Checkbox checked={checked}>Checkbox</Checkbox>
<Radio checked={checked}>Radio</Radio>
<Switch checked={checked} />
</Flex>
);
return (
<Row gutter={[24, 24]}>
<Col span={24}>{nodes}</Col>
<Col span={24}>
<ConfigProvider theme={{ token: { motion: false } }}>{nodes}</ConfigProvider>
</Col>
</Row>
);
};
export default App;

View File

@@ -0,0 +1,7 @@
## zh-CN
在 v5 中,动态切换主题对用户来说是非常简单的,你可以在任何时候通过 `ConfigProvider``theme` 属性来动态切换主题,而不需要任何额外配置。
## en-US
In v5, dynamic theme switching is very simple for users. You can dynamically switch themes through the `theme` property of `ConfigProvider` at any time without any additional configuration.

View File

@@ -0,0 +1,27 @@
import React, { useState } from 'react';
import { Button, ColorPicker, ConfigProvider, Divider, Input, Space } from 'antd';
const App: React.FC = () => {
const [primary, setPrimary] = useState('#1677ff');
return (
<>
<ColorPicker showText value={primary} onChange={(color) => setPrimary(color.toHexString())} />
<Divider />
<ConfigProvider
theme={{
token: {
colorPrimary: primary,
},
}}
>
<Space>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
</>
);
};
export default App;

View File

@@ -0,0 +1,7 @@
## zh-CN
这是一个最简单的 Ant Design 组件的在线 codesandbox 演示,展示 Ant Design React 的用法。
## en-US
Here is a simple online codesandbox demo to show the usage of Ant Design React.

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { Button, DatePicker, Space, version } from 'antd';
const App = () => (
<div style={{ padding: '0 24px' }}>
<h1>antd version: {version}</h1>
<Space>
<DatePicker />
<Button type="primary">Primary Button</Button>
</Space>
</div>
);
export default App;

View File

@@ -0,0 +1,7 @@
## zh-CN
可以嵌套使用 `ConfigProvider` 来实现局部主题的更换。在子主题中未被改变的 Design Token 将会继承父主题。
## en-US
You can nest `ConfigProvider` to achieve local theme changes. Design Tokens that have not been changed in the sub-theme will inherit from the parent theme.

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { Button, ConfigProvider, Space } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
token: {
colorPrimary: '#1677ff',
},
}}
>
<Space>
<Button type="primary">Theme 1</Button>
<ConfigProvider
theme={{
token: {
colorPrimary: '#00b96b',
},
}}
>
<Button type="primary">Theme 2</Button>
</ConfigProvider>
</Space>
</ConfigProvider>
);
export default App;

View File

@@ -0,0 +1,7 @@
## zh-CN
通过 `theme` 中的 `token` 属性,可以修改一些主题变量。部分主题变量会引起其他主题变量的变化,我们把这些主题变量称为 Seed Token。
## en-US
You can modify some theme variables through the `token` property in `theme`. Some theme variables will cause changes to other theme variables, which we call Seed Tokens.

View File

@@ -0,0 +1,24 @@
import React from 'react';
import { Button, ConfigProvider, Space } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
token: {
// Seed Token, affects wide range
colorPrimary: '#00b96b',
borderRadius: 2,
// Derived token, affects narrow range
colorBgContainer: '#f6ffed',
},
}}
>
<Space>
<Button type="primary">Primary</Button>
<Button>Default</Button>
</Space>
</ConfigProvider>
);
export default App;

View File

@@ -0,0 +1,7 @@
## zh-CN
通过修改算法可以快速生成风格迥异的主题,我们默认提供三套预设算法:默认算法 `theme.defaultAlgorithm`、暗色算法 `theme.darkAlgorithm` 和紧凑算法 `theme.compactAlgorithm`。你可以通过 `theme` 中的 `algorithm` 属性来切换算法,并且支持配置多种算法,将会依次生效。
## en-US
You can quickly generate distinct themes by modifying algorithms. We provide three preset algorithms by default: `theme.defaultAlgorithm`, `theme.darkAlgorithm`, and `theme.compactAlgorithm`. You can switch algorithms through the `algorithm` property in `theme`, and multiple algorithms can be configured, which will take effect in order.

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { Button, ConfigProvider, Input, Space, theme } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
// 1. Use dark algorithm alone
algorithm: theme.darkAlgorithm,
// 2. Combine dark algorithm and compact algorithm
// algorithm: [theme.darkAlgorithm, theme.compactAlgorithm],
}}
>
<Space>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
);
export default App;

View File

@@ -0,0 +1,7 @@
## zh-CN
如果你希望使用当前主题下的 Design Token我们提供了 `useToken` 这个 hook 来获取 Design Token。
## en-US
If you want to use the Design Token of the current theme, we provide a `useToken` hook to get it.

View File

@@ -0,0 +1,24 @@
import React from 'react';
import { theme } from 'antd';
const { useToken } = theme;
const App: React.FC = () => {
const { token } = useToken();
return (
<div
style={{
backgroundColor: token.colorPrimaryBg,
padding: token.padding,
borderRadius: token.borderRadius,
color: token.colorPrimaryText,
fontSize: token.fontSize,
}}
>
Use Design Token
</div>
);
};
export default App;

View File

@@ -18,26 +18,8 @@ Finally, if you are working in a local development environment, please refer to
Here is a simple online codesandbox demo of an Ant Design component to show the usage of Ant Design React.
```sandpack
const sandpackConfig = {
autorun: true,
};
import React from 'react';
import { Button, Space, DatePicker, version } from 'antd';
const App = () => (
<div style={{ padding: '0 24px' }}>
<h1>antd version: {version}</h1>
<Space>
<DatePicker />
<Button type="primary">Primary Button</Button>
</Space>
</div>
);
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/first-example.tsx">First Example</code>
Follow the steps below to play around with Ant Design yourself:

View File

@@ -16,26 +16,8 @@ Ant Design React 致力于提供给程序员**愉悦**的开发体验。
这是一个最简单的 Ant Design 组件的在线 codesandbox 演示。
```sandpack
const sandpackConfig = {
autorun: true,
};
import React from 'react';
import { Button, Space, DatePicker, version } from 'antd';
const App = () => (
<div style={{ padding: '0 24px' }}>
<h1>antd version: {version}</h1>
<Space>
<DatePicker />
<Button type="primary">Primary Button</Button>
</Space>
</div>
);
export default App;
```
<!-- prettier-ignore -->
<code src="./demo/first-example.tsx">第一个例子</code>
### 1. 创建一个 codesandbox

View File

@@ -26,7 +26,7 @@ In order to implement a good font system, the first thing is to choose an approp
'Noto Color Emoji';
```
> Referenceshttps://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ and http://markdotto.com/2018/02/07/github-system-fonts/
> References: https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ and https://markdotto.com/blog/github-system-fonts/
In addition, in many applications, numbers often need to be displayed vertically. We recommend setting the CSS property `font-variant-numeric` to `tabular-nums` to use [tabular figures](https://www.fonts.com/content/learning/fontology/level-3/numbers/proportional-vs-tabular-figures).

View File

@@ -26,7 +26,7 @@ title: 字体
'Noto Color Emoji';
```
> 参考自 https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ 和 http://markdotto.com/2018/02/07/github-system-fonts/
> 参考自 https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ 和 https://markdotto.com/blog/github-system-fonts/
另外,在中后台系统中,数字经常需要进行纵向对比展示,我们推荐将数字的字体 [font-variant-numeric](https://www.fonts.com/content/learning/fontology/level-3/numbers/proportional-vs-tabular-figures) 设置为 `tabular-nums`,使其为等宽字体。

View File

@@ -54,7 +54,7 @@
"deploy": "gh-pages -d _site -b gh-pages -f",
"deploy:china-mirror": "git checkout gh-pages && git pull origin gh-pages && git push git@gitee.com:ant-design/ant-design.git gh-pages -f",
"predist": "npm run version && npm run token:statistic && npm run token:meta && npm run style",
"dist": "antd-tools run dist",
"dist": "npm run ut-install-react-18 && antd-tools run dist",
"format": "biome format --write .",
"install-react-18": "npm i --no-save --legacy-peer-deps react@18 react-dom@18 @testing-library/react@16",
"ut-install-react-18": "ut i react@18 react-dom@18 @testing-library/react@16 --save-dev",
@@ -119,7 +119,7 @@
"@rc-component/checkbox": "~1.0.1",
"@rc-component/collapse": "~1.2.0",
"@rc-component/color-picker": "~3.0.3",
"@rc-component/dialog": "~1.8.2",
"@rc-component/dialog": "~1.8.3",
"@rc-component/drawer": "~1.4.1",
"@rc-component/dropdown": "~1.0.2",
"@rc-component/form": "~1.6.2",
@@ -151,7 +151,7 @@
"@rc-component/tree-select": "~1.6.0",
"@rc-component/trigger": "^3.9.0",
"@rc-component/upload": "~1.1.0",
"@rc-component/util": "^1.8.1",
"@rc-component/util": "^1.8.2",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
"scroll-into-view-if-needed": "^3.1.0",
@@ -175,7 +175,7 @@
"@emotion/css": "^11.13.5",
"@emotion/react": "^11.14.0",
"@emotion/server": "^11.11.0",
"@eslint-react/eslint-plugin": "^2.7.2",
"@eslint-react/eslint-plugin": "2.9.4",
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
"@inquirer/prompts": "^8.0.2",
"@madccc/duplicate-package-checker-webpack-plugin": "^1.0.0",
@@ -223,7 +223,7 @@
"adm-zip": "^0.5.16",
"ajv": "^8.17.1",
"ali-oss": "^6.23.0",
"antd-img-crop": "^4.27.0",
"antd-img-crop": "~4.27.0",
"antd-style": "^4.1.0",
"antd-token-previewer": "^3.0.0",
"axios": "^1.13.2",
@@ -237,7 +237,7 @@
"dekko": "^0.2.1",
"domparser-rs": "0.0.7",
"dotenv": "^17.2.3",
"dumi": "~2.4.21",
"dumi": "~2.4.23",
"dumi-plugin-color-chunk": "^2.1.0",
"env-paths": "^4.0.0",
"eslint": "^9.39.2",
@@ -268,7 +268,7 @@
"jest-image-snapshot": "^6.5.1",
"jest-puppeteer": "^11.0.0",
"jquery": "^4.0.0",
"jsdom": "^27.4.0",
"jsdom": "^28.0.0",
"jsonml-to-react-element": "^1.1.11",
"jsonml.js": "^0.1.0",
"lint-staged": "^16.2.7",
@@ -334,12 +334,12 @@
"size-limit": [
{
"path": "./dist/antd.min.js",
"limit": "524 KiB",
"limit": "434 KiB",
"gzip": true
},
{
"path": "./dist/antd-with-locales.min.js",
"limit": "618 KiB",
"limit": "526 KiB",
"gzip": true
}
],

View File

@@ -1,7 +1,32 @@
import util from 'util';
import React from 'react';
import type { DOMWindow } from 'jsdom';
import { MessagePort } from 'node:worker_threads';
import { ReadableStream } from 'node:stream/web';
if (typeof globalThis.ReadableStream === 'undefined') {
Object.defineProperty(
globalThis as typeof globalThis & { ReadableStream: typeof ReadableStream },
'ReadableStream',
{
value: ReadableStream,
writable: true,
configurable: true,
},
);
}
if (typeof globalThis.MessagePort === 'undefined') {
Object.defineProperty(
globalThis as typeof globalThis & { MessagePort: typeof MessagePort },
'MessagePort',
{
value: MessagePort,
writable: true,
configurable: true,
},
);
}
console.log('Current React Version:', React.version);
const originConsoleErr = console.error;

View File

@@ -72,7 +72,7 @@ function addPluginsForProduction(config) {
return newConfig;
}
let webpackConfig = getWebpackConfig(false, { enabledReactCompiler: true });
let webpackConfig = getWebpackConfig(false);
if (process.env.PRODUCTION_ONLY) {
console.log('🍐 Build production only');