mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 17:44:35 +08:00
Merge branch 'feature' into feat/collapse-icon-vertical-position
This commit is contained in:
commit
ffeeb83972
80
.cursor/rules/demo.mdc
Normal file
80
.cursor/rules/demo.mdc
Normal 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
60
.cursor/rules/docs.mdc
Normal 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
128
.cursor/rules/git.mdc
Normal 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/xxx:Bug 修复
|
||||
- docs/xxx:文档更新
|
||||
- PR 说明中选择改动类型:
|
||||
- 🆕 新特性提交
|
||||
- 🐞 Bug 修复
|
||||
- 📝 文档改进
|
||||
- 📽️ 演示代码改进
|
||||
- 💄 样式/交互改进
|
||||
- 🤖 TypeScript 更新
|
||||
- 📦 包体积优化
|
||||
- ⚡️ 性能优化
|
||||
- 🌐 国际化改进
|
||||
- 提供改动背景和解决方案
|
||||
- 更新日志同时提供英文和中文版本
|
94
.cursor/rules/locale.mdc
Normal file
94
.cursor/rules/locale.mdc
Normal 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
108
.cursor/rules/naming.mdc
Normal 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
27
.cursor/rules/project.mdc
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# 项目背景
|
||||
|
||||
这是 ant-design/ant-design(antd)的源代码仓库,是一个 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
79
.cursor/rules/styling.mdc
Normal 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
11
.cursor/rules/testing.mdc
Normal 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
|
81
.cursor/rules/typescript.mdc
Normal file
81
.cursor/rules/typescript.mdc
Normal 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 注释说明用途
|
16
.devcontainer/devcontainer.json
Normal file
16
.devcontainer/devcontainer.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
@ -1,7 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
export const DarkContext = React.createContext(false);
|
||||
|
||||
export default function useDark() {
|
||||
return React.useContext(DarkContext);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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 />;
|
||||
|
@ -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'>[]>(
|
||||
() => [
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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' }}>
|
||||
|
@ -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}`,
|
||||
})),
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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}
|
||||
|
@ -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
89
.dumi/rehypeChangelog.ts
Normal 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
86
.dumi/remarkAnchor.ts
Normal 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;
|
@ -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 });
|
||||
|
23
.dumi/theme/builtins/Badge/index.tsx
Normal file
23
.dumi/theme/builtins/Badge/index.tsx
Normal 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 }}
|
||||
/>
|
||||
);
|
@ -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;
|
||||
|
@ -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>
|
||||
),
|
||||
},
|
||||
|
@ -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);
|
||||
|
@ -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 } }}>
|
||||
|
@ -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>
|
||||
)}
|
||||
/>
|
||||
|
@ -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%' }}>
|
||||
{' '}
|
||||
|
@ -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;
|
||||
|
@ -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 (
|
||||
|
@ -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>
|
||||
);
|
131
.dumi/theme/builtins/RefinedChangelog/index.tsx
Normal file
131
.dumi/theme/builtins/RefinedChangelog/index.tsx
Normal 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,
|
||||
});
|
@ -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;
|
||||
},
|
||||
|
87
.dumi/theme/common/BezierVisualizer/Visualizer.tsx
Normal file
87
.dumi/theme/common/BezierVisualizer/Visualizer.tsx
Normal 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;
|
55
.dumi/theme/common/BezierVisualizer/index.tsx
Normal file
55
.dumi/theme/common/BezierVisualizer/index.tsx
Normal 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;
|
@ -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(),
|
||||
|
@ -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={
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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);
|
||||
|
87
.dumi/theme/common/SelectSemanticTemplate.tsx
Normal file
87
.dumi/theme/common/SelectSemanticTemplate.tsx
Normal 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;
|
@ -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}
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -34,6 +34,8 @@ export default () => {
|
||||
> .icon-link::before {
|
||||
font-size: ${token.fontSizeXL}px;
|
||||
content: '#';
|
||||
color: ${token.colorTextSecondary};
|
||||
font-family: ${token.codeFamily};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
|
@ -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(() => {
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>;
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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": "更多",
|
||||
|
@ -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
|
||||
|
@ -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 && (
|
||||
<>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
@ -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) {
|
||||
|
@ -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>,
|
||||
];
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
52
.dumi/theme/utils/tsToJs.ts
Normal file
52
.dumi/theme/utils/tsToJs.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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() },
|
||||
|
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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
|
||||
|
||||
|
1
.github/PULL_REQUEST_TEMPLATE_CN.md
vendored
1
.github/PULL_REQUEST_TEMPLATE_CN.md
vendored
@ -25,6 +25,7 @@
|
||||
- [ ] ✅ 测试用例
|
||||
- [ ] 🔀 分支合并
|
||||
- [ ] ⏩ 工作流程
|
||||
- [ ] ⌨️ 无障碍改进
|
||||
- [ ] ❓ 其他改动(是关于什么的改动?)
|
||||
|
||||
### 🔗 相关 Issue
|
||||
|
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@ -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:
|
||||
|
91
.github/workflows/issue-inactivity-reminder.yml
vendored
91
.github/workflows/issue-inactivity-reminder.yml
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
|
4
.github/workflows/issue-labeled.yml
vendored
4
.github/workflows/issue-labeled.yml
vendored
@ -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)
|
||||
|
||||
|
4
.github/workflows/preview-deploy.yml
vendored
4
.github/workflows/preview-deploy.yml
vendored
@ -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 }}
|
||||
|
@ -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 }}
|
2
.github/workflows/site-deploy.yml
vendored
2
.github/workflows/site-deploy.yml
vendored
@ -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
|
||||
|
1
.github/workflows/verify-files-modify.yml
vendored
1
.github/workflows/verify-files-modify.yml
vendored
@ -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
|
||||
|
@ -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');
|
||||
|
||||
|
@ -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
4
.gitignore
vendored
@ -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
|
||||
|
14
.ncurc.js
14
.ncurc.js
@ -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`,
|
||||
};
|
||||
|
@ -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"]
|
||||
}
|
||||
|
@ -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)
|
||||
- 🛠 MISC:Refactor 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)
|
||||
|
@ -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)
|
||||
|
@ -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 的修复和改进,请查看我们的赞助列表:
|
||||
|
||||
[](https://issuehunt.io/repos/34526884)
|
||||
|
24
README.md
24
README.md
@ -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 [](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:
|
||||
|
||||
[](https://issuehunt.io/repos/34526884)
|
||||
|
@ -41,4 +41,9 @@ describe('unstable', () => {
|
||||
expect(document.querySelector('.ant-modal')).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it('unstableSetRender without param', async () => {
|
||||
const currentRender = unstableSetRender();
|
||||
expect(currentRender).toBeInstanceOf(Function);
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
20
components/_util/convertToTooltipProps.ts
Normal file
20
components/_util/convertToTooltipProps.ts
Normal 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;
|
@ -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;
|
||||
|
@ -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]);
|
||||
}
|
||||
|
107
components/_util/hooks/useMergeSemantic/index.ts
Normal file
107
components/_util/hooks/useMergeSemantic/index.ts
Normal 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]);
|
||||
}
|
27
components/_util/hooks/useMergeSemantic/interface.ts
Normal file
27
components/_util/hooks/useMergeSemantic/interface.ts
Normal 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';
|
@ -1,3 +0,0 @@
|
||||
const isNumeric = (value: any): boolean => !isNaN(parseFloat(value)) && isFinite(value);
|
||||
|
||||
export default isNumeric;
|
19
components/_util/mediaQueryUtil.ts
Normal file
19
components/_util/mediaQueryUtil.ts
Normal 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);
|
||||
}
|
||||
};
|
@ -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;
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user