Merge branch 'feature' into feat/collapse-icon-vertical-position

This commit is contained in:
afc163 2025-05-30 16:24:11 +08:00 committed by GitHub
commit ffeeb83972
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
911 changed files with 34738 additions and 10813 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 更新
- 📦 包体积优化
- ⚡️ 性能优化
- 🌐 国际化改进
- 提供改动背景和解决方案
- 更新日志同时提供英文和中文版本

94
.cursor/rules/locale.mdc Normal file
View File

@ -0,0 +1,94 @@
---
description: 本地化规范文档
globs: ["components/locale/*_*.ts", "components/locale/index.tsx"]
alwaysApply: false
---
# 本地化规范
antd 中所有的本地化配置都应该在 `components/locale` 目录中完成定义,主要分为两步:类型定义和本地化配置
## 类型定义
antd 的本地化配置的类型定义的入口文件是 `components/locale/index.tsx`, 当需要添加新的本地化配置时,需要检查对应组件或全局配置的类型是否存在,如果不存在,则需要增加相应的类型描述。
如果新增或修改的本地化配置时组件配置,那么具体的本地化类型应该在相应的组件目录定义,定义好后在 `components/locale/index.tsx` 引入对应组件的类型定义。
## 本地化配置
### 纯字符串配置
antd 中的本地化配置文件命名规则是:`*_*.ts`,如:`zh_CN.ts`,文件默认导出一个 `Locale` 类型对象。
通常在为 antd 添加后修改某一项本地化配置时,如无特殊说明,需要同时修改所有语言的本地化配置。
本地化配置文件列表如下(包括但不限于):
```json
["components/locale/ar_EG.ts","components/locale/az_AZ.ts","components/locale/bg_BG.ts","components/locale/bn_BD.ts","components/locale/by_BY.ts","components/locale/ca_ES.ts","components/locale/cs_CZ.ts","components/locale/da_DK.ts","components/locale/de_DE.ts","components/locale/el_GR.ts","components/locale/en_GB.ts","components/locale/en_US.ts","components/locale/es_ES.ts","components/locale/et_EE.ts","components/locale/eu_ES.ts","components/locale/fa_IR.ts","components/locale/fi_FI.ts","components/locale/fr_BE.ts","components/locale/fr_CA.ts","components/locale/fr_FR.ts","components/locale/ga_IE.ts","components/locale/gl_ES.ts","components/locale/he_IL.ts","components/locale/hi_IN.ts","components/locale/hr_HR.ts","components/locale/hu_HU.ts","components/locale/hy_AM.ts","components/locale/id_ID.ts","components/locale/is_IS.ts","components/locale/it_IT.ts","components/locale/ja_JP.ts","components/locale/ka_GE.ts","components/locale/kk_KZ.ts","components/locale/km_KH.ts","components/locale/kmr_IQ.ts","components/locale/kn_IN.ts","components/locale/ko_KR.ts","components/locale/ku_IQ.ts","components/locale/lt_LT.ts","components/locale/lv_LV.ts","components/locale/mk_MK.ts","components/locale/ml_IN.ts","components/locale/mn_MN.ts","components/locale/ms_MY.ts","components/locale/my_MM.ts","components/locale/nb_NO.ts","components/locale/ne_NP.ts","components/locale/nl_BE.ts","components/locale/nl_NL.ts","components/locale/pl_PL.ts","components/locale/pt_BR.ts","components/locale/pt_PT.ts","components/locale/ro_RO.ts","components/locale/ru_RU.ts","components/locale/si_LK.ts","components/locale/sk_SK.ts","components/locale/sl_SI.ts","components/locale/sr_RS.ts","components/locale/sv_SE.ts","components/locale/ta_IN.ts","components/locale/th_TH.ts","components/locale/tk_TK.ts","components/locale/tr_TR.ts","components/locale/uk_UA.ts","components/locale/ur_PK.ts","components/locale/uz_UZ.ts","components/locale/vi_VN.ts","components/locale/zh_CN.ts","components/locale/zh_HK.ts","components/locale/zh_TW.ts"]
```
本地化配置的内容通常是纯字符串,如:
```typescript
{
// ...
Modal: {
okText: '确定',
cancelText: '取消',
justOkText: '知道了',
}
// ...
}
```
### 字符串模版配置
当然,也有一些需要配置需要再运行时实时替换变量的模版配置,带有 `${}` 的变量将在实际使用的地方被实时替换成对应的变量内容:
```typescript
{
// ...
date: {
format: '${label}日期格式无效',
parse: '${label}不能转换为日期',
invalid: '${label}是一个无效日期',
}
// ...
}
```
### 全局配置
如果某个本地化配置不独属于某个组件,而是数据全局的本地化配置,此时应该在 `global` 中添加相关属性,如:
```typescript
{
// ...
// locales for all components
global: {
placeholder: '请选择',
},
// ...
}
```
# 使用本地化
antd 中具体使用本地化配置时,可以使用 `components/locale/index.tsx` 文件中导出的 `useLocale` 获取全局上下文中配置的本地化,并跟组件属性中传入的本地化配置合并后得到最完整的本地化配置,如:
```tsx
import { useLocale } from "../locale";
import enUS from '../locale/en_US';
export function TestComp(props) {
const { locale: propLocale } = props;
const [contextLocale] = useLocale("TestComp", enUs);
const locale = {...contextLocale, ...propLocale};
return (
<div title={locale?.title}>
{locale?.text}
</div>
);
}
```

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. `destroyOnHidden`)
* 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,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 = [
{
@ -15,6 +15,7 @@ const SECONDARY_LIST = [
key: 'mobile',
url: 'https://mobile.ant.design/',
imgScale: 1.5,
scaleOrigin: '15px',
},
{
img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
@ -63,14 +64,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 +86,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 = [
@ -151,7 +152,7 @@ const DesignFramework: React.FC = () => {
);
})}
{SECONDARY_LIST.map(({ img, key, url, imgScale = 1 }, index) => {
{SECONDARY_LIST.map(({ img, key, url, imgScale = 1, scaleOrigin }, index) => {
const title = locale[key as keyof typeof locale];
const desc = locale[`${key}Desc` as keyof typeof locale];
@ -162,7 +163,7 @@ const DesignFramework: React.FC = () => {
draggable={false}
alt={title}
src={img}
style={{ transform: `scale(${imgScale})` }}
style={{ transform: `scale(${imgScale})`, transformOrigin: scaleOrigin }}
/>
<Typography.Title

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,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 { FastColor } from '@ant-design/fast-color';
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';
@ -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';

89
.dumi/rehypeChangelog.ts Normal file
View File

@ -0,0 +1,89 @@
import type { UnifiedTransformer } from 'dumi';
import { unistUtilVisit } from 'dumi';
import set from 'lodash/set';
let hastToString: typeof import('hast-util-to-string').toString;
// workaround to import pure esm module
(async () => {
({ toString: hastToString } = await import('hast-util-to-string'));
})();
const COMPONENT_NAME = 'RefinedChangelog';
function rehypeChangelog(): UnifiedTransformer<any> {
return (tree, vFile) => {
const { filename } = vFile.data.frontmatter as any;
// 只处理 changelog 文件
if (!/^changelog\.\S+\.md$/i.test(filename)) return;
const nodesToWrap: { parent: any; startIdx: number }[] = [];
const WRAPPER_FLAG = 'data-changelog-wrapped'; // 包裹容器唯一标识
unistUtilVisit.visit(tree, 'element', (node, idx, parent) => {
if (node.properties?.[WRAPPER_FLAG]) return unistUtilVisit.SKIP;
if (
idx !== undefined &&
parent &&
idx! + 2 < parent.children.length &&
node.tagName === 'h2' &&
parent.children[idx! + 1].tagName === 'p' &&
parent.children[idx! + 2].tagName === 'ul'
) {
nodesToWrap.push({ parent, startIdx: idx! });
}
});
nodesToWrap.reverse().forEach(({ parent, startIdx }) => {
const [heading, date, list] = parent.children.splice(startIdx, 3);
const version = hastToString(heading);
const dateStr = hastToString(date);
const headingWrap = {
type: 'element',
tagName: `${COMPONENT_NAME}.Version`,
// 为标签添加语义化 className (下面同理)
children: [set(heading, 'properties.className', 'changelog-version')],
};
const dateWrap = {
type: 'element',
tagName: `${COMPONENT_NAME}.Date`,
children: [set(date, 'properties.className', 'changelog-date')],
};
const listWrap = {
type: 'element',
tagName: `${COMPONENT_NAME}.Details`,
children: [set(list, 'properties.className', 'changelog-details')],
};
const wrapper = {
type: 'element',
tagName: COMPONENT_NAME,
properties: {
[WRAPPER_FLAG]: true,
},
JSXAttributes: [
{
type: 'JSXAttribute',
name: 'version',
value: JSON.stringify(version),
},
{
type: 'JSXAttribute',
name: 'date',
value: JSON.stringify(dateStr),
},
],
children: [headingWrap, dateWrap, listWrap],
};
parent.children.splice(startIdx, 0, wrapper);
});
};
}
export default rehypeChangelog;

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 { 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 }) => ({
@ -23,23 +24,46 @@ const useStyle = createStyles(({ token, css }) => ({
interface ColorChunkProps {
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 FastColor(value).toHexString();
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
@ -187,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}`}
@ -196,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} />
@ -209,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}
destroyOnHidden
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,9 +1,8 @@
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, DumiDemo } 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';
@ -22,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);
@ -42,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,
@ -58,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 {
@ -117,8 +115,8 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
<DumiDemoGrid
items={demos}
demoRender={(item) => (
<Suspense fallback={<DemoFallback />}>
<DumiDemo key={item.demo.id} {...item} />
<Suspense key={item.demo.id} fallback={<DemoFallback />}>
<DumiDemo {...item} />
</Suspense>
)}
/>

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

@ -1,41 +1,8 @@
import React, { Suspense, useEffect, useState } from 'react';
import { Tooltip } from 'antd';
import React, { useState } from 'react';
import { Tooltip, App } from 'antd';
import { LoadingOutlined } from '@ant-design/icons';
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>;
@ -43,7 +10,8 @@ interface CodeBlockButtonProps {
}
const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies = {}, jsx }) => {
const showCodeBlockButton = useShowCodeBlockButton();
const { message } = App.useApp();
const [loading, setLoading] = useState(false);
const codeBlockPrefillConfig = {
title: `${title} - antd@${dependencies.antd}`,
@ -57,24 +25,56 @@ const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies =
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
};
return showCodeBlockButton ? (
const openHituCodeBlockFn = () => {
setLoading(false);
// @ts-ignore
if (window.openHituCodeBlock) {
// @ts-ignore
window.openHituCodeBlock(JSON.stringify(codeBlockPrefillConfig));
} else {
message.error('此功能仅在内网环境可用');
}
};
const handleClick = () => {
const scriptId = 'hitu-code-block-js';
const existScript = document.getElementById(scriptId) as HTMLScriptElement | null;
// @ts-ignore
if (existScript?.dataset.loaded) {
openHituCodeBlockFn();
return;
}
setLoading(true);
const script = document.createElement('script');
script.src = `https://renderoffice.alipayobjects.com/p/yuyan/180020010001206410/parseFileData-v1.0.1.js?t=${Date.now()}`;
script.async = true;
script.id = scriptId;
script.onload = () => {
script.dataset.loaded = 'true';
openHituCodeBlockFn();
};
script.onerror = () => {
openHituCodeBlockFn();
};
document.body.appendChild(script);
};
return (
<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));
}}
/>
{loading ? (
<LoadingOutlined className="code-box-codeblock" />
) : (
<img
alt="codeblock"
src="https://mdn.alipayobjects.com/huamei_wtld8u/afts/img/A*K8rjSJpTNQ8AAAAAAAAAAAAADhOIAQ/original"
className="code-box-codeblock"
onClick={handleClick}
/>
)}
</div>
</Tooltip>
) : null;
);
};
export default (props: CodeBlockButtonProps) => (
<Suspense>
<CodeBlockButton {...props} />
</Suspense>
);
export default CodeBlockButton;

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';
@ -17,7 +18,6 @@ import CodePenIcon from '../../icons/CodePenIcon';
import CodeSandboxIcon from '../../icons/CodeSandboxIcon';
import ExternalLinkIcon from '../../icons/ExternalLinkIcon';
import DemoContext from '../../slots/DemoContext';
import type { SiteContextProps } from '../../slots/SiteContext';
import SiteContext from '../../slots/SiteContext';
import CodeBlockButton from './CodeBlockButton';
import type { AntdPreviewerProps } from './Previewer';
@ -86,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();
@ -110,7 +110,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
const codeSandboxIconRef = 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}`;
@ -140,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"
@ -320,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,
},
@ -445,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>
@ -515,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

@ -1,91 +0,0 @@
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

@ -0,0 +1,131 @@
import * as React from 'react';
import { BugOutlined } from '@ant-design/icons';
import { Button, Flex, Popover, theme } from 'antd';
import { createStyles } from 'antd-style';
import dayjs, { Dayjs } from 'dayjs';
import useLocale from '../../../hooks/useLocale';
import { matchDeprecated } from '../../utils';
interface RefinedChangelogProps {
version?: string;
date?: string;
}
interface ContextProps {
version: string;
date?: Dayjs;
isDeprecated?: boolean;
reason?: string[];
}
const ChangelogContext = React.createContext<ContextProps>({
version: '0.0.0',
});
const locales = {
cn: {
deprecatedTitle: '🚨 该版本存在缺陷, 请升级至下一个新版本',
},
en: {
deprecatedTitle: '🚨 This version has defects, please upgrade to the next version',
},
};
const useStyle = createStyles(({ token, css }) => ({
container: css`
margin-block: ${token.margin}px;
padding: ${token.padding}px;
.changelog-version {
line-height: ${token.lineHeight} !important;
margin: 0 !important;
}
`,
isDeprecated: css``,
}));
function RefinedChangelog(props: React.PropsWithChildren<RefinedChangelogProps>) {
const { version, date, children } = props;
const { styles, cx } = useStyle();
const memoizedValue = React.useMemo(() => {
const realVersion = version || '0.0.0';
const bugVersionInfo = matchDeprecated(realVersion);
return {
version: realVersion,
isDeprecated: !!bugVersionInfo?.match,
reason: bugVersionInfo?.reason,
date: date ? dayjs(date) : undefined,
};
}, [version, date]);
return (
<ChangelogContext.Provider value={memoizedValue}>
<div
className={cx('refined-changelog', styles.container, {
[styles.isDeprecated]: memoizedValue.isDeprecated,
})}
>
{children}
</div>
</ChangelogContext.Provider>
);
}
function Version({ children }: React.PropsWithChildren) {
const { isDeprecated, reason } = React.use(ChangelogContext);
const { token } = theme.useToken();
const [locale] = useLocale(locales);
if (!isDeprecated) {
return children;
}
const reasonContent = (
<Flex vertical align="start">
{reason?.map((item, index) => (
<Button
key={index}
type="link"
target="_blank"
rel="noreferrer"
href={item}
icon={<BugOutlined />}
>
{item}
</Button>
))}
</Flex>
);
return (
<Flex align="center" gap="small">
{children}
<Popover placement="right" title={locale.deprecatedTitle} content={reasonContent}>
<BugOutlined
style={{
lineHeight: token.lineHeight,
fontSize: token.fontSize,
color: token.colorErrorText,
}}
/>
</Popover>
</Flex>
);
}
function DateComp(props: React.PropsWithChildren) {
return props.children;
}
function Details(props: React.PropsWithChildren) {
return props.children;
}
export default Object.assign(RefinedChangelog, {
Version,
Date: DateComp,
Details,
});

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;
},

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

@ -3,19 +3,13 @@ import { BugOutlined } from '@ant-design/icons';
import { Button, Drawer, Flex, Grid, Popover, Tag, Timeline, Typography } from 'antd';
import type { TimelineItemProps } from 'antd';
import { createStyles } from 'antd-style';
import semver from 'semver';
import deprecatedVersions from '../../../../BUG_VERSIONS.json';
import useFetch from '../../../hooks/useFetch';
import useLocale from '../../../hooks/useLocale';
import useLocation from '../../../hooks/useLocation';
import { matchDeprecated } from '../../utils';
import Link from '../Link';
interface MatchDeprecatedResult {
match?: string;
reason: string[];
}
interface ChangelogInfo {
version: string;
changelog: string;
@ -24,17 +18,6 @@ interface ChangelogInfo {
releaseDate: string;
}
function matchDeprecated(v: string): MatchDeprecatedResult {
const match = Object.keys(deprecatedVersions).find((depreciated) =>
semver.satisfies(v, depreciated),
);
const reason = deprecatedVersions[match as keyof typeof deprecatedVersions] || [];
return {
match,
reason: Array.isArray(reason) ? reason : [reason],
};
}
const useStyle = createStyles(({ token, css }) => ({
listWrap: css`
> li {
@ -137,9 +120,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);
@ -277,7 +260,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
{version}
{bugVersionInfo.match && (
<Popover
destroyTooltipOnHide
destroyOnHidden
placement="right"
title={<span className={styles.bugReasonTitle}>{locale.bugList}</span>}
content={
@ -327,7 +310,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
onClick: () => setShow(true),
})}
<Drawer
destroyOnClose
destroyOnHidden
className={styles.drawerContent}
title={locale.changelog}
extra={

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);

View File

@ -0,0 +1,87 @@
import React from 'react';
import useLocale from '../../hooks/useLocale';
import SemanticPreview from './SemanticPreview';
export const locales = {
cn: {
root: '根元素',
'popup.root': '弹出菜单元素',
},
en: {
root: 'Root element',
'popup.root': 'Popup element',
},
};
interface BlockProps {
component: React.ComponentType<any>;
options?: { value: string; label: string }[];
defaultValue?: string;
style?: React.CSSProperties;
[key: string]: any;
}
const Block: React.FC<BlockProps> = ({ component: Component, options, defaultValue, ...props }) => {
const divRef = React.useRef<HTMLDivElement>(null);
return (
<div ref={divRef} style={{ position: 'absolute', marginBottom: 80 }}>
<Component
{...props}
open
placement="bottomLeft"
defaultValue={defaultValue}
getPopupContainer={() => divRef.current}
options={options}
styles={{
popup: { zIndex: 1 },
}}
/>
</div>
);
};
export interface SelectSemanticTemplateProps {
component: React.ComponentType<any>;
componentName: string;
defaultValue?: string;
options?: { value: string; label: string }[];
height?: number;
onSearch?: (text: string) => void;
placeholder?: string;
style?: React.CSSProperties;
[key: string]: any;
}
const SelectSemanticTemplate: React.FC<SelectSemanticTemplateProps> = ({
component,
defaultValue,
options,
height,
style,
componentName,
...restProps
}) => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
componentName={componentName}
semantics={[
{ name: 'root', desc: locale.root, version: '5.25.0' },
{ name: 'popup.root', desc: locale['popup.root'], version: '5.25.0' },
]}
height={height}
>
<Block
component={component}
defaultValue={defaultValue}
options={options}
style={style}
{...restProps}
/>
</SemanticPreview>
);
};
export default SelectSemanticTemplate;

View File

@ -1,7 +1,12 @@
/* 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 { InfoCircleOutlined } from '@ant-design/icons';
import get from 'rc-util/lib/utils/get';
import set from 'rc-util/lib/utils/set';
import { Col, ConfigProvider, Flex, Popover, Row, Tag, theme, Typography } from 'antd';
import { createStyles, css } from 'antd-style';
import classnames from 'classnames';
import Prism from 'prismjs';
const MARK_BORDER_SIZE = 2;
@ -17,6 +22,9 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
padding: ${token.paddingMD}px;
overflow: hidden;
`,
colWrapPaddingLess: css`
padding: 0;
`,
listWrap: css`
display: flex;
flex-direction: column;
@ -64,36 +72,77 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
`,
}));
function getSemanticCells(semanticPath: string) {
return semanticPath.split('.');
}
function HighlightExample(props: { componentName: string; semanticName: string }) {
const { componentName, semanticName } = props;
const highlightCode = React.useMemo(() => {
const classNames = set({}, getSemanticCells(semanticName), `my-classname`);
const styles = set({}, getSemanticCells(semanticName), { color: 'red' });
function format(obj: object) {
const str = JSON.stringify(obj, null, 2);
return (
str
// Add space
.split('\n')
.map((line) => ` ${line}`)
.join('\n')
.trim()
// Replace quotes
.replace(/"/g, "'")
// Remove key quotes
.replace(/'([^']+)':/g, '$1:')
);
}
const code = `
<${componentName}
classNames={${format(classNames)}}
styles={${format(styles)}}
/>`.trim();
return Prism.highlight(code, Prism.languages.javascript, 'jsx');
}, [componentName, semanticName]);
return (
// biome-ignore lint: lint/security/noDangerouslySetInnerHtml
<div dangerouslySetInnerHTML={{ __html: highlightCode }} />
);
}
export interface SemanticPreviewProps {
componentName: string;
semantics: { name: string; desc: string; version?: string }[];
children: React.ReactElement<any>;
height?: number;
padding?: false;
}
const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
const { semantics = [], children, height } = props;
const { semantics = [], children, height, padding, componentName = 'Component' } = props;
const { token } = theme.useToken();
// ======================= Semantic =======================
const getMarkClassName = React.useCallback(
(semanticKey: string) => `semantic-mark-${semanticKey}`,
(semanticKey: string) => `semantic-mark-${semanticKey}`.replace(/\./g, '-'),
[],
);
const semanticClassNames = React.useMemo<Record<string, string>>(() => {
const classNames: Record<string, string> = {};
let classNames: Record<string, string> = {};
semantics.forEach((semantic) => {
classNames[semantic.name] = getMarkClassName(semantic.name);
const pathCell = getSemanticCells(semantic.name);
classNames = set(classNames, pathCell, getMarkClassName(semantic.name));
});
return classNames;
}, [semantics]);
const cloneNode = React.cloneElement(children, {
classNames: semanticClassNames,
});
// ======================== Hover =========================
const containerRef = React.useRef<HTMLDivElement>(null);
@ -134,11 +183,33 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
};
}, [hoverSemantic]);
const hoveredSemanticClassNames = React.useMemo(() => {
if (!hoverSemantic) {
return semanticClassNames;
}
const hoverCell = getSemanticCells(hoverSemantic);
const clone = set(
semanticClassNames,
hoverCell,
classnames(get(semanticClassNames, hoverCell), getMarkClassName('active')),
);
return clone;
}, [semanticClassNames, hoverSemantic]);
// ======================== Render ========================
const cloneNode = React.cloneElement(children, {
classNames: hoveredSemanticClassNames,
});
return (
<div className={classnames(styles.container)} ref={containerRef}>
<Row style={{ minHeight: height }}>
<Col span={16} className={classnames(styles.colWrap)}>
<Col
span={16}
className={classnames(styles.colWrap, padding === false && styles.colWrapPaddingLess)}
>
<ConfigProvider theme={{ token: { motion: false } }}>{cloneNode}</ConfigProvider>
</Col>
<Col span={8}>
@ -151,11 +222,31 @@ 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">
<HighlightExample
componentName={componentName}
semanticName={semantic.name}
/>
</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,11 +1,13 @@
import React from 'react';
import { BgColorsOutlined, SmileOutlined } from '@ant-design/icons';
import { FloatButton } from 'antd';
import React, { use, useRef } 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,122 @@ 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();
const lastThemeKey = useRef<string>(theme.includes('dark') ? 'dark' : 'light');
const badge = <Badge color="blue" style={{ marginTop: -1 }} />;
// 主题选项配置
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' || key === lastThemeKey.current) {
return;
}
lastThemeKey.current = key;
// 亮色/暗色模式切换时应用动画效果
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 (
<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);
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>
<Dropdown menu={{ items, onClick }} arrow={{ pointAtCenter: true }} placement="bottomRight">
<Button type="text" icon={<ThemeIcon />} style={{ fontSize: 16 }} />
</Dropdown>
);
};

View File

@ -325,7 +325,6 @@ const GlobalDemoStyles: React.FC = () => {
&-debug {
border-color: ${token.purple3};
display: none;
}
&-debug &-title a {
@ -335,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

@ -44,13 +44,24 @@ export default () => {
html {
direction: initial;
@supports (overflow-x: clip) {
overflow-x: clip;
}
&.rtl {
direction: rtl;
}
}
body {
overflow-x: hidden;
@supports (overflow-x: clip) {
overflow-x: clip;
}
@supports not (overflow-x: clip) {
overflow-x: hidden;
}
color: ${token.colorText};
font-size: ${token.fontSize}px;
font-family: ${token.fontFamily};

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';
@ -38,7 +38,7 @@ const DocLayout: React.FC = () => {
const { pathname, search, hash } = location;
const [locale, lang] = useLocale(locales);
const timerRef = useRef<ReturnType<typeof setTimeout>>(null!);
const { direction } = useContext(SiteContext);
const { direction } = React.use(SiteContext);
const { loading } = useSiteData();
useLayoutEffect(() => {

View File

@ -1,5 +1,7 @@
import React, { Suspense, useCallback, useEffect } from 'react';
import { Monitoring } from 'react-scan/monitoring';
// prettier-ignore
import { scan } from 'react-scan'; // import this BEFORE react
import React, { useCallback, useEffect } from 'react';
import {
createCache,
extractStyle,
@ -13,17 +15,10 @@ import { getSandpackCssText } from '@codesandbox/sandpack-react';
import { theme as antdTheme, App } from 'antd';
import type { MappingAlgorithm } from 'antd';
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
import {
createSearchParams,
useOutlet,
useParams,
useSearchParams,
useServerInsertedHTML,
} from 'dumi';
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
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';
@ -31,8 +26,6 @@ import SiteContext from '../slots/SiteContext';
import '@ant-design/v5-patch-for-react-19';
const ThemeSwitch = React.lazy(() => import('../common/ThemeSwitch'));
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
@ -52,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[] = []) =>
@ -69,8 +69,6 @@ const getAlgorithm = (themes: ThemeName[] = []) =>
const GlobalLayout: React.FC = () => {
const outlet = useOutlet();
const { pathname } = useLocation();
const params = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const [{ theme = [], direction, isMobile, bannerVisible = false }, setSiteState] =
useLayoutState<SiteState>({
@ -205,49 +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}
<Monitoring
apiKey="GhrCCNrHZHXlf4P6E03ntrFwhRLxJL30" // Safe to expose publically
url="https://monitoring.react-scan.com/api/v1/ingest"
commit={process.env.COMMIT_HASH}
branch={process.env.BRANCH}
params={params as Record<string, string>}
path={pathname}
/>
<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",

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": "更多",

View File

@ -4,7 +4,7 @@ import path from 'path';
import createEmotionServer from '@emotion/server/create-instance';
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';
@ -41,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') {
@ -53,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

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, FloatButton, 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,14 @@ 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>
<div style={{ minHeight: 'calc(100vh - 64px)' }}>
{children}
<FloatButton.BackTop />
</div>
<InViewSuspense fallback={null}>
<ColumnCard
zhihuLink={meta.frontmatter.zhihu_url}
yuqueLink={meta.frontmatter.yuque_url}
@ -95,7 +104,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 +114,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 { FastColor } from '@ant-design/fast-color';
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 FastColor(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,10 +302,11 @@ const Header: React.FC = () => {
<Select
key="version"
size="small"
variant="filled"
className={styles.versionSelect}
defaultValue={pkg.version}
onChange={handleVersionChange}
dropdownStyle={getDropdownStyle}
styles={{ popup: { root: getDropdownStyle } }}
popupMatchSelectWidth={false}
getPopupContainer={(trigger) => trigger.parentNode}
options={versionOptions}
@ -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" destroyOnHidden>
<Button type="text" icon={<GithubOutlined />} style={{ fontSize: 16 }} />
</Tooltip>
</a>,
];

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

@ -1,7 +1,8 @@
import semver from 'semver';
import flatten from 'lodash/flatten';
import flattenDeep from 'lodash/flattenDeep';
import themeConfig from './themeConfig';
import deprecatedVersions from '../../../BUG_VERSIONS.json';
import themeConfig from '../themeConfig';
interface Meta {
skip?: boolean;
@ -24,6 +25,11 @@ interface Orders {
[key: string]: number;
}
interface MatchDeprecatedResult {
match?: string;
reason: string[];
}
export function getMenuItems(
moduleData: ModuleDataItem[],
locale: string,
@ -151,28 +157,6 @@ export function getLocalizedPathname(
return { pathname: fullPath, search };
}
export function ping(callback: (status: string) => void) {
const url =
'https://private-a' +
'lipay' +
'objects.alip' +
'ay.com/alip' +
'ay-rmsdeploy-image/rmsportal/RKuAiriJqrUhyqW.png';
const img = new Image();
let done: boolean;
const finish = (status: string) => {
if (!done) {
done = true;
img.src = '';
callback(status);
}
};
img.onload = () => finish('responded');
img.onerror = () => finish('error');
img.src = url;
return setTimeout(() => finish('timeout'), 1500);
}
export function isLocalStorageNameSupported() {
const testKey = 'test';
const storage = window.localStorage;
@ -223,4 +207,15 @@ export function getMetaDescription(jml?: any[] | null) {
return paragraph;
}
export function matchDeprecated(v: string): MatchDeprecatedResult {
const match = Object.keys(deprecatedVersions).find((depreciated) =>
semver.satisfies(v, depreciated),
);
const reason = deprecatedVersions[match as keyof typeof deprecatedVersions] || [];
return {
match,
reason: Array.isArray(reason) ? reason : [reason],
};
}
export const getThemeConfig = () => themeConfig;

View File

@ -0,0 +1,52 @@
import * as ts from 'typescript';
import { format } from '@prettier/sync';
/**
* TypeScript TSX JavaScript JSX
*
* 使 TypeScript API JSX JavaScript Prettier
*
* @param tsCode - TypeScript
* @returns JavaScript
*
* @remark Prettier
*/
export default function tsToJs(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;
}
}

View File

@ -4,7 +4,9 @@ import * as fs from 'fs-extra';
import os from 'node:os';
import rehypeAntd from './.dumi/rehypeAntd';
import rehypeChangelog from './.dumi/rehypeChangelog';
import remarkAntd from './.dumi/remarkAntd';
import remarkAnchor from './.dumi/remarkAnchor';
import { version } from './package.json';
export default defineConfig({
@ -51,8 +53,8 @@ export default defineConfig({
// https://github.com/ant-design/ant-design/issues/46628
'@ant-design/icons$': '@ant-design/icons/lib',
},
extraRehypePlugins: [rehypeAntd],
extraRemarkPlugins: [remarkAntd],
extraRehypePlugins: [rehypeAntd, rehypeChangelog],
extraRemarkPlugins: [remarkAntd, remarkAnchor],
metas: [
{ name: 'theme-color', content: '#1677ff' },
{ name: 'build-time', content: Date.now().toString() },

View File

@ -1,8 +1,8 @@
<!--
First of all, thank you for your contribution! 😄
For requesting to pull a new feature or bugfix, please send it from a feature/bugfix branch based on the `master` branch.
Before submitting your pull request, please make sure the checklist below is confirmed.
Your pull requests will be merged after one of the collaborators approve.
Before submitting your pull request, please make sure the checklist below is filled out.
Your pull requests will be merged after one of the collaborators approves.
Thank you!
-->
@ -25,6 +25,7 @@ Thank you!
- [ ] ✅ Test Case
- [ ] 🔀 Branch merge
- [ ] ⏩ Workflow
- [ ] ⌨️ Accessibility improvement
- [ ] ❓ Other (about what?)
### 🔗 Related Issues
@ -40,7 +41,7 @@ Thank you!
### 📝 Change Log
> - Read [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) like a cat tracks a laser pointer.
> - Read [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)! Track your changes, like a cat tracks a laser pointer.
> - Describe the impact of the changes on developers, not the solution approach.
> - Reference: https://ant.design/changelog

View File

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

View File

@ -9,26 +9,22 @@ updates:
directory: /
schedule:
interval: daily
time: "05:00"
timezone: Asia/Shanghai
groups:
rc-component-patch:
dependency-type: production
patterns:
- "rc-*"
- "@rc-component*"
update-types: [patch]
dependencies:
dependency-type: production
exclude-patterns:
- "rc-*"
- "@rc-component*"
update-types: [major, minor]
dev-dependencies:
dependency-type: development
update-types: [major]
ignore:
- dependency-name: "@ant-design/cssinjs"
- dependency-name: "rc-*"
- dependency-name: "@rc-component*"
- dependency-name: "@ant-design*"
- dependency-name: dayjs
versions: [1.x]
- package-ecosystem: github-actions
directory: /
schedule:

View File

@ -18,69 +18,74 @@ jobs:
const daysBeforeReminder = 14;
let page = 1;
const perPage = 100;
const reminderSignature = "This issue has been inactive for more than 14 days";
while (true) {
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({
try {
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'open',
assignee: '*',
per_page: perPage,
page: page,
});
const hasLinkedPR = timeline.some(event =>
event.event === 'connected' || // PR connected via keywords
event.event === 'referenced' && event.commit_id // Connected via commit
);
if (issues.length === 0) {
break;
}
if (daysInactive >= daysBeforeReminder && !hasLinkedPR) {
// get issue comments
const { data: comments } = await github.rest.issues.listComments({
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,
});
// check if reminder has been sent
const hasReminder = comments.some(comment =>
comment.body.includes(reminderSignature)
const hasLinkedPR = timeline.some(event =>
event.event === 'connected' || // PR connected via keywords
event.event === 'referenced' && event.commit_id // Connected via commit
);
if (!hasReminder) {
const assigneesMentions = issue.assignees
.map(user => `@${user.login}`)
.join(' ');
await github.rest.issues.createComment({
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,
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.`,
});
// 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;
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

@ -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@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
fail_on_unmatched_files: true
files: website.tar.gz

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

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

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

4
.gitignore vendored
View File

@ -36,8 +36,9 @@ config/base.yaml
yarn.lock
package-lock.json
pnpm-lock.yaml
bun.lockb
bun.lock*
.pnpm-debug.log
.pnpm-store
components/**/*.js
components/**/*.jsx
!components/**/__tests__/**/*.js
@ -68,7 +69,6 @@ __image_snapshots__/
/imageDiffSnapshots
/visualRegressionReport*
.devcontainer*
.husky/prepare-commit-msg
.eslintcache

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

@ -75,5 +75,7 @@
"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"]
"5.22.6": ["https://github.com/ant-design/ant-design/issues/52124"],
"5.24.8": ["https://github.com/ant-design/ant-design/issues/53652"],
"5.25.0": ["https://github.com/ant-design/ant-design/issues/53764"]
}

View File

@ -9,12 +9,272 @@ tag: vVERSION
#### Release Schedule
- Weekly release: patch version at the end of every week for routine bugfix (anytime for urgent bugfix).
- Weekly release: patch version at the end of every week for routine bugfixes (anytime for an urgent bugfix).
- Monthly release: minor version at the end of every month for new features.
- Major version release is not included in this schedule for breaking change and new features.
- Major version release is not included in this schedule for breaking changes and new features.
---
## 5.25.3
`2025-05-26`
- 🐞 Fix Typography.Text `delete` property not updating. [#53861](https://github.com/ant-design/ant-design/pull/53861) [@codingories](https://github.com/codingories)
- 🐞 Fix the Statistic.Timer as a subcomponent of Tooltip could not display text prompts properly. [#53888](https://github.com/ant-design/ant-design/pull/53888) [@jin19980928](https://github.com/jin19980928)
- 🐞 Fix the `style` setting of the Upload component did not take effect in more types. [#53877](https://github.com/ant-design/ant-design/pull/53877) [@QuentinHsu](https://github.com/QuentinHsu)
- 💄 Fix the residual focus style after clicking Tabs. [#53901](https://github.com/ant-design/ant-design/pull/53901)
## 5.25.2
`2025-05-19`
- 🐞 Fix AutoComplete `onPaste` event callback not working problem on inside Input. [#53839](https://github.com/ant-design/ant-design/issues/53839) [@Wxh16144](https://github.com/Wxh16144)
- 🐞 Fix ColorPicker cannot input for hex value. [#53814](https://github.com/ant-design/ant-design/pull/53814) [@DDDDD12138](https://github.com/DDDDD12138)
- 🐞 Fix Statistic.Timer ssr hydrate issue. [#53817](https://github.com/ant-design/ant-design/pull/53817) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Table header blink issue when sticky is enable. [#53803](https://github.com/ant-design/ant-design/pull/53803) [@afc163](https://github.com/afc163)
- 💄 Fix Input.Search `variant="filled"` broken UI. [#53787](https://github.com/ant-design/ant-design/pull/53787) [@afc163](https://github.com/afc163)
- TypeScript
- 🤖 Fix Upload.Dragger does not accept generic parameter problem. [#53842](https://github.com/ant-design/ant-design/pull/53842) [@fnoopv](https://github.com/fnoopv)
- 🤖 Remove Modal invalid properties type definition. [#53808](https://github.com/ant-design/ant-design/pull/53808) [@wanpan11](https://github.com/wanpan11)
## 5.25.1
`2025-05-09`
- 🐞 Splitter fix screen frozen when drag finished. [#53767](https://github.com/ant-design/ant-design/pull/53767) [@wanpan11](https://github.com/wanpan11)
- 🌐 Image support Hebrew locale. [#53771](https://github.com/ant-design/ant-design/pull/53771) [@Sagie501](https://github.com/Sagie501)
## 5.25.0
`2025-05-07`
- 🔥 New component Statistic.Timer, supporting both counting up and down. [#53401](https://github.com/ant-design/ant-design/pull/53401) [@lcgash](https://github.com/lcgash)
- 🆕 Tour add `actionsRender` prop to custom action button. [#53067](https://github.com/ant-design/ant-design/pull/53067) [@dengfuping](https://github.com/dengfuping)
- 🆕 Add `size` prop to Divider. [#53570](https://github.com/ant-design/ant-design/pull/53570) [@coding-ice](https://github.com/coding-ice)
- Collapse
- 🆕 Collapse add `borderlessContentPadding` component token. [#52858](https://github.com/ant-design/ant-design/pull/52858) [@coding-ice](https://github.com/coding-ice)
- 🆕 Collapse add `borderlessContentBg` component token. [#50902](https://github.com/ant-design/ant-design/pull/50902) [@coding-ice](https://github.com/coding-ice)
- 🆕 Upload supports paste upload via the `pastable` property. [#53463](https://github.com/ant-design/ant-design/pull/53463) [@madocto](https://github.com/madocto)
- 🆕 AutoComplete component adds `popup` semantic node with support for customizing dropdown menu via `classNames.popup`, `styles.popup`, `popupRender` and `onOpenChange`, while deprecating legacy `popupClassName`, `dropdownClassName`, `dropdownStyle`, `dropdownRender` and `onDropdownVisibleChange` APIs. [#53257](https://github.com/ant-design/ant-design/pull/53257) [@aojunhao123](https://github.com/aojunhao123)
- 🆕 Cascader component adds `popup` semantic node, and deprecated some api. [#53311](https://github.com/ant-design/ant-design/pull/53311) [@aojunhao123](https://github.com/aojunhao123)
- 🆕 ConfigProvider support setting the `variant` and `color` props of Button. [#53165](https://github.com/ant-design/ant-design/pull/53165) [@yellowryan](https://github.com/yellowryan)
- 🆕 TreeSelect component adds `popup` semantic node, and deprecated some api. [#53285](https://github.com/ant-design/ant-design/pull/53285) [@aojunhao123](https://github.com/aojunhao123)
- 🆕 DatePicker and TimePicker add `popup` semantic node, and deprecated some api. [#53718](https://github.com/ant-design/ant-design/pull/53718) [@aojunhao123](https://github.com/aojunhao123)
- 🆕 Select component adds `popup` semantic node with support for customizing dropdown menu via `classNames.popup`, `styles.popup`, `popupRender` and `onOpenChange`, while deprecating legacy `popupClassName`, `dropdownClassName`, `dropdownStyle`, `dropdownRender` and `onDropdownVisibleChange` APIs. [#53243](https://github.com/ant-design/ant-design/pull/53243) [@aojunhao123](https://github.com/aojunhao123)
- 🆕 FloatButton supports `tooltip` props. [#53138](https://github.com/ant-design/ant-design/pull/53138) [@Wxh16144](https://github.com/Wxh16144)
- 🆕 Table `rowSelection` support `align` prop. [#53127](https://github.com/ant-design/ant-design/pull/53127) [@zombieJ](https://github.com/zombieJ)
- 🆕 `options` prop of Radio.Group and Checkbox.Group support `classNames`. [#52917](https://github.com/ant-design/ant-design/pull/52917) [@li-jia-nan](https://github.com/li-jia-nan)
- ⚡️ Optimize ColorPicker components to use derived state pattern instead of setState in useEffect. [#53701](https://github.com/ant-design/ant-design/pull/53701) [@DDDDD12138](https://github.com/DDDDD12138)
- 🐞 Fix Checkbox that render empty dom when `children` is `null`. [#53723](https://github.com/ant-design/ant-design/pull/53723) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 Fix Anchor that would refresh the page after clicking the anchor point. [#53687](https://github.com/ant-design/ant-design/pull/53687) [@765477020](https://github.com/765477020)
- Splitter
- 🐞 Fix Splitter that multiple calls to `onResizeEnd` in lazy mode. [#53708](https://github.com/ant-design/ant-design/pull/53708) [@wanpan11](https://github.com/wanpan11)
- 🐞 Fix Splitter to use minimum value as fallback when historical value out of bound. [#53703](https://github.com/ant-design/ant-design/pull/53703) [@jjlstruggle](https://github.com/jjlstruggle)
- 💄 Fix Form that label is not middle align with input when not required and wrapped. [#53552](https://github.com/ant-design/ant-design/pull/53552) [@pre1ude](https://github.com/pre1ude)
- 🐞 Fix Tabs card type height not working correctly when using `cardHeight` token. [#52837](https://github.com/ant-design/ant-design/pull/52837) [@aojunhao123](https://github.com/aojunhao123)
- 🛠 MISCRefactor compatible code, use standard web API first, and downgrade to deprecated API when not compatibale [#53107](https://github.com/ant-design/ant-design/pull/53107) [@li-jia-nan](https://github.com/li-jia-nan)
- ⌨️ Opt Tour's `aria-*` props. [#53345](https://github.com/ant-design/ant-design/pull/53345) [@kiner-tang](https://github.com/kiner-tang)
- ⌨️ MISC: Optimized closable component's aria props. [#53410](https://github.com/ant-design/ant-design/pull/53410) [@kiner-tang](https://github.com/kiner-tang)
- 🗑 MISC: Deprecate `destory*` of some components for `destroyOnHidden` prop. [#53739](https://github.com/ant-design/ant-design/pull/53739) [@li-jia-nan](https://github.com/li-jia-nan)
- 🗑 Deprecate `dropdownRender` of Dropdown for `popupRender` [#53263](https://github.com/ant-design/ant-design/pull/53263) [@aojunhao123](https://github.com/aojunhao123)
- 🗑 Deprecated `dropdown*` props in Cascader [#53133](https://github.com/ant-design/ant-design/pull/53133) [@aojunhao123](https://github.com/aojunhao123)
- 🇨🇿 Add cs_CZ locale for QRCode and ColorPicker. [#53741](https://github.com/ant-design/ant-design/pull/53741) [@malda26](https://github.com/malda26)
## 5.24.9
`2025-04-29`
- 🐞 Fix Splitter mask not hiding correctly in lazy mode. [#53653](https://github.com/ant-design/ant-design/pull/53653) [@wanpan11](https://github.com/wanpan11)
- 🐞 Fix issue when modifying `offsetBottom` and `offsetTop` of the Affix does not take effect. [#53607](https://github.com/ant-design/ant-design/pull/53607) [@yellowryan](https://github.com/yellowryan)
- ⚡️ Fix Select keeps showing `clearIcon` when it has a value on mobile devices. [#53576](https://github.com/ant-design/ant-design/pull/53576) [@EmilyLiu](https://github.com/EmilyLiu)
- 🐞 Fix Slider formatter returns fixed content, Tooltip position abnormal after dragging. [#53460](https://github.com/ant-design/ant-design/pull/53460) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 Fix Tabs keyboard operation not working. [#53692](https://github.com/ant-design/ant-design/pull/53692) [@afc163](https://github.com/afc163)
- RTL
- 💄 Fix counter element direction of Image in RTL mode. [#53593](https://github.com/ant-design/ant-design/pull/53593) [@aojunhao123](https://github.com/aojunhao123)
## 5.24.8
`2025-04-21`
- 📖 Release [llms.txt](/llms.txt) and [llms-full.txt](/llms-full.txt), help LLM or agent to access detailed information during inference.
- 🐞 Fix Tabs throwing `Maximum update depth exceeded` error in some cases. [#53521](https://github.com/ant-design/ant-design/pull/53521) [@afc163](https://github.com/afc163)
- Splitter
- 💄 Make Splitter collapse icon always visible on mobile devices. [#53575](https://github.com/ant-design/ant-design/pull/53575) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 Fix Splitter `onResizeEnd` callback parameter not being the latest value in lazy mode. [#53574](https://github.com/ant-design/ant-design/pull/53574) [@wanpan11](https://github.com/wanpan11)
- Input
- 🐞 Fix Input.TextArea height flickering during initialization. [#53522](https://github.com/ant-design/ant-design/pull/53522) [@Fang328](https://github.com/Fang328)
- 🐞 Fix Popover position misalignment when Input has suffix. [#53475](https://github.com/ant-design/ant-design/pull/53475)
- 🐞 Fix Input.OTP `mask` attribute not working properly when `type="number"`. [#53550](https://github.com/ant-design/ant-design/pull/53550) [@rajankonar](https://github.com/rajankonar)
- 🐞 Fix Breadcrumb.Item type not supporting `data-*` and `aria-*` attributes. [#53546](https://github.com/ant-design/ant-design/pull/53546) [@John-Feola](https://github.com/John-Feola)
- 🐞 Fix Descriptions incorrect border radius styles when enable `bordered` mode. [#53538](https://github.com/ant-design/ant-design/pull/53538) [@dengfuping](https://github.com/dengfuping)
- 🐞 Fix disabled state of operation buttons in UploadList when used within Form. [#53504](https://github.com/ant-design/ant-design/pull/53504) [@Wxh16144](https://github.com/Wxh16144)
- 🐞 MISC: Fix custom token `colorIcon` not taking effect for some components. [#53511](https://github.com/ant-design/ant-design/pull/53511) [@dengfuping](https://github.com/dengfuping)
- 🐞 Fix message/notification runtime dynamic configuration changes not taking effect. [#53579](https://github.com/ant-design/ant-design/pull/53579) [@Wxh16144](https://github.com/Wxh16144)
- RTL
- 💄 Fix counter element direction of Input.TextArea in RTL mode. [#53530](https://github.com/ant-design/ant-design/pull/53530) [@aojunhao123](https://github.com/aojunhao123)
- 💄 Fix incorrect direction of left and right switch icons in Image.PreviewGroup in RTL mode. [#53525](https://github.com/ant-design/ant-design/pull/53525) [@aojunhao123](https://github.com/aojunhao123)
## 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`
@ -292,7 +552,7 @@ tag: vVERSION
- 🐞 Fix Transfer width issue when customized as TableTransfer. [#50974](https://github.com/ant-design/ant-design/pull/50974) [@zombieJ](https://github.com/zombieJ)
- 🇹🇷 Add Turkish text for `filterCheckall` in Table component. [#51000](https://github.com/ant-design/ant-design/pull/51000) [@ytahirkose](https://github.com/ytahirkose)
## 5.21.0 🔥
## 5.21.0
`2024-09-22`
@ -307,7 +567,7 @@ tag: vVERSION
<img width="300" alt="float button" src="https://github.com/user-attachments/assets/4b53c0f6-7bdd-4e2a-91cc-2fb804f6e6d3" />
- 🆕 FloatButton supports `htmlType` prop. [#50892](https://github.com/ant-design/ant-design/pull/50892) [@li-jia-nan](https://github.com/li-jia-nan)
- 💄 Unify FloatButton and FloatButton.Group button round style. [#50513](https://github.com/ant-design/ant-design/pull/50513) [@Layouwen](https://github.com/Layouwen)
- 💄 Manage FloatButton's `z-index` with `useZIndex` to improve compatibility with other popup components. [#50311](https://github.com/ant-design/ant-design/pull/50311) [@li-jia-nan](https://github.com/li-jia-nan)
- 💄 Unify FloatButton's `z-index` with `useZIndex` to improve compatibility with other popup components. [#50311](https://github.com/ant-design/ant-design/pull/50311) [@li-jia-nan](https://github.com/li-jia-nan)
- Menu
- 🆕 Menu.Item and Dropdown's `menu` supports `extra` prop now. [#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">
@ -824,7 +1084,7 @@ tag: vVERSION
- 🐞 Fix Form's `setFieldsValue` should tread same as `setFields`.
- 🐞 Fix Table that internationalization of table columns fails when searching. [#48126](https://github.com/ant-design/ant-design/pull/48126) [@LingJinT](https://github.com/LingJinT)
- 🐞 Fix Upload that `onChange` should be triggered when `fileList.length` is larger than `maxCount`. [#47747](https://github.com/ant-design/ant-design/pull/47747) [@Zhou-Bill](https://github.com/Zhou-Bill)
- 🐞 Fix Carousel several <a href="https://github.com/ant-design/react-slick/pull/110" data-hovercard-type="pull_request" data-hovercard-url="/ant-design/react-slick/pull/110/hovercard">bugs</a> by upgrading react-slick changes and renewing TS type. [#48093](https://github.com/ant-design/ant-design/pull/48093)
- 🐞 Fix Carousel several [bugs](https://github.com/ant-design/react-slick/pull/110) by upgrading react-slick changes and renewing TS type. [#48093](https://github.com/ant-design/ant-design/pull/48093)
- 🐞 Fix ColorPicker that displayed cleared color not change after `value` changed. [#47816](https://github.com/ant-design/ant-design/pull/47816) [@MadCcc](https://github.com/MadCcc)
- 🐞 Make Badge consistent with Tag that apply `colorInfo` token in processing. [#47695](https://github.com/ant-design/ant-design/pull/47695) [@pfdgithub](https://github.com/pfdgithub)
- 🇮🇸 Add missing form translations in is_IS. [#48104](https://github.com/ant-design/ant-design/pull/48104) [@LonelySnowman](https://github.com/LonelySnowman)

View File

@ -15,6 +15,267 @@ tag: vVERSION
---
## 5.25.3
`2025-05-26`
- 🐞 修复 Typography.Text `delete` 属性无法更新的问题。[#53861](https://github.com/ant-design/ant-design/pull/53861) [@codingories](https://github.com/codingories)
- 🐞 修复 Statistic.Timer 组件作为 Tooltip 的子组件无法正常展示文字提示的问题。[#53888](https://github.com/ant-design/ant-design/pull/53888) [@jin19980928](https://github.com/jin19980928)
- 🐞 修复 Upload 组件在更多类型时 `style` 设置未生效的问题。[#53877](https://github.com/ant-design/ant-design/pull/53877) [@QuentinHsu](https://github.com/QuentinHsu)
- 💄 修复 Tabs 点击后有残留 focus 样式的问题。[#53901](https://github.com/ant-design/ant-design/pull/53901)
## 5.25.2
`2025-05-19`
- 🐞 修复 AutoComplete 中使用 Input 时 `onPaste` 回调不生效的问题。[#53839](https://github.com/ant-design/ant-design/issues/53839) [@Wxh16144](https://github.com/Wxh16144)
- 🐞 修复 ColorPicker 的十六进制输入框无法输入的问题。[#53814](https://github.com/ant-design/ant-design/pull/53814) [@DDDDD12138](https://github.com/DDDDD12138)
- 🐞 修复 Statistic.Timer ssr 注水渲染不一致的问题。[#53817](https://github.com/ant-design/ant-design/pull/53817) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Table 开启 `sticky` 时的一个列头渲染闪烁问题。[#53803](https://github.com/ant-design/ant-design/pull/53803) [@afc163](https://github.com/afc163)
- 💄 修复 Input.Search `variant="filled"` 的样式。[#53787](https://github.com/ant-design/ant-design/pull/53787) [@afc163](https://github.com/afc163)
- TypeScript
- 🤖 修复 Upload.Dragger 不支持泛型参数的问题。[#53842](https://github.com/ant-design/ant-design/pull/53842) [@fnoopv](https://github.com/fnoopv)
- 🤖 移除 Modal 无效的属性类型定义。[#53808](https://github.com/ant-design/ant-design/pull/53808) [@wanpan11](https://github.com/wanpan11)
## 5.25.1
`2025-05-09`
- 🐞 Splitter 修复拖拽结束时,屏幕仍然无法操作的问题。[#53767](https://github.com/ant-design/ant-design/pull/53767) [@wanpan11](https://github.com/wanpan11)
- 🌐 Image 组件国际化添加希伯来语支持。[#53771](https://github.com/ant-design/ant-design/pull/53771) [@Sagie501](https://github.com/Sagie501)
## 5.25.0
`2025-05-07`
- 🔥 新组件 Statistic.Timer支持正计时和倒计时。[#53401](https://github.com/ant-design/ant-design/pull/53401) [@lcgash](https://github.com/lcgash)
- 🆕 Tour 新增 `actionsRender` 属性,用于自定义操作按钮。[#53067](https://github.com/ant-design/ant-design/pull/53067) [@dengfuping](https://github.com/dengfuping)
- 🆕 Divider 组件新增 `size` 属性。[#53570](https://github.com/ant-design/ant-design/pull/53570) [@coding-ice](https://github.com/coding-ice)
- Collapse
- 🆕 Collapse 增加 `borderlessContentPadding` 组件 token。[#52858](https://github.com/ant-design/ant-design/pull/52858) [@coding-ice](https://github.com/coding-ice)
- 🆕 Collapse 增加 `borderlessContentBg` 组件 token。[#50902](https://github.com/ant-design/ant-design/pull/50902) [@coding-ice](https://github.com/coding-ice)
- 🆕 Upload 支持通过 `pastable` 属性粘贴上传文件。[#53463](https://github.com/ant-design/ant-design/pull/53463) [@madocto](https://github.com/madocto)
- 🆕 AutoComplete 组件新增 `popup` 语义节点,支持通过 `classNames.popup`、`styles.popup`、`popupRender` 和 `onOpenChange` 自定义弹出菜单,并废弃旧的 `popupClassName`、`dropdownClassName`、`dropdownStyle`、`dropdownRender` 和 `onDropdownVisibleChange` API。[#53257](https://github.com/ant-design/ant-design/pull/53257) [@aojunhao123](https://github.com/aojunhao123)
- 🆕 Cascader 组件新增 `popup` 语义节点,并且废弃部分 props。[#53311](https://github.com/ant-design/ant-design/pull/53311) [@aojunhao123](https://github.com/aojunhao123)
- 🆕 ConfigProvider 支持设置 Button 的 `variant``color` 属性。[#53165](https://github.com/ant-design/ant-design/pull/53165) [@yellowryan](https://github.com/yellowryan)
- 🆕 TreeSelect 组件新增 `popup` 语义节点,并且废弃部分 props。[#53285](https://github.com/ant-design/ant-design/pull/53285) [@aojunhao123](https://github.com/aojunhao123)
- 🆕 DatePicker 和 TimePicker 组件新增 `popup` 语义节点,并且废弃部分 props。[#53718](https://github.com/ant-design/ant-design/pull/53718) [@aojunhao123](https://github.com/aojunhao123)
- 🆕 Select 组件新增 `popup` 语义节点,支持通过 `classNames.popup`、`styles.popup`、`popupRender` 和 `onOpenChange` 自定义弹出菜单,并废弃旧的 `popupClassName`、`dropdownClassName`、`dropdownStyle`、`dropdownRender` 和 `onDropdownVisibleChange` API。[#53243](https://github.com/ant-design/ant-design/pull/53243) [@aojunhao123](https://github.com/aojunhao123)
- 🆕 FloatButton 支持 `tooltip` 属性。[#53138](https://github.com/ant-design/ant-design/pull/53138) [@Wxh16144](https://github.com/Wxh16144)
- 🆕 Table `rowSelection` 支持 `align` 属性用于定位。[#53127](https://github.com/ant-design/ant-design/pull/53127) [@zombieJ](https://github.com/zombieJ)
- 🆕 Radio.Group 和 Checkbox.Group 组件的 `options` 属性支持传入 `className` 属性。[#52917](https://github.com/ant-design/ant-design/pull/52917) [@li-jia-nan](https://github.com/li-jia-nan)
- ⚡️ 优化 ColorPicker 组件,使用派生状态模式替代在 useEffect 中调用 setState。[#53701](https://github.com/ant-design/ant-design/pull/53701) [@DDDDD12138](https://github.com/DDDDD12138)
- 🐞 修复 Checkbox 组件 `children``null` 的时候渲染多余 dom 的问题。[#53723](https://github.com/ant-design/ant-design/pull/53723) [@li-jia-nan](https://github.com/li-jia-nan)
- 🐞 修复 Anchor 组件点击锚点会刷新页面的问题。[#53687](https://github.com/ant-design/ant-design/pull/53687) [@765477020](https://github.com/765477020)
- Splitter
- 🐞 修复 Splitter 组件在 lazy 模式下 `onResizeEnd` 被多次调用问题。[#53708](https://github.com/ant-design/ant-design/pull/53708) [@wanpan11](https://github.com/wanpan11)
- 🐞 修复 Splitter 组件,当历史值超出范围时使用面板设置的最小值最为兜底值。[#53703](https://github.com/ant-design/ant-design/pull/53703) [@jjlstruggle](https://github.com/jjlstruggle)
- 🐞 修复 Tabs 组件卡片类型页签在使用 `cardHeight` token 时高度设置不正确的问题。[#52837](https://github.com/ant-design/ant-design/pull/52837) [@aojunhao123](https://github.com/aojunhao123)
- 💄 修复 Form 组件中非必选的 label 换行不对齐的问题。[#53552](https://github.com/ant-design/ant-design/pull/53552) [@pre1ude](https://github.com/pre1ude)
- 🛠 杂项:重构兼容逻辑,优先使用标准 Web API不兼容的时候降级为废弃 API。[#53107](https://github.com/ant-design/ant-design/pull/53107) [@li-jia-nan](https://github.com/li-jia-nan)
- ⌨️ 优化 Tour 的 `aria-*` 属性。[#53345](https://github.com/ant-design/ant-design/pull/53345) [@kiner-tang](https://github.com/kiner-tang)
- ⌨️ 杂项:优化可关闭组件的无障碍属性。[#53410](https://github.com/ant-design/ant-design/pull/53410) [@kiner-tang](https://github.com/kiner-tang)
- 🗑 杂项:废弃多个可开关组件的 `destory*` 属性,统一为 `destroyOnHidden`。 [#53739](https://github.com/ant-design/ant-design/pull/53739) [@li-jia-nan](https://github.com/li-jia-nan)
- 🗑 废弃 Dropdown 中 `dropdownRender` 属性,用 `popupRender` 替代。[#53263](https://github.com/ant-design/ant-design/pull/53263) [@aojunhao123](https://github.com/aojunhao123)
- 🗑 废弃 Cascader 组件的 `dropdown*` 等属性。[#53133](https://github.com/ant-design/ant-design/pull/53133) [@aojunhao123](https://github.com/aojunhao123)
- 🇨🇿 添加 QRCode 和 ColorPicker 的捷克语cs_CZ支持。[#53741](https://github.com/ant-design/ant-design/pull/53741) [@malda26](https://github.com/malda26)
## 5.24.9
`2025-04-29`
- 🐞 修复 Splitter 在 `lazy` 模式下遮罩未能正确关闭的问题。[#53653](https://github.com/ant-design/ant-design/pull/53653) [@wanpan11](https://github.com/wanpan11)
- 🐞 修复 Affix 在动态调整 `offsetTop``offsetBottom` 属性值后固定效果失效的问题。[#53607](https://github.com/ant-design/ant-design/pull/53607) [@yellowryan](https://github.com/yellowryan)
- 💄 移动端场景下 Select 当有选中值时始终显示清除按钮。[#53576](https://github.com/ant-design/ant-design/pull/53576) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 修复 Slider 设置 `formatter` 后,拖拽滑块导致 Tooltip 位置异常的问题。[#53460](https://github.com/ant-design/ant-design/pull/53460) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 修复 Tabs 键盘操作失效的问题。[#53692](https://github.com/ant-design/ant-design/pull/53692) [@afc163](https://github.com/afc163)
- RTL
- 💄 修复 Image 计数元素在 RTL 模式下的显示方向问题。[#53593](https://github.com/ant-design/ant-design/pull/53593) [@aojunhao123](https://github.com/aojunhao123)
## 5.24.8
`2025-04-21`
- 📖 发布 [llms.txt](/llms.txt) 和 [llms-full.txt](/llms-full.txt),提供大模型友好的文本信息。
- 🐞 修复 Tabs 有时抛出 `Maximum update depth exceeded` 错误的问题。[#53521](https://github.com/ant-design/ant-design/pull/53521) [@afc163](https://github.com/afc163)
- Splitter
- 💄 Splitter 折叠图标在移动设备上将始终展示。[#53575](https://github.com/ant-design/ant-design/pull/53575) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 修复 Splitter 在 lazy 模式下 `onResizeEnd` 回调参数不是最新值的问题。[#53574](https://github.com/ant-design/ant-design/pull/53574) [@wanpan11](https://github.com/wanpan11)
- Input
- 🐞 修复 Input.TextArea 初始化时渲染高度闪烁问题。[#53522](https://github.com/ant-design/ant-design/pull/53522) [@Fang328](https://github.com/Fang328)
- 🐞 修复 Input 存在 suffix 的情况下 Popover 弹出位置错乱的问题。[#53475](https://github.com/ant-design/ant-design/pull/53475)
- 🐞 修复 Input.OTP `mask` 属性在 `type="number"` 时无法正常工作的问题。[#53550](https://github.com/ant-design/ant-design/pull/53550) [@rajankonar](https://github.com/rajankonar)
- 🐞 修复 Breadcrumb.Item 类型不支持 `data-*``aria-*` 属性的问题。[#53546](https://github.com/ant-design/ant-design/pull/53546) [@John-Feola](https://github.com/John-Feola)
- 🐞 修复 Descriptions 带边框模式下左上和左下圆角样式不正确的问题。[#53538](https://github.com/ant-design/ant-design/pull/53538) [@dengfuping](https://github.com/dengfuping)
- 🐞 修复 UploadList 在 Form 中使用时操作按钮的禁用状态问题。[#53504](https://github.com/ant-design/ant-design/pull/53504) [@Wxh16144](https://github.com/Wxh16144)
- 🐞 MISC: 修复自定义 token `colorIcon` 对部分组件不生效的问题。[#53511](https://github.com/ant-design/ant-design/pull/53511) [@dengfuping](https://github.com/dengfuping)
- 🐞 修复 message/notification 运行时动态修改配置无法生效的问题。[#53579](https://github.com/ant-design/ant-design/pull/53579) [@Wxh16144](https://github.com/Wxh16144)
- RTL
- 💄 修复 Input.TextArea 计数元素在 RTL 模式中的方向问题。[#53530](https://github.com/ant-design/ant-design/pull/53530) [@aojunhao123](https://github.com/aojunhao123)
- 💄 修复 Image.PreviewGroup 在 RTL 模式下左右切换图标方向不正确的问题。[#53525](https://github.com/ant-design/ant-design/pull/53525) [@aojunhao123](https://github.com/aojunhao123)
## 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`
@ -293,7 +554,7 @@ tag: vVERSION
- 💄 修复 Transfer 在自定义为 TableTransfer 时,宽度不正确的问题。[#50974](https://github.com/ant-design/ant-design/pull/50974) [@zombieJ](https://github.com/zombieJ)
- 🇹🇷 补充 Table 组件 `filterCheckall` 的土耳其语文案。[#51000](https://github.com/ant-design/ant-design/pull/51000) [@ytahirkose](https://github.com/ytahirkose)
## 5.21.0 🔥
## 5.21.0
`2024-09-22`
@ -826,7 +1087,7 @@ tag: vVERSION
- 🐞 修复 Form 的 `setFieldsValue``setFields` 的行为应该相同。
- 🐞 修复 Table 表格列在搜索情况下,国际化失效的问题。[#48126](https://github.com/ant-design/ant-design/pull/48126) [@LingJinT](https://github.com/LingJinT)
- 🐞 修复 Upload 当文件数量超出限制时,删除不起作用,无法触发 `onChange` 的问题。[#47747](https://github.com/ant-design/ant-design/pull/47747) [@Zhou-Bill](https://github.com/Zhou-Bill)
- 🐞 Carousel 组件同步上游 react-slick 改动,修复一系列<a href="https://github.com/ant-design/react-slick/pull/110" data-hovercard-type="pull_request" data-hovercard-url="/ant-design/react-slick/pull/110/hovercard">问题</a>,并更新到最新 TS 定义。[#48093](https://github.com/ant-design/ant-design/pull/48093)
- 🐞 Carousel 组件同步上游 react-slick 改动,修复一系列[问题](https://github.com/ant-design/react-slick/pull/110),并更新到最新 TS 定义。[#48093](https://github.com/ant-design/ant-design/pull/48093)
- 🐞 修复 ColorPicker 展示的清除颜色在受控 `value` 变化后不会改变的问题。[#47816](https://github.com/ant-design/ant-design/pull/47816) [@MadCcc](https://github.com/MadCcc)
- 🐞 Badge 与 Tag 组件保持一致processing 状态使用 `colorInfo` token 。[#47695](https://github.com/ant-design/ant-design/pull/47695) [@pfdgithub](https://github.com/pfdgithub)
- 🇮🇸 添加冰岛语缺失的 From 翻译。[#48104](https://github.com/ant-design/ant-design/pull/48104) [@LonelySnowman](https://github.com/LonelySnowman)

View File

@ -8,7 +8,7 @@
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url]
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url] [![DeepWiki][deepwiki-image]][deepwiki-url]
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
@ -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
@ -43,6 +43,8 @@
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
[dumi-url]: https://github.com/umijs/dumi
[github-issues-url]: https://new-issue.ant.design
[deepwiki-url]: https://deepwiki.com/ant-design/ant-design
[deepwiki-image]: https://img.shields.io/badge/Chat%20with-DeepWiki%20🤖-20B2AA?style=flat-square
</div>
@ -83,6 +85,10 @@ yarn add antd
pnpm add antd
```
```bash
bun add antd
```
## 🔨 示例
```tsx
@ -105,7 +111,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)。
## 🌍 国际化
@ -185,6 +191,10 @@ $ npm start
</tr>
</table>
<a href="https://openomy.app/github/ant-design/ant-design" target="_blank" style="display: block; width: 100%;" align="center">
<img src="https://openomy.app/svg?repo=ant-design/ant-design&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
</a>
请参考[贡献指南](https://ant.design/docs/react/contributing-cn).
> 强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) 和 [《如何有效地报告 Bug》](https://www.chiark.greenend.org.uk/%7Esgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393),更好的问题更容易获得帮助。
@ -205,8 +215,6 @@ $ npm start
## Issue 赞助
我们使用 [Polar.sh](https://polar.sh/ant-design) 和 [Issuehunt](https://issuehunt.io/repos/3452688) 来推动您希望看到的针对 antd 的修复和改进,请查看我们的赞助列表:
<a href="https://polar.sh/ant-design"><img src="https://polar.sh/embed/fund-our-backlog.svg?org=ant-design" /></a>
我们使用 [Issuehunt](https://issuehunt.io/repos/3452688) 来推动您希望看到的针对 antd 的修复和改进,请查看我们的赞助列表:
[![Let's fund issues in this repository](https://raw.githubusercontent.com/BoostIO/issuehunt-materials/master/v1/issuehunt-button-v1.svg)](https://issuehunt.io/repos/34526884)

View File

@ -8,7 +8,7 @@ An enterprise-class UI design language and React UI library.
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url]
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url] [![DeepWiki][deepwiki-image]][deepwiki-url]
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
@ -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
@ -43,6 +43,8 @@ An enterprise-class UI design language and React UI library.
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
[dumi-url]: https://github.com/umijs/dumi
[github-issues-url]: https://new-issue.ant.design
[deepwiki-url]: https://deepwiki.com/ant-design/ant-design
[deepwiki-image]: https://img.shields.io/badge/Chat%20with-DeepWiki%20🤖-20B2AA?style=flat-square
</div>
@ -81,6 +83,10 @@ yarn add antd
pnpm add antd
```
```bash
bun add antd
```
## 🔨 Usage
```tsx
@ -132,7 +138,7 @@ $ npm install
$ npm start
```
Open your browser and visit http://127.0.0.1:8001 , see more at [Development](https://github.com/ant-design/ant-design/wiki/Development).
Open your browser and visit http://127.0.0.1:8001, see more at [Development](https://github.com/ant-design/ant-design/wiki/Development).
## 🤝 Contributing [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)
@ -167,16 +173,18 @@ Open your browser and visit http://127.0.0.1:8001 , see more at [Development](ht
</tr>
</table>
<a href="https://openomy.app/github/ant-design/ant-design" target="_blank" style="display: block; width: 100%;" align="center">
<img src="https://openomy.app/svg?repo=ant-design/ant-design&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
</a>
Let's build a better antd together.
We warmly invite contributions from everyone. Before you get started, please take a moment to review our [Contributing Guide](https://ant.design/docs/react/contributing). Feel free to share your ideas through [Pull Requests](https://github.com/ant-design/ant-design/pulls) or [GitHub Issues](https://github.com/ant-design/ant-design/issues). If you're interested in enhancing our codebase, explore the [Development Instructions](https://github.com/ant-design/ant-design/wiki/Development) and enjoy your coding journey! :)
We warmly invite contributions from everyone. Before you get started, please take a moment to review our [Contribution Guide](https://ant.design/docs/react/contributing). Feel free to share your ideas through [Pull Requests](https://github.com/ant-design/ant-design/pulls) or [GitHub Issues](https://github.com/ant-design/ant-design/issues). If you're interested in enhancing our codebase, explore the [Development Instructions](https://github.com/ant-design/ant-design/wiki/Development) and enjoy your coding journey! :)
For collaborators, adhere to our [Pull Request Principle](https://github.com/ant-design/ant-design/wiki/PR-principle) and utilize our [Pull Request Template](https://github.com/ant-design/ant-design/wiki/PR-principle#pull-request-template) when creating a Pull Request.
## Issue funding
We use [Polar.sh](https://polar.sh/ant-design) and [Issuehunt](https://issuehunt.io/repos/3452688) to up-vote and promote specific features that you would like to see and implement. Check our backlog and help us:
<a href="https://polar.sh/ant-design"><img src="https://polar.sh/embed/fund-our-backlog.svg?org=ant-design" /></a>
We use [Issuehunt](https://issuehunt.io/repos/3452688) to up-vote and promote specific features that you would like to see and implement. Check our backlog and help us:
[![Let's fund issues in this repository](https://raw.githubusercontent.com/BoostIO/issuehunt-materials/master/v1/issuehunt-button-v1.svg)](https://issuehunt.io/repos/34526884)

View File

@ -41,4 +41,9 @@ describe('unstable', () => {
expect(document.querySelector('.ant-modal')).toBeTruthy();
}
});
it('unstableSetRender without param', async () => {
const currentRender = unstableSetRender();
expect(currentRender).toBeInstanceOf(Function);
});
});

View File

@ -19,7 +19,7 @@ export interface BaseProps {
/* istanbul ignore next */
const genPurePanel = <ComponentProps extends BaseProps = BaseProps>(
Component: any,
Component: React.ComponentType<Readonly<ComponentProps>>,
alignPropName?: 'align' | 'dropdownAlign' | 'popupAlign',
postProps?: (props: ComponentProps) => ComponentProps,
defaultPrefixCls?: string,

View File

@ -5,23 +5,20 @@ import useResponsiveObserver from '../responsiveObserver';
describe('Test ResponsiveObserve', () => {
it('test ResponsiveObserve subscribe and unsubscribe', () => {
let responsiveObserveRef: any;
const Demo = () => {
let responsiveRef: any = null;
const Demo: React.FC = () => {
const responsiveObserver = useResponsiveObserver();
responsiveObserveRef = responsiveObserver;
responsiveRef = responsiveObserver;
return null;
};
render(<Demo />);
const subscribeFunc = jest.fn();
const token = responsiveObserveRef.subscribe(subscribeFunc);
expect(
responsiveObserveRef.matchHandlers[responsiveObserveRef.responsiveMap.xs].mql.matches,
).toBeTruthy();
const token = responsiveRef.subscribe(subscribeFunc);
expect(responsiveRef.matchHandlers[responsiveRef.responsiveMap.xs].mql.matches).toBeTruthy();
expect(subscribeFunc).toHaveBeenCalledTimes(1);
responsiveObserveRef.unsubscribe(token);
responsiveRef.unsubscribe(token);
expect(
responsiveObserveRef.matchHandlers[responsiveObserveRef.responsiveMap.xs].mql.removeListener,
responsiveRef.matchHandlers[responsiveRef.responsiveMap.xs].mql?.removeEventListener,
).toHaveBeenCalled();
});
});

View File

@ -1,6 +1,7 @@
import { waitFakeTimer } from '../../../tests/utils';
import { isStyleSupport } from '../styleChecker';
import throttleByAnimationFrame from '../throttleByAnimationFrame';
import toList from '../toList';
describe('Test utils function', () => {
describe('throttle', () => {
@ -56,4 +57,12 @@ describe('Test utils function', () => {
spy.mockRestore();
});
});
describe('toList', () => {
it('toList should work', () => {
expect(toList(123)).toEqual([123]);
expect(toList([123])).toEqual([123]);
expect(toList(null, true)).toEqual([]);
expect(toList(undefined, true)).toEqual([]);
});
});
});

View File

@ -0,0 +1,20 @@
import type { ReactNode } from 'react';
import { isValidElement } from 'react';
import type { TooltipProps } from '../tooltip';
function convertToTooltipProps<P extends TooltipProps>(tooltip: P | ReactNode): P | null {
// isNil
if (tooltip === undefined || tooltip === null) {
return null;
}
if (typeof tooltip === 'object' && !isValidElement(tooltip)) {
return tooltip as P;
}
return {
title: tooltip,
} as P;
}
export default convertToTooltipProps;

View File

@ -1,21 +1,19 @@
import type { AnyObject } from './type';
const extendsObject = <T extends AnyObject = AnyObject>(...list: T[]) => {
const result: AnyObject = { ...list[0] };
for (let i = 1; i < list.length; i++) {
const obj = list[i];
if (obj) {
Object.keys(obj).forEach((key) => {
const val = obj[key];
if (val !== undefined) {
result[key] = val;
// copied https://github.com/ant-design/ant-design-mobile/blob/d3b3bae/src/utils/with-default-props.tsx
function mergeProps<A, B>(a: A, b: B): B & A;
function mergeProps<A, B, C>(a: A, b: B, c: C): C & B & A;
function mergeProps<A, B, C, D>(a: A, b: B, c: C, d: D): D & C & B & A;
function mergeProps(...items: any[]) {
const ret: any = {};
items.forEach((item) => {
if (item) {
Object.keys(item).forEach((key) => {
if (item[key] !== undefined) {
ret[key] = item[key];
}
});
}
}
});
return ret;
}
return result;
};
export default extendsObject;
export default mergeProps;

View File

@ -4,6 +4,11 @@ import CloseOutlined from '@ant-design/icons/CloseOutlined';
import type { DialogProps } from 'rc-dialog';
import pickAttrs from 'rc-util/lib/pickAttrs';
import { useLocale } from '../../locale';
import defaultLocale from '../../locale/en_US';
import type { HTMLAriaDataAttributes } from '../aria-data-attrs';
import extendsObject from '../extendsObject';
export type ClosableType = DialogProps['closable'];
export type BaseContextClosable = { closable?: ClosableType; closeIcon?: ReactNode };
@ -58,56 +63,42 @@ function useClosableConfig(closableCollection?: ClosableCollection | null) {
...closable,
};
}
return closableConfig;
}, [closable, closeIcon]);
}
/**
* Assign object without `undefined` field. Will skip if is `false`.
* This helps to handle both closableConfig or false
*/
function assignWithoutUndefined<T extends object>(
...objList: (Partial<T> | false | null | undefined)[]
): Partial<T> {
const target: Partial<T> = {};
objList.forEach((obj) => {
if (obj) {
(Object.keys(obj) as (keyof T)[]).forEach((key) => {
if (obj[key] !== undefined) {
target[key] = obj[key];
}
});
}
});
return target;
}
/** Collection contains the all the props related with closable. e.g. `closable`, `closeIcon` */
interface ClosableCollection {
closable?: ClosableType;
closeIcon?: ReactNode;
}
interface FallbackCloseCollection extends ClosableCollection {
/**
* Some components need to wrap CloseIcon twice,
* this method will be executed once after the final CloseIcon is calculated
*/
closeIconRender?: (closeIcon: ReactNode) => ReactNode;
}
/** Use same object to support `useMemo` optimization */
const EmptyFallbackCloseCollection: ClosableCollection = {};
const EmptyFallbackCloseCollection: FallbackCloseCollection = {};
export default function useClosable(
propCloseCollection?: ClosableCollection,
contextCloseCollection?: ClosableCollection | null,
fallbackCloseCollection: ClosableCollection & {
/**
* Some components need to wrap CloseIcon twice,
* this method will be executed once after the final CloseIcon is calculated
*/
closeIconRender?: (closeIcon: ReactNode) => ReactNode;
} = EmptyFallbackCloseCollection,
): [closable: boolean, closeIcon: React.ReactNode, closeBtnIsDisabled: boolean] {
fallbackCloseCollection: FallbackCloseCollection = EmptyFallbackCloseCollection,
): [
closable: boolean,
closeIcon: React.ReactNode,
closeBtnIsDisabled: boolean,
ariaOrDataProps?: HTMLAriaDataAttributes,
] {
// Align the `props`, `context` `fallback` to config object first
const propCloseConfig = useClosableConfig(propCloseCollection);
const contextCloseConfig = useClosableConfig(contextCloseCollection);
const [contextLocale] = useLocale('global', defaultLocale.global);
const closeBtnIsDisabled =
typeof propCloseConfig !== 'boolean' ? !!propCloseConfig?.disabled : false;
const mergedFallbackCloseCollection = React.useMemo(
@ -127,11 +118,7 @@ export default function useClosable(
}
if (propCloseConfig) {
return assignWithoutUndefined(
mergedFallbackCloseCollection,
contextCloseConfig,
propCloseConfig,
);
return extendsObject(mergedFallbackCloseCollection, contextCloseConfig, propCloseConfig);
}
// =============== Context Second ==============
@ -141,7 +128,7 @@ export default function useClosable(
}
if (contextCloseConfig) {
return assignWithoutUndefined(mergedFallbackCloseCollection, contextCloseConfig);
return extendsObject(mergedFallbackCloseCollection, contextCloseConfig);
}
// ============= Fallback Default ==============
@ -151,30 +138,34 @@ export default function useClosable(
// Calculate the final closeIcon
return React.useMemo(() => {
if (mergedClosableConfig === false) {
return [false, null, closeBtnIsDisabled];
return [false, null, closeBtnIsDisabled, {}];
}
const { closeIconRender } = mergedFallbackCloseCollection;
const { closeIcon } = mergedClosableConfig;
let mergedCloseIcon: ReactNode = closeIcon;
// Wrap the closeIcon with aria props
const ariaOrDataProps = pickAttrs(mergedClosableConfig, true);
if (mergedCloseIcon !== null && mergedCloseIcon !== undefined) {
// Wrap the closeIcon if needed
if (closeIconRender) {
mergedCloseIcon = closeIconRender(closeIcon);
}
// Wrap the closeIcon with aria props
const ariaProps = pickAttrs(mergedClosableConfig, true);
if (Object.keys(ariaProps).length) {
mergedCloseIcon = React.isValidElement(mergedCloseIcon) ? (
React.cloneElement(mergedCloseIcon, ariaProps)
) : (
<span {...ariaProps}>{mergedCloseIcon}</span>
);
}
mergedCloseIcon = React.isValidElement(mergedCloseIcon) ? (
React.cloneElement(mergedCloseIcon, {
'aria-label': contextLocale.close,
...ariaOrDataProps,
} as HTMLAriaDataAttributes)
) : (
<span aria-label={contextLocale.close} {...ariaOrDataProps}>
{mergedCloseIcon}
</span>
);
}
return [true, mergedCloseIcon, closeBtnIsDisabled];
return [true, mergedCloseIcon, closeBtnIsDisabled, ariaOrDataProps];
}, [mergedClosableConfig, mergedFallbackCloseCollection]);
}

View File

@ -0,0 +1,107 @@
import * as React from 'react';
import classnames from 'classnames';
import { ValidChar } from './interface';
type TemplateSemanticClassNames<T extends string> = Partial<Record<T, string>>;
export type SemanticSchema = {
_default?: string;
} & {
[key: `${ValidChar}${string}`]: SemanticSchema;
};
// ========================= ClassNames =========================
export function mergeClassNames<
T extends string,
SemanticClassNames extends Partial<Record<T, any>> = TemplateSemanticClassNames<T>,
>(schema: SemanticSchema | undefined, ...classNames: (SemanticClassNames | undefined)[]) {
const mergedSchema = schema || {};
return classNames.reduce((acc: any, cur) => {
// Loop keys of the current classNames
Object.keys(cur || {}).forEach((key) => {
const keySchema = mergedSchema[key as keyof SemanticSchema] as SemanticSchema;
const curVal = (cur as SemanticClassNames)[key as keyof SemanticClassNames];
if (keySchema && typeof keySchema === 'object') {
if (curVal && typeof curVal === 'object') {
// Loop fill
acc[key] = mergeClassNames(keySchema, acc[key], curVal);
} else {
// Covert string to object structure
const { _default: defaultField } = keySchema;
acc[key] = acc[key] || {};
acc[key][defaultField!] = classnames(acc[key][defaultField!], curVal);
}
} else {
// Flatten fill
acc[key] = classnames(acc[key], curVal);
}
});
return acc;
}, {} as SemanticClassNames) as SemanticClassNames;
}
function useSemanticClassNames<ClassNamesType extends object>(
schema: SemanticSchema | undefined,
...classNames: (Partial<ClassNamesType> | undefined)[]
): Partial<ClassNamesType> {
return React.useMemo(
() => mergeClassNames(schema, ...classNames),
[classNames],
) as ClassNamesType;
}
// =========================== Styles ===========================
function useSemanticStyles<StylesType extends object>(
...styles: (Partial<StylesType> | undefined)[]
) {
return React.useMemo(() => {
return styles.reduce(
(acc, cur = {}) => {
Object.keys(cur).forEach((key) => {
acc[key] = { ...acc[key], ...(cur as Record<string, React.CSSProperties>)[key] };
});
return acc;
},
{} as Record<string, React.CSSProperties>,
);
}, [styles]) as StylesType;
}
// =========================== Export ===========================
function fillObjectBySchema<T extends object>(obj: T, schema: SemanticSchema): T {
const newObj: any = { ...obj };
Object.keys(schema).forEach((key) => {
if (key !== '_default') {
const nestSchema = (schema as any)[key] as SemanticSchema;
const nextValue = newObj[key] || {};
newObj[key] = nestSchema ? fillObjectBySchema(nextValue, nestSchema) : nextValue;
}
});
return newObj;
}
/**
* Merge classNames and styles from multiple sources.
* When `schema` is provided, it will **must** provide the nest object structure.
*/
export default function useMergeSemantic<ClassNamesType extends object, StylesType extends object>(
classNamesList: (ClassNamesType | undefined)[],
stylesList: (StylesType | undefined)[],
schema?: SemanticSchema,
) {
const mergedClassNames = useSemanticClassNames(schema, ...classNamesList) as ClassNamesType;
const mergedStyles = useSemanticStyles(...stylesList) as StylesType;
return React.useMemo(() => {
return [
fillObjectBySchema(mergedClassNames, schema!) as ClassNamesType,
fillObjectBySchema(mergedStyles, schema!) as StylesType,
] as const;
}, [mergedClassNames, mergedStyles]);
}

View File

@ -0,0 +1,27 @@
export type ValidChar =
| 'a'
| 'b'
| 'c'
| 'd'
| 'e'
| 'f'
| 'g'
| 'h'
| 'i'
| 'j'
| 'k'
| 'l'
| 'm'
| 'n'
| 'o'
| 'p'
| 'q'
| 'r'
| 's'
| 't'
| 'u'
| 'v'
| 'w'
| 'x'
| 'y'
| 'z';

View File

@ -1,3 +0,0 @@
const isNumeric = (value: any): boolean => !isNaN(parseFloat(value)) && isFinite(value);
export default isNumeric;

View File

@ -0,0 +1,19 @@
type MQListenerHandler = (mql: MediaQueryList, handler: (e: MediaQueryListEvent) => void) => void;
export const addMediaQueryListener: MQListenerHandler = (mql, handler) => {
// Don't delete here, please keep the code compatible
if (typeof mql?.addEventListener !== 'undefined') {
mql.addEventListener('change', handler);
} else if (typeof mql?.addListener !== 'undefined') {
mql.addListener(handler);
}
};
export const removeMediaQueryListener: MQListenerHandler = (mql, handler) => {
// Don't delete here, please keep the code compatible
if (typeof mql?.removeEventListener !== 'undefined') {
mql.removeEventListener('change', handler);
} else if (typeof mql?.removeListener !== 'undefined') {
mql.removeListener(handler);
}
};

View File

@ -2,6 +2,7 @@ import React from 'react';
import type { GlobalToken } from '../theme/internal';
import { useToken } from '../theme/internal';
import { addMediaQueryListener, removeMediaQueryListener } from './mediaQueryUtil';
export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
export type BreakpointMap = Record<Breakpoint, string>;
@ -28,7 +29,7 @@ const validateBreakpoints = (token: GlobalToken) => {
const indexableToken: any = token;
const revBreakpoints = [...responsiveArray].reverse();
revBreakpoints.forEach((breakpoint: Breakpoint, i: number) => {
revBreakpoints.forEach((breakpoint, i) => {
const breakpointUpper = breakpoint.toUpperCase();
const screenMin = `screen${breakpointUpper}Min`;
const screen = `screen${breakpointUpper}`;
@ -48,7 +49,7 @@ const validateBreakpoints = (token: GlobalToken) => {
);
}
const nextBreakpointUpperMin: string = revBreakpoints[i + 1].toUpperCase();
const nextBreakpointUpperMin = revBreakpoints[i + 1].toUpperCase();
const nextScreenMin = `screen${nextBreakpointUpperMin}Min`;
if (!(indexableToken[screenMax] <= indexableToken[nextScreenMin])) {
@ -61,23 +62,45 @@ const validateBreakpoints = (token: GlobalToken) => {
return token;
};
export default function useResponsiveObserver() {
export const matchScreen = (screens: ScreenMap, screenSizes?: ScreenSizeMap) => {
if (!screenSizes) {
return;
}
for (const breakpoint of responsiveArray) {
if (screens[breakpoint] && screenSizes?.[breakpoint] !== undefined) {
return screenSizes[breakpoint];
}
}
};
interface ResponsiveObserverType {
responsiveMap: BreakpointMap;
dispatch: (map: ScreenMap) => boolean;
subscribe: (func: SubscribeFunc) => number;
unsubscribe: (token: number) => void;
register: () => void;
unregister: () => void;
matchHandlers: Record<
PropertyKey,
{
mql: MediaQueryList;
listener: (this: MediaQueryList, ev: MediaQueryListEvent) => void;
}
>;
}
const useResponsiveObserver = () => {
const [, token] = useToken();
const responsiveMap: BreakpointMap = getResponsiveMap(validateBreakpoints(token));
const responsiveMap = getResponsiveMap(validateBreakpoints(token));
// To avoid repeat create instance, we add `useMemo` here.
return React.useMemo(() => {
return React.useMemo<ResponsiveObserverType>(() => {
const subscribers = new Map<number, SubscribeFunc>();
let subUid = -1;
let screens: Partial<Record<Breakpoint, boolean>> = {};
return {
matchHandlers: {} as {
[prop: string]: {
mql: MediaQueryList;
listener: (this: MediaQueryList, ev: MediaQueryListEvent) => void;
};
},
responsiveMap,
matchHandlers: {},
dispatch(pointMap: ScreenMap) {
screens = pointMap;
subscribers.forEach((func) => func(screens));
@ -98,44 +121,26 @@ export default function useResponsiveObserver() {
this.unregister();
}
},
unregister() {
Object.keys(responsiveMap).forEach((screen) => {
const matchMediaQuery = responsiveMap[screen as Breakpoint];
const handler = this.matchHandlers[matchMediaQuery];
handler?.mql.removeListener(handler?.listener);
});
subscribers.clear();
},
register() {
Object.keys(responsiveMap).forEach((screen) => {
const matchMediaQuery = responsiveMap[screen as Breakpoint];
Object.entries(responsiveMap).forEach(([screen, mediaQuery]) => {
const listener = ({ matches }: { matches: boolean }) => {
this.dispatch({
...screens,
[screen]: matches,
});
};
const mql = window.matchMedia(matchMediaQuery);
mql.addListener(listener);
this.matchHandlers[matchMediaQuery] = {
mql,
listener,
this.dispatch({ ...screens, [screen]: matches });
};
const mql = window.matchMedia(mediaQuery);
addMediaQueryListener(mql, listener);
this.matchHandlers[mediaQuery] = { mql, listener };
listener(mql);
});
},
responsiveMap,
unregister() {
Object.values(responsiveMap).forEach((mediaQuery) => {
const handler = this.matchHandlers[mediaQuery];
removeMediaQueryListener(handler?.mql, handler?.listener);
});
subscribers.clear();
},
};
}, [token]);
}
export const matchScreen = (screens: ScreenMap, screenSizes?: ScreenSizeMap) => {
if (screenSizes && typeof screenSizes === 'object') {
for (let i = 0; i < responsiveArray.length; i++) {
const breakpoint = responsiveArray[i];
if (screens[breakpoint] && screenSizes[breakpoint] !== undefined) {
return screenSizes[breakpoint];
}
}
}
};
export default useResponsiveObserver;

View File

@ -1,5 +1,8 @@
export default function toList<T>(candidate: T | T[], skipEmpty = false): T[] {
if (skipEmpty && (candidate === undefined || candidate === null)) return [];
const toList = <T>(candidate: T | T[], skipEmpty = false): T[] => {
if (skipEmpty && (candidate === undefined || candidate === null)) {
return [];
}
return Array.isArray(candidate) ? candidate : [candidate];
}
};
export default toList;

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