diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index b6c34e021f..ea40291ea4 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -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 }} diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml index 40c96bffcc..6bf0f207d5 100644 --- a/.github/workflows/size-limit.yml +++ b/.github/workflows/size-limit.yml @@ -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 diff --git a/.github/workflows/visual-regression-diff-finish.yml b/.github/workflows/visual-regression-diff-finish.yml index bdb1719a92..0d01ff15b5 100644 --- a/.github/workflows/visual-regression-diff-finish.yml +++ b/.github/workflows/visual-regression-diff-finish.yml @@ -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 }} diff --git a/.github/workflows/visual-regression-persist-finish.yml b/.github/workflows/visual-regression-persist-finish.yml index 43273e85f5..1d9cf2a248 100644 --- a/.github/workflows/visual-regression-persist-finish.yml +++ b/.github/workflows/visual-regression-persist-finish.yml @@ -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 }} diff --git a/.jest.image.js b/.jest.image.js index 78191c45d4..8038d666d8 100644 --- a/.jest.image.js +++ b/.jest.image.js @@ -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', }, diff --git a/.jest.js b/.jest.js index 3a431c0f34..20a17166c0 100644 --- a/.jest.js +++ b/.jest.js @@ -11,6 +11,7 @@ const compileModules = [ 'parse5', '@exodus', 'jsdom', + '@csstools', ]; // cnpm use `_` as prefix @@ -62,7 +63,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', }, diff --git a/.jest.node.js b/.jest.node.js index e4631d406e..aad21544be 100644 --- a/.jest.node.js +++ b/.jest.node.js @@ -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'], 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', }, diff --git a/AGENTS.md b/AGENTS.md index f23dc2694e..940b9e95f3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,92 +1,166 @@ # AGENTS.md +> Ant Design 项目开发指南 - 为 AI 编程助手提供项目上下文和开发规范 + +## 📑 目录 + +- [项目背景](#项目背景) +- [快速开始](#快速开始) +- [代码规范](#代码规范) + - [基本编码规范](#基本编码规范) + - [命名规范](#命名规范) + - [TypeScript 规范](#typescript-规范) + - [样式规范](#样式规范) +- [开发指南](#开发指南) + - [测试指南](#测试指南) + - [演示代码规范](#演示代码规范) + - [国际化规范](#国际化规范) +- [文档和 Changelog](#文档和-changelog-规范) +- [Git 和 Pull Request](#git-和-pull-request-规范) +- [质量保证](#质量保证) +- [工具链和环境](#工具链和环境) + +--- + ## 项目背景 -这是 ant-design/ant-design(antd)的源代码仓库,是一个 React 组件库,发布为 npm 包 antd。 +这是 [ant-design/ant-design](https://github.com/ant-design/ant-design)(antd)的源代码仓库,是一个 React 组件库,发布为 npm 包 `antd`。 + +### 核心特性 - 使用 TypeScript 和 React 开发 - 兼容 React 18 ~ 19 版本 - 组件库设计精美,功能完善,广泛应用于企业级中后台产品 - 遵循 Ant Design 设计规范 -- 支持国际化 +- 支持国际化(i18n) +- 支持主题定制和暗色模式 +- 支持 RTL(从右到左)布局 -## 安装和设置 +--- + +## 快速开始 ### 开发环境要求 -- Node.js 版本 >= 16 -- 推荐使用 npm 或 yarn -- Chrome 80+ 浏览器兼容性 +- **Node.js**: >= 16 +- **包管理器**: npm 或 utoo +- **浏览器兼容性**: Chrome 80+ +- **编辑器**: VS Code(推荐)或其他支持 TypeScript 的编辑器 ### 安装依赖 ```bash npm install # 或 -yarn install +utoo install ``` -### 开发命令 +### 常用开发命令 ```bash -npm start # 启动开发服务器 -npm run build # 构建项目 -npm test # 运行测试 -npm run lint # 代码检查 +# 启动开发服务器(访问 http://127.0.0.1:8001) +npm start + +# 编译 TypeScript 代码到 lib 和 es 目录 +npm run compile + +# 构建 UMD 格式的构建产物 +npm run build + +# 运行所有测试 +npm test + +# 监听模式运行测试 +npm test -- --watch + +# 生成测试覆盖率报告 +npm run test:coverage + +# 代码检查(包括 TypeScript、ESLint、Biome、Markdown、Changelog) +npm run lint + +# 格式化代码 +npm run format + +# 生成 Changelog(交互式) +npm run changelog + +# 清理构建产物 +npm run clean ``` -## 代码风格指南 +### 项目结构 + +``` +ant-design/ +├── components/ # 组件源代码 +│ └── [component]/ # 单个组件目录 +│ ├── demo/ # 演示代码 +│ ├── style/ # 样式文件 +│ ├── index.tsx # 组件入口 +│ └── index.zh-CN.md # 组件文档 +├── scripts/ # 构建和工具脚本 +├── tests/ # 测试文件 +├── CHANGELOG.zh-CN.md # 中文更新日志 +├── CHANGELOG.en-US.md # 英文更新日志 +└── package.json # 项目配置 +``` + +--- + +## 代码规范 ### 基本编码规范 -- 使用 TypeScript 和 React 书写 -- 使用函数式组件和 hooks,避免类组件 -- 使用提前返回(early returns)提高代码可读性 -- 避免引入新依赖,严控打包体积 -- 兼容 Chrome 80+ 浏览器 -- 支持服务端渲染 -- 保持向下兼容,避免 breaking change -- 组件名使用大驼峰(PascalCase) -- 属性名使用小驼峰(camelCase) -- 合理使用 React.memo、useMemo 和 useCallback 优化性能 - -### 命名规范 - -Antd 命名要求使用**完整名称**而非缩写。 +- ✅ 使用 TypeScript 和 React 书写 +- ✅ 使用函数式组件和 Hooks,**避免类组件** +- ✅ 使用提前返回(early returns)提高代码可读性 +- ✅ 避免引入新依赖,严控打包体积 +- ✅ 兼容 Chrome 80+ 浏览器 +- ✅ 支持服务端渲染(SSR) +- ✅ 保持向下兼容,避免 breaking change +- ✅ 组件名使用大驼峰(PascalCase),如 `Button`、`DatePicker` +- ✅ 属性名使用小驼峰(camelCase),如 `onClick`、`defaultValue` +- ✅ 合理使用 `React.memo`、`useMemo` 和 `useCallback` 优化性能 #### Props 命名 -- 初始化属性:`default` + `PropName` -- 强制渲染:`forceRender` -- 子组件强制渲染:`force` + `Sub Component Name` + `Render` -- 子组件渲染:`Sub Component Name` + `Render` -- 数据源:`dataSource` -- 面板开启:使用 `open`,避免使用 `visible` -- 显示相关:`show` + `PropName` -- 功能性:`PropName` + `able` -- 禁用:`disabled` -- 额外内容:`extra` -- 图标:`icon` -- 触发器:`trigger` -- 类名:`className` +| 用途 | 命名规则 | 示例 | +| -------------- | --------------------------------------- | ----------------------------- | +| 初始化属性 | `default` + `PropName` | `defaultValue`、`defaultOpen` | +| 强制渲染 | `forceRender` | `forceRender` | +| 子组件强制渲染 | `force` + `SubComponentName` + `Render` | `forcePanelRender` | +| 子组件渲染 | `SubComponentName` + `Render` | `titleRender`、`footerRender` | +| 数据源 | `dataSource` | `dataSource` | +| 面板开启 | 使用 `open`,避免使用 `visible` | `open`、`defaultOpen` | +| 显示相关 | `show` + `PropName` | `showSearch`、`showHeader` | +| 功能性 | `PropName` + `able` | `disabled`、`readable` | +| 禁用 | `disabled` | `disabled` | +| 额外内容 | `extra` | `extra` | +| 图标 | `icon` | `icon`、`prefixIcon` | +| 触发器 | `trigger` | `trigger` | +| 类名 | `className` | `className` | #### 事件命名 -- 触发事件:`on` + `EventName` -- 子组件事件:`on` + `SubComponentName` + `EventName` -- 前置事件:`before` + `EventName` -- 后置事件:`after` + `EventName` -- 连续动作完成:`on` + `EventName` + `Complete` +| 类型 | 命名规则 | 示例 | +| ------------ | --------------------------------------- | --------------------- | +| 触发事件 | `on` + `EventName` | `onClick`、`onChange` | +| 子组件事件 | `on` + `SubComponentName` + `EventName` | `onPanelChange` | +| 前置事件 | `before` + `EventName` | `beforeUpload` | +| 后置事件 | `after` + `EventName` | `afterClose` | +| 连续动作完成 | `on` + `EventName` + `Complete` | `onUploadComplete` | -#### 组件引用 +#### 组件引用(Ref) 组件应提供 `ref` 属性,结构如下: ```tsx -ComponentRef { +interface ComponentRef { nativeElement: HTMLElement; focus: VoidFunction; - // 其他函数 + blur: VoidFunction; + // 其他方法... } ``` @@ -94,54 +168,99 @@ ComponentRef { 格式:`variant (optional)` + `semantic part` + `semantic part variant (optional)` + `css property` + `size/disabled (optional)` +示例: + +- `buttonPrimaryColor` - Button 主色 +- `inputPaddingBlock` - Input 垂直内边距 +- `menuItemActiveBg` - Menu 激活项背景色 + ### API 文档规范 -| Property | Description | Type | Default | -| --------- | ----------- | ---------------------------- | ------------ | -| htmlType | xxx | string | `button ` | -| type | xxx | `horizontal ` \| `vertical ` | `horizontal` | -| disabled | xxx | boolean | false | -| minLength | xxx | number | 0 | -| style | xxx | CSSProperties | - | +#### API 表格格式 + +| Property | Description | Type | Default | +| --------- | --------------- | ------------------------------------------------------ | --------- | +| htmlType | Button 原生类型 | string | `button` | +| type | 按钮类型 | `primary` \| `default` \| `dashed` \| `link` \| `text` | `default` | +| disabled | 是否禁用 | boolean | false | +| minLength | 最小长度 | number | 0 | +| style | 自定义样式 | CSSProperties | - | #### API 文档要求 -- 字符串类型的默认值使用反引号 -- 布尔类型直接使用 true 或 false -- 数字类型直接使用数字 -- 函数类型使用箭头函数表达式 -- 无默认值使用 `-` -- 描述首字母大写,结尾无句号 -- API 按字母顺序排列 +- ✅ 字符串类型的默认值使用反引号包裹,如 `` `button` `` +- ✅ 布尔类型直接使用 `true` 或 `false` +- ✅ 数字类型直接使用数字,如 `0`、`100` +- ✅ 函数类型使用箭头函数表达式,如 `(e: Event) => void` +- ✅ 无默认值使用 `-` +- ✅ 描述首字母大写,结尾无句号 +- ✅ API 按字母顺序排列 + +--- ## TypeScript 规范 ### 基本原则 -- 所有组件和函数必须提供准确的类型定义 -- 避免使用 `any` 类型,尽可能精确地定义类型 -- 使用接口而非类型别名定义对象结构 -- 导出所有公共接口类型,方便用户使用 -- 严格遵循 TypeScript 类型设计原则,确保类型安全 -- 确保编译无任何类型错误或警告 +- ✅ 所有组件和函数必须提供准确的类型定义 +- ✅ 避免使用 `any` 类型,尽可能精确地定义类型 +- ✅ 使用接口(interface)而非类型别名(type)定义对象结构 +- ✅ 导出所有公共接口类型,方便用户使用 +- ✅ 严格遵循 TypeScript 类型设计原则,确保类型安全 +- ✅ 确保编译无任何类型错误或警告 ### 组件类型定义 -- 组件 props 应使用 interface 定义,便于扩展 -- 组件 props 接口命名应为 `ComponentNameProps` -- 为组件状态定义专门的接口,如 `ComponentNameState` -- 复杂的数据结构应拆分为多个接口定义 -- 组件的 ref 类型应该明确定义,使用 `React.ForwardRefRenderFunction` -- 所有回调函数类型应明确定义参数和返回值 +```tsx +// ✅ 正确:使用 interface 定义 Props +interface ButtonProps { + type?: 'primary' | 'default' | 'dashed'; + onClick?: (e: React.MouseEvent) => void; +} + +// ❌ 错误:避免使用 type 定义对象结构 +type ButtonProps = { + type?: 'primary' | 'default'; +}; + +// ✅ 正确:组件 Props 接口命名 +interface ComponentNameProps { + // ... +} + +// ✅ 正确:组件状态接口命名 +interface ComponentNameState { + // ... +} + +// ✅ 正确:使用 ForwardRefRenderFunction 定义 ref +const Component = React.forwardRef((props, ref) => { + // ... +}); +``` ### 类型使用最佳实践 -- 适当使用泛型增强类型灵活性 -- 使用交叉类型(&)合并多个类型 -- 使用字面量联合类型定义有限的选项集合 -- 避免使用 `enum`,优先使用联合类型和 `as const` -- 尽可能依赖 TypeScript 的类型推断 -- 只在必要时使用类型断言(as) +- ✅ 适当使用泛型增强类型灵活性 +- ✅ 使用交叉类型(&)合并多个类型 +- ✅ 使用字面量联合类型定义有限的选项集合 +- ✅ 避免使用 `enum`,优先使用联合类型和 `as const` +- ✅ 尽可能依赖 TypeScript 的类型推断 +- ✅ 只在必要时使用类型断言(`as`) + +```tsx +// ✅ 推荐:使用联合类型和 as const +const ButtonTypes = ['primary', 'default', 'dashed'] as const; +type ButtonType = (typeof ButtonTypes)[number]; + +// ❌ 不推荐:使用 enum +enum ButtonType { + Primary = 'primary', + Default = 'default', +} +``` + +--- ## 样式规范 @@ -165,17 +284,17 @@ ComponentRef { ### 响应式和主题支持 -- 组件应支持在不同屏幕尺寸下良好展示 -- 所有组件必须支持暗色模式 -- 组件应支持从右到左(RTL)的阅读方向 -- 使用 CSS 逻辑属性(如 margin-inline-start)替代方向性属性(如 margin-left) -- 支持通过 ConfigProvider 进行主题定制 +- ✅ 组件应支持在不同屏幕尺寸下良好展示 +- ✅ 所有组件必须支持暗色模式 +- ✅ 组件应支持从右到左(RTL)的阅读方向 +- ✅ 使用 CSS 逻辑属性(如 `margin-inline-start`)替代方向性属性(如 `margin-left`) +- ✅ 支持通过 `ConfigProvider` 进行主题定制 ### 动画效果 - 使用 CSS 过渡实现简单动画 -- 复杂动画使用 @rc-component/motion 实现 -- 尊重用户的减少动画设置(prefers-reduced-motion) +- 复杂动画使用 `@rc-component/motion` 实现 +- 尊重用户的减少动画设置(`prefers-reduced-motion`) - 动画时长和缓动函数应保持一致性 - 动画不应干扰用户的操作和阅读体验 @@ -188,67 +307,108 @@ ComponentRef { - 支持用户放大页面至 200% 时的正常布局 - 避免使用会导致闪烁的动画 -## 测试指南 +--- -### 测试框架和工具 +## 开发指南 + +### 测试指南 + +#### 测试框架和工具 - 使用 Jest 和 React Testing Library 编写单元测试 -- 对 UI 组件使用快照测试 (Snapshot Testing) -- 测试覆盖率要求 100% -- 测试文件放在 **tests** 目录,命名格式为:index.test.tsx 或 xxx.test.tsx +- 对 UI 组件使用快照测试(Snapshot Testing) +- 测试覆盖率要求 **100%** +- 测试文件放在 `tests/` 目录,命名格式为:`index.test.tsx` 或 `xxx.test.tsx` -### 运行测试 +#### 运行测试 ```bash npm test # 运行所有测试 -npm test -- --watch # 监听模式 -npm run test:coverage # 生成覆盖率报告 +npm test -- --watch # 监听模式 +npm run test:coverage # 生成覆盖率报告 +npm run test:image # UI 快照测试(需要 Docker) ``` -## 演示代码规范 +#### 测试最佳实践 -### Demo 基本要求 +- ✅ 测试用户行为而非实现细节 +- ✅ 使用有意义的测试描述 +- ✅ 每个测试用例应该独立,不依赖其他测试 +- ✅ 测试边界情况和错误处理 -- demo 代码尽可能简洁 -- 避免冗余代码,方便用户复制到项目直接使用 -- 每个 demo 聚焦展示一个功能点 -- 提供中英文两个版本的说明 -- 遵循展示优先原则,确保视觉效果良好 -- 展示组件的主要使用场景 -- 按照由简到繁的顺序排列 demo +### 演示代码规范 -### 文件组织 +#### Demo 基本要求 + +- ✅ demo 代码尽可能简洁 +- ✅ 避免冗余代码,方便用户复制到项目直接使用 +- ✅ 每个 demo 聚焦展示一个功能点 +- ✅ 提供中英文两个版本的说明 +- ✅ 遵循展示优先原则,确保视觉效果良好 +- ✅ 展示组件的主要使用场景 +- ✅ 按照由简到繁的顺序排列 demo + +#### 文件组织 - 每个组件演示包含 `.md`(说明文档)和 `.tsx`(实际代码)两个文件 - 位置:组件目录下的 `demo` 子目录,如 `components/button/demo/` - 命名:短横线连接的小写英文单词,如 `basic.tsx`、`custom-filter.tsx` - 文件名应简洁地描述示例内容 -### TSX 代码规范 +#### TSX 代码规范 + +```tsx +// ✅ 正确的导入顺序 +import React, { useState } from 'react'; +import { Button, Space } from 'antd'; +import type { ButtonProps } from 'antd'; + +import './custom.css'; + +// ✅ 使用函数式组件和 Hooks +const Demo: React.FC = () => { + const [loading, setLoading] = useState(false); + + const handleClick = () => { + setLoading(true); + // ... + }; + + return ( + + + + ); +}; + +export default Demo; +``` + +**规范要点**: - 导入顺序:React → 依赖库 → 组件库 → 自定义组件 → 类型 → 样式 - 类型:为复杂数据定义清晰接口,避免 `any` - 使用函数式组件和 Hooks -- 2空格缩进,箭头函数,驼峰命名 +- 2 空格缩进,箭头函数,驼峰命名 - 优先使用 antd 内置组件,减少外部依赖 - 性能优化:适当使用 `useMemo`/`useCallback`,清理副作用 -## 国际化规范 +### 国际化规范 -### 类型定义 +#### 类型定义 antd 的本地化配置的类型定义的入口文件是 `components/locale/index.tsx`,当需要添加新的本地化配置时,需要检查对应组件或全局配置的类型是否存在,如果不存在,则需要增加相应的类型描述。 -### 本地化配置 +#### 本地化配置 -- 本地化配置文件命名规则:`*_*.ts`,如:`zh_CN.ts` -- 通常在为 antd 添加后修改某一项本地化配置时,如无特殊说明,需要同时修改所有语言的本地化配置 +- 本地化配置文件命名规则:`*_*.ts`,如:`zh_CN.ts`、`en_US.ts` +- 通常在为 antd 添加或修改某一项本地化配置时,如无特殊说明,需要同时修改所有语言的本地化配置 - 本地化配置的内容通常是纯字符串 - 带有 `${}` 的变量将在实际使用的地方被实时替换成对应的变量内容 -### 使用本地化 - -使用 `components/locale/index.tsx` 文件中导出的 `useLocale` 获取全局上下文中配置的本地化: +#### 使用本地化 ```tsx import { useLocale } from '../locale'; @@ -256,7 +416,7 @@ import enUS from '../locale/en_US'; export function TestComp(props) { const { locale: propLocale } = props; - const [contextLocale] = useLocale('TestComp', enUs); + const [contextLocale] = useLocale('TestComp', enUS); const locale = { ...contextLocale, ...propLocale }; @@ -264,162 +424,105 @@ export function TestComp(props) { } ``` +--- + ## 文档和 Changelog 规范 ### 基本要求 -- 提供中英文两个版本 -- 新的属性需要声明可用的版本号 -- 属性命名符合 antd 的 API 命名规则 +- ✅ 提供中英文两个版本 +- ✅ 新的属性需要声明可用的版本号 +- ✅ 属性命名符合 antd 的 API 命名规则 ### 文档锚点 ID 规范 - 针对 Markdown 文件中的标题(# 到 ######)自动生成锚点 ID - - 所有中文标题(H1-H6)必须手动指定一个简洁、有意义的英文锚点。 - - 格式: ## 中文标题 {#english-anchor-id} - - 英文标题通常不需要手动指定锚点,但如果需要,可以使用相同的格式。 -- 锚点 ID 必须符合正则表达式 `^[a-zA-Z][\w-:\.]*$`, 且长度不应超过 32 个字符。 -- 用于演示(demo)且包含 `-demo-` 的 id 不受前面的长度限制。 -- FAQ 章节下的所有标题锚点必须以 `faq-` 作为前缀。 -- 为确保在不同语言间切换时锚点依然有效,同一问题的中英文锚点应保持完全一致。 - - 例如: - - 中文标题:`### 如何使用组件 {#how-to-use-component}` - - 英文标题:`### How to Use the Component {#how-to-use-component}` + - 所有中文标题(H1-H6)必须手动指定一个简洁、有意义的英文锚点 + - 格式: `## 中文标题 {#english-anchor-id}` + - 英文标题通常不需要手动指定锚点,但如果需要,可以使用相同的格式 +- 锚点 ID 必须符合正则表达式 `^[a-zA-Z][\w-:\.]*$`,且长度不应超过 32 个字符 +- 用于演示(demo)且包含 `-demo-` 的 id 不受前面的长度限制 +- FAQ 章节下的所有标题锚点必须以 `faq-` 作为前缀 +- 为确保在不同语言间切换时锚点依然有效,同一问题的中英文锚点应保持完全一致 + +**示例**: + +- 中文标题:`### 如何使用组件 {#how-to-use-component}` +- 英文标题:`### How to Use the Component {#how-to-use-component}` ### Changelog 规范 -- 在 CHANGELOG.en-US.md 和 CHANGELOG.zh-CN.md 书写每个版本的变更 -- 对用户使用上无感知的改动建议不要提及,保持 CHANGELOG 的内容有效性 -- 用面向开发者的角度和叙述方式撰写 CHANGELOG -- 描述用户的原始问题,而非解决方式 -- 尽量给出原始的 PR 链接,社区提交的 PR 改动加上提交者的链接 +#### 🎯 核心原则 -### 🎯 核心原则 +1. **文件位置**:在 `CHANGELOG.en-US.md` 和 `CHANGELOG.zh-CN.md` 书写每个版本的变更 -- 有效性过滤:忽略用户无感知的改动(如文档网站改进、纯测试用例更新、内部重构、工具链优化等),除非其对开发者有直接影响。 -- 开发者视角:描述“用户的原始问题”和“对开发者的影响”,而非“具体的解决代码”。 - - ❌ 修复 Typography 的 DOM 结构问题。 - - ✅ Typography: 💄 重构并简化了 Typography 的 DOM 结构,修复了内容空格丢失的问题。 -- 版本与命名: - - 新增属性必须符合 antd API 命名规则。 - - 新增属性建议在描述中暗示或明确声明可用版本号。 -- 双语输出:每次处理必须同时提供 **中文版** 和 **英文版**。 +2. **有效性过滤**:忽略用户无感知的改动(如文档网站改进、纯测试用例更新、内部重构、工具链优化等),除非其对开发者有直接影响。保持 CHANGELOG 的内容有效性。 -### 🎨 格式与结构规范 +3. **开发者视角**:用面向开发者的角度和叙述方式撰写 CHANGELOG,描述"用户的原始问题"和"对开发者的影响",而非"具体的解决代码"。 + - ❌ 修复 Typography 的 DOM 结构问题。 + - ✅ Typography: 💄 重构并简化了 Typography 的 DOM 结构,修复了内容空格丢失的问题。 -- 单条条目结构:`组件名称: 图标 描述内容 [#PR号](链接) [@贡献者]`。 -- 组件名\*需加粗,后接英文冒号和空格。 -- 分组逻辑: - - 多项改动:同一组件有 2 条及以上改动时,使用 `- 组件名` 作为分类标题(不加粗),具体条目缩进排列。 - - 单项改动:直接编写单行条目,不设分类标题。 -- 文本细节: - - 代码包裹:所有属性名、方法名、API、`role`/`aria` 属性必须使用反引号 ` ` 包裹。 - - 中英空格:中文与英文、数字、链接、`@` 用户名之间必须保留 **一个空格**。 +4. **版本与命名**: + - 新增属性必须符合 antd API 命名规则 + - 新增属性建议在描述中暗示或明确声明可用版本号 -### 🏷️ Emoji 规范 (严格执行) +5. **双语输出**:每次处理必须同时提供 **中文版** 和 **英文版** -- 🐞 修复 Bug -- 💄 样式更新或 token 更新 -- 🆕 新增特性 / 新增属性 -- 🔥 极其值得关注的新增特性 -- 🇺🇸🇨🇳🇬🇧 国际化改动 -- 📖 📝 文档或网站改进 -- ✅ 新增或更新测试用例 -- 🛎 更新警告/提示信息 -- ⌨️ ♿ 可访问性增强 -- 🗑 废弃或移除 -- 🛠 重构或工具链优化 -- ⚡️ 性能提升 +6. **PR 链接**:尽量给出原始的 PR 链接,社区提交的 PR 改动加上提交者的链接 ---- +#### 🎨 格式与结构规范 -### 💡 输出示例参考 - -```md -#### 中文 - -- 🐞 Drawer: 修复 Drawer.PurePanel 无法响应鼠标交互的问题。[#56387](https://github.com/ant-design/ant-design/pull/56387) [@wanpan11](https://github.com/wanpan11) -- 🐞 Select: 修复 Select options 属性透传至原生 DOM 导致 React 未知属性警告的问题。[#56341](https://github.com/ant-design/ant-design/pull/56341) [@afc163](https://github.com/afc163) - -#### English - -- 🐞 Drawer: Fix Drawer.PurePanel failing to respond to mouse interactions. [#56387](https://github.com/ant-design/ant-design/pull/56387) [@wanpan11](https://github.com/wanpan11) -- 🐞 Select: Fix Select `options` props leaking to DOM elements and causing React unknown-prop warnings. [#56341](https://github.com/ant-design/ant-design/pull/56341) [@afc163](https://github.com/afc163) -``` - -### 提示词 - -```md -### 🤖 角色定义 - -你是一位 Ant Design 核心维护者,负责编写 `CHANGELOG.zh-CN.md` 和 `CHANGELOG.en-US.md`。你需要将原始 PR 列表转化为专业、简洁、面向开发者的发布日志。 - -### 🎯 核心原则 - -1. **有效性过滤**:忽略用户无感知的改动(如文档网站改进、纯测试用例更新、内部重构、工具链优化等),除非其对开发者有直接影响。 -2. **开发者视角**:描述“用户的原始问题”和“对开发者的影响”,而非“具体的解决代码”。 - -- ❌ 修复 Typography 的 DOM 结构问题。 -- ✅ Typography: 💄 重构并简化了 Typography 的 DOM 结构,修复了内容空格丢失的问题。 - -3. **版本与命名**: - -- 新增属性必须符合 antd API 命名规则。 -- 新增属性建议在描述中暗示或明确声明可用版本号。 - -### 🎨 格式与结构规范 - -1. **单条条目结构**:`组件名称: 图标 描述内容 [#PR号](链接) [@贡献者]`。 - -- 组件名**无需加粗**,后接英文冒号和空格。 +1. **单条条目结构**:`组件名称: 图标 描述内容 [#PR号](链接) [@贡献者]` + - 组件名**无需加粗**,后接英文冒号和空格 2. **分组逻辑**: - -- **多项改动**:同一组件有 2 条及以上改动时,使用 `- 组件名` 作为分类标题(不加粗),具体条目缩进排列。 -- **单项改动**:直接编写单行条目,不设分类标题。 + - **多项改动**:同一组件有 2 条及以上改动时,使用 `- 组件名` 作为分类标题(不加粗),具体条目缩进排列 + - **单项改动**:直接编写单行条目,不设分类标题 3. **文本细节**: + - **代码包裹**:所有属性名、方法名、API、`role`/`aria` 属性必须使用反引号 `` ` `` 包裹 + - **中英空格**:中文与英文、数字、链接、`@` 用户名之间必须保留 **一个空格** -- **代码包裹**:所有属性名、方法名、API、`role`/`aria` 属性必须使用反引号 ` ` 包裹。 -- **中英空格**:中文与英文、数字、链接、`@` 用户名之间必须保留 **一个空格**。 +#### 🏷️ Emoji 规范(严格执行) -### 🏷️ Emoji 规范 (严格执行) +| Emoji | 用途 | +| ------ | ---------------------- | +| 🐞 | 修复 Bug | +| 💄 | 样式更新或 token 更新 | +| 🆕 | 新增特性 / 新增属性 | +| 🔥 | 极其值得关注的新增特性 | +| 🇺🇸🇨🇳🇬🇧 | 国际化改动 | +| 📖 📝 | 文档或网站改进 | +| ✅ | 新增或更新测试用例 | +| 🛎 | 更新警告/提示信息 | +| ⌨️ ♿ | 可访问性增强 | +| 🗑 | 废弃或移除 | +| 🛠 | 重构或工具链优化 | +| ⚡️ | 性能提升 | -- 🐞 修复 Bug -- 💄 样式更新或 token 更新 -- 🆕 新增特性 / 新增属性 -- 🔥 极其值得关注的新增特性 -- 🇺🇸🇨🇳🇬🇧 国际化改动 -- 📖 📝 文档或网站改进 -- ✅ 新增或更新测试用例 -- 🛎 更新警告/提示信息 -- ⌨️ ♿ 可访问性增强 -- 🗑 废弃或移除 -- 🛠 重构或工具链优化 -- ⚡️ 性能提升 +#### 💡 输出示例参考 ---- - -### 🏗️ 任务执行:请处理以下 PR 数据 - -#### 💡 输出示例参考: - -**中文版:** +**中文版**: +```markdown - 🐞 Drawer: 修复 Drawer.PurePanel 无法响应鼠标交互的问题。[#56387](https://github.com/ant-design/ant-design/pull/56387) [@wanpan11](https://github.com/wanpan11) -- 🐞 Select: 修复 Select options 属性透传至原生 DOM 导致 React 未知属性警告的问题。[#56341](https://github.com/ant-design/ant-design/pull/56341) [@afc163](https://github.com/afc163) +- 🐞 Select: 修复 Select `options` 属性透传至原生 DOM 导致 React 未知属性警告的问题。[#56341](https://github.com/ant-design/ant-design/pull/56341) [@afc163](https://github.com/afc163) +``` -**English Version:** +**English Version**: +```markdown - 🐞 Drawer: Fix Drawer.PurePanel failing to respond to mouse interactions. [#56387](https://github.com/ant-design/ant-design/pull/56387) [@wanpan11](https://github.com/wanpan11) - 🐞 Select: Fix Select `options` props leaking to DOM elements and causing React unknown-prop warnings. [#56341](https://github.com/ant-design/ant-design/pull/56341) [@afc163](https://github.com/afc163) ``` +--- + ## Git 和 Pull Request 规范 ### 分支管理 -禁止直接提交到以下保护分支: +**禁止直接提交到以下保护分支**: - `master`:主分支,用于发布 - `feature`:特性分支,用于开发新版本 @@ -435,18 +538,20 @@ export function TestComp(props) { ### 分支命名规范 -- 功能开发:`feat/description-of-feature` -- 问题修复:`fix/issue-number-or-description` -- 文档更新:`docs/what-is-changed` -- 代码重构:`refactor/what-is-changed` -- 样式修改:`style/what-is-changed` -- 测试相关:`test/what-is-changed` -- 构建相关:`build/what-is-changed` -- 持续集成:`ci/what-is-changed` -- 性能优化:`perf/what-is-changed` -- 依赖升级:`deps/package-name-version` +| 类型 | 格式 | 示例 | +| -------- | --------------------------------- | ----------------------------- | +| 功能开发 | `feat/description-of-feature` | `feat/add-dark-mode` | +| 问题修复 | `fix/issue-number-or-description` | `fix/button-style-issue` | +| 文档更新 | `docs/what-is-changed` | `docs/update-api-docs` | +| 代码重构 | `refactor/what-is-changed` | `refactor/button-component` | +| 样式修改 | `style/what-is-changed` | `style/fix-button-padding` | +| 测试相关 | `test/what-is-changed` | `test/add-button-tests` | +| 构建相关 | `build/what-is-changed` | `build/update-webpack-config` | +| 持续集成 | `ci/what-is-changed` | `ci/add-github-actions` | +| 性能优化 | `perf/what-is-changed` | `perf/optimize-render` | +| 依赖升级 | `deps/package-name-version` | `deps/upgrade-react-19` | -### 分支命名注意事项 +**分支命名注意事项**: 1. 使用小写字母 2. 使用连字符(-)分隔单词 @@ -460,8 +565,9 @@ export function TestComp(props) { - PR 标题始终使用英文 - 遵循格式:`类型: 简短描述` -- 例如:`fix: fix button style issues in Safari browser` -- 例如:`feat: add dark mode support` +- 示例: + - `fix: fix button style issues in Safari browser` + - `feat: add dark mode support` #### PR 内容 @@ -473,8 +579,8 @@ export function TestComp(props) { 提交 PR 时请使用项目中提供的模板: -- 英文模板(推荐):PULL_REQUEST_TEMPLATE.md -- 中文模板:PULL_REQUEST_TEMPLATE_CN.md +- 英文模板(推荐):`PULL_REQUEST_TEMPLATE.md` +- 中文模板:`PULL_REQUEST_TEMPLATE_CN.md` #### PR 提交注意事项 @@ -496,7 +602,7 @@ export function TestComp(props) { 4. **工具标注**: - 如果是用 Cursor 提交的代码,请在 PR body 末尾进行标注:`> Submitted by Cursor` -### PR 改动类型 +#### PR 改动类型 - 🆕 新特性提交 - 🐞 Bug 修复 @@ -508,31 +614,35 @@ export function TestComp(props) { - ⚡️ 性能优化 - 🌐 国际化改进 +--- + ## 质量保证 ### 代码质量要求 -- 确保代码运行正常,无控制台错误 -- 适配常见浏览器 -- 避免过时 API,及时更新到新推荐用法 -- 测试覆盖率达到 100% -- 通过所有 ESLint 和 TypeScript 检查 +- ✅ 确保代码运行正常,无控制台错误 +- ✅ 适配常见浏览器 +- ✅ 避免过时 API,及时更新到新推荐用法 +- ✅ 测试覆盖率达到 100% +- ✅ 通过所有 ESLint 和 TypeScript 检查 ### 性能要求 -- 避免不必要的重新渲染 -- 合理使用 React.memo、useMemo 和 useCallback -- 样式计算应当高效,避免重复计算 -- 图片和资源应当优化 -- 支持 Tree Shaking +- ✅ 避免不必要的重新渲染 +- ✅ 合理使用 `React.memo`、`useMemo` 和 `useCallback` +- ✅ 样式计算应当高效,避免重复计算 +- ✅ 图片和资源应当优化 +- ✅ 支持 Tree Shaking ### 兼容性要求 -- 支持 React 18 ~ 19 版本 -- 兼容 Chrome 80+ 浏览器 -- 支持服务端渲染 -- 保持向下兼容,避免 breaking change -- 支持 TypeScript 4.0+ +- ✅ 支持 React 18 ~ 19 版本 +- ✅ 兼容 Chrome 80+ 浏览器 +- ✅ 支持服务端渲染(SSR) +- ✅ 保持向下兼容,避免 breaking change +- ✅ 支持 TypeScript 4.0+ + +--- ## 工具链和环境 @@ -557,12 +667,11 @@ export function TestComp(props) { - 自动化发布流程 - 支持多环境部署 +--- + ## 参考资料 - [API Naming Rules](https://github.com/ant-design/ant-design/wiki/API-Naming-rules) - [#16048](https://github.com/ant-design/ant-design/issues/16048) - Current listing api & Chinese version - [#25066](https://github.com/ant-design/ant-design/issues/25066) - API standard in the document - -## 特别说明 - -如果使用 AI 编程助手(如 Cursor)进行开发,请在提交 PR 时在末尾标注:`> Submitted by Cursor` +- [Development Guide](https://github.com/ant-design/ant-design/wiki/Development) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 0dc7241f99..cd6618bf56 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -15,6 +15,19 @@ tag: vVERSION --- +## 6.2.3 + +`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) + - 🐞 Fix Button default tokens not taking effect. [#56719](https://github.com/ant-design/ant-design/pull/56719) [@unknowntocka](https://github.com/unknowntocka) + - 🐞 Fix Button `variant="solid"` borders displaying incorrectly inside Space.Compact. [#56486](https://github.com/ant-design/ant-design/pull/56486) [@Pareder](https://github.com/Pareder) +- 🐞 Fix Input.TextArea ref missing `nativeElement` property. [#56803](https://github.com/ant-design/ant-design/pull/56803) [@smith3816](https://github.com/smith3816) +- 🐞 Fix Flex default `align` not taking effect when using `orientation`. [#55950](https://github.com/ant-design/ant-design/pull/55950) [@YingtaoMo](https://github.com/YingtaoMo) +- 🐞 Fix Typography link selector specificity being too low causing styles to be overridden. [#56759](https://github.com/ant-design/ant-design/pull/56759) [@QDyanbing](https://github.com/QDyanbing) +- 🐞 Fix ColorPicker HEX input allowing invalid characters. [#56752](https://github.com/ant-design/ant-design/pull/56752) [@treephesians](https://github.com/treephesians) + ## 6.2.2 `2026-01-26` diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index cd2c42ab3f..4f33856b21 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -15,6 +15,19 @@ tag: vVERSION --- +## 6.2.3 + +`2026-02-02` + +- Button + - 🐞 修复 Button `defaultBg`、`defaultColor`、`defaultHoverColor` 和 `defaultActiveColor` token 不生效的问题。[#56238](https://github.com/ant-design/ant-design/pull/56238) [@ug-hero](https://github.com/ug-hero) + - 🐞 修复 Button 默认 token 不生效的问题。[#56719](https://github.com/ant-design/ant-design/pull/56719) [@unknowntocka](https://github.com/unknowntocka) + - 🐞 修复 Button `variant="solid"` 在 Space.Compact 中边框显示异常的问题。[#56486](https://github.com/ant-design/ant-design/pull/56486) [@Pareder](https://github.com/Pareder) +- 🐞 修复 Input.TextArea ref 缺少 `nativeElement` 属性的问题。[#56803](https://github.com/ant-design/ant-design/pull/56803) [@smith3816](https://github.com/smith3816) +- 🐞 修复 Flex 使用 `orientation` 时默认 `align` 不生效的问题。[#55950](https://github.com/ant-design/ant-design/pull/55950) [@YingtaoMo](https://github.com/YingtaoMo) +- 🐞 修复 Typography 链接选择器特异性过低导致样式被覆盖的问题。[#56759](https://github.com/ant-design/ant-design/pull/56759) [@QDyanbing](https://github.com/QDyanbing) +- 🐞 修复 ColorPicker HEX 输入框可以输入无效字符的问题。[#56752](https://github.com/ant-design/ant-design/pull/56752) [@treephesians](https://github.com/treephesians) + ## 6.2.2 `2026-01-26` diff --git a/components/_util/__tests__/useZIndex.test.tsx b/components/_util/__tests__/useZIndex.test.tsx index 1f2813cf53..76b1bb4c77 100644 --- a/components/_util/__tests__/useZIndex.test.tsx +++ b/components/_util/__tests__/useZIndex.test.tsx @@ -26,7 +26,7 @@ import { import type { ZIndexConsumer, ZIndexContainer } from '../hooks/useZIndex'; import { consumerBaseZIndexOffset, containerBaseZIndexOffset, useZIndex } from '../hooks/useZIndex'; import { resetWarned } from '../warning'; -import zIndexContext from '../zindexContext'; +import ZIndexContext from '../zindexContext'; // TODO: Remove this. Mock for React 19 jest.mock('react-dom', () => { @@ -45,7 +45,7 @@ const WrapWithProvider: React.FC { const [, contextZIndex] = useZIndex(container); - return {children}; + return {children}; }; const containerComponent: Partial< diff --git a/components/_util/hooks/useZIndex.ts b/components/_util/hooks/useZIndex.ts index cc8aa5f86b..e580e6021e 100644 --- a/components/_util/hooks/useZIndex.ts +++ b/components/_util/hooks/useZIndex.ts @@ -2,7 +2,7 @@ import React from 'react'; import useToken from '../../theme/useToken'; import { devUseWarning } from '../warning'; -import zIndexContext from '../zindexContext'; +import ZIndexContext from '../zindexContext'; export type ZIndexContainer = | 'Modal' @@ -61,7 +61,7 @@ export const useZIndex = ( customZIndex?: number, ): ReturnResult => { const [, token] = useToken(); - const parentZIndex = React.useContext(zIndexContext); + const parentZIndex = React.useContext(ZIndexContext); const isContainer = isContainerType(componentType); let result: ReturnResult; diff --git a/components/_util/wave/style.ts b/components/_util/wave/style.ts index dfaa50e54f..2780ce24dd 100644 --- a/components/_util/wave/style.ts +++ b/components/_util/wave/style.ts @@ -8,7 +8,14 @@ export interface ComponentToken {} export interface WaveToken extends FullToken<'Wave'> {} const genWaveStyle: GenerateStyle = (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 = (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(','), }, }, }, diff --git a/components/_util/zindexContext.ts b/components/_util/zindexContext.ts index 39df635c16..93e884547e 100644 --- a/components/_util/zindexContext.ts +++ b/components/_util/zindexContext.ts @@ -1,9 +1,9 @@ import React from 'react'; -const zIndexContext = React.createContext(undefined); +const ZIndexContext = React.createContext(undefined); if (process.env.NODE_ENV !== 'production') { - zIndexContext.displayName = 'zIndexContext'; + ZIndexContext.displayName = 'ZIndexContext'; } -export default zIndexContext; +export default ZIndexContext; diff --git a/components/alert/style/index.ts b/components/alert/style/index.ts index 920b4c80e4..89272659c3 100644 --- a/components/alert/style/index.ts +++ b/components/alert/style/index.ts @@ -98,9 +98,9 @@ export const genBaseStyle: GenerateStyle = (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`]: { diff --git a/components/anchor/style/index.ts b/components/anchor/style/index.ts index 646c796925..25e5ae0164 100644 --- a/components/anchor/style/index.ts +++ b/components/anchor/style/index.ts @@ -170,7 +170,9 @@ const genSharedAnchorHorizontalStyle: GenerateStyle = (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, }, diff --git a/components/button/demo/component-token.tsx b/components/button/demo/component-token.tsx index a7140816ee..91c6153f96 100644 --- a/components/button/demo/component-token.tsx +++ b/components/button/demo/component-token.tsx @@ -1,8 +1,222 @@ import React from 'react'; +import { SearchOutlined } from '@ant-design/icons'; import { Button, ConfigProvider, Flex } from 'antd'; const App: React.FC = () => ( +
Component Token
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Global Token
(
- - - - - - - - - - - - - - - - - ); diff --git a/components/button/style/token.ts b/components/button/style/token.ts index 330604a158..e12a92e204 100644 --- a/components/button/style/token.ts +++ b/components/button/style/token.ts @@ -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 diff --git a/components/button/style/variant.ts b/components/button/style/variant.ts index 34c9aba57e..edb6cbb68c 100644 --- a/components/button/style/variant.ts +++ b/components/button/style/variant.ts @@ -165,6 +165,7 @@ const genVariantStyle: GenerateStyle = (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 = (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 = (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 = (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 = (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, + }, }, }, ], diff --git a/components/card/style/index.ts b/components/card/style/index.ts index 222c70cd32..2c7dff9894 100644 --- a/components/card/style/index.ts +++ b/components/card/style/index.ts @@ -315,6 +315,7 @@ const genCardStyle: GenerateStyle = (token): CSSObject => { boxShadowTertiary, bodyPadding, extraColor, + motionDurationMid, } = token; return { @@ -379,7 +380,9 @@ const genCardStyle: GenerateStyle = (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', diff --git a/components/date-picker/style/index.ts b/components/date-picker/style/index.ts index 51ce8576f3..7485f1e4ac 100644 --- a/components/date-picker/style/index.ts +++ b/components/date-picker/style/index.ts @@ -101,7 +101,9 @@ const genPickerStyle: GenerateStyle = (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', @@ -180,7 +182,7 @@ const genPickerStyle: GenerateStyle = (token) => { color: colorTextQuaternary, lineHeight: 1, pointerEvents: 'none', - transition: `opacity ${motionDurationMid}, color ${motionDurationMid}`, + transition: ['opacity', 'color'].map((prop) => `${prop} ${motionDurationMid}`).join(', '), '> *': { verticalAlign: 'top', @@ -200,7 +202,7 @@ const genPickerStyle: GenerateStyle = (token) => { transform: 'translateY(-50%)', cursor: 'pointer', opacity: 0, - transition: `opacity ${motionDurationMid}, color ${motionDurationMid}`, + transition: ['opacity', 'color'].map((prop) => `${prop} ${motionDurationMid}`).join(', '), '> *': { verticalAlign: 'top', diff --git a/components/date-picker/style/util.ts b/components/date-picker/style/util.ts index 0642d6dbd8..fb4a99b750 100644 --- a/components/date-picker/style/util.ts +++ b/components/date-picker/style/util.ts @@ -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(), diff --git a/components/form/style/explain.ts b/components/form/style/explain.ts index 695fe83609..76c225c2ec 100644 --- a/components/form/style/explain.ts +++ b/components/form/style/explain.ts @@ -2,7 +2,7 @@ import type { FormToken } from '.'; import type { GenerateStyle } from '../../theme/internal'; const genFormValidateMotionStyle: GenerateStyle = (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 = (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 = (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)`, diff --git a/components/menu/style/horizontal.ts b/components/menu/style/horizontal.ts index e7d44c37e9..6b6f8f94cc 100644 --- a/components/menu/style/horizontal.ts +++ b/components/menu/style/horizontal.ts @@ -43,10 +43,9 @@ const getHorizontalStyle: GenerateStyle = (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 ===================== diff --git a/components/menu/style/index.ts b/components/menu/style/index.ts index 35da3e9ae2..91e2064b7a 100644 --- a/components/menu/style/index.ts +++ b/components/menu/style/index.ts @@ -505,7 +505,9 @@ const genSubMenuArrowStyle = (token: MenuToken): CSSObject => { width: menuArrowSize, color: 'currentcolor', transform: 'translateY(-50%)', - transition: `transform ${motionDurationSlow} ${motionEaseInOut}, opacity ${motionDurationSlow}`, + transition: ['transform', 'opacity'] + .map((prop) => `${prop} ${motionDurationSlow}`) + .join(','), }, '&-arrow': { @@ -516,12 +518,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,26 +618,26 @@ const getBaseStyle: GenerateStyle = (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`]: { transition: [ - `border-color ${motionDurationSlow} ${motionEaseInOut}`, - `background-color ${motionDurationSlow} ${motionEaseInOut}`, - `padding ${motionDurationMid} ${motionEaseInOut}`, - ].join(','), + `border-color ${motionDurationSlow}`, + `background-color ${motionDurationSlow}`, + `padding ${motionDurationMid}`, + ] + .map((prop) => `${prop} ${motionEaseInOut}`) + .join(','), }, [`${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`]: { diff --git a/components/menu/style/theme.ts b/components/menu/style/theme.ts index 5054e127ae..0cf3f201d1 100644 --- a/components/menu/style/theme.ts +++ b/components/menu/style/theme.ts @@ -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(','), }, }, }, diff --git a/components/modal/Modal.tsx b/components/modal/Modal.tsx index 846bd60e40..a88b1f30a8 100644 --- a/components/modal/Modal.tsx +++ b/components/modal/Modal.tsx @@ -16,7 +16,7 @@ import { getTransitionName } from '../_util/motion'; import type { Breakpoint } from '../_util/responsiveObserver'; import { canUseDocElement } from '../_util/styleChecker'; import { devUseWarning } from '../_util/warning'; -import zIndexContext from '../_util/zindexContext'; +import ZIndexContext from '../_util/zindexContext'; import { ConfigContext } from '../config-provider'; import { useComponentConfig } from '../config-provider/context'; import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; @@ -241,7 +241,7 @@ const Modal: React.FC = (props) => { // =========================== Render =========================== return ( - + = (props) => { children )} - + ); }; diff --git a/components/modal/__tests__/confirm.test.tsx b/components/modal/__tests__/confirm.test.tsx index 50074e7fae..f99c844a24 100644 --- a/components/modal/__tests__/confirm.test.tsx +++ b/components/modal/__tests__/confirm.test.tsx @@ -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 diff --git a/components/modal/__tests__/hook.test.tsx b/components/modal/__tests__/hook.test.tsx index 4a738fa45f..9fd221e8e1 100644 --- a/components/modal/__tests__/hook.test.tsx +++ b/components/modal/__tests__/hook.test.tsx @@ -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(); diff --git a/components/modal/style/index.ts b/components/modal/style/index.ts index 4392078a45..64dfa89395 100644 --- a/components/modal/style/index.ts +++ b/components/modal/style/index.ts @@ -184,7 +184,7 @@ export const genModalMaskStyle: GenerateStyle> = }; const genModalStyle: GenerateStyle = (token) => { - const { componentCls } = token; + const { componentCls, motionDurationMid } = token; return [ // ======================== Root ========================= @@ -284,8 +284,9 @@ const genModalStyle: GenerateStyle = (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, diff --git a/components/notification/style/index.ts b/components/notification/style/index.ts index ab983b6f0d..40ff472540 100644 --- a/components/notification/style/index.ts +++ b/components/notification/style/index.ts @@ -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', diff --git a/components/popover/style/index.ts b/components/popover/style/index.ts index b3a2a00b3f..5a363236f5 100644 --- a/components/popover/style/index.ts +++ b/components/popover/style/index.ts @@ -108,7 +108,7 @@ const genBaseStyle: GenerateStyle = (token) => { userSelect: 'text', // When use `autoArrow`, origin will follow the arrow position - [varName('valid-offset-x')]: varRef('arrow-offset-horizontal', 'var(--arrow-x)'), + [varName('valid-offset-x')]: varRef('arrow-offset-x', 'var(--arrow-x)'), transformOrigin: [ varRef('valid-offset-x', FALL_BACK_ORIGIN), `var(--arrow-y, ${FALL_BACK_ORIGIN})`, diff --git a/components/radio/style/index.ts b/components/radio/style/index.ts index c7799e8c68..2bc8210f3e 100644 --- a/components/radio/style/index.ts +++ b/components/radio/style/index.ts @@ -388,11 +388,9 @@ const getRadioButtonStyle: GenerateStyle = (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, diff --git a/components/segmented/style/index.ts b/components/segmented/style/index.ts index 9296dfb7ce..39134536b5 100644 --- a/components/segmented/style/index.ts +++ b/components/segmented/style/index.ts @@ -78,7 +78,7 @@ const segmentedTextEllipsisCss: CSSObject = { // ============================== Styles ============================== const genSegmentedStyle: GenerateStyle = (token: SegmentedToken) => { - const { componentCls } = token; + const { componentCls, motionDurationSlow, motionEaseInOut, motionDurationMid } = token; const labelHeight = token .calc(token.controlHeight) @@ -102,7 +102,7 @@ const genSegmentedStyle: GenerateStyle = (token: SegmentedToken) color: token.itemColor, background: token.trackBg, borderRadius: token.borderRadius, - transition: `all ${token.motionDurationMid}`, + transition: `all ${motionDurationMid}`, ...genFocusStyle(token), [`${componentCls}-group`]: { @@ -146,7 +146,7 @@ const genSegmentedStyle: GenerateStyle = (token: SegmentedToken) position: 'relative', textAlign: 'center', cursor: 'pointer', - transition: `color ${token.motionDurationMid}`, + transition: `color ${motionDurationMid}`, borderRadius: token.borderRadiusSM, // Fix Safari render bug // https://github.com/ant-design/ant-design/issues/45250 @@ -169,10 +169,12 @@ const genSegmentedStyle: GenerateStyle = (token: SegmentedToken) insetInlineStart: 0, borderRadius: 'inherit', opacity: 0, - transition: `opacity ${token.motionDurationMid}, background-color ${token.motionDurationMid}`, // This is mandatory to make it not clickable or hoverable // Ref: https://github.com/ant-design/ant-design/issues/40888 pointerEvents: 'none', + transition: ['opacity', 'background-color'] + .map((prop) => `${prop} ${motionDurationMid}`) + .join(', '), }, [`&:not(${componentCls}-item-selected):not(${componentCls}-item-disabled)`]: { @@ -262,8 +264,10 @@ const genSegmentedStyle: GenerateStyle = (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`]: { diff --git a/components/select/demo/search-box.tsx b/components/select/demo/search-box.tsx index a5648997b3..3889dd51e9 100644 --- a/components/select/demo/search-box.tsx +++ b/components/select/demo/search-box.tsx @@ -1,4 +1,3 @@ -/* eslint-disable compat/compat */ import React, { useState } from 'react'; import { Select } from 'antd'; import type { SelectProps } from 'antd'; diff --git a/components/select/style/index.ts b/components/select/style/index.ts index 051069b86b..86fbe7faed 100644 --- a/components/select/style/index.ts +++ b/components/select/style/index.ts @@ -13,7 +13,7 @@ export type { ComponentToken }; // =============================== Base =============================== const genBaseStyle: GenerateStyle = (token) => { - const { antCls, componentCls, inputPaddingHorizontalBase } = token; + const { antCls, componentCls, motionDurationMid, inputPaddingHorizontalBase } = token; const hoverShowClearStyle: CSSObject = { [`${componentCls}-clear`]: { @@ -66,7 +66,9 @@ const genBaseStyle: GenerateStyle = (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 diff --git a/components/slider/style/index.ts b/components/slider/style/index.ts index 51f7b7a4d3..c1569c7cc3 100644 --- a/components/slider/style/index.ts +++ b/components/slider/style/index.ts @@ -228,14 +228,16 @@ const genBaseStyle: GenerateStyle = (token) => { outline: `0px solid transparent`, borderRadius: '50%', cursor: 'pointer', - transition: ` - inset-inline-start ${motionDurationMid}, - inset-block-start ${motionDurationMid}, - width ${motionDurationMid}, - height ${motionDurationMid}, - box-shadow ${motionDurationMid}, - outline ${motionDurationMid} - `, + transition: [ + 'inset-inline-start', + 'inset-block-start', + 'width', + 'height', + 'box-shadow', + 'outline', + ] + .map((prop) => `${prop} ${motionDurationMid}`) + .join(', '), }, '&:hover, &:active, &:focus': { diff --git a/components/spin/style/index.ts b/components/spin/style/index.ts index c29449a746..06863abe94 100644 --- a/components/spin/style/index.ts +++ b/components/spin/style/index.ts @@ -41,7 +41,7 @@ const antRotate = new Keyframes('antRotate', { }); const genSpinStyle: GenerateStyle = (token: SpinToken): CSSObject => { - const { componentCls, calc } = token; + const { componentCls, motionDurationSlow, calc } = token; return { [componentCls]: { ...resetComponent(token), @@ -52,7 +52,7 @@ const genSpinStyle: GenerateStyle = (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 = (token: SpinToken): CSSObject => [`${componentCls}-container`]: { position: 'relative', - transition: `opacity ${token.motionDurationSlow}`, + transition: `opacity ${motionDurationSlow}`, '&::after': { position: 'absolute', @@ -161,7 +161,7 @@ const genSpinStyle: GenerateStyle = (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 = (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 = (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', diff --git a/components/style/index.tsx b/components/style/index.tsx index c282890b92..981248d10f 100644 --- a/components/style/index.tsx +++ b/components/style/index.tsx @@ -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 => ({ diff --git a/components/style/motion/collapse.ts b/components/style/motion/collapse.ts index 37cf3c53ce..e07b7a76c7 100644 --- a/components/style/motion/collapse.ts +++ b/components/style/motion/collapse.ts @@ -1,23 +1,26 @@ import type { AliasToken, GenerateStyle, TokenWithCommonCls } from '../../theme/internal'; -const genCollapseMotion: GenerateStyle> = (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> = (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; diff --git a/components/style/placementArrow.ts b/components/style/placementArrow.ts index cf3a81f30c..ed8e22f871 100644 --- a/components/style/placementArrow.ts +++ b/components/style/placementArrow.ts @@ -106,7 +106,7 @@ const getArrowStyle = < }, '&-placement-topLeft': { - [varName('arrow-offset-horizontal')]: arrowOffsetHorizontal, + [varName('arrow-offset-x')]: arrowOffsetHorizontal, [`> ${componentCls}-arrow`]: { left: { @@ -117,7 +117,7 @@ const getArrowStyle = < }, '&-placement-topRight': { - [varName('arrow-offset-horizontal')]: `calc(100% - ${unit(arrowOffsetHorizontal)})`, + [varName('arrow-offset-x')]: `calc(100% - ${unit(arrowOffsetHorizontal)})`, [`> ${componentCls}-arrow`]: { right: { @@ -148,7 +148,7 @@ const getArrowStyle = < }, '&-placement-bottomLeft': { - [varName('arrow-offset-horizontal')]: arrowOffsetHorizontal, + [varName('arrow-offset-x')]: arrowOffsetHorizontal, [`> ${componentCls}-arrow`]: { left: { @@ -159,7 +159,7 @@ const getArrowStyle = < }, '&-placement-bottomRight': { - [varName('arrow-offset-horizontal')]: `calc(100% - ${unit(arrowOffsetHorizontal)})`, + [varName('arrow-offset-x')]: `calc(100% - ${unit(arrowOffsetHorizontal)})`, [`> ${componentCls}-arrow`]: { right: { diff --git a/components/switch/style/index.ts b/components/switch/style/index.ts index f7745c8c95..7cf19db0a5 100644 --- a/components/switch/style/index.ts +++ b/components/switch/style/index.ts @@ -250,6 +250,7 @@ const genSwitchInnerStyle: GenerateStyle = (token) => { innerMinMargin, innerMaxMargin, handleSize, + switchDuration, calc, } = token; const switchInnerCls = `${componentCls}-inner`; @@ -266,15 +267,19 @@ const genSwitchInnerStyle: GenerateStyle = (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`]: { diff --git a/components/table/demo/ajax.tsx b/components/table/demo/ajax.tsx index cbd198380e..6282b8ed5a 100644 --- a/components/table/demo/ajax.tsx +++ b/components/table/demo/ajax.tsx @@ -1,4 +1,3 @@ -/* eslint-disable compat/compat */ import React, { useEffect, useState } from 'react'; import type { GetProp, TableProps } from 'antd'; import { Table } from 'antd'; diff --git a/components/table/style/index.ts b/components/table/style/index.ts index 42e1dcaf44..5edb7b9339 100644 --- a/components/table/style/index.ts +++ b/components/table/style/index.ts @@ -345,8 +345,10 @@ const genTableStyle: GenerateStyle = (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 =========================== [` diff --git a/components/tabs/style/index.ts b/components/tabs/style/index.ts index f68724570e..a1ac04c24c 100644 --- a/components/tabs/style/index.ts +++ b/components/tabs/style/index.ts @@ -369,6 +369,7 @@ const genPositionStyle: GenerateStyle = (token: TabsToken): CSSObject horizontalMargin, verticalItemPadding, verticalItemMargin, + motionDurationSlow, calc, } = token; return { @@ -397,8 +398,9 @@ const genPositionStyle: GenerateStyle = (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 = (token: TabsToken): CSSObject width: token.lineWidthBold, '&-animated': { - transition: `height ${token.motionDurationSlow}, top ${token.motionDurationSlow}`, + transition: ['height', 'top'].map((prop) => `${prop} ${motionDurationSlow}`).join(', '), }, }, diff --git a/components/theme/themes/dark/index.ts b/components/theme/themes/dark/index.ts index 746905a318..56d8441bcb 100644 --- a/components/theme/themes/dark/index.ts +++ b/components/theme/themes/dark/index.ts @@ -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 = (token, mapToken) => { generateNeutralColorPalettes, }); + const presetColorHoverActiveTokens = PresetColors.reduce>( + (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 = (token, mapToken) => { // Colors ...colorMapToken, + ...presetColorHoverActiveTokens, + // Customize selected item background color // https://github.com/ant-design/ant-design/issues/30524#issuecomment-871961867 colorPrimaryBg: colorMapToken.colorPrimaryBorder, diff --git a/components/theme/themes/shared/genColorMapToken.ts b/components/theme/themes/shared/genColorMapToken.ts index bfc96efb3a..aef704624e 100644 --- a/components/theme/themes/shared/genColorMapToken.ts +++ b/components/theme/themes/shared/genColorMapToken.ts @@ -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 = {}; + 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', }; diff --git a/components/tooltip/index.tsx b/components/tooltip/index.tsx index ca2dd4e041..420f5563e4 100644 --- a/components/tooltip/index.tsx +++ b/components/tooltip/index.tsx @@ -20,7 +20,7 @@ import getPlacements from '../_util/placements'; import { cloneElement, isFragment } from '../_util/reactNode'; import type { LiteralUnion } from '../_util/type'; import { devUseWarning } from '../_util/warning'; -import zIndexContext from '../_util/zindexContext'; +import ZIndexContext from '../_util/zindexContext'; import { useComponentConfig } from '../config-provider/context'; import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; import { useToken } from '../theme/internal'; @@ -399,7 +399,7 @@ const InternalTooltip = React.forwardRef((prop ); - return {content}; + return {content}; }); type CompoundedComponent = typeof InternalTooltip & { diff --git a/components/tooltip/style/index.ts b/components/tooltip/style/index.ts index 647b757f21..6511fad009 100644 --- a/components/tooltip/style/index.ts +++ b/components/tooltip/style/index.ts @@ -83,7 +83,7 @@ const genTooltipStyle: GenerateStyle = (token) => { const sharedTransformOrigin: CSSObject = { // When use `autoArrow`, origin will follow the arrow position - [varName('valid-offset-x')]: varRef('arrow-offset-horizontal', 'var(--arrow-x)'), + [varName('valid-offset-x')]: varRef('arrow-offset-x', 'var(--arrow-x)'), transformOrigin: [ varRef('valid-offset-x', FALL_BACK_ORIGIN), `var(--arrow-y, ${FALL_BACK_ORIGIN})`, diff --git a/components/tour/index.tsx b/components/tour/index.tsx index 5152394167..2996b5ae5f 100644 --- a/components/tour/index.tsx +++ b/components/tour/index.tsx @@ -5,7 +5,7 @@ import { clsx } from 'clsx'; import { useMergeSemantic, useZIndex } from '../_util/hooks'; import getPlacements from '../_util/placements'; -import zIndexContext from '../_util/zindexContext'; +import ZIndexContext from '../_util/zindexContext'; import { useComponentConfig } from '../config-provider/context'; import { useToken } from '../theme/internal'; import type { @@ -132,7 +132,7 @@ const Tour: React.FC & { _InternalPanelDoNotUseOrYouWillBeFired: type const [zIndex, contextZIndex] = useZIndex('Tour', restProps.zIndex); return ( - + & { _InternalPanelDoNotUseOrYouWillBeFired: type builtinPlacements={builtinPlacements} steps={mergedSteps} /> - + ); }; diff --git a/components/tour/style/index.ts b/components/tour/style/index.ts index e27b6c65cd..b48f807f9d 100644 --- a/components/tour/style/index.ts +++ b/components/tour/style/index.ts @@ -67,6 +67,7 @@ const genBaseStyle: GenerateStyle = (token) => { motionDurationSlow, antCls, primaryPrevBtnBg, + motionDurationMid, } = token; const [varName, varRef] = genCssVar(antCls, 'tooltip'); @@ -117,7 +118,11 @@ const genBaseStyle: GenerateStyle = (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', diff --git a/components/tree/style/index.ts b/components/tree/style/index.ts index 6fffe3f5a3..11d0fb7623 100644 --- a/components/tree/style/index.ts +++ b/components/tree/style/index.ts @@ -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': { diff --git a/components/typography/Base/index.tsx b/components/typography/Base/index.tsx index aa888bbd74..6b7d1de548 100644 --- a/components/typography/Base/index.tsx +++ b/components/typography/Base/index.tsx @@ -302,7 +302,6 @@ const Base = React.forwardRef((props, ref) => { return; } - /* eslint-disable-next-line compat/compat */ const observer = new IntersectionObserver(() => { setIsNativeVisible(!!textEle.offsetParent); }); diff --git a/components/upload/style/list.ts b/components/upload/style/list.ts index 1ce30077c6..cf82218013 100644 --- a/components/upload/style/list.ts +++ b/components/upload/style/list.ts @@ -5,7 +5,7 @@ import { clearFix, textEllipsis } from '../../style'; import type { GenerateStyle } from '../../theme/internal'; const genListStyle: GenerateStyle = (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 = (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 = (token) => { padding: `0 ${unit(token.paddingXS)}`, lineHeight, flex: 'auto', - transition: `all ${token.motionDurationSlow}`, + transition: `all ${motionDurationSlow}`, }, [actionsCls]: { @@ -47,7 +47,7 @@ const genListStyle: GenerateStyle = (token) => { [iconCls]: { color: token.actionsColor, - transition: `all ${token.motionDurationSlow}`, + transition: `all ${motionDurationSlow}`, }, [` @@ -100,7 +100,9 @@ const genListStyle: GenerateStyle = (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': { diff --git a/components/upload/style/picture.ts b/components/upload/style/picture.ts index 1f8e184386..b6e65895de 100644 --- a/components/upload/style/picture.ts +++ b/components/upload/style/picture.ts @@ -141,6 +141,8 @@ const genPictureCardStyle: GenerateStyle = (token) => { [`${listCls}${listCls}-picture-card, ${listCls}${listCls}-picture-circle`]: { display: 'flex', flexWrap: 'wrap', + height: uploadPictureCardSize, + '@supports not (gap: 1px)': { '& > *': { marginBlockEnd: token.marginXS, diff --git a/docs/react/customize-theme.en-US.md b/docs/react/customize-theme.en-US.md index 81159c4ad1..5e8a998f6a 100644 --- a/docs/react/customize-theme.en-US.md +++ b/docs/react/customize-theme.en-US.md @@ -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 = () => ( - - - - - - -); - -export default App; -``` + +Customize Design Token ### 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 = () => ( - - - - - - -); - -export default App; -``` + +Use Preset Algorithms ### 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 = () => ( - <> - - -
Enable algorithm:
- - -
-
- - - -
Disable algorithm:
- - -
-
- -); - -export default App; -``` + +Customize Component Token ### 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(false); - const timerRef = React.useRef>(); - React.useEffect(() => { - timerRef.current = setInterval(() => { - setChecked((prev) => !prev); - }, 500); - return () => { - if (timerRef.current) { - clearInterval(timerRef.current); - } - }; - }, []); - - const nodes = ( - - Checkbox - Radio - - - ); - - return ( - - {nodes} - - {nodes} - - - ); -}; - -export default App; -``` + +Disable Motion ## 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 ( - <> - setPrimary(color.toHexString())} /> - - - - - - - - - ); -} - -export default App; -``` + +Switch Themes Dynamically ### 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 = () => ( - - - - - - - - -); - -export default App; -``` + +Nested Theme ### 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 ( -
- Consume Design Token -
- ); -}; - -export default App; -``` + +Consume Design Token ### Static consume (e.g. less) diff --git a/docs/react/customize-theme.zh-CN.md b/docs/react/customize-theme.zh-CN.md index e56dbefe36..6909214622 100644 --- a/docs/react/customize-theme.zh-CN.md +++ b/docs/react/customize-theme.zh-CN.md @@ -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 = () => ( - - - - - - -); - -export default App; -``` + +修改主题变量 ### 使用预设算法 @@ -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 = () => ( - - - - - - -); - -export default App; -``` + +使用预设算法 ### 修改组件变量 @@ -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 = () => ( - <> - - -
开启算法:
- - -
-
- - - -
禁用算法:
- - -
-
- -); - -export default App; -``` + +修改组件变量 ### 禁用动画 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(false); - const timerRef = React.useRef>(); - React.useEffect(() => { - timerRef.current = setInterval(() => { - setChecked((prev) => !prev); - }, 500); - return () => { - if (timerRef.current) { - clearInterval(timerRef.current); - } - }; - }, []); - - const nodes = ( - - Checkbox - Radio - - - ); - - return ( - - {nodes} - - {nodes} - - - ); -}; - -export default App; -``` + +禁用动画 ## 进阶使用 @@ -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 ( - <> - setPrimary(color.toHexString())} /> - - - - - - - - - ); -} - -export default App; -``` + +动态切换 ### 局部主题(嵌套主题) 可以嵌套使用 `ConfigProvider` 来实现局部主题的更换。在子主题中未被改变的 Design Token 将会继承父主题。 -```sandpack -import React from 'react'; -import { Button, ConfigProvider, Space } from 'antd'; - -const App: React.FC = () => ( - - - - - - - - -); - -export default App; -``` + +局部主题 ### 使用 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 ( -
- 使用 Design Token -
- ); -}; - -export default App; -``` + +使用 Design Token ### 静态消费(如 less) diff --git a/docs/react/demo/component-token.md b/docs/react/demo/component-token.md new file mode 100644 index 0000000000..2a4171fb29 --- /dev/null +++ b/docs/react/demo/component-token.md @@ -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. diff --git a/docs/react/demo/component-token.tsx b/docs/react/demo/component-token.tsx new file mode 100644 index 0000000000..3e209d16a8 --- /dev/null +++ b/docs/react/demo/component-token.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { Button, ConfigProvider, Divider, Input, Space } from 'antd'; + +const App: React.FC = () => ( + <> + + +
Algorithm Enabled:
+ + +
+
+ + + +
Algorithm Disabled:
+ + +
+
+ +); + +export default App; diff --git a/docs/react/demo/disable-motion.md b/docs/react/demo/disable-motion.md new file mode 100644 index 0000000000..fff31fe2f7 --- /dev/null +++ b/docs/react/demo/disable-motion.md @@ -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`. diff --git a/docs/react/demo/disable-motion.tsx b/docs/react/demo/disable-motion.tsx new file mode 100644 index 0000000000..476bfd7463 --- /dev/null +++ b/docs/react/demo/disable-motion.tsx @@ -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(false); + const timerRef = useRef>(null); + + useEffect(() => { + timerRef.current = setInterval(() => { + setChecked((prev) => !prev); + }, 500); + return () => { + if (timerRef.current) { + clearInterval(timerRef.current); + } + }; + }, []); + + const nodes = ( + + Checkbox + Radio + + + ); + + return ( + + {nodes} + + {nodes} + + + ); +}; + +export default App; diff --git a/docs/react/demo/dynamic-theme.md b/docs/react/demo/dynamic-theme.md new file mode 100644 index 0000000000..233a2313e5 --- /dev/null +++ b/docs/react/demo/dynamic-theme.md @@ -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. diff --git a/docs/react/demo/dynamic-theme.tsx b/docs/react/demo/dynamic-theme.tsx new file mode 100644 index 0000000000..eee55609bc --- /dev/null +++ b/docs/react/demo/dynamic-theme.tsx @@ -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 ( + <> + setPrimary(color.toHexString())} /> + + + + + + + + + ); +}; + +export default App; diff --git a/docs/react/demo/first-example.md b/docs/react/demo/first-example.md new file mode 100644 index 0000000000..1558feca7e --- /dev/null +++ b/docs/react/demo/first-example.md @@ -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. diff --git a/docs/react/demo/first-example.tsx b/docs/react/demo/first-example.tsx new file mode 100644 index 0000000000..34c8e441d5 --- /dev/null +++ b/docs/react/demo/first-example.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Button, DatePicker, Space, version } from 'antd'; + +const App = () => ( +
+

antd version: {version}

+ + + + +
+); + +export default App; diff --git a/docs/react/demo/local-theme.md b/docs/react/demo/local-theme.md new file mode 100644 index 0000000000..78625d51a2 --- /dev/null +++ b/docs/react/demo/local-theme.md @@ -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. diff --git a/docs/react/demo/local-theme.tsx b/docs/react/demo/local-theme.tsx new file mode 100644 index 0000000000..b3d056b3ae --- /dev/null +++ b/docs/react/demo/local-theme.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Button, ConfigProvider, Space } from 'antd'; + +const App: React.FC = () => ( + + + + + + + + +); + +export default App; diff --git a/docs/react/demo/modify-theme-token.md b/docs/react/demo/modify-theme-token.md new file mode 100644 index 0000000000..4c58979195 --- /dev/null +++ b/docs/react/demo/modify-theme-token.md @@ -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. diff --git a/docs/react/demo/modify-theme-token.tsx b/docs/react/demo/modify-theme-token.tsx new file mode 100644 index 0000000000..6665e77cc7 --- /dev/null +++ b/docs/react/demo/modify-theme-token.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Button, ConfigProvider, Space } from 'antd'; + +const App: React.FC = () => ( + + + + + + +); + +export default App; diff --git a/docs/react/demo/preset-algorithm.md b/docs/react/demo/preset-algorithm.md new file mode 100644 index 0000000000..4fa5e898a2 --- /dev/null +++ b/docs/react/demo/preset-algorithm.md @@ -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. diff --git a/docs/react/demo/preset-algorithm.tsx b/docs/react/demo/preset-algorithm.tsx new file mode 100644 index 0000000000..30b19184b7 --- /dev/null +++ b/docs/react/demo/preset-algorithm.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Button, ConfigProvider, Input, Space, theme } from 'antd'; + +const App: React.FC = () => ( + + + + + + +); + +export default App; diff --git a/docs/react/demo/use-token.md b/docs/react/demo/use-token.md new file mode 100644 index 0000000000..645c056e41 --- /dev/null +++ b/docs/react/demo/use-token.md @@ -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. diff --git a/docs/react/demo/use-token.tsx b/docs/react/demo/use-token.tsx new file mode 100644 index 0000000000..be321c3c12 --- /dev/null +++ b/docs/react/demo/use-token.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { theme } from 'antd'; + +const { useToken } = theme; + +const App: React.FC = () => { + const { token } = useToken(); + + return ( +
+ Use Design Token +
+ ); +}; + +export default App; diff --git a/docs/react/getting-started.en-US.md b/docs/react/getting-started.en-US.md index 60bbeb8e3b..02d3dfd061 100755 --- a/docs/react/getting-started.en-US.md +++ b/docs/react/getting-started.en-US.md @@ -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 = () => ( -
-

antd version: {version}

- - - - -
-); - -export default App; -``` + +First Example Follow the steps below to play around with Ant Design yourself: diff --git a/docs/react/getting-started.zh-CN.md b/docs/react/getting-started.zh-CN.md index da1bcb90a4..0137b87252 100755 --- a/docs/react/getting-started.zh-CN.md +++ b/docs/react/getting-started.zh-CN.md @@ -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 = () => ( -
-

antd version: {version}

- - - - -
-); - -export default App; -``` + +第一个例子 ### 1. 创建一个 codesandbox diff --git a/docs/spec/font.en-US.md b/docs/spec/font.en-US.md index 1ca9206a6f..9f152a3568 100644 --- a/docs/spec/font.en-US.md +++ b/docs/spec/font.en-US.md @@ -26,7 +26,7 @@ In order to implement a good font system, the first thing is to choose an approp 'Noto Color Emoji'; ``` -> References:https://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). diff --git a/docs/spec/font.zh-CN.md b/docs/spec/font.zh-CN.md index 048cf36476..0394003c96 100644 --- a/docs/spec/font.zh-CN.md +++ b/docs/spec/font.zh-CN.md @@ -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`,使其为等宽字体。 diff --git a/eslint.config.mjs b/eslint.config.mjs index ce4a147c42..257cf6756e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -63,9 +63,18 @@ export default antfu( 'react-hooks/preserve-manual-memoization': 'off', 'react-hooks/set-state-in-effect': 'off', 'react-hooks/refs': 'off', + 'react/no-implicit-key': 'off', + 'react-naming-convention/ref-name': 'off', + 'react-naming-convention/use-state': 'off', + }, + }, + { + ...compat.configs['flat/recommended'], + rules: { + ...compat.configs['flat/recommended'].rules, + 'compat/compat': 'off', // Disabled due to incompatibility with ESLint 10.0.0 }, }, - compat.configs['flat/recommended'], jest.configs['flat/recommended'], { ...jsxA11y.flatConfigs.recommended, diff --git a/package.json b/package.json index d43629f26b..e765085da9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "antd", - "version": "6.2.2", + "version": "6.2.3", "description": "An enterprise-class UI design language and React components implementation", "license": "MIT", "funding": { @@ -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,8 +119,8 @@ "@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/drawer": "~1.4.1", + "@rc-component/dialog": "~1.8.4", + "@rc-component/drawer": "~1.4.2", "@rc-component/dropdown": "~1.0.2", "@rc-component/form": "~1.6.2", "@rc-component/image": "~1.6.0", @@ -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.9.0", "clsx": "^2.1.1", "dayjs": "^1.11.11", "scroll-into-view-if-needed": "^3.1.0", @@ -163,7 +163,7 @@ "@ant-design/tools": "^19.1.0", "@ant-design/x": "^2.2.0", "@ant-design/x-sdk": "^2.2.0", - "@antfu/eslint-config": "^7.0.0", + "@antfu/eslint-config": "^7.3.0", "@biomejs/biome": "^2.3.10", "@blazediff/core": "^1.7.0", "@codecov/webpack-plugin": "^1.9.1", @@ -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.12.2", "@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.28.0", "antd-style": "^4.1.0", "antd-token-previewer": "^3.0.0", "axios": "^1.13.2", @@ -237,15 +237,15 @@ "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", - "eslint-plugin-compat": "^6.0.2", + "eslint": "^10.0.0", + "eslint-plugin-compat": "^6.1.0", "eslint-plugin-jest": "^29.12.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.26", + "eslint-plugin-react-refresh": "^0.5.0", "fast-glob": "^3.3.3", "father": "4.6.13", "fs-extra": "^11.3.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 } ], diff --git a/scripts/print-changelog.ts b/scripts/print-changelog.ts index 46af5016e5..77cb00d811 100644 --- a/scripts/print-changelog.ts +++ b/scripts/print-changelog.ts @@ -4,7 +4,6 @@ import { input, select } from '@inquirer/prompts'; import chalk from 'chalk'; import fs from 'fs-extra'; import fetch from 'isomorphic-fetch'; -import jQuery from 'jquery'; import jsdom from 'jsdom'; import openWindow from 'open'; import simpleGit from 'simple-git'; @@ -14,8 +13,11 @@ const { window } = new JSDOM(); const { document } = new JSDOM('').window; global.document = document; +global.window = window as any; -const $ = jQuery(window) as unknown as JQueryStatic; +const jQuery = require('jquery'); + +const $ = jQuery(window) as unknown as JQueryStatic; const QUERY_TITLE = '.gh-header-title .js-issue-title'; const QUERY_DESCRIPTION_LINES = '.comment-body table tbody tr'; diff --git a/tests/setup.ts b/tests/setup.ts index 4b0b953b8e..7ab10e21fc 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -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; diff --git a/tests/shared/accessibilityTest.tsx b/tests/shared/accessibilityTest.tsx index ce150cb1e6..741cfaf8e0 100644 --- a/tests/shared/accessibilityTest.tsx +++ b/tests/shared/accessibilityTest.tsx @@ -144,8 +144,10 @@ export default function accessibilityDemoTest(component: string, options: Option const testMethod = shouldSkip ? describe.skip : describe; testMethod(`Test ${file} accessibility`, () => { - const Demo: React.ComponentType = require(`../../${file}`).default; - accessibilityTest(Demo, options.disabledRules); + if (!shouldSkip) { + const Demo: React.ComponentType = require(`../../${file}`).default; + accessibilityTest(Demo, options.disabledRules); + } }); }); }); diff --git a/tests/shared/imageTest.tsx b/tests/shared/imageTest.tsx index f8454504fa..16d0f13785 100644 --- a/tests/shared/imageTest.tsx +++ b/tests/shared/imageTest.tsx @@ -303,22 +303,22 @@ export function imageDemoTest(component: string, options: Options = {}) { }); files.forEach((file) => { - if (Array.isArray(options.skip) && options.skip.includes(path.basename(file))) { - describeMethod = describe.skip; - } else { - describeMethod = describe; - } + const shouldSkip = Array.isArray(options.skip) && options.skip.includes(path.basename(file)); + describeMethod = shouldSkip ? describe.skip : describe; describeMethod(`Test ${file} image`, () => { - let Demo = require(`../../${file}`).default; - if (typeof Demo === 'function') { - Demo = ; - } - imageTest(Demo, `${component}-${path.basename(file, '.tsx')}`, file, getTestOption(file)); + // Only require the demo file if it's not skipped to avoid dependency issues + if (!shouldSkip) { + let Demo = require(`../../${file}`).default; + if (typeof Demo === 'function') { + Demo = ; + } + imageTest(Demo, `${component}-${path.basename(file, '.tsx')}`, file, getTestOption(file)); - // Check if need mobile test - if ((options.mobile || []).includes(path.basename(file))) { - mobileDemos.push([file, Demo]); + // Check if need mobile test + if ((options.mobile || []).includes(path.basename(file))) { + mobileDemos.push([file, Demo]); + } } }); }); diff --git a/webpack.config.js b/webpack.config.js index d72dc6c4bf..c77fa99630 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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');