Merge branch 'master' into task/add-file-metadata

This commit is contained in:
Hector Ayala 2025-04-14 07:43:58 -04:00 committed by GitHub
commit c4a4cc6241
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
977 changed files with 43903 additions and 10795 deletions

80
.cursor/rules/demo.mdc Normal file
View File

@ -0,0 +1,80 @@
---
description:
globs: components/*/demo/**
alwaysApply: false
---
# Demo 规范
- demo 代码尽可能简洁
- 避免冗余代码,方便用户复制到项目直接使用
- 每个 demo 聚焦展示一个功能点
- 提供中英文两个版本的说明
- demo 文件扩展名:
- 基础 demo.tsx
- markdown 说明:.md
- 遵循展示优先原则,确保视觉效果良好
- 展示组件的主要使用场景
- 按照由简到繁的顺序排列 demo
- 确保 demo 在各种尺寸下都能正常展示
- 对于复杂交互提供必要的操作说明
## 文件组织
- 每个组件演示包含 `.md`(说明文档)和 `.tsx`(实际代码)两个文件
- 位置:组件目录下的 `demo` 子目录,如 `components/button/demo/`
- 命名:短横线连接的小写英文单词,如 `basic.tsx`、`custom-filter.tsx`
- 文件名应简洁地描述示例内容
## MD 文档规范
- 必须包含 `## zh-CN` 和 `## en-US` 两种语言说明
- 内容简洁明了,突出组件特性和用法
- 避免冗长段落,必要时使用列表或粗体
- 标注注意事项和实验性功能
## TSX 代码规范
- 导入顺序React → 依赖库 → 组件库 → 自定义组件 → 类型 → 样式
- 类型:为复杂数据定义清晰接口,避免 `any`
- 结构:
- 使用函数式组件和 Hooks
- 复杂逻辑提取为独立函数
- 状态管理用 `useState`/`useEffect`
- 风格:
- 2空格缩进箭头函数驼峰命名
- 常量大写+下划线布尔props用`is`/`has`前缀
## 示例类型
1. **基础用法**:展示最简用法,置于首位,代码精简
2. **变体展示**:不同大小、类型、状态,合理分组
3. **交互演示**:展示状态变化和用户交互
4. **数据交互**:展示加载、过滤、排序等
5. **边界情况**:空数据、错误状态处理
## 代码质量
- 实用且专注于单一功能
- 关键处添加简洁注释
- 使用有意义的数据和变量
- 优先使用 antd 内置组件,减少外部依赖
- 性能优化:适当使用 `useMemo`/`useCallback`,清理副作用
## 命名规则
- 基础文件:`basic.tsx`、`controlled.tsx` 等
- 调试类:使用 `debug-` 前缀
- 实验性:使用 `experimental-` 前缀
## 特殊示例
- **调试示例**:用于开发测试,通常不在文档显示
- **无障碍**:展示标签关联和键盘导航
- **响应式**:展示不同视口下的行为
- **多语言**:展示国际化配置方法
## 质量要求
- 确保代码运行正常,无控制台错误
- 适配常见浏览器
- 避免过时 API及时更新到新推荐用法

60
.cursor/rules/docs.mdc Normal file
View File

@ -0,0 +1,60 @@
---
description: 规范项目文档和 Changelog
globs: ["**/CHANGELOG*.md", "components/**/index.*.md"]
alwaysApply: false
---
# 文档和 Changelog 规范
## 基本要求
- 提供中英文两个版本
- 新的属性需要声明可用的版本号
- 属性命名符合 antd 的 API 命名规则https://github.com/ant-design/ant-design/wiki/API-Naming-rules
## Changelog 规范
- 在 CHANGELOG.en-US.md 和 CHANGELOG.zh-CN.md 书写每个版本的变更
- 对用户使用上无感知的改动建议(文档修补、微小的样式优化、代码风格重构等等)不要提及,保持 CHANGELOG 的内容有效性
- 非 antd 组件的改动(如工程化、构建工具、开发流程等)在 changelog 区块中写 "-"
- 用面向开发者的角度和叙述方式撰写 CHANGELOG不描述修复细节描述问题和对开发者的影响描述用户的原始问题而非你的解决方式
### 示例
- 不好的例子:修复组件 Typography 的 dom 结构问题
- 好的例子:修复了 List.Item 中内容空格丢失的问题
### 其他要求
- 新增属性时,建议用易于理解的语言补充描述用户可以感知的变化
- 尽量给出原始的 PR 链接,社区提交的 PR 改动加上提交者的链接
- 底层模块升级中间版本要去 rc-component 里找到改动,给出变动说明
- 建议参考之前版本的日志写法
- 将同组件的改动放在一起,内容子级缩进
### Changelog Emoji 规范
- 🐞 Bug 修复
- 💄 样式更新或 token 更新
- 🆕 新增特性,新增属性
- 🔥 极其值得关注的新增特性
- 🇺🇸🇨🇳🇬🇧 国际化改动
- 📖 📝 文档或网站改进
- ✅ 新增或更新测试用例
- 🛎 更新警告/提示信息
- ⌨️ ♿ 可访问性增强
- 🗑 废弃或移除
- 🛠 重构或工具链优化
- ⚡️ 性能提升
# 文档规范
- 提供中英文两个版本
- 新属性需声明可用的版本号
- 属性命名符合 API 命名规则
- 组件文档包含使用场景、基础用法、API 说明
- 文档示例应简洁明了
- 属性的描述应清晰易懂
- 对复杂功能提供详细说明
- 加入 TypeScript 定义
- 提供常见问题解答
- 更新文档时同步更新中英文版本

128
.cursor/rules/git.mdc Normal file
View File

@ -0,0 +1,128 @@
---
description:
globs:
alwaysApply: true
---
# Git 规范
## 分支管理
- 禁止直接提交到以下保护分支:
- `master`:主分支,用于发布
- `feature`:特性分支,用于开发新版本
- `next`:下一个版本分支
## 开发流程
1. 从保护分支(通常是 `master`)创建新的功能分支
2. 在新分支上进行开发
3. 提交 Pull Request 到目标分支
4. 等待 Code Review 和 CI 通过
5. 合并到目标分支
## 分支命名规范
- 功能开发:`feat/description-of-feature`
- 例如:`feat/add-dark-mode`
- 例如:`feat/improve-table-performance`
- 问题修复:`fix/issue-number-or-description`
- 例如:`fix/button-style-issue`
- 例如:`fix/issue-1234`
- 文档更新:`docs/what-is-changed`
- 例如:`docs/update-api-reference`
- 例如:`docs/fix-typos`
- 代码重构:`refactor/what-is-changed`
- 例如:`refactor/button-component`
- 例如:`refactor/remove-deprecated-api`
- 样式修改:`style/what-is-changed`
- 例如:`style/update-button-tokens`
- 例如:`style/improve-mobile-layout`
- 测试相关:`test/what-is-changed`
- 例如:`test/add-button-test`
- 例如:`test/improve-coverage`
- 构建相关:`build/what-is-changed`
- 例如:`build/upgrade-webpack`
- 例如:`build/fix-ts-config`
- 持续集成:`ci/what-is-changed`
- 例如:`ci/add-e2e-test`
- 例如:`ci/fix-deploy-script`
- 性能优化:`perf/what-is-changed`
- 例如:`perf/optimize-render`
- 例如:`perf/reduce-bundle-size`
- 依赖升级:`deps/package-name-version`
- 例如:`deps/upgrade-react-18`
- 例如:`deps/update-dependencies`
## 分支命名注意事项
1. 使用小写字母
2. 使用连字符(-)分隔单词
3. 简短但具有描述性
4. 避免使用下划线或其他特殊字符
5. 如果与 Issue 关联,可以包含 Issue 编号
## Pull Request 规范
### PR 标题
- PR 标题始终使用英文
- 遵循格式:`类型: 简短描述`
- 例如:`fix: fix button style issues in Safari browser`
- 例如:`feat: add dark mode support`
### PR 内容
- PR 内容默认使用英文
- 尽量简洁清晰地描述改动内容和目的
- 可以视需要在英文描述后附上中文说明
### PR 模板
提交 PR 时请使用项目中提供的模板:
- 英文模板(推荐):[PULL_REQUEST_TEMPLATE.md](mdc:.github/PULL_REQUEST_TEMPLATE.md)
- 中文模板:[PULL_REQUEST_TEMPLATE_CN.md](mdc:.github/PULL_REQUEST_TEMPLATE_CN.md)
### PR 提交注意事项
1. **合并策略**
- 新特性请提交至 `feature` 分支
- 其余可提交至 `master` 分支
2. **审核流程**
- PR 需要由至少一名维护者审核通过后才能合并
- 确保所有 CI 检查都通过
- 解决所有 Code Review 中提出的问题
3. **PR 质量要求**
- 确保代码符合项目代码风格
- 添加必要的测试用例
- 更新相关文档
- 大型改动需要更详细的说明和更多的审核者参与
4. **工具标注**
- 如果是用 Cursor 提交的代码,请在 PR body 末尾进行标注:`> Submitted by Cursor`
## 新增内容
- Pull Request 标题格式:[组件名]: 描述
- 从 master 分支创建新分支
- 分支命名规范:
- feature/xxx新特性
- fix/xxxBug 修复
- docs/xxx文档更新
- PR 说明中选择改动类型:
- 🆕 新特性提交
- 🐞 Bug 修复
- 📝 文档改进
- 📽️ 演示代码改进
- 💄 样式/交互改进
- 🤖 TypeScript 更新
- 📦 包体积优化
- ⚡️ 性能优化
- 🌐 国际化改进
- 提供改动背景和解决方案
- 更新日志同时提供英文和中文版本

108
.cursor/rules/naming.mdc Normal file
View File

@ -0,0 +1,108 @@
---
description:
globs:
alwaysApply: true
---
Basically, antd naming requires **FULL NAME** instead of Abbreviation.
## Props
* Initialize prop: `default` + `PropName`
* Force render: `forceRender`
* Force render sub component: `force` + `Sub Component Name` + `Render`
* Sub Component Render: `Sub Component Name` + `Render`. e.g.
> panelRender(originNode, info: { SubComponent1, SubComponent2, [somePassedProps]: someValue })
* Sub Item Render: `Sub Item Name` + `Render`. e.g.
> cellRender(date, info: { [somePassedProps]: someValue })
* Data Source: `dataSource`
* Panel open: popup & dropdown `open`, additional popup `popupName` + `Open` like `tooltipOpen`
* Do not use `visible` to make sure all the visible api align
* `children`:
* Mainly display content. To avoid additional prop name.
* Option list like `Select.Option` or `Tree.TreeNode`.
* Customize wrapped component can consider use `component` prop if `children` may have other usage in future.
* Display related naming: `show` + `PropName`
* Functional: `PropName` + `able`
* Disable: `disabled`
* sub component: `disabled` + `Sub Component Name`
* Extra: `extra`
* sub component: `Sub Component Name` + `extra`. e.g. `titleExtra`
* mainly icon: `icon`
* Merge with function first: `functionName: { icon }`. e.g. `expandable: { icon: <Smile /> }`
* Multiple icons: `FunctionName` + `Icon`
* Trigger: `trigger`
* Sub function trigger: `Sub Function` + `Trigger`
* Trigger on the time point: `xxx` + `On` + `EventName` (e.g. `destroyOnClose`)
* Component use other component config. Naming as component.(e.g. `<Table pagination={{...}} />`)
* ClassName: `className`
* Additional classes should be merged into `classes` (e.g. `<Button classes={{ inner: 'custom-inner' }} />`)
* Format
* Not modify value when blur: `preserveInvalidOnBlur`
## Event
* Trigger event: `on` + `EventName`
* Trigger sub component event: `on` + `SubComponentName` + `EventName` (e.g.`onSearchChange`)
* Trigger prop event: `on` + `PropName` + `EventName` (e.g.`onDragStart`)
* Before trigger event: `before` + `EventName`
* After trigger event: `after` + `EventName`
* After continuous action, such as drag Slider: `on` + `EventName` + `Complete`
## Reference
Component should have `ref` prop. Which should provide the structure:
```tsx
ComponentRef {
nativeElement: HTMLElement;
// Other function
focus: VoidFunction;
}
```
## Component Token
`variant (optional)` + `semantic part` + `semantic part variant (optional)` + `css property` + `size/disabled (optional)`
All component tokens should follow the structure above, and should not conflict with Global Token.
* `variant` means this token only works in certain variant, like `horizontal` `borderless`.
* `semantic part` means the typical element of component, like `item` `header`. If there's.
* `semantic part status` means the variant of the semantic part before it, like `hover` `selected`.
* `css property` means the exact property where the token is used, like `fontSize` `width`.
For example:
| v4 | v5 | Note |
| --- | --- | --- |
| `@menu-item-color` | `itemColor` | Remove the component prefix |
| `@select-item-selected-bg` | `itemSelectedBg` | `selected` is variant of item |
| `@select-single-item-height-lg` | `itemHeightLG` | `single` is variant of Select (by default), `LG` is size of Select |
> Note: If there's no semantic part for one component token, for example, the root borderRadius of Button, it is not suitable to add it. Because we could modify it easily with `className` and `style`.
## Current listing api & Chinese version
ref: [#16048](mdc:https:/github.com/ant-design/ant-design/issues/16048)
## API standard in the document
### Examples
| Property | Description | Type | Default |
| --------- | ---------------------- | ------------------------------ | ------ |
| htmlType | xxx | string | `button ` |
| type | xxx | `horizontal ` \| `vertical ` | `horizontal` |
| disabled | xxx | boolean | false |
| minLength | xxx | number | 0 |
| style | xxx | CSSProperties | - |
| character | xxx | (props) => ReactNode | - |
| offset | xxx| \[number, number] | \[0, 0] |
| value | xxx | string \| number | `small` |
### Promise
- When string type, the **Default** use ` `` `.
- Can also list string optional values in **Type**.
- When boolean type, the **Default** value is true or false.
- When number type, the **Default** value use numbers directly.
- When function type, use an arrow function expression in **Type**.
- No default value use - .
- Capitalize the first letter in **Description** apart from `someProp`.
- No period at the end of the **Description**.
- API order is arranged in alphabetical order, and can be put together under special circumstances (such as: xs sm md).
ref: [#25066](mdc:https:/github.com/ant-design/ant-design/issues/25066)

27
.cursor/rules/project.mdc Normal file
View File

@ -0,0 +1,27 @@
---
description:
globs:
alwaysApply: true
---
# 项目背景
这是 ant-design/ant-designantd的源代码仓库是一个 React 组件库,发布为 npm 包 antd。
- 使用 TypeScript 和 React 开发
- 兼容 React 16 ~ 19 版本
- 组件库设计精美,功能完善,广泛应用于企业级中后台产品
- 遵循 Ant Design 设计规范
- 支持国际化
# 编码规范
- 使用 TypeScript 和 React 书写
- 使用函数式组件和 hooks避免类组件
- 使用提前返回early returns提高代码可读性
- 避免引入新依赖,严控打包体积
- 兼容 Chrome 80+ 浏览器
- 支持服务端渲染
- 保持向下兼容,避免 breaking change
- 组件名使用大驼峰PascalCase
- 属性名使用小驼峰camelCase
- 合理使用 React.memo、useMemo 和 useCallback 优化性能

79
.cursor/rules/styling.mdc Normal file
View File

@ -0,0 +1,79 @@
---
description:
globs: components/*/style/**
alwaysApply: false
---
# 样式规范
## 样式方案
- 使用 `@ant-design/cssinjs` 作为样式解决方案
- 每个组件的样式应该放在 `style/` 目录下
- 样式文件应该与组件结构保持一致
- 使用 CSS-in-JS 时应当注意性能影响,避免不必要的样式重计算
- 样式生成函数应遵循 `gen[ComponentName]Style` 的命名规范
- 样式覆盖应使用类选择器而非标签选择器,提高样式特异性
## Token 系统
- 使用 Ant Design 的设计 Token 系统
- 避免硬编码颜色、尺寸、间距等值
- 组件样式应基于全局 Token 和组件级 Token
- 自定义样式应尽可能使用现有的 Token保持一致性
- 组件级 Token 命名规范:`Component` + 属性名,如 `buttonPrimaryColor`
- 对 Token 的修改应当向下传递,确保设计系统的一致性
## 响应式设计
- 组件应支持在不同屏幕尺寸下良好展示
- 使用相对单位(如 em、rem而非固定像素值
- 关键断点应与设计系统保持一致
- 在小屏幕上提供良好的降级方案
- 使用 CSS Grid 和 Flexbox 布局实现响应式布局
- 考虑移动设备上的触摸交互体验
## 暗色模式
- 所有组件必须支持暗色模式
- 暗色模式应通过 Token 系统实现,不应硬编码
- 测试暗色模式下的颜色对比度,确保可访问性
- 在设计暗色模式时考虑降低亮度和饱和度
- 确保文本在暗色背景上有足够的对比度
- 图片和图标应提供适合暗色模式的版本
## RTL 支持
- 组件应支持从右到左RTL的阅读方向
- 使用 CSS 逻辑属性(如 margin-inline-start替代方向性属性如 margin-left
- 图标和方向性元素应随 RTL 模式翻转
- 测试组件在 RTL 模式下的布局和交互
- 确保文本对齐和方向符合 RTL 规范
- 处理好数字和日期等特殊内容在 RTL 模式下的显示
## 动画效果
- 使用 CSS 过渡实现简单动画
- 复杂动画使用 rc-motion 实现
- 尊重用户的减少动画设置prefers-reduced-motion
- 动画时长和缓动函数应保持一致性
- 动画不应干扰用户的操作和阅读体验
- 为关键操作提供合适的反馈动画
- 避免使用会导致性能问题的 CSS 属性(如 box-shadow进行动画
## 主题定制
- 支持通过 ConfigProvider 进行主题定制
- 提供完整的组件级 Token 配置
- 保持向后兼容性,不轻易改变 Token 含义
- 避免在组件内使用不可覆盖的样式
- 提供主题切换的平滑过渡效果
- 测试自定义主题在各种组件组合下的效果
## 可访问性样式
- 遵循 WCAG 2.1 AA 级别标准
- 确保焦点状态有明显的视觉提示
- 提供足够的色彩对比度
- 不依赖颜色来传达信息
- 支持用户放大页面至 200% 时的正常布局
- 避免使用会导致闪烁的动画

11
.cursor/rules/testing.mdc Normal file
View File

@ -0,0 +1,11 @@
---
description:
globs: **/__tests__/**,**/*.test.tsx,**/*.test.ts
alwaysApply: false
---
# 测试规范
- 使用 Jest 和 React Testing Library 编写单元测试
- 对 UI 组件使用快照测试 (Snapshot Testing)
- 测试覆盖率要求 100%
- 测试文件放在 __tests__ 目录命名格式为index.test.tsx 或 xxx.test.tsx

View File

@ -0,0 +1,81 @@
# TypeScript 规范
## 基本原则
- 所有组件和函数必须提供准确的类型定义
- 避免使用 `any` 类型,尽可能精确地定义类型
- 使用接口而非类型别名定义对象结构
- 导出所有公共接口类型,方便用户使用
- 严格遵循 TypeScript 类型设计原则,确保类型安全
- 确保编译无任何类型错误或警告
## 组件类型定义
- 组件 props 应使用 interface 定义,便于扩展
- 组件 props 接口命名应为 `ComponentNameProps`
- 为组件状态定义专门的接口,如 `ComponentNameState`
- 复杂的数据结构应拆分为多个接口定义
- 组件的 ref 类型应该明确定义,使用 `React.ForwardRefRenderFunction`
- 所有回调函数类型应明确定义参数和返回值
## 泛型使用
- 适当使用泛型增强类型灵活性
- 为泛型参数提供合理的默认类型和约束
- 避免过度使用泛型导致类型复杂化
- 在泛型参数上应用限制条件constraints确保类型安全
- 为复杂泛型提供类型别名以提高可读性
## 类型合并与扩展
- 使用交叉类型(&)合并多个类型
- 使用 Partial<T>、Pick<T, K>、Omit<T, K> 等工具类型修改现有类型
- 扩展原生 DOM 元素属性时,继承相应的内置类型
- 使用 type 定义联合类型和交叉类型
- 优先使用自带的工具类型,避免重复定义
## 枚举和常量
- 使用字面量联合类型定义有限的选项集合
- 为复杂的枚举值提供类型守卫函数
- 避免使用 `enum`,优先使用联合类型和 `as const`
- 对于关键常量,使用 `as const` 断言确保类型严格
- 为联合类型中的每个值提供适当的注释
## 类型推断与断言
- 尽可能依赖 TypeScript 的类型推断
- 只在必要时使用类型断言as
- 使用类型守卫函数进行运行时类型检查
- 避免使用非空断言操作符(!
- 使用 `instanceof` 和 `typeof` 进行类型守卫
- 为自定义类型创建类型谓词type predicates函数
## JSDoc 注释
- 为复杂的类型、函数、组件添加 JSDoc 注释
- 使用 `@deprecated` 标记已废弃的 API
- 在注释中提供使用示例
- 说明参数和返回值的含义与约束
- 在 interface 和重要类型定义上添加文档注释
- 使用 `@template` 标记泛型参数
## 类型兼容性
- 确保类型定义兼容不同版本的 React
- 避免使用实验性或不稳定的 TypeScript 特性
- 为第三方库未提供的类型编写声明文件
- 使用条件类型处理复杂的类型逻辑
- 验证类型在不同 TypeScript 版本下的兼容性
## 严格使用 TypeScript 类型
- 导出组件类型和接口
- 使用 React.FC<Props> 或明确的返回类型
- 避免使用 any优先使用 unknown
- 组件 Props 使用 interface 定义
- 工具类型使用 type 定义
- 使用明确的命名约定
- 合理使用泛型提高复用性
- 导出类型时使用 export type
- 组件属性使用 JSDoc 注释说明用途

View File

@ -0,0 +1,16 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "ant-design",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
"postCreateCommand": "pnpm install",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"shezhangzhang.antd-design-token",
"fi3ework.vscode-antd-rush"
]
}
}
}

View File

@ -1,7 +1,9 @@
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
import React from 'react';
import { Col, ConfigProvider, Flex, Row, Tag, theme, Typography } from 'antd';
import { Col, ConfigProvider, Flex, Popover, Row, Tag, theme, Typography } from 'antd';
import { createStyles, css } from 'antd-style';
import classnames from 'classnames';
import { InfoCircleOutlined } from '@ant-design/icons';
const MARK_BORDER_SIZE = 2;
@ -65,13 +67,14 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
}));
export interface SemanticPreviewProps {
componentName: string;
semantics: { name: string; desc: string; version?: string }[];
children: React.ReactElement;
children: React.ReactElement<any>;
height?: number;
}
const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
const { semantics = [], children, height } = props;
const { semantics = [], children, height, componentName = 'Component' } = props;
const { token } = theme.useToken();
// ======================= Semantic =======================
@ -97,7 +100,7 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
// ======================== Hover =========================
const containerRef = React.useRef<HTMLDivElement>(null);
const timerRef = React.useRef<ReturnType<typeof setTimeout>>();
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(null);
const [positionMotion, setPositionMotion] = React.useState<boolean>(false);
const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null);
@ -111,12 +114,14 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
const targetElement = containerRef.current?.querySelector<HTMLElement>(`.${targetClassName}`);
const containerRect = containerRef.current?.getBoundingClientRect();
const targetRect = targetElement?.getBoundingClientRect();
setMarkPos([
(targetRect?.left || 0) - (containerRect?.left || 0),
(targetRect?.top || 0) - (containerRect?.top || 0),
targetRect?.width || 0,
targetRect?.height || 0,
]);
timerRef.current = setTimeout(() => {
setPositionMotion(true);
}, 10);
@ -149,11 +154,37 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
onMouseLeave={() => setHoverSemantic(null)}
>
<Flex vertical gap="small">
<Flex gap="small" align="center">
<Typography.Title level={5} style={{ margin: 0 }}>
{semantic.name}
</Typography.Title>
{semantic.version && <Tag color="blue">{semantic.version}</Tag>}
<Flex gap="small" align="center" justify="space-between">
<Flex gap="small" align="center">
<Typography.Title level={5} style={{ margin: 0 }}>
{semantic.name}
</Typography.Title>
{semantic.version && <Tag color="blue">{semantic.version}</Tag>}
</Flex>
<Popover
content={
<Typography style={{ fontSize: 12, minWidth: 300 }}>
<pre dir="ltr">
<code dir="ltr">
{`<${componentName}
classNames={{
${semantic.name}: 'my-${componentName.toLowerCase()}',
}}
styles={{
${semantic.name}: { color: 'red' },
}}
>
...
</${componentName}>`}
</code>
</pre>
</Typography>
}
>
<InfoCircleOutlined
style={{ cursor: 'pointer', color: token.colorTextSecondary }}
/>
</Popover>
</Flex>
<Typography.Paragraph style={{ margin: 0, fontSize: token.fontSizeSM }}>
{semantic.desc}

View File

@ -1,30 +0,0 @@
function use<T>(promise: PromiseLike<T>): T {
const internal: PromiseLike<T> & {
status?: 'pending' | 'fulfilled' | 'rejected';
value?: T;
reason?: any;
} = promise;
if (internal.status === 'fulfilled') {
return internal.value as T;
}
if (internal.status === 'rejected') {
throw internal.reason;
}
if (internal.status === 'pending') {
throw internal;
}
internal.status = 'pending';
internal.then(
(result) => {
internal.status = 'fulfilled';
internal.value = result;
},
(reason) => {
internal.status = 'rejected';
internal.reason = reason;
},
);
throw internal;
}
export default use;

View File

@ -1,7 +1,3 @@
import React from 'react';
export const DarkContext = React.createContext(false);
export default function useDark() {
return React.useContext(DarkContext);
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import fetch from 'cross-fetch';
import use from '../use';
import FetchCache from './cache';
const cache = new FetchCache();
@ -15,7 +15,7 @@ const useFetch = <T>(options: string | { request: () => PromiseLike<T>; key: str
request = options.request;
key = options.key;
}
return use(cache.promise<T>(key, request));
return React.use<T>(cache.promise<T>(key, request));
};
export default useFetch;

View File

@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import type { MenuProps } from 'antd';
import { Space, Tag, version } from 'antd';
import { Flex, Tag, version } from 'antd';
import { createStyles } from 'antd-style';
import classnames from 'classnames';
import { useFullSidebarData, useSidebarData } from 'dumi';
@ -12,6 +12,22 @@ function isVersionNumber(value?: string) {
return value && /^\d+\.\d+\.\d+$/.test(value);
}
const getTagColor = (val?: string) => {
if (isVersionNumber(val)) {
return 'success';
}
if (val?.toUpperCase() === 'NEW') {
return 'success';
}
if (val?.toUpperCase() === 'UPDATED') {
return 'processing';
}
if (val?.toUpperCase() === 'DEPRECATED') {
return 'red';
}
return 'success';
};
const useStyle = createStyles(({ css, token }) => ({
link: css`
display: flex;
@ -42,20 +58,17 @@ interface MenuItemLabelProps {
const MenuItemLabelWithTag: React.FC<MenuItemLabelProps> = (props) => {
const { styles } = useStyle();
const { before, after, link, title, subtitle, search, tag, className } = props;
if (!before && !after) {
return (
<Link to={`${link}${search}`} className={classnames(className, { [styles.link]: tag })}>
<Space>
<Flex justify="flex-start" align="center" gap="small">
<span>{title}</span>
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
</Space>
</Flex>
{tag && (
<Tag
bordered={false}
className={classnames(styles.tag)}
color={isVersionNumber(tag) || tag === 'New' ? 'success' : 'processing'}
>
{tag.replace('VERSION', version)}
<Tag bordered={false} className={classnames(styles.tag)} color={getTagColor(tag)}>
{tag.replace(/VERSION/i, version)}
</Tag>
)}
</Link>

View File

@ -64,7 +64,6 @@ const useThemeAnimation = () => {
event: React.MouseEvent<HTMLElement, MouseEvent>,
isDark: boolean,
) => {
// @ts-ignore
if (!(event && typeof document.startViewTransition === 'function')) {
return;
}
@ -85,13 +84,10 @@ const useThemeAnimation = () => {
'color-scheme',
);
document
// @ts-ignore
.startViewTransition(async () => {
// wait for theme change end
while (colorBgElevated === animateRef.current.colorBgElevated) {
await new Promise((resolve) => {
setTimeout(resolve, 1000 / 60);
});
await new Promise<void>((resolve) => setTimeout(resolve, 1000 / 60));
}
const root = document.documentElement;
root.classList.remove(isDark ? 'dark' : 'light');
@ -111,7 +107,6 @@ const useThemeAnimation = () => {
// inject transition style
useEffect(() => {
// @ts-ignore
if (typeof document.startViewTransition === 'function') {
updateCSS(viewTransitionStyle, 'view-transition-style');
}

View File

@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React from 'react';
import { Badge, Carousel, Flex, Skeleton, Typography } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
@ -110,10 +110,10 @@ const RecommendItem: React.FC<RecommendItemProps> = ({ extra, index, icons, clas
};
export const BannerRecommendsFallback: React.FC = () => {
const { isMobile } = useContext(SiteContext);
const { isMobile } = React.use(SiteContext);
const { styles } = useStyle();
const list = new Array(3).fill(1);
const list = Array.from({ length: 3 });
return isMobile ? (
<Carousel className={styles.carousel}>
@ -137,11 +137,12 @@ export const BannerRecommendsFallback: React.FC = () => {
const BannerRecommends: React.FC = () => {
const { styles } = useStyle();
const [, lang] = useLocale();
const { isMobile } = React.useContext(SiteContext);
const { isMobile } = React.use(SiteContext);
const data = useSiteData();
const extras = data?.extras?.[lang];
const icons = data?.icons || [];
const first3 = !extras || extras.length === 0 ? new Array(3).fill(null) : extras.slice(0, 3);
const first3 =
!extras || extras.length === 0 ? Array.from<any>({ length: 3 }) : extras.slice(0, 3);
if (!data) {
return <BannerRecommendsFallback />;

View File

@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React from 'react';
import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
import {
Alert,
@ -16,9 +16,9 @@ import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
import dayjs from 'dayjs';
import useDark from '../../../hooks/useDark';
import useLocale from '../../../hooks/useLocale';
import SiteContext from '../../../theme/slots/SiteContext';
import { DarkContext } from './../../../hooks/useDark';
import { getCarouselStyle } from './util';
const { _InternalPanelDoNotUseOrYouWillBeFired: ModalDoNotUseOrYouWillBeFired } = Modal;
@ -61,66 +61,62 @@ const locales = {
},
};
const useStyle = () => {
const isRootDark = useDark();
const useStyle = createStyles(({ token }, isDark: boolean) => {
const { carousel } = getCarouselStyle();
return {
card: css`
border-radius: ${token.borderRadius}px;
border: 1px solid ${isDark ? token.colorBorder : 'transparent'};
background-color: ${isDark ? token.colorBgContainer : '#f5f8ff'};
padding: ${token.paddingXL}px;
flex: none;
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
return createStyles(({ token }) => {
const { carousel } = getCarouselStyle();
return {
card: css`
border-radius: ${token.borderRadius}px;
border: 1px solid ${isRootDark ? token.colorBorder : 'transparent'};
background: ${isRootDark ? token.colorBgContainer : '#f5f8ff'};
padding: ${token.paddingXL}px;
> * {
flex: none;
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
> * {
flex: none;
}
`,
cardCircle: css`
position: absolute;
width: 120px;
height: 120px;
background: #1677ff;
border-radius: 50%;
filter: blur(40px);
opacity: 0.1;
`,
mobileCard: css`
height: 395px;
`,
nodeWrap: css`
margin-top: ${token.paddingLG}px;
flex: auto;
display: flex;
align-items: center;
justify-content: center;
`,
carousel,
componentsList: css`
width: 100%;
overflow: hidden;
`,
mobileComponentsList: css`
margin: 0 ${token.margin}px;
`,
};
})();
};
}
`,
cardCircle: css`
position: absolute;
width: 120px;
height: 120px;
background: #1677ff;
border-radius: 50%;
filter: blur(40px);
opacity: 0.1;
`,
mobileCard: css`
height: 395px;
`,
nodeWrap: css`
margin-top: ${token.paddingLG}px;
flex: auto;
display: flex;
align-items: center;
justify-content: center;
`,
carousel,
componentsList: css`
width: 100%;
overflow: hidden;
`,
mobileComponentsList: css`
margin: 0 ${token.margin}px;
`,
};
});
const ComponentItem: React.FC<ComponentItemProps> = ({ title, node, type, index }) => {
const tagColor = type === 'new' ? 'processing' : 'warning';
const [locale] = useLocale(locales);
const tagText = type === 'new' ? locale.new : locale.update;
const { styles } = useStyle();
const { isMobile } = useContext(SiteContext);
const isDark = React.use(DarkContext);
const { isMobile } = React.use(SiteContext);
const { styles } = useStyle(isDark);
return (
<div className={classNames(styles.card, isMobile && styles.mobileCard)}>
{/* Decorator */}
@ -151,7 +147,7 @@ interface ComponentItemProps {
const ComponentsList: React.FC = () => {
const { styles } = useStyle();
const [locale] = useLocale(locales);
const { isMobile } = useContext(SiteContext);
const { isMobile } = React.use(SiteContext);
const COMPONENTS = React.useMemo<Omit<ComponentItemProps, 'index'>[]>(
() => [
{

View File

@ -1,13 +1,13 @@
import React, { useContext } from 'react';
import React from 'react';
import { Col, Row, Typography } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import { useLocation } from 'dumi';
import useDark from '../../../hooks/useDark';
import useLocale from '../../../hooks/useLocale';
import Link from '../../../theme/common/Link';
import SiteContext from '../../../theme/slots/SiteContext';
import * as utils from '../../../theme/utils';
import { DarkContext } from './../../../hooks/useDark';
const SECONDARY_LIST = [
{
@ -63,14 +63,12 @@ const locales = {
},
};
const useStyle = () => {
const isRootDark = useDark();
return createStyles(({ token, css }) => ({
const useStyle = createStyles(({ token, css }, isDark: boolean) => {
return {
card: css`
padding: ${token.paddingSM}px;
border-radius: ${token.borderRadius * 2}px;
background: ${isRootDark ? 'rgba(0, 0, 0, 0.45)' : token.colorBgElevated};
background: ${isDark ? 'rgba(0, 0, 0, 0.45)' : token.colorBgElevated};
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.03),
0 1px 6px -1px rgba(0, 0, 0, 0.02),
@ -87,23 +85,25 @@ const useStyle = () => {
display: block;
border-radius: ${token.borderRadius * 2}px;
padding: ${token.paddingMD}px ${token.paddingLG}px;
background: ${isRootDark ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.02)'};
border: 1px solid ${isRootDark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.06)'};
background: ${isDark ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.02)'};
border: 1px solid ${isDark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.06)'};
img {
height: 48px;
}
`,
}))();
};
};
});
const DesignFramework: React.FC = () => {
const [locale] = useLocale(locales);
const token = useTheme();
const { styles } = useStyle();
const { isMobile } = React.use(SiteContext);
const isDark = React.use(DarkContext);
const { styles } = useStyle(isDark);
const { pathname, search } = useLocation();
const isZhCN = utils.isZhCN(pathname);
const { isMobile } = useContext(SiteContext);
const colSpan = isMobile ? 24 : 8;
const MAINLY_LIST = [

View File

@ -1,5 +1,4 @@
import * as React from 'react';
import { useContext } from 'react';
import { Typography } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import classNames from 'classnames';
@ -40,7 +39,7 @@ const Group: React.FC<React.PropsWithChildren<GroupProps>> = (props) => {
const { id, title, titleColor, description, children, decoration, background, collapse } = props;
const token = useTheme();
const { styles } = useStyle();
const { isMobile } = useContext(SiteContext);
const { isMobile } = React.use(SiteContext);
const childNode = (
<>
<div style={{ textAlign: 'center' }}>

View File

@ -108,7 +108,7 @@ const ComponentsBlock: React.FC = () => {
const { styles } = useStyle();
return (
<Tilt options={{ max: 20, glare: true, scale: 1 }} className={styles.holder}>
<Tilt options={{ max: 4, glare: false, scale: 0.98 }} className={styles.holder}>
<ModalPanel title="Ant Design 5.0" width="100%">
{locale.text}
</ModalPanel>
@ -119,7 +119,7 @@ const ComponentsBlock: React.FC = () => {
<div style={{ flex: 'none' }}>
<Dropdown.Button
menu={{
items: new Array(5).fill(null).map((_, index) => ({
items: Array.from({ length: 5 }).map((_, index) => ({
key: `opt${index}`,
label: `${locale.option} ${index}`,
})),

View File

@ -1,5 +1,5 @@
import React, { Suspense } from 'react';
import { ConfigProvider, Flex, Typography } from 'antd';
import React, { Suspense, use } from 'react';
import { Flex, Typography } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import { useLocation } from 'dumi';
@ -7,9 +7,12 @@ import { useLocation } from 'dumi';
import useLocale from '../../../../hooks/useLocale';
import LinkButton from '../../../../theme/common/LinkButton';
import SiteContext from '../../../../theme/slots/SiteContext';
import type { SiteContextProps } from '../../../../theme/slots/SiteContext';
import * as utils from '../../../../theme/utils';
import GroupMaskLayer from '../GroupMaskLayer';
import '../SiteContext';
const ComponentsBlock = React.lazy(() => import('./ComponentsBlock'));
const locales = {
@ -26,105 +29,103 @@ const locales = {
},
};
const useStyle = () => {
const { direction } = React.useContext(ConfigProvider.ConfigContext);
const { isMobile } = React.useContext(SiteContext);
const isRTL = direction === 'rtl';
return createStyles(({ token, css, cx }) => {
const textShadow = `0 0 4px ${token.colorBgContainer}`;
const useStyle = createStyles(({ token, css, cx }, siteConfig: SiteContextProps) => {
const textShadow = `0 0 4px ${token.colorBgContainer}`;
const isDark = siteConfig.theme.includes('dark');
const mask = cx(css`
position: absolute;
inset: 0;
backdrop-filter: blur(2px);
opacity: 1;
background-color: ${isDark ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.2)'};
transition: all 1s ease;
pointer-events: none;
`);
const mask = cx(css`
position: absolute;
inset: 0;
backdrop-filter: blur(4px);
opacity: 1;
background-color: rgba(255, 255, 255, 0.2);
transition: all 1s ease;
pointer-events: none;
`);
const block = cx(css`
position: absolute;
inset-inline-end: -60px;
top: -24px;
transition: all 1s cubic-bezier(0.03, 0.98, 0.52, 0.99);
`);
return {
holder: css`
height: 640px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
perspective: 800px;
/* fix safari bug by removing blur style */
transform: translateZ(1000px);
row-gap: ${token.marginXL}px;
return {
holder: css`
height: 640px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
perspective: 800px;
/* fix safari bug by removing blur style */
transform: translateZ(1000px);
row-gap: ${token.marginXL}px;
&:hover .${mask} {
&:hover {
.${mask} {
opacity: 0;
}
`,
mask,
typography: css`
text-align: center;
position: relative;
z-index: 1;
padding-inline: ${token.paddingXL}px;
text-shadow: ${new Array(5)
.fill(null)
.map(() => textShadow)
.join(', ')};
h1 {
font-family: AliPuHui, ${token.fontFamily} !important;
font-weight: 900 !important;
font-size: ${token.fontSizeHeading2 * 2}px !important;
line-height: ${token.lineHeightHeading2} !important;
.${block} {
transform: scale(0.96);
}
}
`,
p {
font-size: ${token.fontSizeLG}px !important;
font-weight: normal !important;
margin-bottom: 0;
}
`,
mask,
block: css`
position: absolute;
inset-inline-end: 0;
top: -38px;
transform: ${isRTL ? 'rotate3d(24, 83, -45, 57deg)' : 'rotate3d(24, -83, 45, 57deg)'};
`,
child: css`
position: relative;
width: 100%;
max-width: 1200px;
margin: 0 auto;
z-index: 1;
`,
btnWrap: css`
margin-bottom: ${token.marginXL}px;
`,
bgImg: css`
position: absolute;
width: 240px;
`,
bgImgTop: css`
top: 0;
inset-inline-start: ${isMobile ? '-120px' : 0};
`,
bgImgBottom: css`
bottom: 120px;
inset-inline-end: ${isMobile ? 0 : '40%'};
`,
};
})();
};
typography: css`
text-align: center;
position: relative;
z-index: 1;
padding-inline: ${token.paddingXL}px;
text-shadow: ${Array.from({ length: 5 }, () => textShadow).join(', ')};
h1 {
font-family: AliPuHui, ${token.fontFamily} !important;
font-weight: 900 !important;
font-size: ${token.fontSizeHeading2 * 2}px !important;
line-height: ${token.lineHeightHeading2} !important;
}
p {
font-size: ${token.fontSizeLG}px !important;
font-weight: normal !important;
margin-bottom: 0;
}
`,
block,
child: css`
position: relative;
width: 100%;
max-width: 1200px;
margin: 0 auto;
z-index: 1;
`,
btnWrap: css`
margin-bottom: ${token.marginXL}px;
`,
bgImg: css`
position: absolute;
width: 240px;
`,
bgImgTop: css`
top: 0;
inset-inline-start: ${siteConfig.isMobile ? '-120px' : 0};
`,
bgImgBottom: css`
bottom: 120px;
inset-inline-end: ${siteConfig.isMobile ? 0 : '40%'};
`,
};
});
const PreviewBanner: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
const { children } = props;
const [locale] = useLocale(locales);
const { styles } = useStyle();
const { isMobile } = React.useContext(SiteContext);
const siteConfig = use(SiteContext);
const { styles } = useStyle(siteConfig);
const { pathname, search } = useLocation();
const isZhCN = utils.isZhCN(pathname);
@ -148,7 +149,7 @@ const PreviewBanner: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
<div className={styles.holder}>
{/* Mobile not show the component preview */}
<Suspense fallback={null}>
{isMobile ? null : (
{siteConfig.isMobile ? null : (
<div className={styles.block}>
<ComponentsBlock />
</div>

View File

@ -4,8 +4,6 @@ export interface SiteContextProps {
isMobile: boolean;
}
const SiteContext = React.createContext<SiteContextProps>({
isMobile: false,
});
const SiteContext = React.createContext<SiteContextProps>({ isMobile: false });
export default SiteContext;

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { ColorPicker, Flex, Input } from 'antd';
import { createStyles } from 'antd-style';
import type { ColorPickerProps, GetProp } from 'antd';
import { createStyles } from 'antd-style';
import { generateColor } from 'antd/es/color-picker/util';
import classNames from 'classnames';
@ -61,7 +61,7 @@ const DebouncedColorPicker: React.FC<React.PropsWithChildren<ThemeColorPickerPro
<ColorPicker
value={value}
onChange={setValue}
presets={[{ label: 'PresetColors', colors: PRESET_COLORS }]}
presets={[{ label: 'PresetColors', key: 'PresetColors', colors: PRESET_COLORS }]}
>
{children}
</ColorPicker>

View File

@ -1,12 +1,13 @@
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
import * as React from 'react';
import { defaultAlgorithm, defaultTheme } from '@ant-design/compatible';
import { FastColor } from '@ant-design/fast-color';
import {
BellOutlined,
FolderOutlined,
HomeOutlined,
QuestionCircleOutlined,
} from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import type { ColorPickerProps, GetProp, MenuProps, ThemeConfig } from 'antd';
import {
Breadcrumb,
@ -25,13 +26,13 @@ import { generateColor } from 'antd/es/color-picker/util';
import classNames from 'classnames';
import { useLocation } from 'dumi';
import useDark from '../../../../hooks/useDark';
import useLocale from '../../../../hooks/useLocale';
import LinkButton from '../../../../theme/common/LinkButton';
import SiteContext from '../../../../theme/slots/SiteContext';
import { getLocalizedPathname } from '../../../../theme/utils';
import Group from '../Group';
import { getCarouselStyle } from '../util';
import { DarkContext } from './../../../../hooks/useDark';
import BackgroundImage from './BackgroundImage';
import ColorPicker from './ColorPicker';
import { DEFAULT_COLOR, getAvatarURL, getClosetColor, PINK_COLOR } from './colorUtil';
@ -324,7 +325,7 @@ const ThemesInfo: Record<THEME, Partial<ThemeData>> = {
const normalize = (value: number) => value / 255;
function rgbToColorMatrix(color: string) {
const rgb = new TinyColor(color).toRgb();
const rgb = new FastColor(color).toRgb();
const { r, g, b } = rgb;
const invertValue = normalize(r) * 100;
@ -360,7 +361,7 @@ const Theme: React.FC = () => {
const { compact, themeType, colorPrimary, ...themeToken } = themeData;
const isLight = themeType !== 'dark';
const [form] = Form.useForm();
const { isMobile } = React.useContext(SiteContext);
const { isMobile } = React.use(SiteContext);
const colorPrimaryValue = React.useMemo(
() => (typeof colorPrimary === 'string' ? colorPrimary : colorPrimary.toHexString()),
[colorPrimary],
@ -393,11 +394,11 @@ const Theme: React.FC = () => {
form.setFieldsValue(mergedData);
}, [themeType]);
const isRootDark = useDark();
const isDark = React.use(DarkContext);
React.useEffect(() => {
onThemeChange({}, { ...themeData, themeType: isRootDark ? 'dark' : 'default' });
}, [isRootDark]);
onThemeChange({}, { ...themeData, themeType: isDark ? 'dark' : 'default' });
}, [isDark]);
// ================================ Tokens ================================
const closestColor = getClosetColor(colorPrimaryValue);

View File

@ -2,8 +2,8 @@ import React, { Suspense } from 'react';
import { ConfigProvider, theme } from 'antd';
import { createStyles, css } from 'antd-style';
import useDark from '../../hooks/useDark';
import useLocale from '../../hooks/useLocale';
import { DarkContext } from './../../hooks/useDark';
import BannerRecommends from './components/BannerRecommends';
import Group from './components/Group';
import PreviewBanner from './components/PreviewBanner';
@ -41,7 +41,7 @@ const Homepage: React.FC = () => {
const { styles } = useStyle();
const { token } = theme.useToken();
const isRootDark = useDark();
const isDark = React.use(DarkContext);
return (
<section>
@ -78,7 +78,7 @@ const Homepage: React.FC = () => {
<Group
title={locale.designTitle}
description={locale.designDesc}
background={isRootDark ? '#393F4A' : '#F5F8FF'}
background={isDark ? '#393F4A' : '#F5F8FF'}
decoration={
<img
draggable={false}

View File

@ -1,5 +1,6 @@
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
import React, { Suspense, useEffect } from 'react';
import { Button, App, Skeleton } from 'antd';
import { App, Button, Skeleton } from 'antd';
import { enUS, zhCN } from 'antd-token-previewer';
import type { ThemeConfig } from 'antd/es/config-provider/context';
import { Helmet } from 'dumi';

86
.dumi/remarkAnchor.ts Normal file
View File

@ -0,0 +1,86 @@
import { unistUtilVisit } from 'dumi';
import type { UnifiedTransformer } from 'dumi';
let toSlug: typeof import('github-slugger').slug;
// workaround to import pure esm module
(async () => {
({ slug: toSlug } = await import('github-slugger'));
})();
const isNil = (value: any) => value == null;
const toArr = <T>(value: T | T[]) => {
if (isNil(value)) return [];
return Array.isArray(value) ? value : [value];
};
const patch = (context: Record<string, any>, key: string, value: any) => {
if (!context[key]) {
context[key] = value;
}
return context[key];
};
interface Options {
level?: number;
}
const remarkAnchor = (opt: Options = {}): UnifiedTransformer<any> => {
// https://regex101.com/r/WDjkK0/1
const RE = /\s*\{#([^}]+)\}$/;
const realOpt = {
level: [1, 2, 3, 4, 5, 6],
...opt,
};
return function transformer(tree) {
const ids = new Set();
unistUtilVisit.visit(tree, 'heading', (node) => {
if (toArr(realOpt.level).indexOf(node.depth) === -1) {
return unistUtilVisit.CONTINUE;
}
const lastChild = node.children.at(-1);
if (lastChild?.type === 'text') {
const text = lastChild.value;
const match = text.match(RE);
if (match) {
const id = match[1];
if (id !== toSlug(id)) {
throw new Error(
`Expected header ID to be a valid slug. You specified: {#${id}}. Replace it with: {#${toSlug(id)}}`,
);
}
node.data ??= {};
node.data.hProperties = { ...node.data.hProperties, id };
lastChild.value = text.replace(RE, '');
if (lastChild.value === '') {
node.children.pop();
}
if (ids.has(id)) {
throw new Error(`Cannot have a duplicate header with id "${id}" on the page.
Rename the section or give it an explicit unique ID. For example: #### Arguments {#setstate-arguments}`);
}
ids.add(id);
const data = patch(node, 'data', {});
patch(data, 'id', id);
patch(data, 'htmlAttributes', {});
patch(data, 'hProperties', {});
patch(data.htmlAttributes, 'id', id);
patch(data.hProperties, 'id', id);
}
}
});
};
};
export default remarkAnchor;

View File

@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React from 'react';
import { theme as antdTheme, ConfigProvider } from 'antd';
import type { ThemeConfig } from 'antd';
import type { ThemeProviderProps } from 'antd-style';
@ -31,10 +31,10 @@ const headerHeight = 64;
const bannerHeight = 38;
const SiteThemeProvider: React.FC<ThemeProviderProps<any>> = ({ children, theme, ...rest }) => {
const { getPrefixCls, iconPrefixCls } = useContext(ConfigProvider.ConfigContext);
const { getPrefixCls, iconPrefixCls } = React.use(ConfigProvider.ConfigContext);
const rootPrefixCls = getPrefixCls();
const { token } = antdTheme.useToken();
const { bannerVisible } = useContext(SiteContext);
const { bannerVisible } = React.use(SiteContext);
React.useEffect(() => {
// 需要注意与 components/config-provider/demo/holderRender.tsx 配置冲突
ConfigProvider.config({ theme: theme as ThemeConfig });

View File

@ -0,0 +1,23 @@
import * as React from 'react';
import { Tag, TagProps } from 'antd';
// https://github.com/umijs/dumi/blob/master/src/client/theme-default/builtins/Badge/index.tsx
interface BadgeProps extends TagProps {
type: 'info' | 'warning' | 'error' | 'success';
}
const colorMap = {
info: 'blue',
warning: 'orange',
error: 'red',
success: 'green',
};
export default ({ type = 'info', ...props }: BadgeProps) => (
<Tag
bordered={false}
color={colorMap[type]}
{...props}
style={{ verticalAlign: 'top', ...props.style }}
/>
);

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import type { ColorInput } from '@ctrl/tinycolor';
import { TinyColor } from '@ctrl/tinycolor';
import { FastColor } from '@ant-design/fast-color';
import type { ColorInput } from '@ant-design/fast-color';
import { Popover } from 'antd';
import { createStyles } from 'antd-style';
const useStyle = createStyles(({ token, css }) => ({
@ -22,24 +23,47 @@ const useStyle = createStyles(({ token, css }) => ({
}));
interface ColorChunkProps {
value?: ColorInput;
value: ColorInput;
enablePopover?: boolean;
}
const ColorChunk: React.FC<React.PropsWithChildren<ColorChunkProps>> = (props) => {
const { styles } = useStyle();
const { value, children } = props;
const { styles, theme } = useStyle();
const { value, children, enablePopover } = props;
const dotColor = React.useMemo(() => {
const _color = new TinyColor(value).toHex8String();
return _color.endsWith('ff') ? _color.slice(0, -2) : _color;
}, [value]);
const dotColor = React.useMemo(() => new FastColor(value).toHexString(), [value]);
return (
let dotNode = (
<span className={styles.codeSpan}>
<span className={styles.dot} style={{ backgroundColor: dotColor }} />
{children ?? dotColor}
</span>
);
if (enablePopover) {
dotNode = (
<Popover
placement="left"
content={<div hidden />}
styles={{
body: {
backgroundColor: dotColor,
width: 120,
height: 120,
borderRadius: theme.borderRadiusLG,
},
root: {
'--antd-arrow-background-color': dotColor,
backgroundColor: 'transparent',
} as React.CSSProperties,
}}
>
{dotNode}
</Popover>
);
}
return dotNode;
};
export default ColorChunk;

View File

@ -1,10 +1,11 @@
import React from 'react';
import { EditOutlined, GithubOutlined, HistoryOutlined } from '@ant-design/icons';
import { EditOutlined, GithubOutlined, HistoryOutlined, CompassOutlined } from '@ant-design/icons';
import type { GetProp } from 'antd';
import { Descriptions, Flex, theme, Tooltip, Typography } from 'antd';
import { createStyles, css } from 'antd-style';
import kebabCase from 'lodash/kebabCase';
import CopyToClipboard from 'react-copy-to-clipboard';
import Link from '../../common/Link';
import useLocale from '../../../hooks/useLocale';
import ComponentChangelog from '../../common/ComponentChangelog';
@ -18,6 +19,7 @@ const locales = {
docs: '文档',
edit: '编辑此页',
changelog: '更新日志',
design: '设计指南',
version: '版本',
},
en: {
@ -28,6 +30,7 @@ const locales = {
docs: 'Docs',
edit: 'Edit this page',
changelog: 'Changelog',
design: 'Design',
version: 'Version',
},
};
@ -57,24 +60,8 @@ const useStyle = createStyles(({ token }) => ({
text-decoration: underline !important;
}
`,
import: css`
color: ${token.magenta8};
`,
component: css`
color: ${token.colorText};
`,
from: css`
color: ${token.magenta8};
margin-inline-end: 0.5em;
`,
antd: css`
color: ${token.green8};
`,
semicolon: css`
color: ${token.colorText};
`,
icon: css`
margin-inline-end: ${token.marginXXS}px;
margin-inline-end: 3px;
`,
}));
@ -83,10 +70,11 @@ export interface ComponentMetaProps {
source: string | true;
filename?: string;
version?: string;
designUrl?: string;
}
const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
const { component, source, filename, version } = props;
const { component, source, filename, version, designUrl } = props;
const { token } = theme.useToken();
const [locale, lang] = useLocale(locales);
const isZhCN = lang === 'cn';
@ -130,23 +118,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
};
// ======================== Render ========================
const importList = [
<span key="import" className={styles.import}>
import
</span>,
<span key="component" className={styles.component}>{`{ ${transformComponentName(
component,
)} }`}</span>,
<span key="from" className={styles.from}>
from
</span>,
<span key="antd" className={styles.antd}>
{`"antd"`}
</span>,
<span key="semicolon" className={styles.semicolon}>
;
</span>,
];
const importList = `import { ${transformComponentName(component)} } from "antd";`;
return (
<Descriptions
@ -154,7 +126,9 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
colon={false}
column={1}
style={{ marginTop: token.margin }}
labelStyle={{ paddingInlineEnd: token.padding, width: 56 }}
styles={{
label: { paddingInlineEnd: token.padding, width: 56 },
}}
items={
[
{
@ -185,7 +159,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
filename && {
label: locale.docs,
children: (
<Flex justify="flex-start" align="center" gap="middle">
<Flex justify="flex-start" align="center" gap="small">
<Typography.Link
className={styles.code}
href={`${branchUrl}${filename}`}
@ -194,6 +168,12 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
<EditOutlined className={styles.icon} />
<span>{locale.edit}</span>
</Typography.Link>
{designUrl && (
<Link className={styles.code} to={designUrl}>
<CompassOutlined className={styles.icon} />
<span>{locale.design}</span>
</Link>
)}
<ComponentChangelog>
<Typography.Link className={styles.code}>
<HistoryOutlined className={styles.icon} />
@ -207,7 +187,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
label: locale.version,
children: (
<Typography.Text className={styles.code}>
{isZhCN ? `${version} 支持` : `supported since ${version}`}
{isZhCN ? `${version} 支持` : `supported since ${version}`}
</Typography.Text>
),
},

View File

@ -1,4 +1,4 @@
import React, { memo, useContext, useMemo, useRef, useState } from 'react';
import React, { memo, useMemo, useRef, useState } from 'react';
import type { CSSProperties } from 'react';
import { SearchOutlined } from '@ant-design/icons';
import { Affix, Card, Col, Divider, Flex, Input, Row, Tag, Typography } from 'antd';
@ -83,7 +83,7 @@ const { Title } = Typography;
const Overview: React.FC = () => {
const { styles } = useStyle();
const { theme } = useContext(SiteContext);
const { theme } = React.use(SiteContext);
const data = useSidebarData();
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean>(false);

View File

@ -1,6 +1,6 @@
import React, { useMemo, useState } from 'react';
import { LinkOutlined, QuestionCircleOutlined, RightOutlined } from '@ant-design/icons';
import { ConfigProvider, Popover, Table, Typography } from 'antd';
import { ConfigProvider, Flex, Popover, Table, Typography } from 'antd';
import { createStyles, css, useTheme } from 'antd-style';
import { getDesignToken } from 'antd-token-previewer';
import tokenMeta from 'antd/es/version/token-meta.json';
@ -61,16 +61,15 @@ const useStyle = createStyles(({ token }) => ({
align-items: center;
justify-content: flex-start;
line-height: 40px;
gap: ${token.marginXS}px;
`,
arrowIcon: css`
font-size: ${token.fontSizeLG}px;
margin-inline-end: ${token.marginXS}px;
& svg {
transition: all ${token.motionDurationSlow};
}
`,
help: css`
margin-inline-start: ${token.marginXS}px;
font-size: ${token.fontSizeSM}px;
font-weight: normal;
color: #999;
@ -78,6 +77,10 @@ const useStyle = createStyles(({ token }) => ({
color: #999;
}
`,
tokenTitle: css`
font-size: ${token.fontSizeLG}px;
font-weight: bold;
`,
}));
interface SubTokenTableProps {
@ -153,15 +156,17 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
<>
<div className={styles.tableTitle} onClick={() => setOpen(!open)}>
<RightOutlined className={styles.arrowIcon} rotate={open ? 90 : 0} />
<h3>
<Flex className={styles.tokenTitle} gap="small" justify="flex-start" align="center">
{title}
<Popover
title={null}
overlayStyle={{ width: 400 }}
destroyTooltipOnHide
styles={{ root: { width: 400 } }}
content={
<Typography>
{/* <SourceCode lang="jsx">{code}</SourceCode> */}
<pre style={{ fontSize: 12 }}>{code}</pre>
<pre dir="ltr" style={{ fontSize: 12 }}>
<code dir="ltr">{code}</code>
</pre>
<a href={helpLink} target="_blank" rel="noreferrer">
<LinkOutlined style={{ marginInlineEnd: 4 }} />
{helpText}
@ -174,7 +179,7 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
{helpText}
</span>
</Popover>
</h3>
</Flex>
</div>
{open && (
<ConfigProvider theme={{ token: { borderRadius: 0 } }}>

View File

@ -1,13 +1,13 @@
import React, { Suspense, useContext } from 'react';
import React, { Suspense } from 'react';
import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons';
import { ConfigProvider, Tooltip, Button } from 'antd';
import classNames from 'classnames';
import { DumiDemoGrid, FormattedMessage } from 'dumi';
import { css, Global } from '@emotion/react';
import { Button, ConfigProvider, Tooltip } from 'antd';
import { DumiDemo, DumiDemoGrid, FormattedMessage } from 'dumi';
import useLayoutState from '../../../hooks/useLayoutState';
import useLocale from '../../../hooks/useLocale';
import DemoContext from '../../slots/DemoContext';
import DemoFallback from '../Previewer/DemoFallback';
const locales = {
cn: {
@ -21,7 +21,7 @@ const locales = {
};
const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
const { showDebug, setShowDebug } = useContext(DemoContext);
const { showDebug, setShowDebug } = React.use(DemoContext);
const [locale] = useLocale(locales);
const [expandAll, setExpandAll] = useLayoutState(false);
@ -41,13 +41,16 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
const demos = React.useMemo(
() =>
items.map((item: any) => {
items.reduce<typeof items>((acc, item) => {
const { previewerProps } = item;
const { debug } = previewerProps;
return {
if (debug && !showDebug) {
return acc;
}
return acc.concat({
...item,
previewerProps: {
...item.previewerProps,
...previewerProps,
expand: expandAll,
// always override debug property, because dumi will hide debug demo in production
debug: false,
@ -57,17 +60,13 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
*/
originDebug: debug,
},
};
}),
});
}, []),
[expandAll, showDebug],
);
return (
<div
className={classNames('demo-wrapper', {
'demo-wrapper-show-debug': showDebug,
})}
>
<div className="demo-wrapper">
<Global
styles={css`
:root {
@ -113,9 +112,14 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
</Tooltip>
</span>
<ConfigProvider theme={{ cssVar: enableCssVar, hashed: !enableCssVar }}>
<Suspense>
<DumiDemoGrid items={demos} />
</Suspense>
<DumiDemoGrid
items={demos}
demoRender={(item) => (
<Suspense key={item.demo.id} fallback={<DemoFallback />}>
<DumiDemo {...item} />
</Suspense>
)}
/>
</ConfigProvider>
</div>
);

View File

@ -44,7 +44,7 @@ const IconSearchFallback: React.FC = () => {
</div>
<Skeleton.Button active style={{ margin: '28px 0 10px', width: 100 }} />
<div className={styles.fallbackWrapper}>
{new Array(24).fill(1).map((_, index) => (
{Array.from({ length: 24 }).map((_, index) => (
<div key={index} className={styles.skeletonWrapper}>
<Skeleton.Node active style={{ height: 110, width: '100%' }}>
{' '}

View File

@ -0,0 +1,80 @@
import React, { Suspense, useEffect, useState } from 'react';
import { Tooltip } from 'antd';
import { FormattedMessage } from 'dumi';
import { ping } from '../../utils';
let pingDeferrer: PromiseLike<boolean>;
const codeBlockJs =
'https://renderoffice.a' +
'lipay' +
'objects.com/p' +
'/yuyan/180020010001206410/parseFileData-v1.0.1.js';
function useShowCodeBlockButton() {
const [showCodeBlockButton, setShowCodeBlockButton] = useState(false);
useEffect(() => {
pingDeferrer ??= new Promise<boolean>((resolve) => {
ping((status) => {
if (status !== 'timeout' && status !== 'error') {
// Async insert `codeBlockJs` into body end
const script = document.createElement('script');
script.src = codeBlockJs;
script.async = true;
document.body.appendChild(script);
return resolve(true);
}
return resolve(false);
});
});
pingDeferrer.then(setShowCodeBlockButton);
}, []);
return showCodeBlockButton;
}
interface CodeBlockButtonProps {
title?: string;
dependencies: Record<PropertyKey, string>;
jsx: string;
}
const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies = {}, jsx }) => {
const showCodeBlockButton = useShowCodeBlockButton();
const codeBlockPrefillConfig = {
title: `${title} - antd@${dependencies.antd}`,
js: `${
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
/export default/,
'const ComponentDemo =',
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
css: '',
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
};
return showCodeBlockButton ? (
<Tooltip title={<FormattedMessage id="app.demo.codeblock" />}>
<div className="code-box-code-action">
<img
alt="codeblock"
src="https://mdn.alipayobjects.com/huamei_wtld8u/afts/img/A*K8rjSJpTNQ8AAAAAAAAAAAAADhOIAQ/original"
className="code-box-codeblock"
onClick={() => {
openHituCodeBlock(JSON.stringify(codeBlockPrefillConfig));
}}
/>
</div>
</Tooltip>
) : null;
};
export default (props: CodeBlockButtonProps) => (
<Suspense>
<CodeBlockButton {...props} />
</Suspense>
);

View File

@ -1,4 +1,5 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
import React, { useEffect, useRef, useState } from 'react';
import { LinkOutlined, ThunderboltOutlined, UpOutlined } from '@ant-design/icons';
import type { Project } from '@stackblitz/sdk';
import stackblitzSdk from '@stackblitz/sdk';
@ -16,11 +17,9 @@ import EditButton from '../../common/EditButton';
import CodePenIcon from '../../icons/CodePenIcon';
import CodeSandboxIcon from '../../icons/CodeSandboxIcon';
import ExternalLinkIcon from '../../icons/ExternalLinkIcon';
import RiddleIcon from '../../icons/RiddleIcon';
import DemoContext from '../../slots/DemoContext';
import type { SiteContextProps } from '../../slots/SiteContext';
import SiteContext from '../../slots/SiteContext';
import { ping } from '../../utils';
import CodeBlockButton from './CodeBlockButton';
import type { AntdPreviewerProps } from './Previewer';
const { ErrorBoundary } = Alert;
@ -39,27 +38,6 @@ const track = ({ type, demo }: { type: string; demo: string }) => {
window.gtag('event', 'demo', { event_category: type, event_label: demo });
};
let pingDeferrer: PromiseLike<boolean>;
function useShowRiddleButton() {
const [showRiddleButton, setShowRiddleButton] = useState(false);
useEffect(() => {
pingDeferrer ??= new Promise<boolean>((resolve) => {
ping((status) => {
if (status !== 'timeout' && status !== 'error') {
return resolve(true);
}
return resolve(false);
});
});
pingDeferrer.then(setShowRiddleButton);
}, []);
return showRiddleButton;
}
const useStyle = createStyles(({ token }) => {
const { borderRadius } = token;
return {
@ -98,7 +76,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
title,
description,
originDebug,
jsx,
jsx = '',
style,
compact,
background,
@ -108,7 +86,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
clientOnly,
pkgDependencyList,
} = props;
const { codeType } = useContext(DemoContext);
const { codeType } = React.use(DemoContext);
const { pkg } = useSiteData();
const location = useLocation();
@ -117,7 +95,6 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
const entryName = 'index.tsx';
const entryCode = asset.dependencies[entryName].value;
const showRiddleButton = useShowRiddleButton();
const previewDemo = useRef<React.ReactNode>(null);
const demoContainer = useRef<HTMLElement>(null);
@ -127,14 +104,13 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
setSource: setLiveDemoSource,
} = useLiveDemo(asset.id, {
iframe: Boolean(iframe),
containerRef: demoContainer,
containerRef: demoContainer as React.RefObject<HTMLElement>,
});
const anchorRef = useRef<HTMLAnchorElement>(null);
const codeSandboxIconRef = useRef<HTMLFormElement>(null);
const riddleIconRef = useRef<HTMLFormElement>(null);
const codepenIconRef = useRef<HTMLFormElement>(null);
const [codeExpand, setCodeExpand] = useState<boolean>(false);
const { theme } = useContext<SiteContextProps>(SiteContext);
const { theme } = React.use(SiteContext);
const { hash, pathname, search } = location;
const docsOnlineUrl = `https://ant.design${pathname}${search}#${asset.id}`;
@ -164,12 +140,13 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
}, [expand]);
const mergedChildren = !iframe && clientOnly ? <ClientOnly>{children}</ClientOnly> : children;
const demoUrlWithTheme = `${demoUrl}${theme.includes('dark') ? '?theme=dark' : ''}`;
if (!previewDemo.current) {
previewDemo.current = iframe ? (
<BrowserFrame>
<iframe
src={demoUrl}
src={demoUrlWithTheme}
height={iframe === true ? undefined : iframe}
title="demo"
className="iframe-demo"
@ -278,18 +255,6 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
js_pre_processor: 'typescript',
};
const riddlePrefillConfig = {
title: `${localizedTitle} - antd@${dependencies.antd}`,
js: `${
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
/export default/,
'const ComponentDemo =',
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
css: '',
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
};
// Reorder source code
let parsedSourceCode = suffix === 'tsx' ? entryCode : jsx;
let importReactContent = "import React from 'react';";
@ -356,11 +321,18 @@ createRoot(document.getElementById('container')).render(<Demo />);
const stackblitzPrefillConfig: Project = {
title: `${localizedTitle} - antd@${dependencies.antd}`,
template: 'create-react-app',
dependencies,
dependencies:{
...dependencies,
react: '^19.0.0',
'react-dom': '^19.0.0',
'@types/react': '^19.0.0',
'@types/react-dom': '^19.0.0',
'@ant-design/v5-patch-for-react-19': '^1.0.3'
},
description: '',
files: {
'index.css': indexCssContent,
[`index.${suffix}`]: indexJsContent,
[`index.${suffix}`]: `import '@ant-design/v5-patch-for-react-19';\n${indexJsContent}`,
[`demo.${suffix}`]: demoJsContent,
'index.html': html,
},
@ -440,24 +412,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
<CodeSandboxIcon className="code-box-codesandbox" />
</Tooltip>
</form>
{showRiddleButton ? (
<form
className="code-box-code-action"
action="//riddle.alibaba-inc.com/riddles/define"
method="POST"
target="_blank"
ref={riddleIconRef}
onClick={() => {
track({ type: 'riddle', demo: asset.id });
riddleIconRef.current?.submit();
}}
>
<input type="hidden" name="data" value={JSON.stringify(riddlePrefillConfig)} />
<Tooltip title={<FormattedMessage id="app.demo.riddle" />}>
<RiddleIcon className="code-box-riddle" />
</Tooltip>
</form>
) : null}
<CodeBlockButton title={localizedTitle} dependencies={dependencies} jsx={jsx} />
<Tooltip title={<FormattedMessage id="app.demo.stackblitz" />}>
<span
className="code-box-code-action"
@ -498,7 +453,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
aria-label="open in new tab"
target="_blank"
rel="noreferrer"
href={demoUrl}
href={demoUrlWithTheme}
>
<ExternalLinkIcon className="code-box-separate" />
</a>
@ -568,12 +523,12 @@ createRoot(document.getElementById('container')).render(<Demo />);
const styleTag = document.createElement('style') as HTMLStyleElement;
styleTag.type = 'text/css';
styleTag.innerHTML = style;
(styleTag as any)['data-demo-url'] = demoUrl;
(styleTag as any)['data-demo-url'] = demoUrlWithTheme;
document.head.appendChild(styleTag);
return () => {
document.head.removeChild(styleTag);
};
}, [style, demoUrl]);
}, [style, demoUrlWithTheme]);
if (version) {
return (

View File

@ -0,0 +1,27 @@
import React from 'react';
import { Skeleton } from 'antd';
import { createStyles } from 'antd-style';
const useStyle = createStyles(({ token, css }) => ({
skeletonWrapper: css`
width: 100% !important;
height: 250px;
margin-bottom: ${token.margin}px;
border-radius: ${token.borderRadiusLG}px;
`,
}));
const DemoFallback = () => {
const { styles } = useStyle();
return (
<Skeleton.Node
active
className={styles.skeletonWrapper}
style={{ width: '100%', height: '100%' }}
>
{' '}
</Skeleton.Node>
);
};
export default DemoFallback;

View File

@ -7,6 +7,7 @@ import DesignPreviewer from './DesignPreviewer';
export interface AntdPreviewerProps extends IPreviewerProps {
originDebug?: IPreviewerProps['debug'];
jsx?: string;
}
const Previewer: React.FC<AntdPreviewerProps> = (props) => {

View File

@ -0,0 +1,91 @@
import React, { Suspense, useEffect, useRef, useState } from 'react';
import { Tooltip } from 'antd';
import { FormattedMessage } from 'dumi';
import type { IPreviewerProps } from 'dumi';
import RiddleIcon from '../../icons/RiddleIcon';
import { ping } from '../../utils';
let pingDeferrer: PromiseLike<boolean>;
function useShowRiddleButton() {
const [showRiddleButton, setShowRiddleButton] = useState(false);
useEffect(() => {
pingDeferrer ??= new Promise<boolean>((resolve) => {
ping((status) => {
if (status !== 'timeout' && status !== 'error') {
return resolve(true);
}
return resolve(false);
});
});
pingDeferrer.then(setShowRiddleButton);
}, []);
return showRiddleButton;
}
interface RiddleButtonProps {
title?: string;
dependencies: Record<PropertyKey, string>;
jsx: string;
track: ({
type,
demo,
}: {
type: string;
demo: string;
}) => void;
asset: IPreviewerProps['asset'];
}
const RiddleButton: React.FC<RiddleButtonProps> = ({
title,
dependencies = {},
jsx,
track,
asset,
}) => {
const riddleIconRef = useRef<HTMLFormElement>(null);
const showRiddleButton = useShowRiddleButton();
const riddlePrefillConfig = {
title: `${title} - antd@${dependencies.antd}`,
js: `${
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
/export default/,
'const ComponentDemo =',
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
css: '',
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
};
return showRiddleButton ? (
<form
className="code-box-code-action"
action="//riddle.alibaba-inc.com/riddles/define"
method="POST"
target="_blank"
ref={riddleIconRef}
onClick={() => {
track({ type: 'riddle', demo: asset.id });
riddleIconRef.current?.submit();
}}
>
<input type="hidden" name="data" value={JSON.stringify(riddlePrefillConfig)} />
<Tooltip title={<FormattedMessage id="app.demo.riddle" />}>
<RiddleIcon className="code-box-riddle" />
</Tooltip>
</form>
) : null;
};
export default (props: RiddleButtonProps) => (
<Suspense>
<RiddleButton {...props} />
</Suspense>
);

View File

@ -1,39 +1,17 @@
import React, { Suspense } from 'react';
import { Alert, Skeleton } from 'antd';
import { createStyles } from 'antd-style';
import { Alert } from 'antd';
import Previewer from './Previewer';
import DemoFallback from './DemoFallback';
import type { IPreviewerProps } from 'dumi';
const { ErrorBoundary } = Alert;
const useStyle = createStyles(({ token, css }) => ({
skeletonWrapper: css`
width: 100% !important;
height: 250px;
margin-bottom: ${token.margin}px;
border-radius: ${token.borderRadiusLG}px;
`,
}));
const PreviewerSuspense: React.FC<IPreviewerProps> = (props) => {
const { styles } = useStyle();
return (
<ErrorBoundary>
<Suspense
fallback={
<Skeleton.Node
active
className={styles.skeletonWrapper}
style={{ width: '100%', height: '100%' }}
>
{' '}
</Skeleton.Node>
}
>
<Previewer {...props} />
</Suspense>
</ErrorBoundary>
);
};
const PreviewerSuspense: React.FC<IPreviewerProps> = (props) => (
<ErrorBoundary>
<Suspense fallback={<DemoFallback />}>
<Previewer {...props} />
</Suspense>
</ErrorBoundary>
);
export default PreviewerSuspense;

View File

@ -1,6 +1,6 @@
// 用于 color.md 中的颜色对比
import React from 'react';
import { TinyColor } from '@ctrl/tinycolor';
import { FastColor } from '@ant-design/fast-color';
import { Flex, theme } from 'antd';
import { createStyles } from 'antd-style';
import tokenMeta from 'antd/es/version/token-meta.json';
@ -55,7 +55,7 @@ const useStyle = createStyles(({ token, css }) => {
});
function color2Rgba(color: string) {
return `#${new TinyColor(color).toHex8().toUpperCase()}`;
return `#${new FastColor(color).toHexString().toUpperCase()}`;
}
interface ColorCircleProps {

View File

@ -7,6 +7,7 @@ import { getDesignToken } from 'antd-token-previewer';
import tokenMeta from 'antd/es/version/token-meta.json';
import useLocale from '../../../hooks/useLocale';
import BezierVisualizer from '../../common/BezierVisualizer';
import ColorChunk from '../ColorChunk';
type TokenTableProps = {
@ -79,7 +80,19 @@ export function useColumns(): Exclude<TableProps<TokenData>['columns'], undefine
typeof record.value === 'string' &&
(record.value.startsWith('#') || record.value.startsWith('rgb'));
if (isColor) {
return <ColorChunk value={record.value}>{record.value}</ColorChunk>;
return (
<ColorChunk value={record.value} enablePopover>
{record.value}
</ColorChunk>
);
}
const isBezier =
typeof record.value === 'string' &&
record.value.toLowerCase().trim().startsWith('cubic-bezier');
if (isBezier) {
return <BezierVisualizer value={record.value} />;
}
return typeof record.value !== 'string' ? JSON.stringify(record.value) : record.value;
},
@ -99,7 +112,7 @@ const TokenTable: FC<TokenTableProps> = ({ type }) => {
name: token,
desc: lang === 'cn' ? meta.desc : meta.descEn,
type: meta.type,
value: defaultToken[token],
value: defaultToken[token as keyof typeof defaultToken],
})),
[type, lang],
);

View File

@ -0,0 +1,87 @@
import React, { useId } from 'react';
import { theme } from 'antd';
export interface VisualizerProps {
/**
*
* @description [0, 1]
* @example [0.78, 0.14, 0.15, 0.86]
*/
controls: [number, number, number, number];
width?: number;
height?: number;
duration?: number;
}
const Visualizer: React.FC<VisualizerProps> = (props) => {
const {
controls: [x1, y1, x2, y2],
width = 180,
height = width,
} = props;
const { token } = theme.useToken();
// 坐标转换到SVG视图
const scale = (val: number, axis: 'x' | 'y') =>
axis === 'x' ? val * width : height - val * height;
const gridStep = width / 5; // 网格步长
const patternId = useId(); // 生成唯一ID
return (
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
<title>Cubic Bezier Visualizer</title>
{/* 背景 */}
<rect width="100%" height="100%" fill={token.colorBgContainer} />
{/* 修正后的网格 */}
<pattern id={patternId} width={gridStep} height={gridStep} patternUnits="userSpaceOnUse">
<path
d={`
M 0 0 H ${gridStep}
M 0 0 V ${gridStep}
M ${gridStep} 0 V ${gridStep}
M 0 ${gridStep} H ${gridStep}
`}
stroke={token.colorBorderSecondary}
strokeWidth={token.controlOutlineWidth}
shapeRendering="crispEdges"
/>
</pattern>
<rect width="100%" height="100%" fill={`url(#${patternId})`} />
{/* 贝塞尔曲线路径 */}
<path
d={`
M 0 ${height}
C ${scale(x1, 'x')} ${scale(y1, 'y')},
${scale(x2, 'x')} ${scale(y2, 'y')},
${width} 0
`}
fill="none"
stroke={token.colorPrimary}
strokeWidth={token.controlOutlineWidth * 2}
/>
{/* 控制点连线 */}
<path
d={`
M 0 ${height}
L ${scale(x1, 'x')} ${scale(y1, 'y')}
L ${scale(x2, 'x')} ${scale(y2, 'y')}
L ${width} 0
`}
fill="none"
stroke={token.colorPrimaryActive}
strokeDasharray="4 2"
strokeWidth={token.controlOutlineWidth}
/>
{/* 控制点 */}
<circle cx={scale(x1, 'x')} cy={scale(y1, 'y')} r="5" fill={token['red-6']} />
<circle cx={scale(x2, 'x')} cy={scale(y2, 'y')} r="5" fill={token['green-6']} />
</svg>
);
};
export default Visualizer;

View File

@ -0,0 +1,55 @@
import React, { useMemo } from 'react';
import { Button, Flex, Tooltip, Typography } from 'antd';
import useLocale from '../../../hooks/useLocale';
import ExternalLinkIcon from '../../icons/ExternalLinkIcon';
import Visualizer from './Visualizer';
export interface BezierVisualizerProps {
value: string;
}
const RE = /^cubic-bezier\((.*)\)$/;
const locales = {
cn: {
open: '在 cubic-bezier.com 中打开',
},
en: {
open: 'Open in cubic-bezier.com',
},
};
const BezierVisualizer = (props: BezierVisualizerProps) => {
const { value } = props;
const [locale] = useLocale(locales);
const controls = useMemo(() => {
const m = RE.exec(value.toLowerCase().trim());
if (m) {
return m[1].split(',').map((v) => parseFloat(v.trim())) as [number, number, number, number];
}
return null;
}, [value]);
if (!controls) return null;
return (
<Flex vertical gap="small">
<Visualizer controls={controls} />
<Flex align="center">
<Typography.Text>{value}</Typography.Text>
<Tooltip title={locale.open}>
<Button
type="link"
href={`https://cubic-bezier.com/#${controls.join(',')}`}
target="_blank"
icon={<ExternalLinkIcon />}
/>
</Tooltip>
</Flex>
</Flex>
);
};
export default BezierVisualizer;

View File

@ -1,5 +1,6 @@
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
import type { ComponentProps } from 'react';
import React, { useContext, useEffect, useMemo } from 'react';
import React, { useEffect, useMemo } from 'react';
import { Button, Tabs, Typography } from 'antd';
import { createStyles } from 'antd-style';
import toReactElement from 'jsonml-to-react-element';
@ -108,7 +109,7 @@ const CodePreview: React.FC<CodePreviewProps> = ({
initialCodes.style = '';
}
const [highlightedCodes, setHighlightedCodes] = React.useState(initialCodes);
const { codeType, setCodeType } = useContext(DemoContext);
const { codeType, setCodeType } = React.use(DemoContext);
const sourceCodes = {
// omit trailing line break
tsx: sourceCode?.trim(),

View File

@ -20,6 +20,7 @@ interface ChangelogInfo {
version: string;
changelog: string;
refs: string[];
contributors: string[];
releaseDate: string;
}
@ -136,9 +137,9 @@ const ParseChangelog: React.FC<{ changelog: string }> = (props) => {
} else {
let node: React.ReactNode = lastStr;
if (isQuota) {
node = <code>{node}</code>;
node = <code key={`code-${i}`}>{node}</code>;
} else if (isBold) {
node = <strong>{node}</strong>;
node = <strong key={`strong-${i}`}>{node}</strong>;
}
nodes.push(node);
@ -160,14 +161,30 @@ const ParseChangelog: React.FC<{ changelog: string }> = (props) => {
return <span>{parsedChangelog}</span>;
};
const RefLinks: React.FC<{ refs: string[] }> = ({ refs }) => {
const RefLinks: React.FC<{ refs: string[]; contributors: string[] }> = ({ refs, contributors }) => {
const { styles } = useStyle();
return (
<>
{refs?.map((ref) => (
<a className={styles.linkRef} key={ref} href={ref} target="_blank" rel="noreferrer">
#{ref.match(/^.*\/(\d+)$/)?.[1]}
</a>
<React.Fragment key={ref}>
<a className={styles.linkRef} key={ref} href={ref} target="_blank" rel="noreferrer">
#{ref.match(/[^/]+$/)?.[0]}
</a>
</React.Fragment>
))}
{contributors?.map((contributor) => (
<React.Fragment key={contributor}>
<a
className={styles.linkRef}
key={contributor}
href={`https://github.com/${contributor}`}
target="_blank"
rel="noreferrer"
>
@{contributor}
</a>
</React.Fragment>
))}
</>
);
@ -178,7 +195,7 @@ const RenderChangelogList: React.FC<{ changelogList: ChangelogInfo[] }> = ({ cha
const { styles } = useStyle();
const len = changelogList.length;
for (let i = 0; i < len; i += 1) {
const { refs, changelog } = changelogList[i];
const { refs, changelog, contributors } = changelogList[i];
// Check if the next line is an image link and append it to the current line
if (i + 1 < len && changelogList[i + 1].changelog.trim().startsWith('<img')) {
const imgDom = new DOMParser().parseFromString(changelogList[i + 1].changelog, 'text/html');
@ -186,7 +203,7 @@ const RenderChangelogList: React.FC<{ changelogList: ChangelogInfo[] }> = ({ cha
elements.push(
<li key={i}>
<ParseChangelog changelog={changelog} />
<RefLinks refs={refs} />
<RefLinks refs={refs} contributors={contributors} />
<br />
<img
src={imgElement?.getAttribute('src') || ''}
@ -200,7 +217,7 @@ const RenderChangelogList: React.FC<{ changelogList: ChangelogInfo[] }> = ({ cha
elements.push(
<li key={i}>
<ParseChangelog changelog={changelog} />
<RefLinks refs={refs} />
<RefLinks refs={refs} contributors={contributors} />
</li>,
);
}
@ -306,7 +323,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
return (
<>
{isValidElement(children) &&
cloneElement(children as React.ReactElement, {
cloneElement(children as React.ReactElement<any>, {
onClick: () => setShow(true),
})}
<Drawer

View File

@ -3,7 +3,7 @@ import React from 'react';
import ComponentChangelog from './ComponentChangelog';
const ChangeLog: React.FC<Readonly<React.PropsWithChildren>> = ({ children }) => (
<React.Suspense fallback={null}>
<React.Suspense fallback="...">
<ComponentChangelog>{children}</ComponentChangelog>
</React.Suspense>
);

View File

@ -1,5 +1,5 @@
import type { ReactElement } from 'react';
import React, { useContext, useMemo } from 'react';
import React, { useMemo } from 'react';
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import type { GetProp, MenuProps } from 'antd';
import { createStyles } from 'antd-style';
@ -7,7 +7,6 @@ import classNames from 'classnames';
import useMenu from '../../hooks/useMenu';
import SiteContext from '../slots/SiteContext';
import type { SiteContextProps } from '../slots/SiteContext';
type MenuItemType = Extract<GetProp<MenuProps, 'items'>[number], { type?: 'item' }>;
@ -115,7 +114,7 @@ const PrevAndNext: React.FC<{ rtl?: boolean }> = ({ rtl }) => {
const [menuItems, selectedKey] = useMenu({ before, after });
const { isMobile } = useContext<SiteContextProps>(SiteContext);
const { isMobile } = React.use(SiteContext);
const [prev, next] = useMemo(() => {
const flatMenu = flattenMenu(menuItems);
@ -141,13 +140,23 @@ const PrevAndNext: React.FC<{ rtl?: boolean }> = ({ rtl }) => {
return (
<section className={styles.prevNextNav}>
{prev &&
React.cloneElement(prev.label as ReactElement, {
className: classNames(styles.pageNav, styles.prevNav, prev.className),
})}
React.cloneElement(
prev.label as ReactElement<{
className: string;
}>,
{
className: classNames(styles.pageNav, styles.prevNav, prev.className),
},
)}
{next &&
React.cloneElement(next.label as ReactElement, {
className: classNames(styles.pageNav, styles.nextNav, next.className),
})}
React.cloneElement(
next.label as ReactElement<{
className: string;
}>,
{
className: classNames(styles.pageNav, styles.nextNav, next.className),
},
)}
</section>
);
};

View File

@ -1,11 +1,13 @@
import React from 'react';
import { BgColorsOutlined, SmileOutlined } from '@ant-design/icons';
import { FloatButton } from 'antd';
import React, { use } from 'react';
import { BgColorsOutlined, LinkOutlined, SmileOutlined, SunOutlined } from '@ant-design/icons';
import { Badge, Button, Dropdown } from 'antd';
import type { MenuProps } from 'antd';
import { CompactTheme, DarkTheme } from 'antd-token-previewer/es/icons';
// import { Motion } from 'antd-token-previewer/es/icons';
import { FormattedMessage, useLocation } from 'dumi';
import useThemeAnimation from '../../../hooks/useThemeAnimation';
import type { SiteContextProps } from '../../slots/SiteContext';
import SiteContext from '../../slots/SiteContext';
import { getLocalizedPathname, isZhCN } from '../../utils';
import Link from '../Link';
import ThemeIcon from './ThemeIcon';
@ -14,80 +16,119 @@ export type ThemeName = 'light' | 'dark' | 'compact' | 'motion-off' | 'happy-wor
export interface ThemeSwitchProps {
value?: ThemeName[];
onChange: (value: ThemeName[]) => void;
}
const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {
const { value = ['light'], onChange } = props;
const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
const { pathname, search } = useLocation();
// const isMotionOff = value.includes('motion-off');
const isHappyWork = value.includes('happy-work');
const isDark = value.includes('dark');
const { theme, updateSiteConfig } = use<SiteContextProps>(SiteContext);
const toggleAnimationTheme = useThemeAnimation();
return (
<FloatButton.Group
trigger="click"
icon={<ThemeIcon />}
aria-label="Theme Switcher"
badge={{ dot: true }}
>
<Link
to={getLocalizedPathname('/theme-editor', isZhCN(pathname), search)}
style={{ display: 'block' }}
>
<FloatButton
icon={<BgColorsOutlined />}
tooltip={<FormattedMessage id="app.footer.theme" />}
/>
</Link>
<FloatButton
icon={<DarkTheme />}
type={isDark ? 'primary' : 'default'}
onClick={(e) => {
// Toggle animation when switch theme
toggleAnimationTheme(e, isDark);
const badge = <Badge color="blue" style={{ marginTop: -1 }} />;
if (isDark) {
onChange(value.filter((theme) => theme !== 'dark'));
} else {
onChange([...value, 'dark']);
}
}}
tooltip={<FormattedMessage id="app.theme.switch.dark" />}
/>
<FloatButton
icon={<CompactTheme />}
type={value.includes('compact') ? 'primary' : 'default'}
onClick={() => {
if (value.includes('compact')) {
onChange(value.filter((theme) => theme !== 'compact'));
} else {
onChange([...value, 'compact']);
}
}}
tooltip={<FormattedMessage id="app.theme.switch.compact" />}
/>
<FloatButton
badge={{ dot: true }}
icon={<SmileOutlined />}
type={isHappyWork ? 'primary' : 'default'}
onClick={() => {
if (isHappyWork) {
onChange(value.filter((theme) => theme !== 'happy-work'));
} else {
onChange([...value, 'happy-work']);
}
}}
tooltip={
<FormattedMessage
id={isHappyWork ? 'app.theme.switch.happy-work.off' : 'app.theme.switch.happy-work.on'}
/>
}
/>
</FloatButton.Group>
// 主题选项配置
const themeOptions = [
{
id: 'app.theme.switch.default',
icon: <SunOutlined />,
key: 'light',
showBadge: () => theme.includes('light') || theme.length === 0,
},
{
id: 'app.theme.switch.dark',
icon: <DarkTheme />,
key: 'dark',
showBadge: () => theme.includes('dark'),
},
{
type: 'divider',
},
{
id: 'app.theme.switch.compact',
icon: <CompactTheme />,
key: 'compact',
showBadge: () => theme.includes('compact'),
},
{
type: 'divider',
},
{
id: 'app.theme.switch.happy-work',
icon: <SmileOutlined />,
key: 'happy-work',
showBadge: () => theme.includes('happy-work'),
},
{
type: 'divider',
},
{
id: 'app.footer.theme',
icon: <BgColorsOutlined />,
key: 'theme-editor',
extra: <LinkOutlined />,
isLink: true,
linkPath: '/theme-editor',
},
];
// 构建下拉菜单项
const items = themeOptions.map((option, i) => {
if (option.type === 'divider') {
return { type: 'divider' as const, key: `divider-${i}` };
}
const { id, icon, key, showBadge, extra, isLink, linkPath } = option;
return {
label: isLink ? (
<Link to={getLocalizedPathname(linkPath!, isZhCN(pathname), search)}>
<FormattedMessage id={id} />
</Link>
) : (
<FormattedMessage id={id} />
),
icon,
key: key || i,
extra: showBadge ? (showBadge() ? badge : null) : extra,
};
});
// 处理主题切换
const handleThemeChange = (key: string, domEvent: React.MouseEvent<HTMLElement, MouseEvent>) => {
// 主题编辑器特殊处理
if (key === 'theme-editor') {
return;
}
// 亮色/暗色模式切换时应用动画效果
if (key === 'dark' || key === 'light') {
toggleAnimationTheme(domEvent, theme.includes('dark'));
}
const themeKey = key as ThemeName;
// 亮色/暗色模式是互斥的
if (['light', 'dark'].includes(key)) {
const filteredTheme = theme.filter((t) => !['light', 'dark'].includes(t));
updateSiteConfig({
theme: [...filteredTheme, themeKey],
});
} else {
// 其他主题选项是开关式的
const hasTheme = theme.includes(themeKey);
updateSiteConfig({
theme: hasTheme ? theme.filter((t) => t !== themeKey) : [...theme, themeKey],
});
}
};
const onClick: MenuProps['onClick'] = ({ key, domEvent }) => {
handleThemeChange(key, domEvent as React.MouseEvent<HTMLElement, MouseEvent>);
};
return (
<Dropdown menu={{ items, onClick }} arrow={{ pointAtCenter: true }} placement="bottomRight">
<Button type="text" icon={<ThemeIcon />} style={{ fontSize: 16 }} />
</Dropdown>
);
};

View File

@ -282,12 +282,13 @@ const GlobalDemoStyles: React.FC = () => {
cursor: pointer;
}
&-riddle {
width: 14px;
height: 14px;
&-codeblock {
width: 16px;
height: 16px;
overflow: hidden;
border: 0;
cursor: pointer;
max-width: 100% !important;
}
&-codesandbox {
@ -324,7 +325,6 @@ const GlobalDemoStyles: React.FC = () => {
&-debug {
border-color: ${token.purple3};
display: none;
}
&-debug &-title a {
@ -334,10 +334,6 @@ const GlobalDemoStyles: React.FC = () => {
.demo-wrapper {
position: relative;
&-show-debug .code-box-debug {
display: block;
}
}
.all-code-box-controls {

View File

@ -34,6 +34,8 @@ export default () => {
> .icon-link::before {
font-size: ${token.fontSizeXL}px;
content: '#';
color: ${token.colorTextSecondary};
font-family: ${token.codeFamily};
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { TinyColor } from '@ctrl/tinycolor';
import { FastColor } from '@ant-design/fast-color';
import { css, Global } from '@emotion/react';
import { useTheme } from 'antd-style';
@ -410,7 +410,7 @@ const GlobalStyle: React.FC = () => {
background: ${demoGridColor};
&:nth-child(2n + 1) {
background: ${new TinyColor(demoGridColor).setAlpha(0.75).toHex8String()};
background: ${new FastColor(demoGridColor).setA(0.75).toHexString()};
}
}
@ -426,12 +426,12 @@ const GlobalStyle: React.FC = () => {
}
${antCls}-row .demo-col-1 {
background: ${new TinyColor(demoGridColor).setAlpha(0.75).toHexString()};
background: ${new FastColor(demoGridColor).setA(0.75).toHexString()};
}
${antCls}-row .demo-col-2,
.code-box-demo ${antCls}-row .demo-col-2 {
background: ${new TinyColor(demoGridColor).setAlpha(0.75).toHexString()};
background: ${new FastColor(demoGridColor).setA(0.75).toHexString()};
}
${antCls}-row .demo-col-3,
@ -442,7 +442,7 @@ const GlobalStyle: React.FC = () => {
${antCls}-row .demo-col-4,
.code-box-demo ${antCls}-row .demo-col-4 {
background: ${new TinyColor(demoGridColor).setAlpha(0.6).toHexString()};
background: ${new FastColor(demoGridColor).setA(0.6).toHexString()};
}
${antCls}-row .demo-col-5,

View File

@ -3,7 +3,7 @@ import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import React, { useContext, useEffect, useLayoutEffect, useRef } from 'react';
import React, { useEffect, useLayoutEffect, useRef } from 'react';
import ConfigProvider from 'antd/es/config-provider';
import zhCN from 'antd/es/locale/zh_CN';
import { Helmet, useOutlet, useSiteData } from 'dumi';
@ -37,8 +37,8 @@ const DocLayout: React.FC = () => {
const location = useLocation();
const { pathname, search, hash } = location;
const [locale, lang] = useLocale(locales);
const timerRef = useRef<ReturnType<typeof setTimeout>>();
const { direction } = useContext(SiteContext);
const timerRef = useRef<ReturnType<typeof setTimeout>>(null!);
const { direction } = React.use(SiteContext);
const { loading } = useSiteData();
useLayoutEffect(() => {

View File

@ -1,4 +1,7 @@
import React, { Suspense, useCallback, useEffect } from 'react';
// prettier-ignore
import { scan } from 'react-scan'; // import this BEFORE react
import React, { useCallback, useEffect } from 'react';
import {
createCache,
extractStyle,
@ -16,13 +19,12 @@ import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML }
import { DarkContext } from '../../hooks/useDark';
import useLayoutState from '../../hooks/useLayoutState';
import useLocation from '../../hooks/useLocation';
import type { ThemeName } from '../common/ThemeSwitch';
import SiteThemeProvider from '../SiteThemeProvider';
import type { SiteContextProps } from '../slots/SiteContext';
import SiteContext from '../slots/SiteContext';
const ThemeSwitch = React.lazy(() => import('../common/ThemeSwitch'));
import '@ant-design/v5-patch-for-react-19';
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
@ -43,6 +45,13 @@ if (typeof window !== 'undefined') {
location.hash = `#${hashId.replace(/^components-/, '')}`;
}
}
if (process.env.NODE_ENV !== 'production') {
scan({
enabled: false,
showToolbar: true,
});
}
}
const getAlgorithm = (themes: ThemeName[] = []) =>
@ -60,7 +69,6 @@ const getAlgorithm = (themes: ThemeName[] = []) =>
const GlobalLayout: React.FC = () => {
const outlet = useOutlet();
const { pathname } = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const [{ theme = [], direction, isMobile, bannerVisible = false }, setSiteState] =
useLayoutState<SiteState>({
@ -70,6 +78,9 @@ const GlobalLayout: React.FC = () => {
bannerVisible: false,
});
// TODO: This can be remove in v6
const useCssVar = searchParams.get('cssVar') !== 'false';
const updateSiteConfig = useCallback(
(props: SiteState) => {
setSiteState((prev) => ({ ...prev, ...props }));
@ -150,8 +161,8 @@ const GlobalLayout: React.FC = () => {
() => ({
algorithm: getAlgorithm(theme),
token: { motion: !theme.includes('motion-off') },
cssVar: true,
hashed: false,
cssVar: useCssVar,
hashed: !useCssVar,
}),
[theme],
);
@ -192,39 +203,21 @@ const GlobalLayout: React.FC = () => {
/>
));
const demoPage = pathname.startsWith('/~demos');
// ============================ Render ============================
let content: React.ReactNode = outlet;
// Demo page should not contain App component
if (!demoPage) {
content = (
<App>
{outlet}
<Suspense>
<ThemeSwitch
value={theme}
onChange={(nextTheme) => updateSiteConfig({ theme: nextTheme })}
/>
</Suspense>
</App>
);
}
return (
<DarkContext.Provider value={theme.includes('dark')}>
<DarkContext value={theme.includes('dark')}>
<StyleProvider
cache={styleCache}
linters={[legacyNotSelectorLinter, parentSelectorLinter, NaNLinter]}
>
<SiteContext.Provider value={siteContextValue}>
<SiteContext value={siteContextValue}>
<SiteThemeProvider theme={themeConfig}>
<HappyProvider disabled={!theme.includes('happy-work')}>{content}</HappyProvider>
<HappyProvider disabled={!theme.includes('happy-work')}>
<App>{outlet}</App>
</HappyProvider>
</SiteThemeProvider>
</SiteContext.Provider>
</SiteContext>
</StyleProvider>
</DarkContext.Provider>
</DarkContext>
);
};

View File

@ -4,10 +4,10 @@ import { ConfigProvider, Layout, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { FormattedMessage, useRouteMeta } from 'dumi';
import useDark from '../../../hooks/useDark';
import CommonHelmet from '../../common/CommonHelmet';
import EditButton from '../../common/EditButton';
import Footer from '../../slots/Footer';
import { DarkContext } from './../../../hooks/useDark';
import AffixTabs from './AffixTabs';
export type ResourceLayoutProps = PropsWithChildren<NonNullable<any>>;
@ -16,85 +16,76 @@ const resourcePadding = 40;
const articleMaxWidth = 1208;
const resourcePaddingXS = 24;
const useStyle = () => {
const isRootDark = useDark();
return createStyles((config) => {
const { token, css } = config;
const { antCls } = token;
return {
resourcePage: css`
footer {
margin-top: 176px;
.rc-footer-container {
max-width: ${articleMaxWidth}px;
margin: 0 auto;
padding-inline-end: 0;
padding-inline-start: 0;
}
}
`,
resourceContent: css`
padding: 0 ${resourcePadding}px;
max-width: ${articleMaxWidth}px;
margin: 0 auto;
box-sizing: content-box;
min-height: 100vh;
@media only screen and (max-width: 767.99px) {
& {
article {
padding: 0 ${resourcePaddingXS}px;
}
${antCls}-col {
padding-top: ${token.padding}px !important;
padding-bottom: ${token.padding}px !important;
}
}
}
`,
banner: css`
padding: 0 ${resourcePadding}px;
overflow: hidden;
${
isRootDark
? ``
: `background: url('https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*y_r7RogIG1wAAAAAAAAAAABkARQnAQ');`
}
background-size: cover;
h1 {
box-sizing: content-box;
const useStyle = createStyles(({ token, css }, isDark: boolean) => {
return {
resourcePage: css`
footer {
margin-top: 176px;
.rc-footer-container {
max-width: ${articleMaxWidth}px;
margin: 56px auto 16px;
margin: 0 auto;
padding-inline-end: 0;
padding-inline-start: 0;
}
}
`,
resourceContent: css`
padding: 0 ${resourcePadding}px;
max-width: ${articleMaxWidth}px;
margin: 0 auto;
box-sizing: content-box;
min-height: 100vh;
section {
max-width: ${articleMaxWidth}px;
margin: 0 auto 56px;
font-weight: 200;
font-size: ${token.fontSizeLG}px;
line-height: 24px;
}
@media only screen and (max-width: 767.99px) {
& {
margin: 0 -${resourcePaddingXS}px;
@media only screen and (max-width: 767.99px) {
& {
article {
padding: 0 ${resourcePaddingXS}px;
}
${token.antCls}-col {
padding-top: ${token.padding}px !important;
padding-bottom: ${token.padding}px !important;
}
}
`,
};
})();
};
}
`,
banner: css`
padding: 0 ${resourcePadding}px;
overflow: hidden;
${
isDark
? ``
: `background: url('https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*y_r7RogIG1wAAAAAAAAAAABkARQnAQ');`
}
background-size: cover;
h1 {
box-sizing: content-box;
max-width: ${articleMaxWidth}px;
margin: 56px auto 16px;
}
section {
max-width: ${articleMaxWidth}px;
margin: 0 auto 56px;
font-weight: 200;
font-size: ${token.fontSizeLG}px;
line-height: 24px;
}
@media only screen and (max-width: 767.99px) {
& {
margin: 0 -${resourcePaddingXS}px;
padding: 0 ${resourcePaddingXS}px;
}
}
`,
};
});
const ResourceLayout: React.FC<ResourceLayoutProps> = ({ children }) => {
const { styles } = useStyle();
const isDark = React.use(DarkContext);
const { styles } = useStyle(isDark);
const meta = useRouteMeta();
const isRootDark = useDark();
const node = (
<Layout>
<CommonHelmet />
@ -116,7 +107,7 @@ const ResourceLayout: React.FC<ResourceLayoutProps> = ({ children }) => {
</Layout>
);
if (!isRootDark) {
if (!isDark) {
return <ConfigProvider theme={{ token: { colorBgLayout: '#fff' } }}>{node}</ConfigProvider>;
}

View File

@ -5,8 +5,7 @@
"app.theme.switch.compact": "Compact theme",
"app.theme.switch.motion.on": "Motion On",
"app.theme.switch.motion.off": "Motion Off",
"app.theme.switch.happy-work.on": "Happy Work Effect On",
"app.theme.switch.happy-work.off": "Happy Work Effect Off",
"app.theme.switch.happy-work": "Happy Work Effect",
"app.header.search": "Search...",
"app.header.menu.documentation": "Docs",
"app.header.menu.more": "More",
@ -35,7 +34,7 @@
"app.demo.codepen": "Open in CodePen",
"app.demo.codesandbox": "Open in CodeSandbox",
"app.demo.stackblitz": "Open in Stackblitz",
"app.demo.riddle": "Open in Riddle",
"app.demo.codeblock": "Open in Hitu",
"app.demo.separate": "Open in a new window",
"app.demo.online": "Online Address",
"app.home.introduce": "A design system for enterprise-level products. Create an efficient and enjoyable work experience.",

View File

@ -5,8 +5,7 @@
"app.theme.switch.compact": "紧凑主题",
"app.theme.switch.motion.on": "动画开启",
"app.theme.switch.motion.off": "动画关闭",
"app.theme.switch.happy-work.on": "快乐工作特效开启",
"app.theme.switch.happy-work.off": "快乐工作特效关闭",
"app.theme.switch.happy-work": "快乐工作特效",
"app.header.search": "全文本搜索...",
"app.header.menu.documentation": "文档",
"app.header.menu.more": "更多",
@ -35,7 +34,7 @@
"app.demo.codepen": "在 CodePen 中打开",
"app.demo.codesandbox": "在 CodeSandbox 中打开",
"app.demo.stackblitz": "在 Stackblitz 中打开",
"app.demo.riddle": "在 Riddle 中打开",
"app.demo.codeblock": "在海兔中打开",
"app.demo.separate": "在新窗口打开",
"app.demo.online": "线上地址",
"app.home.introduce": "企业级产品设计体系,创造高效愉悦的工作体验",

View File

@ -2,10 +2,9 @@ import { createHash } from 'crypto';
import fs from 'fs';
import path from 'path';
import createEmotionServer from '@emotion/server/create-instance';
import chalk from 'chalk';
import type { IApi, IRoute } from 'dumi';
import ReactTechStack from 'dumi/dist/techStacks/react';
import sylvanas from 'sylvanas';
import tsToJs from './utils/tsToJs';
import { dependencies, devDependencies } from '../../package.json';
@ -42,7 +41,7 @@ class AntdReactTechStack extends ReactTechStack {
props.jsx ??= '';
if (opts.type === 'code-block') {
props.jsx = opts?.entryPointCode ? sylvanas.parseText(opts.entryPointCode) : '';
props.jsx = opts?.entryPointCode ? tsToJs(opts.entryPointCode) : '';
}
if (opts.type === 'external') {
@ -54,7 +53,7 @@ class AntdReactTechStack extends ReactTechStack {
const codePath = opts.fileAbsPath!.replace(/\.\w+$/, '.tsx');
const code = fs.existsSync(codePath) ? fs.readFileSync(codePath, 'utf-8') : '';
props.jsx = sylvanas.parseText(code);
props.jsx = tsToJs(code);
if (md) {
// extract description & css style from md file
@ -126,7 +125,8 @@ class AntdReactTechStack extends ReactTechStack {
const resolve = (p: string): string => require.resolve(p);
const RoutesPlugin = (api: IApi) => {
const RoutesPlugin = async (api: IApi) => {
const chalk = await import('chalk').then((m) => m.default);
// const ssrCssFileName = `ssr-${Date.now()}.css`;
const writeCSSFile = (key: string, hashKey: string, cssString: string) => {

View File

@ -130,7 +130,7 @@ const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
return null;
}
return (
<Card className={card} bordered={false}>
<Card className={card} variant="borderless">
<h3 className={bigTitle}>{locale.bigTitle}</h3>
{zhihuLink && (
<>

View File

@ -1,19 +1,6 @@
import React from 'react';
import type { AvatarListItem } from '@qixian.cs/github-contributors-list/dist/AvatarList';
import { Avatar, Skeleton, Tooltip } from 'antd';
const AvatarPlaceholder: React.FC<{ num?: number }> = ({ num = 3 }) => (
<li>
{Array.from({ length: num }).map<React.ReactNode>((_, i) => (
<Skeleton.Avatar
size="small"
active
key={i}
style={{ marginInlineStart: i === 0 ? 0 : -8 }}
/>
))}
</li>
);
import { Avatar, Tooltip } from 'antd';
interface ContributorAvatarProps {
loading?: boolean;
@ -23,11 +10,7 @@ interface ContributorAvatarProps {
const ContributorAvatar: React.FC<ContributorAvatarProps> = (props) => {
const {
item: { username, url } = {},
loading,
} = props;
if (loading) {
return <AvatarPlaceholder />;
}
if (username?.includes('github-actions')) {
return null;
}

View File

@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React from 'react';
import ContributorsList from '@qixian.cs/github-contributors-list';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
@ -40,7 +40,7 @@ interface ContributorsProps {
const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
const { formatMessage } = useIntl();
const { styles } = useStyle();
const { isMobile } = useContext(SiteContext);
const { isMobile } = React.use(SiteContext);
if (!filename) {
return null;

View File

@ -1,5 +1,5 @@
import React, { useContext, useLayoutEffect, useMemo, useState } from 'react';
import { Col, Flex, Space, Typography } from 'antd';
import React, { useLayoutEffect, useMemo, useState } from 'react';
import { Col, Flex, Skeleton, Space, Typography } from 'antd';
import classNames from 'classnames';
import { FormattedMessage, useRouteMeta } from 'dumi';
@ -9,8 +9,8 @@ import ComponentMeta from '../../builtins/ComponentMeta';
import type { DemoContextProps } from '../DemoContext';
import DemoContext from '../DemoContext';
import SiteContext from '../SiteContext';
import InViewSuspense from './InViewSuspense';
import { useStyle } from './DocAnchor';
import InViewSuspense from './InViewSuspense';
const Contributors = React.lazy(() => import('./Contributors'));
const ColumnCard = React.lazy(() => import('./ColumnCard'));
@ -20,10 +20,15 @@ const Footer = React.lazy(() => import('../Footer'));
const PrevAndNext = React.lazy(() => import('../../common/PrevAndNext'));
const EditButton = React.lazy(() => import('../../common/EditButton'));
const AvatarPlaceholder: React.FC<{ num?: number }> = ({ num = 6 }) =>
Array.from({ length: num }).map<React.ReactNode>((_, i) => (
<Skeleton.Avatar size="small" active key={i} style={{ marginInlineStart: i === 0 ? 0 : -8 }} />
));
const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
const meta = useRouteMeta();
const { pathname, hash } = useLocation();
const { direction } = useContext(SiteContext);
const { direction } = React.use(SiteContext);
const { styles } = useStyle();
const [showDebug, setShowDebug] = useLayoutState(false);
@ -47,7 +52,7 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
const isRTL = direction === 'rtl';
return (
<DemoContext.Provider value={contextValue}>
<DemoContext value={contextValue}>
<Col xxl={20} xl={19} lg={18} md={18} sm={24} xs={24}>
<InViewSuspense fallback={null}>
<DocAnchor showDebug={showDebug} debugDemos={debugDemos} />
@ -84,10 +89,11 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
component={meta.frontmatter.title}
filename={meta.frontmatter.filename}
version={meta.frontmatter.tag}
designUrl={meta.frontmatter.designUrl}
/>
)}
<div style={{ minHeight: 'calc(100vh - 64px)' }}>{children}</div>
<InViewSuspense>
<InViewSuspense fallback={null}>
<ColumnCard
zhihuLink={meta.frontmatter.zhihu_url}
yuqueLink={meta.frontmatter.yuque_url}
@ -95,7 +101,7 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
/>
</InViewSuspense>
<div style={{ marginTop: 120 }}>
<InViewSuspense fallback={<div style={{ height: 50 }} />}>
<InViewSuspense fallback={<AvatarPlaceholder />}>
<Contributors filename={meta.frontmatter.filename} />
</InViewSuspense>
</div>
@ -105,7 +111,7 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
</InViewSuspense>
<Footer />
</Col>
</DemoContext.Provider>
</DemoContext>
);
};

View File

@ -1,4 +1,5 @@
import React, { useContext } from 'react';
import React from 'react';
import { FastColor } from '@ant-design/fast-color';
import {
AntDesignOutlined,
BgColorsOutlined,
@ -13,7 +14,6 @@ import {
UsergroupAddOutlined,
ZhihuOutlined,
} from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import { createStyles } from 'antd-style';
import getAlphaColor from 'antd/es/theme/util/getAlphaColor';
import { FormattedMessage, Link } from 'dumi';
@ -34,64 +34,57 @@ const locales = {
},
};
const useStyle = () => {
const { isMobile } = useContext(SiteContext);
return createStyles(({ token, css }) => {
const background = new TinyColor(getAlphaColor('#f0f3fa', '#fff'))
.onBackground(token.colorBgContainer)
.toHexString();
const useStyle = createStyles(({ token, css }, isMobile: boolean) => {
const background = new FastColor(getAlphaColor('#f0f3fa', '#fff'))
.onBackground(token.colorBgContainer)
.toHexString();
return {
holder: css`
background: ${background};
`,
return {
holder: css`
background: ${background};
`,
footer: css`
background: ${background};
color: ${token.colorTextSecondary};
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
footer: css`
background: ${background};
color: ${token.colorTextSecondary};
* {
box-sizing: border-box;
}
h2,
a {
color: ${token.colorText};
}
.rc-footer-column {
margin-bottom: ${isMobile ? 60 : 0}px;
:last-child {
margin-bottom: ${isMobile ? 20 : 0}px;
}
}
.rc-footer-item-icon {
top: -1.5px;
}
.rc-footer-container {
max-width: 1208px;
margin-inline: auto;
padding-inline: ${token.marginXXL}px;
}
.rc-footer-bottom {
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
* {
box-sizing: border-box;
.rc-footer-bottom-container {
font-size: ${token.fontSize}px;
}
h2,
a {
color: ${token.colorText};
}
.rc-footer-column {
margin-bottom: ${isMobile ? 60 : 0}px;
:last-child {
margin-bottom: ${isMobile ? 20 : 0}px;
}
}
.rc-footer-item-icon {
top: -1.5px;
}
.rc-footer-container {
max-width: 1208px;
margin-inline: auto;
padding-inline: ${token.marginXXL}px;
}
.rc-footer-bottom {
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
.rc-footer-bottom-container {
font-size: ${token.fontSize}px;
}
}
`,
};
})();
};
}
`,
};
});
const Footer: React.FC = () => {
const location = useLocation();
const [locale, lang] = useLocale(locales);
const { styles } = useStyle();
const { isMobile } = React.use(SiteContext);
const { styles } = useStyle(isMobile);
const { getLink } = location;
@ -118,7 +111,9 @@ const Footer: React.FC = () => {
},
{
title: 'Pro Components',
url: 'https://procomponents.ant.design',
url: isZhCN
? 'https://pro-components.antdigital.dev'
: 'https://procomponents.ant.design',
openExternal: true,
},
{

View File

@ -1,8 +1,8 @@
import React from 'react';
import { Tooltip } from 'antd';
import { Tooltip, Button } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import omit from 'rc-util/lib/omit';
export interface LangBtnProps {
label1: React.ReactNode;
label2: React.ReactNode;
@ -12,49 +12,24 @@ export interface LangBtnProps {
pure?: boolean;
onClick?: React.MouseEventHandler;
'aria-label'?: string;
className?: string;
}
const BASE_SIZE = '1.2em';
const useStyle = createStyles(({ token, css }) => {
const {
colorText,
colorBorder,
colorBgContainer,
colorBgTextHover,
borderRadius,
controlHeight,
motionDurationMid,
} = token;
const { colorText, controlHeight, colorBgContainer, motionDurationMid } = token;
return {
btn: css`
color: ${colorText};
border-color: ${colorBorder};
padding: 0 !important;
width: ${controlHeight}px;
height: ${controlHeight}px;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
border-radius: ${borderRadius}px;
transition: all ${motionDurationMid};
cursor: pointer;
.btn-inner {
transition: all ${motionDurationMid};
}
&:hover {
background: ${colorBgTextHover};
}
img {
width: ${BASE_SIZE};
height: ${BASE_SIZE};
}
.anticon {
font-size: ${BASE_SIZE};
}
`,
innerDiv: css`
position: relative;
@ -88,19 +63,19 @@ const useStyle = createStyles(({ token, css }) => {
});
const LangBtn: React.FC<LangBtnProps> = (props) => {
const { label1, label2, tooltip1, tooltip2, value, pure, onClick } = props;
const { label1, label2, tooltip1, tooltip2, value, pure, onClick, ...rest } = props;
const {
styles: { btn, innerDiv, labelStyle, label1Style, label2Style },
} = useStyle();
const node = (
<button
type="button"
<Button
type="text"
onClick={onClick}
className={btn}
key="lang-button"
aria-label={props['aria-label']}
{...omit(rest, ['className'])}
>
<div className="btn-inner">
{pure && (value === 1 ? label1 : label2)}
@ -115,7 +90,7 @@ const LangBtn: React.FC<LangBtnProps> = (props) => {
</div>
)}
</div>
</button>
</Button>
);
if (tooltip1 || tooltip2) {

View File

@ -1,6 +1,7 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { GithubOutlined, MenuOutlined } from '@ant-design/icons';
import { Alert, Col, ConfigProvider, Popover, Row, Select } from 'antd';
import { Alert, Button, Col, ConfigProvider, Popover, Row, Select, Tooltip } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import dayjs from 'dayjs';
@ -8,11 +9,11 @@ import { useLocation, useSiteData } from 'dumi';
import DumiSearchBar from 'dumi/theme-default/slots/SearchBar';
import useLocale from '../../../hooks/useLocale';
import ThemeSwitch from '../../common/ThemeSwitch';
import DirectionIcon from '../../icons/DirectionIcon';
import { ANT_DESIGN_NOT_SHOW_BANNER } from '../../layouts/GlobalLayout';
import * as utils from '../../utils';
import { getThemeConfig } from '../../utils';
import type { SiteContextProps } from '../SiteContext';
import SiteContext from '../SiteContext';
import type { SharedProps } from './interface';
import Logo from './Logo';
@ -168,8 +169,7 @@ const Header: React.FC = () => {
windowWidth: 1400,
searching: false,
});
const { direction, isMobile, bannerVisible, updateSiteConfig } =
useContext<SiteContextProps>(SiteContext);
const { direction, isMobile, bannerVisible, updateSiteConfig } = React.use(SiteContext);
const pingTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const location = useLocation();
const { pathname, search } = location;
@ -302,6 +302,7 @@ const Header: React.FC = () => {
<Select
key="version"
size="small"
variant="filled"
className={styles.versionSelect}
defaultValue={pkg.version}
onChange={handleVersionChange}
@ -330,13 +331,16 @@ const Header: React.FC = () => {
pure
aria-label="RTL Switch Button"
/>,
<ThemeSwitch key="theme" />,
<a
key="github"
href="https://github.com/ant-design/ant-design"
target="_blank"
rel="noreferrer"
>
<SwitchBtn value={1} label1={<GithubOutlined />} tooltip1="Github" label2={null} pure />
<Tooltip title="GitHub" destroyTooltipOnHide>
<Button type="text" icon={<GithubOutlined />} style={{ fontSize: 16 }} />
</Tooltip>
</a>,
];
@ -357,7 +361,7 @@ const Header: React.FC = () => {
<header className={headerClassName}>
{isMobile && (
<Popover
overlayClassName={styles.popoverMenu}
classNames={{ root: styles.popoverMenu }}
placement="bottomRight"
content={menu}
trigger="click"

View File

@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React from 'react';
import { Col, ConfigProvider, Menu } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import { useSidebarData } from 'dumi';
@ -109,7 +109,7 @@ const useStyle = createStyles(({ token, css }) => {
const Sidebar: React.FC = () => {
const sidebarData = useSidebarData();
const { isMobile, theme } = useContext(SiteContext);
const { isMobile, theme } = React.use(SiteContext);
const { styles } = useStyle();
const [menuItems, selectedKey] = useMenu();

View File

@ -0,0 +1,107 @@
import tsToJs from '../tsToJs';
// 简单测试用例:基本的 TypeScript 到 JavaScript 转换
console.log('测试 1: 基本 TypeScript 转换');
const tsInput = `
interface Person {
name: string;
age: number;
}
function greet(person: Person): string {
return \`Hello, \${person.name}!\`;
}
const john: Person = { name: 'John', age: 30 };
greet(john);
`;
const jsOutput = tsToJs(tsInput);
console.log('输入:', tsInput);
console.log('输出:', jsOutput);
console.log('检查点:');
console.log('- interface 被移除:', !jsOutput.includes('interface'));
console.log('- 类型注解被移除:', !jsOutput.includes(': string') && !jsOutput.includes(': Person'));
console.log('- 函数定义正确:', jsOutput.includes('function greet(person)'));
console.log('- 对象定义正确:', jsOutput.includes('const john = { name:'));
// 测试用例 2: JSX 转换
console.log('\n测试 2: JSX 转换');
const tsxInput = `
import React, { FC } from 'react';
interface ButtonProps {
text: string;
onClick: () => void;
}
const Button: FC<ButtonProps> = ({ text, onClick }) => {
return (
<button
className="primary-button"
onClick={onClick}
>
{text}
</button>
);
};
export default Button;
`;
const jsxOutput = tsToJs(tsxInput);
console.log('输入:', tsxInput);
console.log('输出:', jsxOutput);
console.log('检查点:');
console.log('- interface 被移除:', !jsxOutput.includes('interface ButtonProps'));
console.log('- 类型注解被移除:', !jsxOutput.includes(': FC<ButtonProps>'));
console.log('- JSX 被保留:', jsxOutput.includes('<button') && jsxOutput.includes('</button>'));
console.log(
'- 属性被保留:',
jsxOutput.includes('className="primary-button"') && jsxOutput.includes('onClick={onClick}'),
);
// 测试用例 3: 类型导入处理
console.log('\n测试 3: 类型导入处理');
const typeImportInput = `
import React from 'react';
import type { ReactNode } from 'react';
import { Button } from 'antd';
import type { ButtonProps } from 'antd/es/button';
const MyButton = (props: ButtonProps) => {
return <Button {...props} />;
};
`;
const typeImportOutput = tsToJs(typeImportInput);
console.log('输入:', typeImportInput);
console.log('输出:', typeImportOutput);
console.log('检查点:');
console.log('- 类型导入被移除:', !typeImportOutput.includes('import type'));
console.log('- ReactNode 被移除:', !typeImportOutput.includes('ReactNode'));
console.log('- ButtonProps 被移除:', !typeImportOutput.includes('ButtonProps'));
console.log(
'- 普通导入被保留:',
typeImportOutput.includes("import React from 'react'") &&
typeImportOutput.includes("import { Button } from 'antd'"),
);
// 总结测试结果
console.log('\n测试总结:');
const test1Pass =
!jsOutput.includes('interface') &&
!jsOutput.includes(': string') &&
jsOutput.includes('function greet(person)');
const test2Pass =
!jsxOutput.includes('interface ButtonProps') &&
jsxOutput.includes('<button') &&
jsxOutput.includes('</button>');
const test3Pass =
!typeImportOutput.includes('import type') &&
typeImportOutput.includes("import React from 'react'");
console.log('测试 1 (基本 TypeScript 转换):', test1Pass ? '通过' : '失败');
console.log('测试 2 (JSX 转换):', test2Pass ? '通过' : '失败');
console.log('测试 3 (类型导入处理):', test3Pass ? '通过' : '失败');
console.log('所有测试:', test1Pass && test2Pass && test3Pass ? '通过' : '失败');

View File

@ -0,0 +1,93 @@
import tsToJs from '../tsToJs';
describe('tsToJs', () => {
it('应该将基本的 TypeScript 转换为 JavaScript', () => {
const tsInput = `
interface Person {
name: string;
age: number;
}
function greet(person: Person): string {
return \`Hello, \${person.name}!\`;
}
const john: Person = { name: 'John', age: 30 };
greet(john);
`;
const jsOutput = tsToJs(tsInput);
// 检查结果中不应包含 TypeScript 特有的语法
expect(jsOutput).not.toContain('interface');
expect(jsOutput).not.toContain(': string');
expect(jsOutput).not.toContain(': Person');
expect(jsOutput).not.toContain(': number');
// 检查结果应包含 JavaScript 代码
expect(jsOutput).toContain('function greet(person)');
expect(jsOutput).toContain('return');
expect(jsOutput).toContain('Hello');
expect(jsOutput).toContain("const john = { name: 'John', age: 30 }");
});
it('应该保留 JSX 语法', () => {
const tsxInput = `
import React, { FC } from 'react';
interface ButtonProps {
text: string;
onClick: () => void;
}
const Button: FC<ButtonProps> = ({ text, onClick }) => {
return (
<button
className="primary-button"
onClick={onClick}
>
{text}
</button>
);
};
export default Button;
`;
const jsxOutput = tsToJs(tsxInput);
// 检查结果中不应包含 TypeScript 特有的语法
expect(jsxOutput).not.toContain('interface ButtonProps');
expect(jsxOutput).not.toContain(': FC<ButtonProps>');
// 检查结果应保留 JSX 语法
expect(jsxOutput).toContain('<button');
expect(jsxOutput).toContain('className="primary-button"');
expect(jsxOutput).toContain('</button>');
expect(jsxOutput).toContain('onClick={onClick}');
});
it('应该删除类型导入', () => {
const tsInput = `
import React from 'react';
import type { ReactNode } from 'react';
import { Button } from 'antd';
import type { ButtonProps } from 'antd/es/button';
const MyButton = (props: ButtonProps) => {
return <Button {...props} />;
};
`;
const jsOutput = tsToJs(tsInput);
// 检查结果中不应包含类型导入
expect(jsOutput).not.toContain('import type');
expect(jsOutput).not.toContain('ReactNode');
expect(jsOutput).not.toContain('ButtonProps');
// 保留普通导入
expect(jsOutput).toContain("import React from 'react'");
expect(jsOutput).toContain("import { Button } from 'antd'");
});
});

View File

@ -0,0 +1,273 @@
import { parseText } from './tsToJs';
// 简单 TypeScript 代码示例
const tsCode = `
interface Person {
name: string;
age: number;
}
class Employee implements Person {
name: string;
age: number;
department: string;
constructor(name: string, age: number, department: string) {
this.name = name;
this.age = age;
this.department = department;
}
getInfo(): string {
return \`\${this.name}, \${this.age}, \${this.department}\`;
}
}
const employee: Employee = new Employee('张三', 30, '研发部');
console.log(employee.getInfo());
`;
// 包含 JSX 的 TypeScript 代码示例
const tsxCode = `
import React, { FC, useState } from 'react';
import { Button } from 'antd';
interface CounterProps {
initialCount?: number;
label: string;
}
const Counter: FC<CounterProps> = ({ initialCount = 0, label }) => {
const [count, setCount] = useState<number>(initialCount);
const increment = (): void => {
setCount(count + 1);
};
const decrement = (): void => {
setCount(count - 1);
};
return (
<div className="counter">
<h3>{label}: {count}</h3>
<Button type="primary" onClick={increment}>+</Button>
<Button onClick={decrement}>-</Button>
</div>
);
};
export default Counter;
`;
// 复杂 TypeScript 代码示例,包含泛型、类型导入、类型别名等
const complexTsCode = `
import React from 'react';
import type { ReactNode } from 'react';
import { Table } from 'antd';
import type { TableProps, TableColumnType } from 'antd/es/table';
// 类型别名
type Status = 'pending' | 'processing' | 'success' | 'failed';
// 泛型接口
interface DataItem<T = string> {
id: number;
name: string;
status: Status;
details: T;
createdAt: Date;
}
// 类型映射和条件类型
type ReadonlyDataItem<T> = {
readonly [K in keyof DataItem<T>]: DataItem<T>[K];
};
// 工具类型
type OptionalId<T> = Omit<T, 'id'> & { id?: number };
// 类型断言函数
function assertIsDataItem<T>(item: any): asserts item is DataItem<T> {
if (!item || typeof item.id !== 'number') {
throw new Error('Invalid DataItem: missing or invalid id');
}
}
// 使用泛型组件
const DataTable = <T extends string>(props: {
data: DataItem<T>[];
renderDetails?: (details: T) => ReactNode;
}) => {
const { data, renderDetails } = props;
// 定义表格列
const columns: TableColumnType<DataItem<T>>[] = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: Status) => {
const statusColors = {
pending: 'blue',
processing: 'orange',
success: 'green',
failed: 'red',
};
return <span style={{ color: statusColors[status] }}>{status}</span>;
},
},
{
title: '详情',
dataIndex: 'details',
key: 'details',
render: (details: T) => renderDetails ? renderDetails(details) : details,
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
render: (date: Date) => date.toLocaleString(),
},
];
return <Table<DataItem<T>> columns={columns} dataSource={data} rowKey="id" />;
};
// 使用工具类型创建数据
const createDataItem = <T extends string>(item: OptionalId<DataItem<T>>): DataItem<T> => {
return {
id: item.id ?? Math.floor(Math.random() * 1000),
name: item.name,
status: item.status,
details: item.details,
createdAt: item.createdAt || new Date(),
};
};
// 示例数据
const exampleData: DataItem<string>[] = [
createDataItem({
name: '项目 A',
status: 'success',
details: '项目顺利完成',
createdAt: new Date(2023, 0, 15),
}),
createDataItem({
name: '项目 B',
status: 'processing',
details: '正在进行中...',
createdAt: new Date(2023, 2, 10),
}),
];
// 渲染组件
const App = () => {
return (
<div>
<h1></h1>
<DataTable
data={exampleData}
renderDetails={(details) => <em>{details}</em>}
/>
</div>
);
};
export default App;
`;
// 加入Jest测试用例
describe('tsToJs函数测试', () => {
// 转换普通 TypeScript 代码
it('应该能正确转换普通TypeScript代码', () => {
const jsCode = parseText(tsCode);
// 验证类型注解被移除
expect(jsCode).not.toContain('interface Person');
expect(jsCode).not.toContain(': string');
expect(jsCode).not.toContain(': number');
// 验证类实现被保留
expect(jsCode).toContain('class Employee');
expect(jsCode).toContain('constructor(name, age, department)');
expect(jsCode).toContain('getInfo()');
// 验证实例创建
expect(jsCode).toContain("new Employee('张三', 30, '研发部')");
console.log('转换前的 TypeScript 代码:');
console.log(tsCode);
console.log('\n转换后的 JavaScript 代码:');
console.log(jsCode);
});
// 转换包含 JSX 的 TypeScript 代码
it('应该能正确转换TSX代码', () => {
const jsxCode = parseText(tsxCode);
// 验证React导入被保留
expect(jsxCode).toContain('import React');
// 验证类型注解和类型导入被移除
expect(jsxCode).not.toContain('FC<');
expect(jsxCode).not.toContain('interface CounterProps');
expect(jsxCode).not.toContain('<number>');
expect(jsxCode).not.toContain(': void');
// 验证JSX结构被保留
expect(jsxCode).toContain('<div className="counter">');
expect(jsxCode).toContain('<Button type="primary"');
// 验证默认参数被保留
expect(jsxCode).toContain('initialCount = 0');
console.log('\n\n转换前的 TSX 代码:');
console.log(tsxCode);
console.log('\n转换后的 JSX 代码:');
console.log(jsxCode);
});
// 转换复杂 TypeScript 代码
it('应该能正确转换复杂TypeScript代码', () => {
const complexJsCode = parseText(complexTsCode);
// 验证类型导入被移除
expect(complexJsCode).not.toContain('import type');
// 验证泛型被移除
expect(complexJsCode).not.toContain('<T>');
expect(complexJsCode).not.toContain('<T extends string>');
// 验证类型别名和接口被移除
expect(complexJsCode).not.toContain('type Status');
expect(complexJsCode).not.toContain('interface DataItem');
// 验证函数和组件结构被保留
expect(complexJsCode).toContain('function assertIsDataItem');
expect(complexJsCode).toContain('const DataTable = ');
expect(complexJsCode).toContain('const createDataItem = ');
// 验证JSX结构被保留
expect(complexJsCode).toContain('<Table');
expect(complexJsCode).toContain('<span style=');
// 验证空值合并运算符的处理
expect(complexJsCode).toContain('_a = item.id'); // TypeScript会将 ?? 转换为更兼容的语法
console.log('\n\n转换前的复杂 TypeScript 代码:');
console.log(complexTsCode);
console.log('\n转换后的 JavaScript 代码:');
console.log(complexJsCode);
});
});

View File

@ -0,0 +1,75 @@
import * as ts from 'typescript';
import { format } from '@prettier/sync';
/**
* TypeScript JavaScript
*
* TypeScript TSX JavaScript JSX
* sylvanas
*
* 使 TypeScript API TS JS
*
*
* 1.
* 2. JSX
* 3.
* 4. ES6+
* 5.
* 6. 使 Prettier
* 7. React hooks
* 8. TypeScript
*
* @param tsCode TypeScript
* @returns JavaScript
*/
export default function (tsCode: string): string {
// 设置编译器选项,保留 JSX 语法
const compilerOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ES2016, // 目标 ECMAScript 版本
module: ts.ModuleKind.ESNext, // 使用 ES 模块
jsx: ts.JsxEmit.Preserve, // 保留 JSX 语法
esModuleInterop: true, // 启用 ES 模块互操作性
removeComments: false, // 保留注释
isolatedModules: true, // 将每个文件视为单独模块
declaration: false, // 不生成类型声明文件
};
// 直接使用 TypeScript 编译器 API 进行转换
const result = ts.transpileModule(tsCode, { compilerOptions });
try {
// 使用 Prettier 同步格式化代码
const formatted = format(result.outputText, {
// Prettier 格式化选项
parser: 'babel',
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
jsxSingleQuote: false,
trailingComma: 'all',
bracketSpacing: true,
jsxBracketSameLine: false,
arrowParens: 'avoid',
});
return formatted;
} catch (error) {
// 如果格式化出错,返回未格式化的代码
console.warn('Prettier 格式化出错:', error);
return result.outputText;
}
}
/**
* TypeScript JavaScript
*
* API使
*
* @param tsCode TypeScript
* @returns JavaScript
*/
export function parseText(tsCode: string): string {
return exports.default(tsCode);
}

View File

@ -5,6 +5,7 @@ import os from 'node:os';
import rehypeAntd from './.dumi/rehypeAntd';
import remarkAntd from './.dumi/remarkAntd';
import remarkAnchor from './.dumi/remarkAnchor';
import { version } from './package.json';
export default defineConfig({
@ -52,7 +53,7 @@ export default defineConfig({
'@ant-design/icons$': '@ant-design/icons/lib',
},
extraRehypePlugins: [rehypeAntd],
extraRemarkPlugins: [remarkAntd],
extraRemarkPlugins: [remarkAntd, remarkAnchor],
metas: [
{ name: 'theme-color', content: '#1677ff' },
{ name: 'build-time', content: Date.now().toString() },

View File

@ -25,6 +25,7 @@ Thank you!
- [ ] ✅ Test Case
- [ ] 🔀 Branch merge
- [ ] ⏩ Workflow
- [ ] ⌨️ Accessibility improvement
- [ ] ❓ Other (about what?)
### 🔗 Related Issues

View File

@ -25,6 +25,7 @@
- [ ] ✅ 测试用例
- [ ] 🔀 分支合并
- [ ] ⏩ 工作流程
- [ ] ⌨️ 无障碍改进
- [ ] ❓ 其他改动(是关于什么的改动?)
### 🔗 相关 Issue

View File

@ -1,79 +0,0 @@
name: Auto Unassign
on:
schedule:
- cron: "0 0 * * *" # Run at 00:00 every day
permissions:
issues: write # Need write permission to modify issue assignees
jobs:
unassign_job:
runs-on: ubuntu-latest
steps:
- name: Unassign inactive issues
uses: actions/github-script@v6
with:
script: |
const daysBeforeUnassign = 14;
let page = 1;
const perPage = 100;
while (true) {
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
assignee: '*', // Filter assigned issues
per_page: perPage,
page: page,
});
if (issues.length === 0) {
break;
}
const now = new Date();
for (const issue of issues) {
if (issue.pull_request) continue;
const updatedAt = new Date(issue.updated_at);
const daysInactive = (now - updatedAt) / (1000 * 60 * 60 * 24);
const { data: timeline } = await github.rest.issues.listEventsForTimeline({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
});
const hasLinkedPR = timeline.some(event =>
event.event === 'cross-referenced' && event.source?.issue?.pull_request || // PR referenced this issue
event.event === 'connected' || // PR connected via keywords
event.event === 'referenced' && event.commit_id // Connected via commit
);
if (daysInactive >= daysBeforeUnassign && !hasLinkedPR) {
const assigneesMentions = issue.assignees
.map(user => `@${user.login}`)
.join(' ');
// Remove assignees
await github.rest.issues.removeAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
assignees: issue.assignees.map(user => user.login),
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `${assigneesMentions} Assignees have been automatically removed since no PR was linked to this issue within 14 days. Please contact maintainers for reassignment if you wish to continue working on this issue.`,
});
}
}
page += 1;
}

View File

@ -0,0 +1,91 @@
name: Issue Inactivity Reminder
on:
schedule:
- cron: "0 0 * * *" # Run at 00:00 every day
permissions:
issues: write
jobs:
reminder_job:
runs-on: ubuntu-latest
steps:
- name: Send reminders for inactive issues
uses: actions/github-script@v7
with:
script: |
const daysBeforeReminder = 14;
let page = 1;
const perPage = 100;
const reminderSignature = "This issue has been inactive for more than 14 days";
while (true) {
try {
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
assignee: '*',
per_page: perPage,
page: page,
});
if (issues.length === 0) {
break;
}
const now = new Date();
for (const issue of issues) {
if (issue.pull_request) continue;
const updatedAt = new Date(issue.updated_at);
const daysInactive = (now - updatedAt) / (1000 * 60 * 60 * 24);
const { data: timeline } = await github.rest.issues.listEventsForTimeline({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
});
const hasLinkedPR = timeline.some(event =>
event.event === 'connected' || // PR connected via keywords
event.event === 'referenced' && event.commit_id // Connected via commit
);
if (daysInactive >= daysBeforeReminder && !hasLinkedPR) {
// get issue comments
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
});
// check if reminder has been sent
const hasReminder = comments.some(comment =>
comment.body.includes(reminderSignature)
);
if (!hasReminder) {
const assigneesMentions = issue.assignees
.map(user => `@${user.login}`)
.join(' ');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `${assigneesMentions} 这个 issue 已经超过 14 天没有更新或关联 PR。如果您仍在处理这个 issue请更新进度如果您无法继续处理请联系维护者重新分配。\n\nThis issue has been inactive for more than 14 days without any updates or linked PR. If you are still working on this issue, please provide a progress update. If you are unable to continue, please contact the maintainers for reassignment.`,
});
}
}
}
page += 1;
} catch (error) {
console.error(`Error processing page ${page}:`, error);
break;
}
}

View File

@ -38,9 +38,9 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}. Please provide a online reproduction by forking [this one](https://u.ant.design/reproduce) or provide a minimal GitHub repository like [create-react-app-antd](https://github.com/ant-design/create-react-app-antd). Issues labeled by `Need Reproduce` will be closed if no activities in 3 days.
Hello @${{ github.event.issue.user.login }}, We need you to provide an online reproduction example to help us investigate the issue. You can either fork this [online reproduction template](https://u.ant.design/reproduce) or create a minimal project repository using our [Scaffolding Guide](https://u.ant.design/guide). This issue will be automatically closed if no follow-up is made within 3 days.
你好 @${{ github.event.issue.user.login }},我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过 fork 这个[在线重现案例](https://u.ant.design/reproduce) ,或者提供一个最小化的 GitHub 仓库(类似 [create-react-app-antd](https://github.com/ant-design/create-react-app-antd)。3 天内未跟进此 issue 将会被自动关闭。
你好 @${{ github.event.issue.user.login }},我们需要你提供一个在线的重现示例以便于我们帮你排查问题。你可以通过 fork 这个[在线重现模板](https://u.ant.design/reproduce),或者基于我们的[脚手架指南](https://u.ant.design/guide) 新建一个最小化项目仓库。3 天内未跟进此 issue 将会被自动关闭。
> [什么是最小化重现,为什么这是必需的?](https://github.com/ant-design/ant-design/wiki/%E4%BB%80%E4%B9%88%E6%98%AF%E6%9C%80%E5%B0%8F%E5%8C%96%E9%87%8D%E7%8E%B0%EF%BC%8C%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%99%E6%98%AF%E5%BF%85%E9%9C%80%E7%9A%84%EF%BC%9F)

View File

@ -13,7 +13,7 @@ jobs:
issues: write # for actions-cool/issues-helper to update issues
pull-requests: write # for actions-cool/issues-helper to update PRs
runs-on: ubuntu-latest
if: (github.event.pull_request.head.ref == 'feature' || github.event.pull_request.head.ref == 'master') && github.event.pull_request.head.user.login == 'ant-design'
if: (github.event.pull_request.head.ref == 'next' || github.event.pull_request.head.ref == 'feature' || github.event.pull_request.head.ref == 'master') && github.event.pull_request.head.user.login == 'ant-design'
steps:
- uses: actions-cool/issues-helper@v3
with:

View File

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

View File

@ -1,22 +1,19 @@
name: 🐦 Release to Tweet
name: 🆇 Release to X
on:
create
permissions:
contents: read
jobs:
tweet:
if: ${{ github.event.ref_type == 'tag' && !contains(github.event.ref, 'alpha') }}
runs-on: ubuntu-latest
if: ${{ github.event.ref_type == 'tag' && !contains(github.event.ref, 'alpha') }}
steps:
- name: Tweet
uses: nearform-actions/github-action-notify-twitter@v1
uses: nearform-actions/github-action-notify-twitter@master
with:
message: |
🤖 Ant Design just released antd@${{ github.event.ref }} ✨🎊✨ Check out the full release note: https://github.com/ant-design/ant-design/releases/tag/${{ github.event.ref }}
twitter-app-key: ${{ secrets.TWITTER_API_KEY }}
twitter-app-secret: ${{ secrets.TWITTER_API_SECRET_KEY }}
twitter-app-secret: ${{ secrets.TWITTER_API_SECRET }}
twitter-access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
twitter-access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}

View File

@ -117,7 +117,7 @@ jobs:
cd ..
- name: Upload to Release
uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
with:
fail_on_unmatched_files: true
files: website.tar.gz

233
.github/workflows/test-v6.yml vendored Normal file
View File

@ -0,0 +1,233 @@
# Origin Source
# https://github.com/ant-design/ant-design/blob/79f566b7f8abb1012ef55b0d2793bfdf5595b85d/.github/workflows/test.yml
name: ✅ test v6
on:
push:
branches: [next]
pull_request:
branches: [next]
# Cancel prev CI if new commit come
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun run lint
################################ Test ################################
test-react-legacy:
name: test-react-legacy
strategy:
matrix:
react: ['18']
shard: [1/2, 2/2]
env:
REACT: ${{ matrix.react }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: install react 18
if: ${{ matrix.react == '18' }}
run: bun run bun-install-react-18
# dom test
- name: dom test
run: bun run test -- --maxWorkers=2 --shard=${{matrix.shard}}
test-node:
name: test-node
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun run test:node
test-react-latest:
name: test-react-latest
strategy:
matrix:
module: [dom]
shard: [1/2, 2/2]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
# dom test
- name: dom test
run: bun run test -- --maxWorkers=2 --shard=${{matrix.shard}} --coverage
- name: persist coverages
run: |
mkdir persist-coverage
mv coverage/coverage-final.json persist-coverage/react-test-${{matrix.module}}-${{strategy.job-index}}.json
- uses: actions/upload-artifact@v4
name: upload coverages
with:
name: coverage-artifacts-${{ matrix.module }}-${{ strategy.job-index }}
path: persist-coverage/
test-react-latest-dist:
name: test-react-latest-dist
strategy:
matrix:
module: [dist, dist-min]
shard: [1/2, 2/2]
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: restore cache from dist
uses: actions/cache@v4
with:
path: dist
key: dist-${{ github.sha }}
- name: dist-min test
if: ${{ matrix.module == 'dist-min' }}
run: bun run test
env:
LIB_DIR: dist-min
- name: dist test
if: ${{ matrix.module == 'dist' }}
run: bun run test
env:
LIB_DIR: dist
############################ Test Coverage ###########################
upload-test-coverage:
name: test-coverage
runs-on: ubuntu-latest
needs: test-react-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- uses: actions/download-artifact@v4
with:
pattern: coverage-artifacts-*
merge-multiple: true
path: persist-coverage
- name: Merge Code Coverage
run: |
bunx nyc merge persist-coverage/ coverage/coverage-final.json
bunx nyc report --reporter text -t coverage --report-dir coverage
rm -rf persist-coverage
- name: Upload coverage to codecov
uses: codecov/codecov-action@v5
with:
# use own token to upload coverage reports
token: ${{ secrets.CODECOV_TOKEN }}
########################### Compile & Test ###########################
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: cache lib
uses: actions/cache@v4
with:
path: lib
key: lib-${{ github.sha }}
- name: cache es
uses: actions/cache@v4
with:
path: es
key: es-${{ github.sha }}
- name: compile
run: bun run compile
- name: cache dist
uses: actions/cache@v4
with:
path: dist
key: dist-${{ github.sha }}
- name: dist
run: bun run dist
env:
NODE_OPTIONS: --max_old_space_size=4096
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
CI: 1
- name: check build files
run: bun run test:dekko
# Artifact build files
- uses: actions/upload-artifact@v4
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
with:
name: build artifacts
path: |
dist
locale
es
lib
- name: zip builds
if: github.repository == 'ant-design/ant-design' && github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
ALI_OSS_AK_ID: ${{ secrets.ALI_OSS_AK_ID }}
ALI_OSS_AK_SECRET: ${{ secrets.ALI_OSS_AK_SECRET }}
HEAD_SHA: ${{ github.sha }}
run: |
zip -r oss-artifacts.zip dist locale es lib
echo "🤖 Uploading"
node scripts/visual-regression/upload.js ./oss-artifacts.zip --ref=$HEAD_SHA
test-lib-es:
name: test lib/es module
runs-on: ubuntu-latest
strategy:
matrix:
module: [lib, es]
shard: [1/2, 2/2]
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: restore cache from ${{ matrix.module }}
# lib only run in master branch not in pull request
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
uses: actions/cache@v4
with:
path: ${{ matrix.module }}
key: ${{ matrix.module }}-${{ github.sha }}
- name: compile
# lib only run in master branch not in pull request
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
run: bun run compile
- name: test
# lib only run in master branch not in pull request
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
run: bun run test -- --maxWorkers=2 --shard=${{matrix.shard}}
env:
LIB_DIR: ${{ matrix.module }}

View File

@ -2,7 +2,11 @@
# https://github.com/ant-design/ant-design/blob/79f566b7f8abb1012ef55b0d2793bfdf5595b85d/.github/workflows/test.yml
name: ✅ test
on: [push, pull_request]
on:
push:
branches: [master, feature]
pull_request:
branches: [master, feature]
# Cancel prev CI if new commit come
concurrency:
@ -175,7 +179,7 @@ jobs:
CI: 1
- name: check build files
run: node ./tests/dekko/index.test.js
run: bun run test:dekko
# Artifact build files
- uses: actions/upload-artifact@v4

View File

@ -9,6 +9,7 @@ permissions:
jobs:
verify:
if: github.event.pull_request.user.login != 'renovate[bot]'
permissions:
pull-requests: write # for actions-cool/verify-files-modify to update status of PRs
runs-on: ubuntu-latest

View File

@ -4,7 +4,7 @@ name: 👀 Visual Regression Diff Build
on:
pull_request:
branches: [master, feature]
branches: [master, feature, next]
types: [opened, synchronize, reopened]
# Cancel prev CI if new commit come

View File

@ -68,7 +68,7 @@ jobs:
# We need get persist-index first
- name: download image snapshot artifact
uses: dawidd6/action-download-artifact@v7
uses: dawidd6/action-download-artifact@v9
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
@ -90,7 +90,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@v7
uses: dawidd6/action-download-artifact@v9
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
@ -166,7 +166,7 @@ jobs:
});
const prHeadSha = prResponse.data.head.sha;
const fullContent = `${{ toJSON(steps.report.outputs.content) }}`;
const fullContent = ${{ toJSON(steps.report.outputs.content) }};
const hasVisualDiffSuccess = fullContent.includes('VISUAL_DIFF_SUCCESS');
const hasVisualDiffFailed = fullContent.includes('VISUAL_DIFF_FAILED');

View File

@ -8,7 +8,7 @@ name: 👀 Visual Regression Diff Start
on:
pull_request_target:
branches: [master, feature]
branches: [master, feature, next]
types: [opened, synchronize, reopened]
permissions:

View File

@ -65,7 +65,7 @@ jobs:
# We need get persist key first
- name: Download Visual Regression Ref
uses: dawidd6/action-download-artifact@v7
uses: dawidd6/action-download-artifact@v9
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
@ -79,7 +79,7 @@ jobs:
- name: Download Visual-Regression Artifact
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
uses: dawidd6/action-download-artifact@v7
uses: dawidd6/action-download-artifact@v9
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
@ -87,7 +87,7 @@ jobs:
path: ./tmp
- name: Persist Image Snapshot to OSS
if: github.repository == 'ant-design/ant-design' && github.event.workflow_run.event == 'push' && (github.event.workflow_run.head_branch == 'master' || github.event.workflow_run.head_branch == 'feature')
if: github.repository == 'ant-design/ant-design' && github.event.workflow_run.event == 'push' && (github.event.workflow_run.head_branch == 'master' || github.event.workflow_run.head_branch == 'feature' || github.event.workflow_run.head_branch == 'next')
env:
ALI_OSS_AK_ID: ${{ secrets.ALI_OSS_AK_ID }}
ALI_OSS_AK_SECRET: ${{ secrets.ALI_OSS_AK_SECRET }}

View File

@ -7,6 +7,7 @@ on:
branches:
- master
- feature
- next
permissions:
contents: read

1
.gitignore vendored
View File

@ -68,7 +68,6 @@ __image_snapshots__/
/imageDiffSnapshots
/visualRegressionReport*
.devcontainer*
.husky/prepare-commit-msg
.eslintcache

2
.husky/pre-commit Executable file → Normal file
View File

@ -1 +1 @@
lint-staged
lint-staged

View File

@ -5,6 +5,7 @@ const compileModules = [
'@ant-design',
'countup.js',
'.pnpm',
'@asamuzakjp/css-color',
];
const ignoreList = [];

View File

@ -18,17 +18,5 @@ module.exports = {
return check.some((prefix) => name.startsWith(prefix));
},
// https://github.com/raineorshine/npm-check-updates#target
target: (name, semver) => {
const { operator } = semver[0] ?? {};
// rc-component
if (rcOrg.some((prefix) => name.startsWith(prefix))) {
// `^` always upgrade latest, otherwise follow semver.
if (operator === '^') {
return 'latest';
}
}
return 'semver';
},
target: () => `semver`,
};

View File

@ -74,5 +74,6 @@
"5.22.1": [
"https://github.com/ant-design/ant-design/issues/51420",
"https://github.com/ant-design/ant-design/issues/51430"
]
],
"5.22.6": ["https://github.com/ant-design/ant-design/issues/52124"]
}

View File

@ -15,6 +15,253 @@ tag: vVERSION
---
## 5.24.7
`2025-04-14`
- 🐞 Fix Input causing incorrect Popover positioning when a `suffix` is present. [#53475](https://github.com/ant-design/ant-design/pull/53475)
- 🐞 Fix Table filter menu selection state loss when `column.filterDropdown` is set to `undefined`. [#53421](https://github.com/ant-design/ant-design/pull/53421)
- 🇨🇳 ColorPicker add `zh_HK` `zh_TW` locales. [#53440](https://github.com/ant-design/ant-design/pull/53440) [@mjsong07](https://github.com/mjsong07)
## 5.24.6
`2025-04-01`
- 🐞 Fix Modal show loading with async call, still can close with click outer space. [#53227](https://github.com/ant-design/ant-design/pull/53227) [@jin19980928](https://github.com/jin19980928)
- 🐞 Fix when Table `size` is `small`, the theme config will take effect on the sub Pagination. [#52829](https://github.com/ant-design/ant-design/pull/52829) [@Can-Chen](https://github.com/Can-Chen)
## 5.24.5
`2025-03-24`
- 🐞 Fixed the issue that the suffix of InputNumber moves left after the mouse enters when it is disabled. [#53184](https://github.com/ant-design/ant-design/pull/53184) [@yellowryan](https://github.com/yellowryan)
- 💄 Fix Form syntax errors of style selector. [#53236](https://github.com/ant-design/ant-design/pull/53236) [@Wxh16144](https://github.com/Wxh16144)
- 💄 Refactor TextArea resize logic when set `resize: both` style to fit with React life cycle. [#53235](https://github.com/ant-design/ant-design/pull/53235) [@zombieJ](https://github.com/zombieJ)
- 🇮🇷 Add missing translations and fix typos for Farsi language (fa_IR). [#53251](https://github.com/ant-design/ant-design/pull/53251) [@AliReza-Kamkar](https://github.com/AliReza-Kamkar)
## 5.24.4
`2025-03-17`
- 🐞 Fix Input.TextArea width synchronization issue during resizing. [#53024](https://github.com/ant-design/ant-design/pull/53024) [@triyys](https://github.com/triyys)
- 🐞 Fix Typography type color not follow `color[Status]Text` instead of `color[Status]`. [#53086](https://github.com/ant-design/ant-design/pull/53086) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Affix abnormal `onChange` event behavior in React versions below 18. [#53038](https://github.com/ant-design/ant-design/pull/53038) [@waiter](https://github.com/waiter)
- 🐞 Fix Form `requiredMark` not working when component is false. [#52950](https://github.com/ant-design/ant-design/pull/52950) [@Wxh16144](https://github.com/Wxh16144)
- 🇹🇷 Add Turkish (tr_TR) localization support for the Tour component. [#53117](https://github.com/ant-design/ant-design/pull/53117) [@hakankosdag](https://github.com/hakankosdag)
## 5.24.3
`2025-03-05`
- Input
- 🐞 Fix the next element was not correctly selected after pressing the Tab key when `allowClear` was turned on for Input. [#52977](https://github.com/ant-design/ant-design/pull/52977) [@wanpan11](https://github.com/wanpan11)
- 💄 Fix the border display issue when hovering in the `disabled` state when Input has `variant="underlined"` turned on. [#52959](https://github.com/ant-design/ant-design/pull/52959) [@ustcfury](https://github.com/ustcfury)
- 💄 Fix DatePicker header buttons misalignment caused by unexpected spacing. [#53007](https://github.com/ant-design/ant-design/pull/53007) [@DDDDD12138](https://github.com/DDDDD12138)
- 💄 Fix AutoComplete input text not centered when `size="large"`. [#52819](https://github.com/ant-design/ant-design/pull/52819) [@aojunhao123](https://github.com/aojunhao123)
- 🇩🇪 Add missing `de_DE` translations for Transfer. [#53047](https://github.com/ant-design/ant-design/pull/53047) [@chrisinick](https://github.com/chrisinick)
## 5.24.2
`2025-02-24`
- Input
- 🐞 Fix Input with component token `inputFontSize` breaks the height of `controlHeight`. [#52865](https://github.com/ant-design/ant-design/pull/52865) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Input.Search has a border that is not aligned with the bottom of the search button when configure `variable` as `underlined`. [#52861](https://github.com/ant-design/ant-design/pull/52861) [@ustcfury](https://github.com/ustcfury)
- 🛠 Improve Input.OTP logic for create default state. [#52878](https://github.com/ant-design/ant-design/pull/52878) [@Dandelion-F](https://github.com/Dandelion-F)
- 🛠 Improve Input.OTP implementation for render separator. [#52841](https://github.com/ant-design/ant-design/pull/52841) [@li-jia-nan](https://github.com/li-jia-nan)
- Watermark
- 🐞 Fix Watermark may cause page unresponsive when re-rendering. [#52897](https://github.com/ant-design/ant-design/pull/52897) [@765477020](https://github.com/765477020)
- 🆕 Improve Watermark rendering logic to avoid disable it via developer tools and `hidden` attribute. [#52891](https://github.com/ant-design/ant-design/pull/52891) [@arronlai](https://github.com/arronlai)
- 🐞 Fix DatePicker.RangePicker arrow position not correctly when sometime reopened. [#52854](https://github.com/ant-design/ant-design/pull/52854) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Layout.Sider content overflow issue when `collapsedWidth={0}`. [#52862](https://github.com/ant-design/ant-design/pull/52862) [@afc163](https://github.com/afc163)
- 🛠 Refactor Grid internal useBreakpoint logic to be same as other component, this will not affect usage. [#52870](https://github.com/ant-design/ant-design/pull/52870) [@zombieJ](https://github.com/zombieJ)
- 💄 Fix Button styles for hyperlink mode. [#52888](https://github.com/ant-design/ant-design/pull/52888) [@DDDDD12138](https://github.com/DDDDD12138)
- 💄 Fix Table sortable column headers could not wrap automatically. [#52899](https://github.com/ant-design/ant-design/pull/52899) [@765477020](https://github.com/765477020)
- ⚡️ Improve Menu re-rendering performance when pass function to `expandIcon` property. [#52863](https://github.com/ant-design/ant-design/pull/52863) [@wanpan11](https://github.com/wanpan11)
- ⚡️ Improve Carousel indicator animation performance. [#52881](https://github.com/ant-design/ant-design/pull/52881) [@li-jia-nan](https://github.com/li-jia-nan)
- RTL
- 💄 Fix DatePicker wrong icon direction for RTL mode. [#52896](https://github.com/ant-design/ant-design/pull/52896) [@li-jia-nan](https://github.com/li-jia-nan)
- 💄 Fix Dropdown wrong arrow direction of multi-level menu for RTL mode. [#52885](https://github.com/ant-design/ant-design/pull/52885) [@yellowryan](https://github.com/yellowryan)
## 5.24.1
`2025-02-17`
- 🐞 Fix Button with `color` to be `primary` and `variant` to be `text` or `link` will not use correct color. [#52812](https://github.com/ant-design/ant-design/pull/52812) [@zombieJ](https://github.com/zombieJ)
- 💄 Fix Input.Group & Input.OTP style issues caused by undefined CSS variables. [#52799](https://github.com/ant-design/ant-design/pull/52799) [@afc163](https://github.com/afc163)
- 🐞 Fix DatePicker with long content `prefix` breaks the layout. [#52776](https://github.com/ant-design/ant-design/pull/52776) [@guoyunhe](https://github.com/guoyunhe)
- 🐞 Fix Table title missing `aria-label` when sorting. [#52772](https://github.com/ant-design/ant-design/pull/52772) [@mellis481](https://github.com/mellis481)
- 🐞 Fix Alert.ErrorBoundary type error when used as a JSX component with `@types/react@18.x`. [#52766](https://github.com/ant-design/ant-design/pull/52766) [@afc163](https://github.com/afc163)
- 💄 Fix Segmented `shape` not working with `size`. [#52757](https://github.com/ant-design/ant-design/pull/52757) [@yellowryan](https://github.com/yellowryan)
## 5.24.0
`2025-02-11`
- 🆕 Notification support `actions` prop and deprecated `btn` prop. [#52703](https://github.com/ant-design/ant-design/pull/52703) [@thinkasany](https://github.com/thinkasany)
- 🆕 Carousel support show dot duration. [#52672](https://github.com/ant-design/ant-design/pull/52672) [@yellowryan](https://github.com/yellowryan)
- 🆕 Input.OTP support `separator` prop. [#52668](https://github.com/ant-design/ant-design/pull/52668) [@HaceraI](https://github.com/HaceraI)
- 🆕 Descriptions add `labelColor` component token. [#52700](https://github.com/ant-design/ant-design/pull/52700) [@guoyunhe](https://github.com/guoyunhe)
- 🆕 Segmented supports `shape="round"`. [#52685](https://github.com/ant-design/ant-design/pull/52685) [@afc163](https://github.com/afc163)
- 🆕 ConfigProvider support `variant` for Card. [#52552](https://github.com/ant-design/ant-design/pull/52552) [@thinkasany](https://github.com/thinkasany)
- 🆕 Progress/Step supports custom rounding with `rounding` prop. [#52017](https://github.com/ant-design/ant-design/pull/52017) [@yanghoxom](https://github.com/yanghoxom)
- 🆕 Divider `orientation` support `start` and `end`. [#52567](https://github.com/ant-design/ant-design/pull/52567) [@li-jia-nan](https://github.com/li-jia-nan)
- 🆕 Add `underlined` to `variant` of Input, InputNumber, Mentions, Form, Select, Cascader, TreeSelect, DatePicker and TimePicker. [#52546](https://github.com/ant-design/ant-design/pull/52546) [@ustcfury](https://github.com/ustcfury)
- 🆕 ConfigProvider support global config of Modal `centered` . [#52343](https://github.com/ant-design/ant-design/pull/52343) [@guoyunhe](https://github.com/guoyunhe)
- 🆕 Add `label` class name for Checkbox and Radio. [#52322](https://github.com/ant-design/ant-design/pull/52322) [@guoyunhe](https://github.com/guoyunhe)
- 🐞 Fix Tooltip/Popover/Popconfirm/Dropdown misaligned popup positions with custom children in React 19. [react-component/util#623](https://github.com/react-component/util/pull/623) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix DatePicker.RangePicker arrow position when popup auto adjust position. [#52719](https://github.com/ant-design/ant-design/pull/52719) [@zombieJ](https://github.com/zombieJ)
- 🐞 Update locale `filterCheckall` to `filterCheckAll`. [#52517](https://github.com/ant-design/ant-design/pull/52517) [@thinkasany](https://github.com/thinkasany)
- 🐞 Fix Form that `scrollToField` and `scrollToFirstError` cannot focus components of antd. [#52726](https://github.com/ant-design/ant-design/pull/52726) [@Wxh16144](https://github.com/Wxh16144)
- 💄 Fix Button shadow color appearing awkward on dark backgrounds. [#52701](https://github.com/ant-design/ant-design/pull/52701) [@afc163](https://github.com/afc163)
- 💄 Fixed the unnatural animation transition effect of Segmented component in dark mode. [#52708](https://github.com/ant-design/ant-design/pull/52708) [@yellowryan](https://github.com/yellowryan)
- 💄 Separate style of Input and TextArea. [#52570](https://github.com/ant-design/ant-design/pull/52570) [@guoyunhe](https://github.com/guoyunhe)
- 💄 Fix Input and Select style issue under css var mode. [#52554](https://github.com/ant-design/ant-design/pull/52554) [@li-jia-nan](https://github.com/li-jia-nan)
- ⌨️ Remove role="alert" from Form field error to improve screen reader experience. [#52661](https://github.com/ant-design/ant-design/pull/52661) [@mellis481](https://github.com/mellis481)
- ⌨️ Improve accessibility by adding localized labels for empty table header and panel buttons. [#52689](https://github.com/ant-design/ant-design/pull/52689) [@aojunhao123](https://github.com/aojunhao123)
- ⌨️ Improve Tabs accessibility by fixing error `Certain ARIA roles must contain particular children`. [#52677](https://github.com/ant-design/ant-design/pull/52677) [@afc163](https://github.com/afc163)
- ⌨️ Add screen reader support for Tooltip. [#52293](https://github.com/ant-design/ant-design/pull/52293) [@aojunhao123](https://github.com/aojunhao123)
- TypeScript
- 🤖 Separate type of Button `onClick` event by `href`. [#52654](https://github.com/ant-design/ant-design/pull/52654) [@Brew-Brew](https://github.com/Brew-Brew)
- 🤖 Deprecate Button.Group, prefer Space.Compact. [#52572](https://github.com/ant-design/ant-design/pull/52572) [@guoyunhe](https://github.com/guoyunhe)
- 🤖 Deprecate Input.Group, prefer Space.Compact. [#52571](https://github.com/ant-design/ant-design/pull/52571) [@guoyunhe](https://github.com/guoyunhe)
- 🤖 Tooltip export TooltipRef type. [#49230](https://github.com/ant-design/ant-design/pull/49230) [@nuintun](https://github.com/nuintun)
## 5.23.4
`2025-02-05`
First release in the Year of the Snake, wishing you a prosperous start! 🐍
- 🐞 Fixed Pagination accessibility issue by supplementing missing ARIA attributes support. [#52616](https://github.com/ant-design/ant-design/pull/52616) [@aojunhao123](https://github.com/aojunhao123)
- 🐞 Added TextArea component support in `Space.Compact`. [#52639](https://github.com/ant-design/ant-design/pull/52639) [@Can-Chen](https://github.com/Can-Chen)
- 🐞 Fixed Menu with `theme="dark"` and `mode="horizontal"` identical text/background color issue. [#52617](https://github.com/ant-design/ant-design/pull/52617) [@afc163](https://github.com/afc163)
- 🇦🇪 Add Tour Arabic translation. [#52642](https://github.com/ant-design/ant-design/pull/52642) [@Sagie501](https://github.com/Sagie501)
- 🇮🇱 Add Tour Hebrew translation. [#52641](https://github.com/ant-design/ant-design/pull/52641) [@Sagie501](https://github.com/Sagie501)
## 5.23.3
`2025-01-28`
Last version of the Dragon Year, Happy Chinese New Year! 🐲
- ⌨️ MISC: Add accessibility tests for all component demos to ensure compliance with accessibility standards. Optimize accessibility support for some components, improving compatibility with screen readers and keyboard operations. [#51372](https://github.com/ant-design/ant-design/pull/51372) [@aojunhao123](https://github.com/aojunhao123)
- 🐞 MISC: Fix importing `antd/dist/reset.css` file issue. [#52559](https://github.com/ant-design/ant-design/pull/52559) [@CaptainVolcom](https://github.com/CaptainVolcom)
- 🐞 Fix Button throwing error when `loading` is `null`. [#52508](https://github.com/ant-design/ant-design/pull/52508) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 Fix Table last row border color transition issue. [#52549](https://github.com/ant-design/ant-design/pull/52549) [@DDDDD12138](https://github.com/DDDDD12138)
- 💄 Fix Cascader checkbox cursor style in disabled state. [#52539](https://github.com/ant-design/ant-design/pull/52539) [@aojunhao123](https://github.com/aojunhao123)
- 💄 Fix ConfigProvider not correctly modifying icon style priority when StyleProvider configures `layer`. [#52533](https://github.com/ant-design/ant-design/pull/52533) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Layout sidebar toggle button style missing in non-cssVar mode. [#52537](https://github.com/ant-design/ant-design/pull/52537) [@afc163](https://github.com/afc163)
- 🐞 Fix Tree checkbox cursor style in disabled state. [#52525](https://github.com/ant-design/ant-design/pull/52525) [@aojunhao123](https://github.com/aojunhao123)
- notification
- 🐞 Fix notification `useNotification` `closeIcon` configuration not working. [#52516](https://github.com/ant-design/ant-design/pull/52516) [@typenoob](https://github.com/typenoob)
- 🐞 Fix notification component display flicker issue under App component. [#52499](https://github.com/ant-design/ant-design/pull/52499) [@afc163](https://github.com/afc163)
- RTL
- 🐞 Fix Splitter abnormal collapse behavior in RTL mode. [#52501](https://github.com/ant-design/ant-design/pull/52501) [@aojunhao123](https://github.com/aojunhao123)
- 💄 Fix Spin style issue in RTL mode. [#52538](https://github.com/ant-design/ant-design/pull/52538) [@afc163](https://github.com/afc163)
## 5.23.2
`2025-01-20`
- 🐞 Fix Space.Compact throwing `Should not use more than one & in a selector` warning. [#52489](https://github.com/ant-design/ant-design/pull/52489)
- 💄 Fix the Layout switching sidebar button style was lost. [#52477](https://github.com/ant-design/ant-design/pull/52477)
- 💄 Fix the scroll bar height is not 0 when the first row of the virtual scroll Table is collapsed. [#52447](https://github.com/ant-design/ant-design/pull/52447) [@LeeSSHH](https://github.com/LeeSSHH)
- 💄 Fix the last item in Descriptions did not correctly fill the remaining space. [#52410](https://github.com/ant-design/ant-design/pull/52410) [@anyuxuan](https://github.com/anyuxuan)
- 💄 Fix extra margin for the last item in Radio. [#52433](https://github.com/ant-design/ant-design/pull/52433)
- 💄 Fix the Input/Mentions clear button padding was incorrect. [#52407](https://github.com/ant-design/ant-design/pull/52407) [@ustcfury](https://github.com/ustcfury)
- 💄 Fix rounded corners of `addonAfter` in Input compact mode. [#52490](https://github.com/ant-design/ant-design/pull/52490) [@DDDDD12138](https://github.com/DDDDD12138)
- 💄 Fix Menu.Item links were still clickable and lacked disabled styles when in disabled state. [#52402](https://github.com/ant-design/ant-design/pull/52402) [@aojunhao123](https://github.com/aojunhao123)
- TypeScript
- 🤖 MISC: Optimize PurePanel to use React.ComponentType type. [#52480](https://github.com/ant-design/ant-design/pull/52480)
- 🤖 Fix missing token type for Skeleton and Rate. [#52406](https://github.com/ant-design/ant-design/pull/52406) [@coding-ice](https://github.com/coding-ice)
## 5.23.1
`2025-01-13`
- 🆕 Add Tree leaf node className for differentiate node type. [#52274](https://github.com/ant-design/ant-design/pull/52274) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 Fix DatePicker switch buttons is not hidden when `superPrevIcon/superNextIcon/prevIcon/nextIcon` is null. [#52327](https://github.com/ant-design/ant-design/pull/52327) [@afc163](https://github.com/afc163)
- 🐞 Fix Select throws `error not a valid selector` in Jest tests. [#51844](https://github.com/ant-design/ant-design/pull/51844) [@renovate](https://github.com/renovate)
- 🐞 Fix Layout.Sider under ConfigProvider directly, the `theme` not working. [#52302](https://github.com/ant-design/ant-design/pull/52302) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Splitter lost previous state when re-expanding. [#52222](https://github.com/ant-design/ant-design/pull/52222) [@jjlstruggle](https://github.com/jjlstruggle)
- 🐞 Fix Table unexpected row selections when set `checkStrictly` to false in tree mode. [#52338](https://github.com/ant-design/ant-design/pull/52338) [@LeeSSHH](https://github.com/LeeSSHH)
- Button
- 🐞 Fix Button alignment and icon centering by adjusting the icon size for icon-only Buttons. [#52353](https://github.com/ant-design/ant-design/pull/52353) [@afc163](https://github.com/afc163)
- 💄 Fix Button missing `box-shadow` style. [#52304](https://github.com/ant-design/ant-design/pull/52304) [@zombieJ](https://github.com/zombieJ)
- RTL
- 💄 Fix Collapse arrow direction in RTL mode. [#52374](https://github.com/ant-design/ant-design/pull/52374) [@aojunhao123](https://github.com/aojunhao123)
- 💄 Fix Layout.Sider arrow direction in RTL mode. [#52374](https://github.com/ant-design/ant-design/pull/52374) [@aojunhao123](https://github.com/aojunhao123)
## 5.23.0
`2025-01-06`
- 🔥 TreeSelect support `maxCount` to limit the maximum number of selections. [#51759](https://github.com/ant-design/ant-design/pull/51759) [@aojunhao123](https://github.com/aojunhao123)
- 🔥 Modal `width` support responsive size. [#51653](https://github.com/ant-design/ant-design/pull/51653) [@zombieJ](https://github.com/zombieJ)
- 🔥 Splitter support `lazy` mode. [#51557](https://github.com/ant-design/ant-design/pull/51557) [@OysterD3](https://github.com/OysterD3)
- Button
- 🔥 Button `color` support full color palette. [#51550](https://github.com/ant-design/ant-design/pull/51550) [@OysterD3](https://github.com/OysterD3)
<img width="520" alt="Button Colors" src="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*ApyYQpXQQfgAAAAAAAAAAAAADgCCAQ/original">
- 🆕 Button support `loading={{ icon: ReactNode }}` to customize loading icon. [#51758](https://github.com/ant-design/ant-design/pull/51758) [@zhangchao-wooc](https://github.com/zhangchao-wooc)
- Menu
- 🐞 Fix Menu `extra` font size and vertical align issue. [#52217](https://github.com/ant-design/ant-design/pull/52217) [@guoyunhe](https://github.com/guoyunhe)
- 🆕 Menu add token `subMenuItemSelectedColor` to resolve submenu title color being overrided by `itemSelectedColor`. [#52182](https://github.com/ant-design/ant-design/pull/52182) [@afc163](https://github.com/afc163)
- 🆕 Semantic Props
- 🆕 ConfigProvider support Empty semantic props `classNames` and `styles`. [#52208](https://github.com/ant-design/ant-design/pull/52208) [@thinkasany](https://github.com/thinkasany)
- 🆕 ConfigProvider support Popconfirm semantic props `classNames` and `styles`. [#52126](https://github.com/ant-design/ant-design/pull/52126) [@thinkasany](https://github.com/thinkasany)
- 🆕 ConfigProvider support Popover semantic props `classNames` and `styles`. [#52110](https://github.com/ant-design/ant-design/pull/52110) [@thinkasany](https://github.com/thinkasany)
- 🆕 ConfigProvider support Tooltip semantic props `classNames` and `styles`. [#51872](https://github.com/ant-design/ant-design/pull/51872) [@thinkasany](https://github.com/thinkasany)
- 🆕 ConfigProvider support Descriptions semantic props `classNames` and `styles`. [#52120](https://github.com/ant-design/ant-design/pull/52120) [@thinkasany](https://github.com/thinkasany)
- 🆕 ConfigProvider support Slider semantic props `classNames` and `styles`. [#52185](https://github.com/ant-design/ant-design/pull/52185) [@thinkasany](https://github.com/thinkasany)
- 🆕 Transfer support `showSearch` config `defaultValue` & `placeholder`. [#52125](https://github.com/ant-design/ant-design/pull/52125) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🆕 Calendar now supports `showWeek` prop. [#52072](https://github.com/ant-design/ant-design/pull/52072) [@afc163](https://github.com/afc163)
- 🆕 Mentions support `onPopupScroll` props. [#51858](https://github.com/ant-design/ant-design/pull/51858) [@OysterD3](https://github.com/OysterD3)
- 🆕 Card support `bodyPaddingSM`, `headerPaddingSM`, `bodyPadding`, `headerPadding` component token. [#51762](https://github.com/ant-design/ant-design/pull/51762) [@thinkasany](https://github.com/thinkasany)
- 🆕 ColorPicker `presets` support `key` prop. [#51794](https://github.com/ant-design/ant-design/pull/51794) [@li-jia-nan](https://github.com/li-jia-nan)
- 🆕 Cascader support `optionSelectedColor` token. [#51769](https://github.com/ant-design/ant-design/pull/51769) [@thinkasany](https://github.com/thinkasany)
- Tree
- 🛠 Refactor Tree part code to Function Component for React 19 perf preparing. [#52209](https://github.com/ant-design/ant-design/pull/52209) [@li-jia-nan](https://github.com/li-jia-nan)
- 💄 Optimize Tree `disabled` & `selected` node display style. [#52173](https://github.com/ant-design/ant-design/pull/52173) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 Fix Slider crash when `tipFormatter` is undefined. [#52184](https://github.com/ant-design/ant-design/pull/52184) [@thinkasany](https://github.com/thinkasany)
- 🐞 Fix Layout.Sider `trigger` style not correct. [#46a8eff](https://github.com/ant-design/ant-design/commit/46a8eff) [@Wxh16144](https://github.com/Wxh16144)
- Table
- 🐞 Fix Table `fixedright` is not working in `expandable`. [#52176](https://github.com/ant-design/ant-design/pull/52176) [@afc163](https://github.com/afc163)
- 🐞 Fix Table sticky scrollbar not working in rtl direction. [#52176](https://github.com/ant-design/ant-design/pull/52176) [@afc163](https://github.com/afc163)
- 💄 Optimize Flex to always reset `margin` & `padding` for customize component. [#52170](https://github.com/ant-design/ant-design/pull/52170) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 Fix DatePicker.RangePicker `needConfirm` sometime can switch panel without confirm. [#52102](https://github.com/ant-design/ant-design/pull/52102) [@Zyf665](https://github.com/Zyf665)
- 💄 Optimize Collapse focus styles and items border radius. [#52086](https://github.com/ant-design/ant-design/pull/52086) [@aojunhao123](https://github.com/aojunhao123)
- ⌨️ Add Radio.Group default `name` prop to improve a11y. [#52076](https://github.com/ant-design/ant-design/pull/52076) [@aojunhao123](https://github.com/aojunhao123)
- ⌨️ Input.Search add `type=search` by default. [#52083](https://github.com/ant-design/ant-design/pull/52083) [@Kaikiat1126](https://github.com/Kaikiat1126)
- ⌨️ Improve Tabs focus style for keyboard operation. [#52002](https://github.com/ant-design/ant-design/pull/52002) [@aojunhao123](https://github.com/aojunhao123)
- Segmented
- ⌨️ Optimize Segmented focus style to improve a11y. [#51934](https://github.com/ant-design/ant-design/pull/51934) [@aojunhao123](https://github.com/aojunhao123)
- ⌨️ Segmented support `name` prop to improve a11y. [#51725](https://github.com/ant-design/ant-design/pull/51725) [@thinkasany](https://github.com/thinkasany)
- 📦 MISC: Reduce bundle size by replacing `@ctrl/tinycolor` with `@ant-design/fast-color`. [#52190](https://github.com/ant-design/ant-design/pull/52190) [#52157](https://github.com/ant-design/ant-design/pull/52157) [@aojunhao123](https://github.com/aojunhao123)
- ⌨️ Adjust Input, InputNumber, Mentions, Textarea clear icon from `span` to `button` to improve a11y. [#52180](https://github.com/ant-design/ant-design/pull/52180) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 MISC: Fix build error when using React 19. [#52168](https://github.com/ant-design/ant-design/pull/52168) [@zombieJ](https://github.com/zombieJ)
- TypeScript
- 🤖 Adjust Table `ref` type to React.Ref. [#52205](https://github.com/ant-design/ant-design/pull/52205) [@li-jia-nan](https://github.com/li-jia-nan)
- 🤖 Calendar export CalendarMode type. [#52160](https://github.com/ant-design/ant-design/pull/52160) [@Kaikiat1126](https://github.com/Kaikiat1126)
## 5.22.7
`2024-12-27`
- 🐞 Fix Button text and icon not align. [#52132](https://github.com/ant-design/ant-design/pull/52132) [@afc163](https://github.com/afc163)
- 🐞 Fix Button throws `reactRender is not a function` under React 19. [#52105](https://github.com/ant-design/ant-design/pull/52105) [@afc163](https://github.com/afc163)
- TypeScript
- 🤖 Fix Menu interface type error from external module. [#51715](https://github.com/ant-design/ant-design/pull/51715) [@msyavuz](https://github.com/msyavuz)
## 5.22.6
`2024-12-23`
- 🐞 Align Button with and without icons consistently. [#52070](https://github.com/ant-design/ant-design/pull/52070)
- 🐞 Fix Splitter collapsible icon `z-index` too low. [#52065](https://github.com/ant-design/ant-design/pull/52065) [@wanpan11](https://github.com/wanpan11)
- 🐞 Fix Button motion not smooth when set `loading`. [#52059](https://github.com/ant-design/ant-design/pull/52059) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Button issue where solid default button text disappears on hover in dark mode. [#52024](https://github.com/ant-design/ant-design/pull/52024) [@DDDDD12138](https://github.com/DDDDD12138)
## 5.22.5
`2024-12-15`
@ -2016,7 +2263,7 @@ tag: vVERSION
- 💄 Adjust Select, TreeSelect, Cascader always show the `arrow` by default when multiple. [#41028](https://github.com/ant-design/ant-design/pull/41028)
- 🐞 Fix Form `Form.Item.useStatus` problem with sever-side-rendering. [#40977](https://github.com/ant-design/ant-design/pull/40977) [@AndyBoat](https://github.com/AndyBoat)
- 🐞 MISC: Fix arrow shape in some components. [#40971](https://github.com/ant-design/ant-design/pull/40971)
- 🐞 Fix Layout throw `React does not recognize the `suffixCls` prop on a DOM element` warning. [#40969](https://github.com/ant-design/ant-design/pull/40969)
- 🐞 Fix Layout throw "React does not recognize the `suffixCls` prop on a DOM element" warning. [#40969](https://github.com/ant-design/ant-design/pull/40969)
- 🐞 Fix Watermark that text will be displayed when the picture loads abnormally. [#40770](https://github.com/ant-design/ant-design/pull/40770) [@OriginRing](https://github.com/OriginRing)
- 🐞 Image support flip function in preview mode. Fix Image `fallback` when used in ssr. [#40660](https://github.com/ant-design/ant-design/pull/40660)
- 🐞 Fix Typography component is not centered in the Select component. [#40422](https://github.com/ant-design/ant-design/pull/40422) [@Yuiai01](https://github.com/Yuiai01)

View File

@ -15,6 +15,254 @@ tag: vVERSION
---
## 5.24.7
`2025-04-14`
- 🐞 修复 Input 存在 `suffix` 的情况下 Popover 弹出位置错乱的问题。[#53475](https://github.com/ant-design/ant-design/pull/53475)
- 🐞 修复 Table `column.filterDropdown` 指定为 `undefined` 导致筛选菜单选中状态丢失的问题。[#53421](https://github.com/ant-design/ant-design/pull/53421)
- 🇨🇳 补充 ColorPicker 的 `zh_HK` `zh_TW` 本地化文案。[#53440](https://github.com/ant-design/ant-design/pull/53440) [@mjsong07](https://github.com/mjsong07)
## 5.24.6
`2025-04-01`
- 🐞 修复 Modal 通过异步方法显示 loading 态时,点击外侧仍能触发关闭的问题。[#53227](https://github.com/ant-design/ant-design/pull/53227) [@jin19980928](https://github.com/jin19980928)
- 🐞 修复 Table 在 `size``small` 时,主题配置 Pagination 无效的问题。[#52829](https://github.com/ant-design/ant-design/pull/52829) [@Can-Chen](https://github.com/Can-Chen)
## 5.24.5
`2025-03-24`
- 🐞 修复 InputNumber 在禁用状态下鼠标划入后 `suffix` 出现左移的问题。[#53184](https://github.com/ant-design/ant-design/pull/53184) [@yellowryan](https://github.com/yellowryan)
- 💄 修复 Form 组件样式选择器语法错误。[#53236](https://github.com/ant-design/ant-design/pull/53236) [@Wxh16144](https://github.com/Wxh16144)
- 💄 重构 TextArea 对 `resize: both` 时处理尺寸的逻辑以更符合 React 生命周期。[#53235](https://github.com/ant-design/ant-design/pull/53235) [@zombieJ](https://github.com/zombieJ)
- 🇮🇷 添加缺失的波斯语 (fa_IR) 翻译并修正拼写错误。[#53251](https://github.com/ant-design/ant-design/pull/53251) [@AliReza-Kamkar](https://github.com/AliReza-Kamkar)
## 5.24.4
`2025-03-17`
- 🐞 修复 Input.TextArea 调整大小时宽度同步问题。[#53024](https://github.com/ant-design/ant-design/pull/53024) [@triyys](https://github.com/triyys)
- 🐞 修复 Typography `type` 颜色没有跟随 token `color[Status]Text` 而是 `color[Status]` 的问题。[#53086](https://github.com/ant-design/ant-design/pull/53086) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Affix 组件在 React 18 以下版本中 `onChange` 参数值异常的问题。[#53038](https://github.com/ant-design/ant-design/pull/53038) [@waiter](https://github.com/waiter)
- 🐞 修复 Form `requiredMark``component=false` 时不起作用的问题。[#52950](https://github.com/ant-design/ant-design/pull/52950) [@Wxh16144](https://github.com/Wxh16144)
- 🇹🇷 新增 Tour 组件的土耳其语tr_TR本地化支持。[#53117](https://github.com/ant-design/ant-design/pull/53117) [@hakankosdag](https://github.com/hakankosdag)
## 5.24.3
`2025-03-05`
- Input
- 🐞 修复 Input 开启 `allowClear` 后按下 Tab 键后没有正确选中下个元素的问题。[#52977](https://github.com/ant-design/ant-design/pull/52977) [@wanpan11](https://github.com/wanpan11)
- 💄 修复 Input 开启 `variant="underlined"``disabled` 状态下 hover 时边框显示问题。[#52959](https://github.com/ant-design/ant-design/pull/52959) [@ustcfury](https://github.com/ustcfury)
- 💄 修复 DatePicker 头部按钮意外间距导致的未对齐问题。[#53007](https://github.com/ant-design/ant-design/pull/53007) [@DDDDD12138](https://github.com/DDDDD12138)
- 💄 修复 AutoComplete 在 `size="large"` 时文字未居中对齐的问题。[#52819](https://github.com/ant-design/ant-design/pull/52819) [@aojunhao123](https://github.com/aojunhao123)
- 🇩🇪 完善 `de_DE` Transfer 本地化. [#53047](https://github.com/ant-design/ant-design/pull/53047) [@chrisinick](https://github.com/chrisinick)
## 5.24.2
`2025-02-24`
- Input
- 🐞 修复 Input 配置 `inputFontSize` component token 时,`controlHeight` 会不生效的问题。[#52865](https://github.com/ant-design/ant-design/pull/52865) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Input.Search 在设置 `variant``underlined` 时下边框与搜索按钮底部没对齐的问题。[#52861](https://github.com/ant-design/ant-design/pull/52861) [@ustcfury](https://github.com/ustcfury)
- 🛠 优化 Input.OTP 的默认状态创建逻辑。[#52878](https://github.com/ant-design/ant-design/pull/52878) [@Dandelion-F](https://github.com/Dandelion-F)
- 🛠 优化 Input.OTP 的分隔符渲染实现。[#52841](https://github.com/ant-design/ant-design/pull/52841) [@li-jia-nan](https://github.com/li-jia-nan)
- Watermark
- 🐞 修复 Watermark 重新渲染时可能导致页面卡死的问题。[#52897](https://github.com/ant-design/ant-design/pull/52897) [@765477020](https://github.com/765477020)
- 🆕 调整 Watermark 渲染逻辑,防止通过开发者工具添加 `hidden` 属性来去掉水印。[#52891](https://github.com/ant-design/ant-design/pull/52891) [@arronlai](https://github.com/arronlai)
- 🐞 修复 DatePicker.RangePicker 在弹层重新打开的时候,有可能出现箭头位置不正确的问题。[#52854](https://github.com/ant-design/ant-design/pull/52854) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Layout.Sider 当 `collapsedWidth={0}` 时的内容溢出的问题。[#52862](https://github.com/ant-design/ant-design/pull/52862) [@afc163](https://github.com/afc163)
- 🛠 重构 Grid 内部响应式逻辑以复用其他组件类似的逻辑,该更新不会于使用上有所变化。[#52870](https://github.com/ant-design/ant-design/pull/52870) [@zombieJ](https://github.com/zombieJ)
- 💄 修复 Button 超链接模式的样式。[#52888](https://github.com/ant-design/ant-design/pull/52888) [@DDDDD12138](https://github.com/DDDDD12138)
- 💄 修复 Table 可排序列头不自动换行的问题。[#52899](https://github.com/ant-design/ant-design/pull/52899) [@765477020](https://github.com/765477020)
- ⚡️ 优化 Menu 在 `expandIcon` 属性传入函数时重新渲染的性能。[#52863](https://github.com/ant-design/ant-design/pull/52863) [@wanpan11](https://github.com/wanpan11)
- ⚡️ 优化 Carousel 指示器的动画性能。[#52881](https://github.com/ant-design/ant-design/pull/52881) [@li-jia-nan](https://github.com/li-jia-nan)
- RTL
- 💄 修复 DatePicker 在 RTL 模式下图标方向错误的问题。[#52896](https://github.com/ant-design/ant-design/pull/52896) [@li-jia-nan](https://github.com/li-jia-nan)
- 💄 修复 Dropdown 在 RTL 模式下多级菜单箭头方向错误的问题。[#52885](https://github.com/ant-design/ant-design/pull/52885) [@yellowryan](https://github.com/yellowryan)
## 5.24.1
`2025-02-17`
- 🐞 修复 Button 配置 `color``primary` 并且 `variant``text``link` 时,没有取用正确的色板的问题。[#52812](https://github.com/ant-design/ant-design/pull/52812) [@zombieJ](https://github.com/zombieJ)
- 💄 修复 Input.Group 与 Input.OTP 由于 css 变量未定义导致样式异常的问题。[#52799](https://github.com/ant-design/ant-design/pull/52799) [@afc163](https://github.com/afc163)
- 🐞 修复 DatePicker 的 `prefix` 内容多时会换行的问题。[#52776](https://github.com/ant-design/ant-design/pull/52776) [@guoyunhe](https://github.com/guoyunhe)
- 🐞 修复 Table 列标题在排序时丢失 `aria-label` 的问题。[#52772](https://github.com/ant-design/ant-design/pull/52772) [@mellis481](https://github.com/mellis481)
- 🐞 修复 Alert.ErrorBoundary 在 `@types/react@18.x` 中无法作为 JSX 组件使用的类型错误问题。[#52766](https://github.com/ant-design/ant-design/pull/52766) [@afc163](https://github.com/afc163)
- 💄 修复 Segmented 设置 `size` 时,`shape` 不生效的问题。[#52757](https://github.com/ant-design/ant-design/pull/52757) [@yellowryan](https://github.com/yellowryan)
## 5.24.0
`2025-02-11`
- 🆕 Notification 支持 `actions` 属性并废弃 `btn` 属性。[#52703](https://github.com/ant-design/ant-design/pull/52703) [@thinkasany](https://github.com/thinkasany)
- 🆕 Carousel 支持展示指示点进度。[#52672](https://github.com/ant-design/ant-design/pull/52672) [@yellowryan](https://github.com/yellowryan)
- 🆕 Input.OTP 支持 `separator` 属性。[#52668](https://github.com/ant-design/ant-design/pull/52668) [@HaceraI](https://github.com/HaceraI)
- 🆕 Descriptions 增加 `labelColor` 组件 token。[#52700](https://github.com/ant-design/ant-design/pull/52700) [@guoyunhe](https://github.com/guoyunhe)
- 🆕 Segmented 支持 `shape="round"` 的胶囊形状的样式。[#52685](https://github.com/ant-design/ant-design/pull/52685) [@afc163](https://github.com/afc163)
- 🆕 ConfigProvider 支持 Card 组件的 `variant` 配置。[#52552](https://github.com/ant-design/ant-design/pull/52552) [@thinkasany](https://github.com/thinkasany)
- 🆕 Progress/Step 支持使用 `rounding` 属性自定义取整方法。[#52017](https://github.com/ant-design/ant-design/pull/52017) [@yanghoxom](https://github.com/yanghoxom)
- 🆕 Divider 的 `orientation` 属性支持 `start``end`.[#52567](https://github.com/ant-design/ant-design/pull/52567) [@li-jia-nan](https://github.com/li-jia-nan)
- 🆕 为 Input、InputNumber、Mentions、Form、Select、Cascader、TreeSelect、DatePicker、TimePicker 组件的 `variant` 添加 `underlined` 属性。[#52546](https://github.com/ant-design/ant-design/pull/52546) [@ustcfury](https://github.com/ustcfury)
- 🆕 ConfigProvider 支持 Modal `centered` 全局配置。[#52343](https://github.com/ant-design/ant-design/pull/52343) [@guoyunhe](https://github.com/guoyunhe)
- 🆕 为 Checkbox 和 Radio 增加 `label` 类名。[#52322](https://github.com/ant-design/ant-design/pull/52322) [@guoyunhe](https://github.com/guoyunhe)
- 🐞 修复 Tooltip/Popover/Popconfirm/Dropdown 在 React 19 下 children 为自定义组件时弹层位置错乱的问题。[react-component/util#623](https://github.com/react-component/util/pull/623) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 DatePicker.RangePicker 弹出窗体自动调整位置时,面板的箭头位置不正确的问题。[#52719](https://github.com/ant-design/ant-design/pull/52719) [@zombieJ](https://github.com/zombieJ)
- 🐞 修正 locale 中 `filterCheckall``filterCheckAll`。[#52517](https://github.com/ant-design/ant-design/pull/52517) [@thinkasany](https://github.com/thinkasany)
- 🐞 修复 Form `scrollToField``scrollToFirstError` 无法聚焦 antd 组件的问题。[#52726](https://github.com/ant-design/ant-design/pull/52726) [@Wxh16144](https://github.com/Wxh16144)
- 💄 修复 Button 预设值按钮的阴影色在暗色背景下显示突兀的问题。[#52701](https://github.com/ant-design/ant-design/pull/52701) [@afc163](https://github.com/afc163)
- 💄 修复 Segmented 组件在暗黑模式下的动画过渡效果不自然的问题。[#52708](https://github.com/ant-design/ant-design/pull/52708) [@yellowryan](https://github.com/yellowryan)
- 💄 拆分 Input 和 TextArea 样式。[#52570](https://github.com/ant-design/ant-design/pull/52570) [@guoyunhe](https://github.com/guoyunhe)
- 💄 修复 Input 和 Select 在 css var 模式下的样式问题。[#52554](https://github.com/ant-design/ant-design/pull/52554) [@li-jia-nan](https://github.com/li-jia-nan)
- ⌨️ Form field error 移除 role="alert" 以提升可访问性。[#52661](https://github.com/ant-design/ant-design/pull/52661) [@mellis481](https://github.com/mellis481)
- ⌨️ 优化无障碍体验,为空表头和面板按钮添加本地化标签。[#52689](https://github.com/ant-design/ant-design/pull/52689) [@aojunhao123](https://github.com/aojunhao123)
- ⌨️ 优化 Tabs 组件的可访问性,修复 `Certain ARIA roles must contain particular children` 的报错。[#52677](https://github.com/ant-design/ant-design/pull/52677) [@afc163](https://github.com/afc163)
- ⌨️ 为 Tooltip 添加读屏器支持。[#52293](https://github.com/ant-design/ant-design/pull/52293) [@aojunhao123](https://github.com/aojunhao123)
- TypeScript
- 🤖 Button 根据 `href` 属性区分 `onClick` 事件类型。[#52654](https://github.com/ant-design/ant-design/pull/52654) [@Brew-Brew](https://github.com/Brew-Brew)
- 🤖 废弃 Button.Group, 推荐 Space.Compact。[#52572](https://github.com/ant-design/ant-design/pull/52572) [@guoyunhe](https://github.com/guoyunhe)
- 🤖 废弃 Input.Group推荐 Space.Compact。[#52571](https://github.com/ant-design/ant-design/pull/52571) [@guoyunhe](https://github.com/guoyunhe)
- 🤖 Tooltip 导出 TooltipRef 类型。[#49230](https://github.com/ant-design/ant-design/pull/49230) [@nuintun](https://github.com/nuintun)
## 5.23.4
`2025-02-05`
蛇年第一个版本,开工大吉!🐍
- 🐞 修复 Pagination 可访问性问题,补充缺失的 ARIA 属性支持。[#52616](https://github.com/ant-design/ant-design/pull/52616) [@aojunhao123](https://github.com/aojunhao123)
- 🐞 Space.Compact 支持 textarea 组件。[#52639](https://github.com/ant-design/ant-design/pull/52639) [@Can-Chen](https://github.com/Can-Chen)
- 🐞 修复 Menu `theme="dark"` 时水平菜单的文字色和背景色同色的问题。[#52617](https://github.com/ant-design/ant-design/pull/52617) [@afc163](https://github.com/afc163)
- 🇪🇬 Tour 增加阿拉伯文(埃及) (ar_EG) 的翻译。 [#52642](https://github.com/ant-design/ant-design/pull/52642) [@Sagie501](https://github.com/Sagie501)
- 🇮🇱 Tour 增加以色列的国际化翻译。[#52641](https://github.com/ant-design/ant-design/pull/52641) [@Sagie501](https://github.com/Sagie501)
## 5.23.3
`2025-01-28`
龙年最后一版,祝各位新春愉快!🐲
- ⌨️ MISC: 为所有组件的示例添加了可访问性测试,确保符合无障碍标准。并优化了部分组件的可访问性支持,改进了对屏幕阅读器和键盘操作的兼容性。[#51372](https://github.com/ant-design/ant-design/pull/51372) [@aojunhao123](https://github.com/aojunhao123)
- 🐞 MISC: 修复导入 `antd/dist/reset.css` 文件的问题。[#52559](https://github.com/ant-design/ant-design/pull/52559) [@CaptainVolcom](https://github.com/CaptainVolcom)
- 🐞 修复 Button `loading``null` 时抛错的问题。[#52508](https://github.com/ant-design/ant-design/pull/52508) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 修复 Table 最后一行边框颜色过渡问题。[#52549](https://github.com/ant-design/ant-design/pull/52549) [@DDDDD12138](https://github.com/DDDDD12138)
- 💄 修复 Cascader 组件禁用状态下复选框的鼠标指针样式问题。[#52539](https://github.com/ant-design/ant-design/pull/52539) [@aojunhao123](https://github.com/aojunhao123)
- 💄 修复 ConfigProvider 在 StyleProvider 配置 `layer` 时不会正确修改图标对应样式优先级的问题。[#52533](https://github.com/ant-design/ant-design/pull/52533) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Layout 切换侧边栏按钮在非 cssVar 模式下样式丢失的问题。. [#52537](https://github.com/ant-design/ant-design/pull/52537) [@afc163](https://github.com/afc163)
- 🐞 修复 Tree 组件禁用状态下复选框的鼠标指针样式问题。[#52525](https://github.com/ant-design/ant-design/pull/52525) [@aojunhao123](https://github.com/aojunhao123)
- notification
- 🐞 修复 notification `useNotification``closeIcon` 配置无效的问题。[#52516](https://github.com/ant-design/ant-design/pull/52516) [@typenoob](https://github.com/typenoob)
- 🐞 修复 notification 组件在 App 组件下的显示闪烁问题。[#52499](https://github.com/ant-design/ant-design/pull/52499) [@afc163](https://github.com/afc163)
- RTL
- 🐞 修复 Splitter 在 rtl 模式下折叠行为异常的问题。[#52501](https://github.com/ant-design/ant-design/pull/52501) [@aojunhao123](https://github.com/aojunhao123)
- 💄 修复 Spin 在 rtl 模式下的样式问题。[#52538](https://github.com/ant-design/ant-design/pull/52538) [@afc163](https://github.com/afc163)
## 5.23.2
`2025-01-20`
- 🐞 修复 Space.Compact 抛出 `Should not use more than one & in a selector` 警告信息的问题。[#52489](https://github.com/ant-design/ant-design/pull/52489)
- 💄 修复 Layout 切换侧边栏按钮样式丢失的问题。[#52477](https://github.com/ant-design/ant-design/pull/52477)
- 💄 修复 Table 收起虚拟滚动表格第一行时滚动条高度不为 0 的问题。[#52447](https://github.com/ant-design/ant-design/pull/52447) [@LeeSSHH](https://github.com/LeeSSHH)
- 💄 修复 Descriptions 最后一项未正确填充满剩余空间的问题。[#52410](https://github.com/ant-design/ant-design/pull/52410) [@anyuxuan](https://github.com/anyuxuan)
- 💄 修复 Radio.Group 最后一项多余 margin 的问题。[#52433](https://github.com/ant-design/ant-design/pull/52433)
- 💄 修复 Input/Mentions 清除按钮 padding 不正确的问题。[#52407](https://github.com/ant-design/ant-design/pull/52407) [@ustcfury](https://github.com/ustcfury)
- 💄 修复 Input 紧凑模式中 `addonAfter` 的圆角问题。[#52490](https://github.com/ant-design/ant-design/pull/52490) [@DDDDD12138](https://github.com/DDDDD12138)
- 💄 修复 Menu.Item 在禁用状态下链接仍可点击且缺少禁用样式的问题。[#52402](https://github.com/ant-design/ant-design/pull/52402) [@aojunhao123](https://github.com/aojunhao123)
- TypeScript
- 🤖 MISC: 优化 PurePanel 使用 React.ComponentType 类型。[#52480](https://github.com/ant-design/ant-design/pull/52480)
- 🤖 修复 Skeleton 和 Rate 缺失的 token 类型。[#52406](https://github.com/ant-design/ant-design/pull/52406) [@coding-ice](https://github.com/coding-ice)
## 5.23.1
`2025-01-13`
- 🆕 新增 Tree 组件叶子节点的 className 用于区分节点类型。[#52274](https://github.com/ant-design/ant-design/pull/52274) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 修复 DatePicker `superPrevIcon/superNextIcon/prevIcon/nextIcon` 设置为 null 时切换按钮依旧存在的问题。[#52327](https://github.com/ant-design/ant-design/pull/52327) [@afc163](https://github.com/afc163)
- 🐞 修复 Select 组件在 jest 测试中报错 `not a valid selector` 的问题。[#51844](https://github.com/ant-design/ant-design/pull/51844) [@renovate](https://github.com/renovate)
- 🐞 修复 Layout.Sider 直接嵌套在 ConfigProvider 下时,`theme` 配置无效的问题。[#52302](https://github.com/ant-design/ant-design/pull/52302) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Splitter 二次展开时丢失上一次状态的问题。[#52222](https://github.com/ant-design/ant-design/pull/52222) [@jjlstruggle](https://github.com/jjlstruggle)
- 🐞 修复 Table 树形展示且设置 `checkStrictly` 为 false 时,某些行被错误选中的问题。[#52338](https://github.com/ant-design/ant-design/pull/52338) [@LeeSSHH](https://github.com/LeeSSHH)
- Button
- 🐞 调整 Button 纯图标的大小从而修复按钮对齐和图标居中问题。[#52353](https://github.com/ant-design/ant-design/pull/52353) [@afc163](https://github.com/afc163)
- 💄 修复 Button 丢失阴影样式的问题。[#52304](https://github.com/ant-design/ant-design/pull/52304) [@zombieJ](https://github.com/zombieJ)
- RTL
- 💄 修复 Collapse 在 RTL 模式下的箭头方向。[#52374](https://github.com/ant-design/ant-design/pull/52374) [@aojunhao123](https://github.com/aojunhao123)
- 💄 修复 Layout.Sider 在 RTL 模式下的箭头方向。[#52374](https://github.com/ant-design/ant-design/pull/52374) [@aojunhao123](https://github.com/aojunhao123)
## 5.23.0
`2025-01-06`
- 🔥 TreeSelect 新增 `maxCount` 属性以限制最大选择数量。[#51759](https://github.com/ant-design/ant-design/pull/51759) [@aojunhao123](https://github.com/aojunhao123)
- 🔥 Modal `width` 支持响应式尺寸。[#51653](https://github.com/ant-design/ant-design/pull/51653) [@zombieJ](https://github.com/zombieJ)
- 🔥 Splitter 增加 `lazy` 模式。[#51557](https://github.com/ant-design/ant-design/pull/51557) [@OysterD3](https://github.com/OysterD3)
- Button
- 🔥 Button `color` 属性支持完整色板。[#51550](https://github.com/ant-design/ant-design/pull/51550) [@OysterD3](https://github.com/OysterD3)
<img width="520" alt="Button Colors" src="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*ApyYQpXQQfgAAAAAAAAAAAAADgCCAQ/original">
- 🆕 Button 组件新增 `loading={{ icon: ReactNode }}` 以自定义加载图标。[#51758](https://github.com/ant-design/ant-design/pull/51758) [@zhangchao-wooc](https://github.com/zhangchao-wooc)
- Menu
- 🆕 Menu 新增 token `subMenuItemSelectedColor`,避免 `itemSelectedColor` 覆盖子菜单标题样式。[#52182](https://github.com/ant-design/ant-design/pull/52182) [@afc163](https://github.com/afc163)
- 🐞 修复 Menu `extra` 字体大小和垂直居中对齐问题。[#52217](https://github.com/ant-design/ant-design/pull/52217) [@guoyunhe](https://github.com/guoyunhe)
- 🆕 语义化
- 🆕 ConfigProvider 支持 Empty 组件语义化 `classNames``styles`。[#52208](https://github.com/ant-design/ant-design/pull/52208) [@thinkasany](https://github.com/thinkasany)
- 🆕 ConfigProvider 支持 Slider 组件语义化 `classNames``styles`。[#52185](https://github.com/ant-design/ant-design/pull/52185) [@thinkasany](https://github.com/thinkasany)
- 🆕 ConfigProvider 支持 Popconfirm 组件语义化 `classNames``styles`。[#52126](https://github.com/ant-design/ant-design/pull/52126) [@thinkasany](https://github.com/thinkasany)
- 🆕 ConfigProvider 支持 Popover 组件语义化 `classNames``styles`。[#52110](https://github.com/ant-design/ant-design/pull/52110) [@thinkasany](https://github.com/thinkasany)
- 🆕 ConfigProvider 支持 Tooltip 组件语义化 `classNames``styles`。[#51872](https://github.com/ant-design/ant-design/pull/51872) [@thinkasany](https://github.com/thinkasany)
- 🆕 ConfigProvider 支持 Descriptions 组件语义化 `classNames``styles`。[#52120](https://github.com/ant-design/ant-design/pull/52120) [@thinkasany](https://github.com/thinkasany)
- Tree
- 🛠 重构 Tree 部分代码为 Function Component 以为 React 19 做更好性能准备。[#52209](https://github.com/ant-design/ant-design/pull/52209) [@li-jia-nan](https://github.com/li-jia-nan)
- 💄 优化 Tree `disabled``selected` 节点状态下的颜色展示。[#52173](https://github.com/ant-design/ant-design/pull/52173) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🆕 Transfer 支持 `showSearch` 配置 `defaultValue``placeholder`。[#52125](https://github.com/ant-design/ant-design/pull/52125) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🆕 Calendar 支持 `showWeek` 属性用于显示周数列。[#52072](https://github.com/ant-design/ant-design/pull/52072) [@afc163](https://github.com/afc163)
- 🆕 Mentions 新增 `onPopupScroll` 属性。[#51858](https://github.com/ant-design/ant-design/pull/51858) [@OysterD3](https://github.com/OysterD3)
- 🆕 Card 增加 `bodyPaddingSM`、`headerPaddingSM`、`bodyPadding`、`headerPadding` 组件 token。[#51762](https://github.com/ant-design/ant-design/pull/51762) [@thinkasany](https://github.com/thinkasany)
- 🆕 ColorPicker `presets` 支持传入 `key`。[#51794](https://github.com/ant-design/ant-design/pull/51794) [@li-jia-nan](https://github.com/li-jia-nan)
- 🆕 Cascader 新增 `optionSelectedColor` token。[#51769](https://github.com/ant-design/ant-design/pull/51769) [@thinkasany](https://github.com/thinkasany)
- 🐞 修复 Layout.Sider `trigger` 样式不正确的问题。[#46a8eff](https://github.com/ant-design/ant-design/commit/46a8eff) [@Wxh16144](https://github.com/Wxh16144)
- Table
- 🐞 修复 Table `expandable` 中设置 `fixedright` 不生效的问题。[#52176](https://github.com/ant-design/ant-design/pull/52176) [@afc163](https://github.com/afc163)
- 🐞 修复 Table `sticky` 模式下水平固定滚动条在 rtl 模式下不生效的问题。[#52176](https://github.com/ant-design/ant-design/pull/52176) [@afc163](https://github.com/afc163)
- 💄 优化 Flex 使其在自定义渲染组件时总是重置 `margin`、`padding` 样式。[#52170](https://github.com/ant-design/ant-design/pull/52170) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 修复 DatePicker.RangePicker `needConfirm` 模式偶尔在不确认仍然可以切换面板的问题。[#52102](https://github.com/ant-design/ant-design/pull/52102) [@Zyf665](https://github.com/Zyf665)
- 🐞 修复 Slider 当 `tipFormatter` 未定义时导致崩溃的问题。[#52184](https://github.com/ant-design/ant-design/pull/52184) [@thinkasany](https://github.com/thinkasany)
- 💄 优化 Collapse 聚焦样式以及折叠项圆角。[#52086](https://github.com/ant-design/ant-design/pull/52086) [@aojunhao123](https://github.com/aojunhao123)
- ⌨️ 为 Radio.Group 添加默认 `name` 属性以提升无障碍体验。[#52076](https://github.com/ant-design/ant-design/pull/52076) [@aojunhao123](https://github.com/aojunhao123)
- ⌨️ Input.Search 添加默认 `type=search` 类型。[#52083](https://github.com/ant-design/ant-design/pull/52083) [@Kaikiat1126](https://github.com/Kaikiat1126)
- ⌨️ 优化 Tabs 键盘操作时的焦点样式。[#52002](https://github.com/ant-design/ant-design/pull/52002) [@aojunhao123](https://github.com/aojunhao123)
- Segmented
- ⌨️ 优化 Segmented 聚焦样式以提升无障碍体验。[#51934](https://github.com/ant-design/ant-design/pull/51934) [@aojunhao123](https://github.com/aojunhao123)
- ⌨️ Segmented 支持 `name` 属性以提升无障碍体验。[#51725](https://github.com/ant-design/ant-design/pull/51725) [@thinkasany](https://github.com/thinkasany)
- 📦 MISC: 用 `@ant-design/fast-color` 替换 `@ctrl/tinycolor` 以降低打包体积。[#52190](https://github.com/ant-design/ant-design/pull/52190) [#52157](https://github.com/ant-design/ant-design/pull/52157) [@aojunhao123](https://github.com/aojunhao123)
- ⌨️ 调整 Input、InputNumber、Mentions、Textarea 组件清除图标从 `span` 元素更改为 `button` 元素,提高了可访问性和交互性。[#52180](https://github.com/ant-design/ant-design/pull/52180) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 MISC: 修复 React 19 下构建报错的问题。[#52168](https://github.com/ant-design/ant-design/pull/52168) [@zombieJ](https://github.com/zombieJ)
- TypeScript
- 🤖 调整 Table `ref` 类型为 React.Ref。[#52205](https://github.com/ant-design/ant-design/pull/52205) [@li-jia-nan](https://github.com/li-jia-nan)
- 🤖 Calendar 导出 CalendarMode 类型。[#52160](https://github.com/ant-design/ant-design/pull/52160) [@Kaikiat1126](https://github.com/Kaikiat1126)
## 5.22.7
`2024-12-27`
- 🐞 修复 Button 文字和图标不对齐的问题。[#52132](https://github.com/ant-design/ant-design/pull/52132) [@afc163](https://github.com/afc163)
- 🐞 修复在 React 19 下点击 Button 时抛出 `reactRender is not a function` 错误的问题。[#52105](https://github.com/ant-design/ant-design/pull/52105) [@afc163](https://github.com/afc163)
- TypeScript
- 🤖 修复 Menu `component` 属性类型抛错。[#51715](https://github.com/ant-design/ant-design/pull/51715) [@msyavuz](https://github.com/msyavuz)
## 5.22.6
`2024-12-23`
- 🐞 修复 Button 有图标和无图标按钮对齐差一像素的问题。[#52070](https://github.com/ant-design/ant-design/pull/52070)
- 🐞 修复 Splitter 组件折叠图标 `z-index` 层级过低问题。[#52065](https://github.com/ant-design/ant-design/pull/52065) [@wanpan11](https://github.com/wanpan11)
- 🐞 修复 Button 启用 `loading` 时,动画不够顺滑的问题。[#52059](https://github.com/ant-design/ant-design/pull/52059) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Button 暗色模式下默认填充按钮文本在悬停时消失的问题。[#52024](https://github.com/ant-design/ant-design/pull/52024) [@DDDDD12138](https://github.com/DDDDD12138)
## 5.22.5
`2024-12-15`
@ -92,7 +340,7 @@ tag: vVERSION
- Form
- 🆕 Form.Item 支持隐藏 label。[#51524](https://github.com/ant-design/ant-design/pull/51524) [@crazyair](https://github.com/crazyair)
- 🐞 Form 移除了用于撑开 error 高度的 div将 errorDom 和 extraDom 用一个 div 包裹,并为该 div 设置了最小高度。[#51254](https://github.com/ant-design/ant-design/pull/51254) [@hongzzz](https://github.com/hongzzz)
- 🐞 Form 移除了用于撑开 error 高度的 div将 errorDom 和 extraDom 用一个 div 包裹,并为该 div 设置了最小高度。[#51254](https://github.com/ant-design/ant-design/pull/51254) [@hongzzz](https://github.com/hongzzz)
- 🐞 修复 Form 在字段触发 change 但是值没有变化时,`onValuesChange` 仍然会触发的问题。[#51437](https://github.com/ant-design/ant-design/pull/51437) [@crazyair](https://github.com/crazyair)
- 🆕 Form 支持在表单验证失败时scrollToFirstError 中的 focus 属性。[#51231](https://github.com/ant-design/ant-design/pull/51231) [@nathanlao](https://github.com/nathanlao)
- Table
@ -166,6 +414,7 @@ tag: vVERSION
- 💄 修改 Button `textHoverBg` 在悬浮状态下的背景色为 `colorFillTertiary`。[#51187](https://github.com/ant-design/ant-design/pull/51187) [@coding-ice](https://github.com/coding-ice)
- TypeScript
- 🤖 优化 Switch `eventHandler` 类型。[#51165](https://github.com/ant-design/ant-design/pull/51165) [@thinkasany](https://github.com/thinkasany)
## 5.21.3
`2024-10-09`
@ -214,17 +463,17 @@ tag: vVERSION
<img width="520" alt="Splitter" src="https://github.com/user-attachments/assets/25fc4e3c-1aa5-41bb-8f39-f34f7149e0f6">
- Button
- 🔥 Button 支持 `variant` 变体和 `color` 颜色属性,以支持更多组合样式。[#50051](https://github.com/ant-design/ant-design/pull/50051) [@coding-ice](https://github.com/coding-ice)
<img width="420" alt="Button" src="https://github.com/user-attachments/assets/cd5cb7fb-25e8-445f-b217-7fdd4ae0f9b4">
<img width="420" alt="Button" src="https://github.com/user-attachments/assets/cd5cb7fb-25e8-445f-b217-7fdd4ae0f9b4">
- 💄 Button 添加 `textColor`、`textHoverColor` 和 `textActiveColor` 三个 token。[#47870](https://github.com/ant-design/ant-design/pull/47870) [@madocto](https://github.com/madocto)
- FloatButton
- 🆕 FloatButton 组件支持 `placement` 属性,支持从四个方向弹出菜单。(实现方式改为 `position: absolute` + flex 布局,可能会对你现有的布局造成 breaking change请注意兼容[#50407](https://github.com/ant-design/ant-design/pull/50407) [@li-jia-nan](https://github.com/li-jia-nan)
<img width="300" alt="float button" src="https://github.com/user-attachments/assets/4b53c0f6-7bdd-4e2a-91cc-2fb804f6e6d3" />
<img width="300" alt="float button" src="https://github.com/user-attachments/assets/4b53c0f6-7bdd-4e2a-91cc-2fb804f6e6d3" />
- 💄 统一 FloatButton 和 FloatButton.Group 的按钮圆角。[#50513](https://github.com/ant-design/ant-design/pull/50513) [@Layouwen](https://github.com/Layouwen)
- 💄 FloatButton 组件的 `z-index` 加入 `useZIndex` 管理,兼容弹层类组件。[#50311](https://github.com/ant-design/ant-design/pull/50311) [@li-jia-nan](https://github.com/li-jia-nan)
- 🆕 FloatButton 支持传入 `htmlType` 属性。[#50892](https://github.com/ant-design/ant-design/pull/50892) [@li-jia-nan](https://github.com/li-jia-nan)
- Menu
- 🆕 Menu.Item 和 Dropdown 的 menu 支持 `extra` 属性。[#50431](https://github.com/ant-design/ant-design/pull/50431) [@coding-ice](https://github.com/coding-ice)
<img width="259" alt="menu extra" src="https://github.com/user-attachments/assets/fee57c43-b948-4f98-8a6b-0d94094a8a65">
<img width="259" alt="menu extra" src="https://github.com/user-attachments/assets/fee57c43-b948-4f98-8a6b-0d94094a8a65">
- 🐞 修复 Menu `popupStyle` 在 SubMenu 上失效的问题。[#50922](https://github.com/ant-design/ant-design/pull/50922) [@Wxh16144](https://github.com/Wxh16144)
- Table
- 🆕 Table 列支持配置 `minWidth` 属性。[#50416](https://github.com/ant-design/ant-design/pull/50416) [@linxianxi](https://github.com/linxianxi)
@ -2016,7 +2265,7 @@ tag: vVERSION
- 💄 调整 Select, TreeSelect, Cascader 在多选时总是默认显示下拉箭头。[#41028](https://github.com/ant-design/ant-design/pull/41028)
- 🐞 修复 Form 组件 `Form.Item.useStatus` 导致的服务端渲染问题。[#40977](https://github.com/ant-design/ant-design/pull/40977) [@AndyBoat](https://github.com/AndyBoat)
- 🐞 杂项:修复部分组件箭头形状问题。[#40971](https://github.com/ant-design/ant-design/pull/40971)
- 🐞 修复 Layout 报错 `React does not recognize the `suffixCls` prop on a DOM element` 的问题。[#40969](https://github.com/ant-design/ant-design/pull/40969)
- 🐞 修复 Layout 报错 "React does not recognize the `suffixCls` prop on a DOM element" 的问题。[#40969](https://github.com/ant-design/ant-design/pull/40969)
- 🐞 修复 Watermark 组件图片加载异常时的问题,默认展示文字。[#40770](https://github.com/ant-design/ant-design/pull/40770) [@OriginRing](https://github.com/OriginRing)
- 🐞 Image 预览新增图片翻转功能。并修复 Image `fallback` 在 ssr 下失效的问题。[#40660](https://github.com/ant-design/ant-design/pull/40660)
- 🐞 修复 Select 中使用 Typography 不居中的问题。[#40422](https://github.com/ant-design/ant-design/pull/40422) [@Yuiai01](https://github.com/Yuiai01)

View File

@ -20,8 +20,8 @@
[npm-image]: https://img.shields.io/npm/v/antd.svg?style=flat-square
[npm-url]: https://npmjs.org/package/antd
[github-action-image]: https://github.com/ant-design/ant-design/workflows/%E2%9C%85%20test/badge.svg
[github-action-url]: https://github.com/ant-design/ant-design/actions?query=workflow%3A%22%E2%9C%85+test%22
[github-action-image]: https://github.com/ant-design/ant-design/actions/workflows/test.yml/badge.svg
[github-action-url]: https://github.com/ant-design/ant-design/actions/workflows/test.yml
[codecov-image]: https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square
[codecov-url]: https://codecov.io/gh/ant-design/ant-design/branch/master
[download-image]: https://img.shields.io/npm/dm/antd.svg?style=flat-square
@ -83,6 +83,10 @@ yarn add antd
pnpm add antd
```
```bash
bun add antd
```
## 🔨 示例
```tsx
@ -105,7 +109,7 @@ export default App;
### 🛡 TypeScript
`antd` 使用 TypeScript 编写,具有完整的类型定义,参考 [create-react-app 中使用](https://ant.design/docs/react/use-with-create-react-app-cn)。
`antd` 使用 TypeScript 编写,具有完整的类型定义,参考 [Next.js 中使用](https://ant.design/docs/react/use-with-next-cn)。
## 🌍 国际化

View File

@ -20,8 +20,8 @@ An enterprise-class UI design language and React UI library.
[npm-image]: https://img.shields.io/npm/v/antd.svg?style=flat-square
[npm-url]: https://npmjs.org/package/antd
[github-action-image]: https://github.com/ant-design/ant-design/workflows/%E2%9C%85%20test/badge.svg
[github-action-url]: https://github.com/ant-design/ant-design/actions?query=workflow%3A%22%E2%9C%85+test%22
[github-action-image]: https://github.com/ant-design/ant-design/actions/workflows/test.yml/badge.svg
[github-action-url]: https://github.com/ant-design/ant-design/actions/workflows/test.yml
[codecov-image]: https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square
[codecov-url]: https://codecov.io/gh/ant-design/ant-design/branch/master
[download-image]: https://img.shields.io/npm/dm/antd.svg?style=flat-square
@ -81,6 +81,10 @@ yarn add antd
pnpm add antd
```
```bash
bun add antd
```
## 🔨 Usage
```tsx

View File

@ -74,6 +74,7 @@ exports[`antd exports modules correctly 1`] = `
"message",
"notification",
"theme",
"unstableSetRender",
"version",
]
`;

View File

@ -0,0 +1,44 @@
import * as ReactDOM from 'react-dom';
import { Modal, unstableSetRender } from 'antd';
import { waitFakeTimer19 } from '../../tests/utils';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('unstable', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('unstableSetRender', async () => {
if (ReactDOM.version.startsWith('19')) {
unstableSetRender((node, container) => {
const root = (ReactDOM as any).createRoot(container);
root.render(node);
return async () => {
root.unmount();
};
});
Modal.info({ content: 'unstableSetRender' });
await waitFakeTimer19();
expect(document.querySelector('.ant-modal')).toBeTruthy();
}
});
});

Some files were not shown because too many files have changed in this diff Show More