mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-08 01:53:34 +08:00
Merge branch 'master' into fix/tooltip
This commit is contained in:
commit
a91aafc292
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';
|
import React from 'react';
|
||||||
|
|
||||||
export const DarkContext = React.createContext(false);
|
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 fetch from 'cross-fetch';
|
||||||
|
|
||||||
import use from '../use';
|
|
||||||
import FetchCache from './cache';
|
import FetchCache from './cache';
|
||||||
|
|
||||||
const cache = new FetchCache();
|
const cache = new FetchCache();
|
||||||
@ -15,7 +15,7 @@ const useFetch = <T>(options: string | { request: () => PromiseLike<T>; key: str
|
|||||||
request = options.request;
|
request = options.request;
|
||||||
key = options.key;
|
key = options.key;
|
||||||
}
|
}
|
||||||
return use(cache.promise<T>(key, request));
|
return React.use<T>(cache.promise<T>(key, request));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useFetch;
|
export default useFetch;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import type { MenuProps } from 'antd';
|
import type { MenuProps } from 'antd';
|
||||||
import { Space, Tag, version } from 'antd';
|
import { Flex, Tag, version } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useFullSidebarData, useSidebarData } from 'dumi';
|
import { useFullSidebarData, useSidebarData } from 'dumi';
|
||||||
@ -12,6 +12,22 @@ function isVersionNumber(value?: string) {
|
|||||||
return value && /^\d+\.\d+\.\d+$/.test(value);
|
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 }) => ({
|
const useStyle = createStyles(({ css, token }) => ({
|
||||||
link: css`
|
link: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -42,20 +58,17 @@ interface MenuItemLabelProps {
|
|||||||
const MenuItemLabelWithTag: React.FC<MenuItemLabelProps> = (props) => {
|
const MenuItemLabelWithTag: React.FC<MenuItemLabelProps> = (props) => {
|
||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
const { before, after, link, title, subtitle, search, tag, className } = props;
|
const { before, after, link, title, subtitle, search, tag, className } = props;
|
||||||
|
|
||||||
if (!before && !after) {
|
if (!before && !after) {
|
||||||
return (
|
return (
|
||||||
<Link to={`${link}${search}`} className={classnames(className, { [styles.link]: tag })}>
|
<Link to={`${link}${search}`} className={classnames(className, { [styles.link]: tag })}>
|
||||||
<Space>
|
<Flex justify="flex-start" align="center" gap="small">
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
|
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
|
||||||
</Space>
|
</Flex>
|
||||||
{tag && (
|
{tag && (
|
||||||
<Tag
|
<Tag bordered={false} className={classnames(styles.tag)} color={getTagColor(tag)}>
|
||||||
bordered={false}
|
{tag.replace(/VERSION/i, version)}
|
||||||
className={classnames(styles.tag)}
|
|
||||||
color={isVersionNumber(tag) || tag === 'New' ? 'success' : 'processing'}
|
|
||||||
>
|
|
||||||
{tag.replace('VERSION', version)}
|
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -64,7 +64,6 @@ const useThemeAnimation = () => {
|
|||||||
event: React.MouseEvent<HTMLElement, MouseEvent>,
|
event: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||||
isDark: boolean,
|
isDark: boolean,
|
||||||
) => {
|
) => {
|
||||||
// @ts-ignore
|
|
||||||
if (!(event && typeof document.startViewTransition === 'function')) {
|
if (!(event && typeof document.startViewTransition === 'function')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -85,13 +84,10 @@ const useThemeAnimation = () => {
|
|||||||
'color-scheme',
|
'color-scheme',
|
||||||
);
|
);
|
||||||
document
|
document
|
||||||
// @ts-ignore
|
|
||||||
.startViewTransition(async () => {
|
.startViewTransition(async () => {
|
||||||
// wait for theme change end
|
// wait for theme change end
|
||||||
while (colorBgElevated === animateRef.current.colorBgElevated) {
|
while (colorBgElevated === animateRef.current.colorBgElevated) {
|
||||||
await new Promise((resolve) => {
|
await new Promise<void>((resolve) => setTimeout(resolve, 1000 / 60));
|
||||||
setTimeout(resolve, 1000 / 60);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
root.classList.remove(isDark ? 'dark' : 'light');
|
root.classList.remove(isDark ? 'dark' : 'light');
|
||||||
@ -111,7 +107,6 @@ const useThemeAnimation = () => {
|
|||||||
|
|
||||||
// inject transition style
|
// inject transition style
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// @ts-ignore
|
|
||||||
if (typeof document.startViewTransition === 'function') {
|
if (typeof document.startViewTransition === 'function') {
|
||||||
updateCSS(viewTransitionStyle, 'view-transition-style');
|
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 { Badge, Carousel, Flex, Skeleton, Typography } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@ -110,10 +110,10 @@ const RecommendItem: React.FC<RecommendItemProps> = ({ extra, index, icons, clas
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const BannerRecommendsFallback: React.FC = () => {
|
export const BannerRecommendsFallback: React.FC = () => {
|
||||||
const { isMobile } = useContext(SiteContext);
|
const { isMobile } = React.use(SiteContext);
|
||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
|
|
||||||
const list = new Array(3).fill(1);
|
const list = Array.from({ length: 3 });
|
||||||
|
|
||||||
return isMobile ? (
|
return isMobile ? (
|
||||||
<Carousel className={styles.carousel}>
|
<Carousel className={styles.carousel}>
|
||||||
@ -137,11 +137,12 @@ export const BannerRecommendsFallback: React.FC = () => {
|
|||||||
const BannerRecommends: React.FC = () => {
|
const BannerRecommends: React.FC = () => {
|
||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
const [, lang] = useLocale();
|
const [, lang] = useLocale();
|
||||||
const { isMobile } = React.useContext(SiteContext);
|
const { isMobile } = React.use(SiteContext);
|
||||||
const data = useSiteData();
|
const data = useSiteData();
|
||||||
const extras = data?.extras?.[lang];
|
const extras = data?.extras?.[lang];
|
||||||
const icons = data?.icons || [];
|
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) {
|
if (!data) {
|
||||||
return <BannerRecommendsFallback />;
|
return <BannerRecommendsFallback />;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
@ -16,9 +16,9 @@ import { createStyles, css } from 'antd-style';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import useDark from '../../../hooks/useDark';
|
|
||||||
import useLocale from '../../../hooks/useLocale';
|
import useLocale from '../../../hooks/useLocale';
|
||||||
import SiteContext from '../../../theme/slots/SiteContext';
|
import SiteContext from '../../../theme/slots/SiteContext';
|
||||||
|
import { DarkContext } from './../../../hooks/useDark';
|
||||||
import { getCarouselStyle } from './util';
|
import { getCarouselStyle } from './util';
|
||||||
|
|
||||||
const { _InternalPanelDoNotUseOrYouWillBeFired: ModalDoNotUseOrYouWillBeFired } = Modal;
|
const { _InternalPanelDoNotUseOrYouWillBeFired: ModalDoNotUseOrYouWillBeFired } = Modal;
|
||||||
@ -61,66 +61,62 @@ const locales = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyle = () => {
|
const useStyle = createStyles(({ token }, isDark: boolean) => {
|
||||||
const isRootDark = useDark();
|
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;
|
flex: none;
|
||||||
overflow: hidden;
|
}
|
||||||
position: relative;
|
`,
|
||||||
display: flex;
|
cardCircle: css`
|
||||||
flex-direction: column;
|
position: absolute;
|
||||||
align-items: stretch;
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
> * {
|
background: #1677ff;
|
||||||
flex: none;
|
border-radius: 50%;
|
||||||
}
|
filter: blur(40px);
|
||||||
`,
|
opacity: 0.1;
|
||||||
cardCircle: css`
|
`,
|
||||||
position: absolute;
|
mobileCard: css`
|
||||||
width: 120px;
|
height: 395px;
|
||||||
height: 120px;
|
`,
|
||||||
background: #1677ff;
|
nodeWrap: css`
|
||||||
border-radius: 50%;
|
margin-top: ${token.paddingLG}px;
|
||||||
filter: blur(40px);
|
flex: auto;
|
||||||
opacity: 0.1;
|
display: flex;
|
||||||
`,
|
align-items: center;
|
||||||
mobileCard: css`
|
justify-content: center;
|
||||||
height: 395px;
|
`,
|
||||||
`,
|
carousel,
|
||||||
nodeWrap: css`
|
componentsList: css`
|
||||||
margin-top: ${token.paddingLG}px;
|
width: 100%;
|
||||||
flex: auto;
|
overflow: hidden;
|
||||||
display: flex;
|
`,
|
||||||
align-items: center;
|
mobileComponentsList: css`
|
||||||
justify-content: center;
|
margin: 0 ${token.margin}px;
|
||||||
`,
|
`,
|
||||||
carousel,
|
};
|
||||||
componentsList: css`
|
});
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
`,
|
|
||||||
mobileComponentsList: css`
|
|
||||||
margin: 0 ${token.margin}px;
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
};
|
|
||||||
|
|
||||||
const ComponentItem: React.FC<ComponentItemProps> = ({ title, node, type, index }) => {
|
const ComponentItem: React.FC<ComponentItemProps> = ({ title, node, type, index }) => {
|
||||||
const tagColor = type === 'new' ? 'processing' : 'warning';
|
const tagColor = type === 'new' ? 'processing' : 'warning';
|
||||||
const [locale] = useLocale(locales);
|
const [locale] = useLocale(locales);
|
||||||
const tagText = type === 'new' ? locale.new : locale.update;
|
const tagText = type === 'new' ? locale.new : locale.update;
|
||||||
const { styles } = useStyle();
|
const isDark = React.use(DarkContext);
|
||||||
const { isMobile } = useContext(SiteContext);
|
const { isMobile } = React.use(SiteContext);
|
||||||
|
const { styles } = useStyle(isDark);
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.card, isMobile && styles.mobileCard)}>
|
<div className={classNames(styles.card, isMobile && styles.mobileCard)}>
|
||||||
{/* Decorator */}
|
{/* Decorator */}
|
||||||
@ -151,7 +147,7 @@ interface ComponentItemProps {
|
|||||||
const ComponentsList: React.FC = () => {
|
const ComponentsList: React.FC = () => {
|
||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
const [locale] = useLocale(locales);
|
const [locale] = useLocale(locales);
|
||||||
const { isMobile } = useContext(SiteContext);
|
const { isMobile } = React.use(SiteContext);
|
||||||
const COMPONENTS = React.useMemo<Omit<ComponentItemProps, 'index'>[]>(
|
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 { Col, Row, Typography } from 'antd';
|
||||||
import { createStyles, useTheme } from 'antd-style';
|
import { createStyles, useTheme } from 'antd-style';
|
||||||
import { useLocation } from 'dumi';
|
import { useLocation } from 'dumi';
|
||||||
|
|
||||||
import useDark from '../../../hooks/useDark';
|
|
||||||
import useLocale from '../../../hooks/useLocale';
|
import useLocale from '../../../hooks/useLocale';
|
||||||
import Link from '../../../theme/common/Link';
|
import Link from '../../../theme/common/Link';
|
||||||
import SiteContext from '../../../theme/slots/SiteContext';
|
import SiteContext from '../../../theme/slots/SiteContext';
|
||||||
import * as utils from '../../../theme/utils';
|
import * as utils from '../../../theme/utils';
|
||||||
|
import { DarkContext } from './../../../hooks/useDark';
|
||||||
|
|
||||||
const SECONDARY_LIST = [
|
const SECONDARY_LIST = [
|
||||||
{
|
{
|
||||||
@ -15,6 +15,7 @@ const SECONDARY_LIST = [
|
|||||||
key: 'mobile',
|
key: 'mobile',
|
||||||
url: 'https://mobile.ant.design/',
|
url: 'https://mobile.ant.design/',
|
||||||
imgScale: 1.5,
|
imgScale: 1.5,
|
||||||
|
scaleOrigin: '15px',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
|
img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
|
||||||
@ -63,14 +64,12 @@ const locales = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyle = () => {
|
const useStyle = createStyles(({ token, css }, isDark: boolean) => {
|
||||||
const isRootDark = useDark();
|
return {
|
||||||
|
|
||||||
return createStyles(({ token, css }) => ({
|
|
||||||
card: css`
|
card: css`
|
||||||
padding: ${token.paddingSM}px;
|
padding: ${token.paddingSM}px;
|
||||||
border-radius: ${token.borderRadius * 2}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:
|
box-shadow:
|
||||||
0 1px 2px rgba(0, 0, 0, 0.03),
|
0 1px 2px rgba(0, 0, 0, 0.03),
|
||||||
0 1px 6px -1px rgba(0, 0, 0, 0.02),
|
0 1px 6px -1px rgba(0, 0, 0, 0.02),
|
||||||
@ -87,23 +86,25 @@ const useStyle = () => {
|
|||||||
display: block;
|
display: block;
|
||||||
border-radius: ${token.borderRadius * 2}px;
|
border-radius: ${token.borderRadius * 2}px;
|
||||||
padding: ${token.paddingMD}px ${token.paddingLG}px;
|
padding: ${token.paddingMD}px ${token.paddingLG}px;
|
||||||
background: ${isRootDark ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.02)'};
|
background: ${isDark ? '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)'};
|
border: 1px solid ${isDark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.06)'};
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
}))();
|
};
|
||||||
};
|
});
|
||||||
|
|
||||||
const DesignFramework: React.FC = () => {
|
const DesignFramework: React.FC = () => {
|
||||||
const [locale] = useLocale(locales);
|
const [locale] = useLocale(locales);
|
||||||
const token = useTheme();
|
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 { pathname, search } = useLocation();
|
||||||
const isZhCN = utils.isZhCN(pathname);
|
const isZhCN = utils.isZhCN(pathname);
|
||||||
const { isMobile } = useContext(SiteContext);
|
|
||||||
const colSpan = isMobile ? 24 : 8;
|
const colSpan = isMobile ? 24 : 8;
|
||||||
|
|
||||||
const MAINLY_LIST = [
|
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 title = locale[key as keyof typeof locale];
|
||||||
const desc = locale[`${key}Desc` as keyof typeof locale];
|
const desc = locale[`${key}Desc` as keyof typeof locale];
|
||||||
|
|
||||||
@ -162,7 +163,7 @@ const DesignFramework: React.FC = () => {
|
|||||||
draggable={false}
|
draggable={false}
|
||||||
alt={title}
|
alt={title}
|
||||||
src={img}
|
src={img}
|
||||||
style={{ transform: `scale(${imgScale})` }}
|
style={{ transform: `scale(${imgScale})`, transformOrigin: scaleOrigin }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Typography.Title
|
<Typography.Title
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useContext } from 'react';
|
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import { createStyles, useTheme } from 'antd-style';
|
import { createStyles, useTheme } from 'antd-style';
|
||||||
import classNames from 'classnames';
|
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 { id, title, titleColor, description, children, decoration, background, collapse } = props;
|
||||||
const token = useTheme();
|
const token = useTheme();
|
||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
const { isMobile } = useContext(SiteContext);
|
const { isMobile } = React.use(SiteContext);
|
||||||
const childNode = (
|
const childNode = (
|
||||||
<>
|
<>
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
@ -108,7 +108,7 @@ const ComponentsBlock: React.FC = () => {
|
|||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
|
|
||||||
return (
|
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%">
|
<ModalPanel title="Ant Design 5.0" width="100%">
|
||||||
{locale.text}
|
{locale.text}
|
||||||
</ModalPanel>
|
</ModalPanel>
|
||||||
@ -119,7 +119,7 @@ const ComponentsBlock: React.FC = () => {
|
|||||||
<div style={{ flex: 'none' }}>
|
<div style={{ flex: 'none' }}>
|
||||||
<Dropdown.Button
|
<Dropdown.Button
|
||||||
menu={{
|
menu={{
|
||||||
items: new Array(5).fill(null).map((_, index) => ({
|
items: Array.from({ length: 5 }).map((_, index) => ({
|
||||||
key: `opt${index}`,
|
key: `opt${index}`,
|
||||||
label: `${locale.option} ${index}`,
|
label: `${locale.option} ${index}`,
|
||||||
})),
|
})),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { Suspense } from 'react';
|
import React, { Suspense, use } from 'react';
|
||||||
import { ConfigProvider, Flex, Typography } from 'antd';
|
import { Flex, Typography } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useLocation } from 'dumi';
|
import { useLocation } from 'dumi';
|
||||||
@ -7,9 +7,12 @@ import { useLocation } from 'dumi';
|
|||||||
import useLocale from '../../../../hooks/useLocale';
|
import useLocale from '../../../../hooks/useLocale';
|
||||||
import LinkButton from '../../../../theme/common/LinkButton';
|
import LinkButton from '../../../../theme/common/LinkButton';
|
||||||
import SiteContext from '../../../../theme/slots/SiteContext';
|
import SiteContext from '../../../../theme/slots/SiteContext';
|
||||||
|
import type { SiteContextProps } from '../../../../theme/slots/SiteContext';
|
||||||
import * as utils from '../../../../theme/utils';
|
import * as utils from '../../../../theme/utils';
|
||||||
import GroupMaskLayer from '../GroupMaskLayer';
|
import GroupMaskLayer from '../GroupMaskLayer';
|
||||||
|
|
||||||
|
import '../SiteContext';
|
||||||
|
|
||||||
const ComponentsBlock = React.lazy(() => import('./ComponentsBlock'));
|
const ComponentsBlock = React.lazy(() => import('./ComponentsBlock'));
|
||||||
|
|
||||||
const locales = {
|
const locales = {
|
||||||
@ -26,105 +29,103 @@ const locales = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyle = () => {
|
const useStyle = createStyles(({ token, css, cx }, siteConfig: SiteContextProps) => {
|
||||||
const { direction } = React.useContext(ConfigProvider.ConfigContext);
|
const textShadow = `0 0 4px ${token.colorBgContainer}`;
|
||||||
const { isMobile } = React.useContext(SiteContext);
|
const isDark = siteConfig.theme.includes('dark');
|
||||||
const isRTL = direction === 'rtl';
|
const mask = cx(css`
|
||||||
return createStyles(({ token, css, cx }) => {
|
position: absolute;
|
||||||
const textShadow = `0 0 4px ${token.colorBgContainer}`;
|
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`
|
const block = cx(css`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset-inline-end: -60px;
|
||||||
backdrop-filter: blur(4px);
|
top: -24px;
|
||||||
opacity: 1;
|
transition: all 1s cubic-bezier(0.03, 0.98, 0.52, 0.99);
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
`);
|
||||||
transition: all 1s ease;
|
|
||||||
pointer-events: none;
|
|
||||||
`);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
holder: css`
|
holder: css`
|
||||||
height: 640px;
|
height: 640px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
perspective: 800px;
|
perspective: 800px;
|
||||||
/* fix safari bug by removing blur style */
|
/* fix safari bug by removing blur style */
|
||||||
transform: translateZ(1000px);
|
transform: translateZ(1000px);
|
||||||
row-gap: ${token.marginXL}px;
|
row-gap: ${token.marginXL}px;
|
||||||
|
|
||||||
&:hover .${mask} {
|
&:hover {
|
||||||
|
.${mask} {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
|
|
||||||
mask,
|
.${block} {
|
||||||
|
transform: scale(0.96);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
p {
|
mask,
|
||||||
font-size: ${token.fontSizeLG}px !important;
|
|
||||||
font-weight: normal !important;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
|
|
||||||
block: css`
|
typography: css`
|
||||||
position: absolute;
|
text-align: center;
|
||||||
inset-inline-end: 0;
|
position: relative;
|
||||||
top: -38px;
|
z-index: 1;
|
||||||
transform: ${isRTL ? 'rotate3d(24, 83, -45, 57deg)' : 'rotate3d(24, -83, 45, 57deg)'};
|
padding-inline: ${token.paddingXL}px;
|
||||||
`,
|
text-shadow: ${Array.from({ length: 5 }, () => textShadow).join(', ')};
|
||||||
child: css`
|
h1 {
|
||||||
position: relative;
|
font-family: AliPuHui, ${token.fontFamily} !important;
|
||||||
width: 100%;
|
font-weight: 900 !important;
|
||||||
max-width: 1200px;
|
font-size: ${token.fontSizeHeading2 * 2}px !important;
|
||||||
margin: 0 auto;
|
line-height: ${token.lineHeightHeading2} !important;
|
||||||
z-index: 1;
|
}
|
||||||
`,
|
|
||||||
btnWrap: css`
|
p {
|
||||||
margin-bottom: ${token.marginXL}px;
|
font-size: ${token.fontSizeLG}px !important;
|
||||||
`,
|
font-weight: normal !important;
|
||||||
bgImg: css`
|
margin-bottom: 0;
|
||||||
position: absolute;
|
}
|
||||||
width: 240px;
|
`,
|
||||||
`,
|
block,
|
||||||
bgImgTop: css`
|
child: css`
|
||||||
top: 0;
|
position: relative;
|
||||||
inset-inline-start: ${isMobile ? '-120px' : 0};
|
width: 100%;
|
||||||
`,
|
max-width: 1200px;
|
||||||
bgImgBottom: css`
|
margin: 0 auto;
|
||||||
bottom: 120px;
|
z-index: 1;
|
||||||
inset-inline-end: ${isMobile ? 0 : '40%'};
|
`,
|
||||||
`,
|
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 PreviewBanner: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
const [locale] = useLocale(locales);
|
const [locale] = useLocale(locales);
|
||||||
const { styles } = useStyle();
|
const siteConfig = use(SiteContext);
|
||||||
const { isMobile } = React.useContext(SiteContext);
|
const { styles } = useStyle(siteConfig);
|
||||||
const { pathname, search } = useLocation();
|
const { pathname, search } = useLocation();
|
||||||
const isZhCN = utils.isZhCN(pathname);
|
const isZhCN = utils.isZhCN(pathname);
|
||||||
|
|
||||||
@ -148,7 +149,7 @@ const PreviewBanner: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
|
|||||||
<div className={styles.holder}>
|
<div className={styles.holder}>
|
||||||
{/* Mobile not show the component preview */}
|
{/* Mobile not show the component preview */}
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
{isMobile ? null : (
|
{siteConfig.isMobile ? null : (
|
||||||
<div className={styles.block}>
|
<div className={styles.block}>
|
||||||
<ComponentsBlock />
|
<ComponentsBlock />
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,8 +4,6 @@ export interface SiteContextProps {
|
|||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SiteContext = React.createContext<SiteContextProps>({
|
const SiteContext = React.createContext<SiteContextProps>({ isMobile: false });
|
||||||
isMobile: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SiteContext;
|
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 * as React from 'react';
|
||||||
import { defaultAlgorithm, defaultTheme } from '@ant-design/compatible';
|
import { defaultAlgorithm, defaultTheme } from '@ant-design/compatible';
|
||||||
|
import { FastColor } from '@ant-design/fast-color';
|
||||||
import {
|
import {
|
||||||
BellOutlined,
|
BellOutlined,
|
||||||
FolderOutlined,
|
FolderOutlined,
|
||||||
HomeOutlined,
|
HomeOutlined,
|
||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { FastColor } from '@ant-design/fast-color';
|
|
||||||
import type { ColorPickerProps, GetProp, MenuProps, ThemeConfig } from 'antd';
|
import type { ColorPickerProps, GetProp, MenuProps, ThemeConfig } from 'antd';
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
@ -25,13 +26,13 @@ import { generateColor } from 'antd/es/color-picker/util';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useLocation } from 'dumi';
|
import { useLocation } from 'dumi';
|
||||||
|
|
||||||
import useDark from '../../../../hooks/useDark';
|
|
||||||
import useLocale from '../../../../hooks/useLocale';
|
import useLocale from '../../../../hooks/useLocale';
|
||||||
import LinkButton from '../../../../theme/common/LinkButton';
|
import LinkButton from '../../../../theme/common/LinkButton';
|
||||||
import SiteContext from '../../../../theme/slots/SiteContext';
|
import SiteContext from '../../../../theme/slots/SiteContext';
|
||||||
import { getLocalizedPathname } from '../../../../theme/utils';
|
import { getLocalizedPathname } from '../../../../theme/utils';
|
||||||
import Group from '../Group';
|
import Group from '../Group';
|
||||||
import { getCarouselStyle } from '../util';
|
import { getCarouselStyle } from '../util';
|
||||||
|
import { DarkContext } from './../../../../hooks/useDark';
|
||||||
import BackgroundImage from './BackgroundImage';
|
import BackgroundImage from './BackgroundImage';
|
||||||
import ColorPicker from './ColorPicker';
|
import ColorPicker from './ColorPicker';
|
||||||
import { DEFAULT_COLOR, getAvatarURL, getClosetColor, PINK_COLOR } from './colorUtil';
|
import { DEFAULT_COLOR, getAvatarURL, getClosetColor, PINK_COLOR } from './colorUtil';
|
||||||
@ -360,7 +361,7 @@ const Theme: React.FC = () => {
|
|||||||
const { compact, themeType, colorPrimary, ...themeToken } = themeData;
|
const { compact, themeType, colorPrimary, ...themeToken } = themeData;
|
||||||
const isLight = themeType !== 'dark';
|
const isLight = themeType !== 'dark';
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { isMobile } = React.useContext(SiteContext);
|
const { isMobile } = React.use(SiteContext);
|
||||||
const colorPrimaryValue = React.useMemo(
|
const colorPrimaryValue = React.useMemo(
|
||||||
() => (typeof colorPrimary === 'string' ? colorPrimary : colorPrimary.toHexString()),
|
() => (typeof colorPrimary === 'string' ? colorPrimary : colorPrimary.toHexString()),
|
||||||
[colorPrimary],
|
[colorPrimary],
|
||||||
@ -393,11 +394,11 @@ const Theme: React.FC = () => {
|
|||||||
form.setFieldsValue(mergedData);
|
form.setFieldsValue(mergedData);
|
||||||
}, [themeType]);
|
}, [themeType]);
|
||||||
|
|
||||||
const isRootDark = useDark();
|
const isDark = React.use(DarkContext);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
onThemeChange({}, { ...themeData, themeType: isRootDark ? 'dark' : 'default' });
|
onThemeChange({}, { ...themeData, themeType: isDark ? 'dark' : 'default' });
|
||||||
}, [isRootDark]);
|
}, [isDark]);
|
||||||
|
|
||||||
// ================================ Tokens ================================
|
// ================================ Tokens ================================
|
||||||
const closestColor = getClosetColor(colorPrimaryValue);
|
const closestColor = getClosetColor(colorPrimaryValue);
|
||||||
|
@ -2,8 +2,8 @@ import React, { Suspense } from 'react';
|
|||||||
import { ConfigProvider, theme } from 'antd';
|
import { ConfigProvider, theme } from 'antd';
|
||||||
import { createStyles, css } from 'antd-style';
|
import { createStyles, css } from 'antd-style';
|
||||||
|
|
||||||
import useDark from '../../hooks/useDark';
|
|
||||||
import useLocale from '../../hooks/useLocale';
|
import useLocale from '../../hooks/useLocale';
|
||||||
|
import { DarkContext } from './../../hooks/useDark';
|
||||||
import BannerRecommends from './components/BannerRecommends';
|
import BannerRecommends from './components/BannerRecommends';
|
||||||
import Group from './components/Group';
|
import Group from './components/Group';
|
||||||
import PreviewBanner from './components/PreviewBanner';
|
import PreviewBanner from './components/PreviewBanner';
|
||||||
@ -41,7 +41,7 @@ const Homepage: React.FC = () => {
|
|||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
|
|
||||||
const isRootDark = useDark();
|
const isDark = React.use(DarkContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
@ -78,7 +78,7 @@ const Homepage: React.FC = () => {
|
|||||||
<Group
|
<Group
|
||||||
title={locale.designTitle}
|
title={locale.designTitle}
|
||||||
description={locale.designDesc}
|
description={locale.designDesc}
|
||||||
background={isRootDark ? '#393F4A' : '#F5F8FF'}
|
background={isDark ? '#393F4A' : '#F5F8FF'}
|
||||||
decoration={
|
decoration={
|
||||||
<img
|
<img
|
||||||
draggable={false}
|
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 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 { enUS, zhCN } from 'antd-token-previewer';
|
||||||
import type { ThemeConfig } from 'antd/es/config-provider/context';
|
import type { ThemeConfig } from 'antd/es/config-provider/context';
|
||||||
import { Helmet } from 'dumi';
|
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,176 +0,0 @@
|
|||||||
(function createMirrorModal() {
|
|
||||||
if (
|
|
||||||
(navigator.languages.includes('zh') || navigator.languages.includes('zh-CN')) &&
|
|
||||||
/-cn\/?$/.test(window.location.pathname) &&
|
|
||||||
!['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(window.location.hostname) &&
|
|
||||||
!window.location.host.includes('surge') &&
|
|
||||||
window.location.hostname !== 'localhost'
|
|
||||||
) {
|
|
||||||
const ANTD_DOT_NOT_SHOW_MIRROR_MODAL = 'ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL';
|
|
||||||
|
|
||||||
const lastShowTime = window.localStorage.getItem(ANTD_DOT_NOT_SHOW_MIRROR_MODAL);
|
|
||||||
if (
|
|
||||||
lastShowTime &&
|
|
||||||
lastShowTime !== 'true' &&
|
|
||||||
Date.now() - new Date(lastShowTime).getTime() < 7 * 24 * 60 * 60 * 1000
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.innerHTML = `
|
|
||||||
@keyframes mirror-fade-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes mirror-zoom-in {
|
|
||||||
from {
|
|
||||||
transform: scale(0.8);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-mask {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
z-index: 9999;
|
|
||||||
animation: mirror-fade-in 0.3s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-dialog {
|
|
||||||
position: fixed;
|
|
||||||
top: 120px;
|
|
||||||
inset-inline-start: 0;
|
|
||||||
inset-inline-end: 0;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 420px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
background: #fff;
|
|
||||||
padding: 20px 24px;
|
|
||||||
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
|
||||||
animation: mirror-zoom-in 0.3s forwards;
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-width: 100vw;
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
align-self: flex-start;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-content {
|
|
||||||
font-size: 14px;
|
|
||||||
align-self: flex-start;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-btns {
|
|
||||||
align-self: flex-end;
|
|
||||||
margin-top: auto;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-btn {
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
height: 32px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 4px 16px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-confirm-btn {
|
|
||||||
background: #1677ff;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-confirm-btn:hover {
|
|
||||||
background: #4096ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-confirm-btn:active {
|
|
||||||
background: #0958d9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-cancel-btn {
|
|
||||||
border: 1px solid #eee;
|
|
||||||
color: #000;
|
|
||||||
margin-inline-end: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-cancel-btn:hover {
|
|
||||||
border-color: #4096ff;
|
|
||||||
color: #4096ff
|
|
||||||
}
|
|
||||||
|
|
||||||
.mirror-modal-cancel-btn:active {
|
|
||||||
border-color: #0958d9;
|
|
||||||
color: #0958d9;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.append(style);
|
|
||||||
|
|
||||||
const modal = document.createElement('div');
|
|
||||||
modal.className = 'mirror-modal-mask';
|
|
||||||
|
|
||||||
const dialog = document.createElement('div');
|
|
||||||
dialog.className = 'mirror-modal-dialog';
|
|
||||||
modal.append(dialog);
|
|
||||||
|
|
||||||
const title = document.createElement('div');
|
|
||||||
title.className = 'mirror-modal-title';
|
|
||||||
title.textContent = '提示';
|
|
||||||
dialog.append(title);
|
|
||||||
|
|
||||||
const content = document.createElement('div');
|
|
||||||
content.className = 'mirror-modal-content';
|
|
||||||
content.textContent = '🚀 国内用户推荐访问国内镜像以获得极速体验~';
|
|
||||||
dialog.append(content);
|
|
||||||
|
|
||||||
const btnWrapper = document.createElement('div');
|
|
||||||
btnWrapper.className = 'mirror-modal-btns';
|
|
||||||
dialog.append(btnWrapper);
|
|
||||||
|
|
||||||
const cancelBtn = document.createElement('a');
|
|
||||||
cancelBtn.className = 'mirror-modal-cancel-btn mirror-modal-btn';
|
|
||||||
cancelBtn.textContent = '7 天内不再显示';
|
|
||||||
btnWrapper.append(cancelBtn);
|
|
||||||
cancelBtn.addEventListener('click', () => {
|
|
||||||
window.localStorage.setItem(ANTD_DOT_NOT_SHOW_MIRROR_MODAL, new Date().toISOString());
|
|
||||||
document.body.removeChild(modal);
|
|
||||||
document.head.removeChild(style);
|
|
||||||
document.body.style.overflow = '';
|
|
||||||
});
|
|
||||||
|
|
||||||
const confirmBtn = document.createElement('a');
|
|
||||||
confirmBtn.className = 'mirror-modal-confirm-btn mirror-modal-btn';
|
|
||||||
confirmBtn.href = window.location.href.replace(window.location.host, 'ant-design.antgroup.com');
|
|
||||||
confirmBtn.textContent = '🚀 立刻前往';
|
|
||||||
btnWrapper.append(confirmBtn);
|
|
||||||
|
|
||||||
document.body.append(modal);
|
|
||||||
document.body.style.overflow = 'hidden';
|
|
||||||
}
|
|
||||||
})();
|
|
230
.dumi/scripts/mirror-notify.js
Normal file
230
.dumi/scripts/mirror-notify.js
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
(function createMirrorModal() {
|
||||||
|
const SIGN = Symbol.for('antd.mirror-notify');
|
||||||
|
const always = window.localStorage.getItem('DEBUG') === 'antd';
|
||||||
|
const officialChinaMirror = 'https://ant-design.antgroup.com';
|
||||||
|
|
||||||
|
const enabledCondition = [
|
||||||
|
// Check if the browser language is Chinese
|
||||||
|
navigator.languages.includes('zh') || navigator.languages.includes('zh-CN'),
|
||||||
|
// Check if the URL path ends with -cn
|
||||||
|
/-cn\/?$/.test(window.location.pathname),
|
||||||
|
// chinese mirror URL
|
||||||
|
!['ant-design.gitee.io', new URL(officialChinaMirror).hostname].includes(
|
||||||
|
window.location.hostname,
|
||||||
|
),
|
||||||
|
// PR review URL
|
||||||
|
!window.location.host.includes('surge'),
|
||||||
|
// development mode
|
||||||
|
!['127.0.0.1', 'localhost'].includes(window.location.hostname),
|
||||||
|
];
|
||||||
|
|
||||||
|
const isEnabled = always || enabledCondition.every(Boolean);
|
||||||
|
|
||||||
|
if (!isEnabled) return;
|
||||||
|
|
||||||
|
const prefixCls = 'antd-mirror-notify';
|
||||||
|
const primaryColor = '#1677ff';
|
||||||
|
|
||||||
|
function insertCss() {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.innerHTML = `
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translate3d(100%, 0, 0);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.${prefixCls} {
|
||||||
|
position: fixed;
|
||||||
|
inset-inline-end: 12px;
|
||||||
|
inset-block-start: 12px;
|
||||||
|
z-index: 9999;
|
||||||
|
width: 360px;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: slideInRight 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
.${prefixCls}-content {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.${prefixCls}-content a {
|
||||||
|
color: ${primaryColor};
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.${prefixCls}-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-block-end: 8px;
|
||||||
|
}
|
||||||
|
.${prefixCls}-message {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.57;
|
||||||
|
}
|
||||||
|
.${prefixCls}-footer {
|
||||||
|
display: none;
|
||||||
|
margin-block-start: 16px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${prefixCls}-progress {
|
||||||
|
position: relative;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${prefixCls}-progress::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: var(--progress, 0%);
|
||||||
|
background-color: ${primaryColor};
|
||||||
|
transition: width 0.05s linear; /* Adjusted for smoother animation matching refreshRate */
|
||||||
|
}
|
||||||
|
.${prefixCls}-close {
|
||||||
|
all: unset;
|
||||||
|
position: absolute;
|
||||||
|
inset-inline-end: 2px;
|
||||||
|
inset-block-start: 2px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${prefixCls}-close:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.${prefixCls}-action {
|
||||||
|
all: unset;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
background-color: ${primaryColor};
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.head.append(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNotification() {
|
||||||
|
insertCss();
|
||||||
|
|
||||||
|
const notify = document.createElement('div');
|
||||||
|
notify.className = `${prefixCls} slideInRight`;
|
||||||
|
notify.innerHTML = `
|
||||||
|
<div class="${prefixCls}-content">
|
||||||
|
<div class="${prefixCls}-title">🇨🇳 访问不畅?试试国内镜像</div>
|
||||||
|
<div class="${prefixCls}-message">
|
||||||
|
国内镜像站点可以帮助您更快地访问文档和资源。<br>
|
||||||
|
请尝试访问 <a href="${officialChinaMirror}">国内镜像站点</a>,以获得更好的体验。
|
||||||
|
</div>
|
||||||
|
<div class="${prefixCls}-footer">
|
||||||
|
<button class="${prefixCls}-action">🚀 立即前往</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="${prefixCls}-close">X</button>
|
||||||
|
<div class="${prefixCls}-progress" style="--progress: 100%;"></div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(notify);
|
||||||
|
|
||||||
|
notify.querySelector(`.${prefixCls}-close`).addEventListener('click', () => {
|
||||||
|
removeNotify();
|
||||||
|
});
|
||||||
|
|
||||||
|
notify.querySelector(`.${prefixCls}-action`).addEventListener('click', () => {
|
||||||
|
window.location.href = officialChinaMirror;
|
||||||
|
removeNotify();
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshRate = 50; // ms
|
||||||
|
const duration = 10; // s
|
||||||
|
const step = 100 / ((duration * 1000) / refreshRate);
|
||||||
|
let progressInterval = -1;
|
||||||
|
|
||||||
|
function removeNotify() {
|
||||||
|
clearInterval(progressInterval);
|
||||||
|
notify.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const progressEl = notify.querySelector(`.${prefixCls}-progress`);
|
||||||
|
let currentProgressValue = 100;
|
||||||
|
|
||||||
|
const progress = {
|
||||||
|
get value() {
|
||||||
|
return currentProgressValue;
|
||||||
|
},
|
||||||
|
set value(val) {
|
||||||
|
currentProgressValue = Math.max(0, Math.min(100, val));
|
||||||
|
progressEl.style.setProperty('--progress', `${currentProgressValue}%`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function startProgressTimer() {
|
||||||
|
if (progressInterval !== -1) {
|
||||||
|
clearInterval(progressInterval);
|
||||||
|
}
|
||||||
|
progressInterval = setInterval(() => {
|
||||||
|
if (progress.value <= 0) {
|
||||||
|
removeNotify();
|
||||||
|
} else {
|
||||||
|
progress.value -= step;
|
||||||
|
}
|
||||||
|
}, refreshRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
startProgressTimer();
|
||||||
|
|
||||||
|
notify.addEventListener('mouseenter', () => {
|
||||||
|
clearInterval(progressInterval);
|
||||||
|
});
|
||||||
|
|
||||||
|
notify.addEventListener('mouseleave', () => {
|
||||||
|
startProgressTimer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 断定网络不畅阈值(秒)
|
||||||
|
const delayDuration = 3;
|
||||||
|
|
||||||
|
const reactTimeoutId = setTimeout(() => {
|
||||||
|
if (typeof window[SIGN]?.YES === 'undefined') {
|
||||||
|
console.error(
|
||||||
|
`antd.mirror-notify: 页面加载超过 ${delayDuration} 秒,可能是网络不畅。\n请尝试访问国内镜像站点。%c${officialChinaMirror}`,
|
||||||
|
`color: ${primaryColor}; font-weight: bold;`,
|
||||||
|
);
|
||||||
|
createNotification();
|
||||||
|
}
|
||||||
|
}, delayDuration * 1000);
|
||||||
|
|
||||||
|
// 交给 React effect 清理
|
||||||
|
window[SIGN] = function stopMirrorNotify() {
|
||||||
|
window[SIGN].YES = Date.now();
|
||||||
|
clearTimeout(reactTimeoutId);
|
||||||
|
};
|
||||||
|
})();
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import { theme as antdTheme, ConfigProvider } from 'antd';
|
import { theme as antdTheme, ConfigProvider } from 'antd';
|
||||||
import type { ThemeConfig } from 'antd';
|
import type { ThemeConfig } from 'antd';
|
||||||
import type { ThemeProviderProps } from 'antd-style';
|
import type { ThemeProviderProps } from 'antd-style';
|
||||||
@ -31,10 +31,10 @@ const headerHeight = 64;
|
|||||||
const bannerHeight = 38;
|
const bannerHeight = 38;
|
||||||
|
|
||||||
const SiteThemeProvider: React.FC<ThemeProviderProps<any>> = ({ children, theme, ...rest }) => {
|
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 rootPrefixCls = getPrefixCls();
|
||||||
const { token } = antdTheme.useToken();
|
const { token } = antdTheme.useToken();
|
||||||
const { bannerVisible } = useContext(SiteContext);
|
const { bannerVisible } = React.use(SiteContext);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// 需要注意与 components/config-provider/demo/holderRender.tsx 配置冲突
|
// 需要注意与 components/config-provider/demo/holderRender.tsx 配置冲突
|
||||||
ConfigProvider.config({ theme: theme as ThemeConfig });
|
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 * as React from 'react';
|
||||||
// @ts-ignore
|
import { FastColor } from '@ant-design/fast-color';
|
||||||
import { TinyColor } from 'dumi-plugin-color-chunk/component';
|
import type { ColorInput } from '@ant-design/fast-color';
|
||||||
|
import { Popover } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
|
|
||||||
const useStyle = createStyles(({ token, css }) => ({
|
const useStyle = createStyles(({ token, css }) => ({
|
||||||
@ -22,24 +23,47 @@ const useStyle = createStyles(({ token, css }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
interface ColorChunkProps {
|
interface ColorChunkProps {
|
||||||
value: any;
|
value: ColorInput;
|
||||||
|
enablePopover?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ColorChunk: React.FC<React.PropsWithChildren<ColorChunkProps>> = (props) => {
|
const ColorChunk: React.FC<React.PropsWithChildren<ColorChunkProps>> = (props) => {
|
||||||
const { styles } = useStyle();
|
const { styles, theme } = useStyle();
|
||||||
const { value, children } = props;
|
const { value, children, enablePopover } = props;
|
||||||
|
|
||||||
const dotColor = React.useMemo(() => {
|
const dotColor = React.useMemo(() => new FastColor(value).toHexString(), [value]);
|
||||||
const _color = new TinyColor(value).toHex8String();
|
|
||||||
return _color.endsWith('ff') ? _color.slice(0, -2) : _color;
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return (
|
let dotNode = (
|
||||||
<span className={styles.codeSpan}>
|
<span className={styles.codeSpan}>
|
||||||
<span className={styles.dot} style={{ backgroundColor: dotColor }} />
|
<span className={styles.dot} style={{ backgroundColor: dotColor }} />
|
||||||
{children ?? dotColor}
|
{children ?? dotColor}
|
||||||
</span>
|
</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;
|
export default ColorChunk;
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
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 type { GetProp } from 'antd';
|
||||||
import { Descriptions, Flex, theme, Tooltip, Typography } from 'antd';
|
import { Descriptions, Flex, theme, Tooltip, Typography } from 'antd';
|
||||||
import { createStyles, css } from 'antd-style';
|
import { createStyles, css } from 'antd-style';
|
||||||
import kebabCase from 'lodash/kebabCase';
|
import kebabCase from 'lodash/kebabCase';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
|
import Link from '../../common/Link';
|
||||||
|
|
||||||
import useLocale from '../../../hooks/useLocale';
|
import useLocale from '../../../hooks/useLocale';
|
||||||
import ComponentChangelog from '../../common/ComponentChangelog';
|
import ComponentChangelog from '../../common/ComponentChangelog';
|
||||||
@ -18,6 +19,7 @@ const locales = {
|
|||||||
docs: '文档',
|
docs: '文档',
|
||||||
edit: '编辑此页',
|
edit: '编辑此页',
|
||||||
changelog: '更新日志',
|
changelog: '更新日志',
|
||||||
|
design: '设计指南',
|
||||||
version: '版本',
|
version: '版本',
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
@ -28,6 +30,7 @@ const locales = {
|
|||||||
docs: 'Docs',
|
docs: 'Docs',
|
||||||
edit: 'Edit this page',
|
edit: 'Edit this page',
|
||||||
changelog: 'Changelog',
|
changelog: 'Changelog',
|
||||||
|
design: 'Design',
|
||||||
version: 'Version',
|
version: 'Version',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -57,24 +60,8 @@ const useStyle = createStyles(({ token }) => ({
|
|||||||
text-decoration: underline !important;
|
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`
|
icon: css`
|
||||||
margin-inline-end: ${token.marginXXS}px;
|
margin-inline-end: 3px;
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -83,10 +70,11 @@ export interface ComponentMetaProps {
|
|||||||
source: string | true;
|
source: string | true;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
|
designUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||||
const { component, source, filename, version } = props;
|
const { component, source, filename, version, designUrl } = props;
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
const [locale, lang] = useLocale(locales);
|
const [locale, lang] = useLocale(locales);
|
||||||
const isZhCN = lang === 'cn';
|
const isZhCN = lang === 'cn';
|
||||||
@ -130,23 +118,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ======================== Render ========================
|
// ======================== Render ========================
|
||||||
const importList = [
|
const importList = `import { ${transformComponentName(component)} } from "antd";`;
|
||||||
<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>,
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Descriptions
|
<Descriptions
|
||||||
@ -187,7 +159,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
|||||||
filename && {
|
filename && {
|
||||||
label: locale.docs,
|
label: locale.docs,
|
||||||
children: (
|
children: (
|
||||||
<Flex justify="flex-start" align="center" gap="middle">
|
<Flex justify="flex-start" align="center" gap="small">
|
||||||
<Typography.Link
|
<Typography.Link
|
||||||
className={styles.code}
|
className={styles.code}
|
||||||
href={`${branchUrl}${filename}`}
|
href={`${branchUrl}${filename}`}
|
||||||
@ -196,6 +168,12 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
|||||||
<EditOutlined className={styles.icon} />
|
<EditOutlined className={styles.icon} />
|
||||||
<span>{locale.edit}</span>
|
<span>{locale.edit}</span>
|
||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
|
{designUrl && (
|
||||||
|
<Link className={styles.code} to={designUrl}>
|
||||||
|
<CompassOutlined className={styles.icon} />
|
||||||
|
<span>{locale.design}</span>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
<ComponentChangelog>
|
<ComponentChangelog>
|
||||||
<Typography.Link className={styles.code}>
|
<Typography.Link className={styles.code}>
|
||||||
<HistoryOutlined className={styles.icon} />
|
<HistoryOutlined className={styles.icon} />
|
||||||
@ -209,7 +187,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
|||||||
label: locale.version,
|
label: locale.version,
|
||||||
children: (
|
children: (
|
||||||
<Typography.Text className={styles.code}>
|
<Typography.Text className={styles.code}>
|
||||||
{isZhCN ? `自 ${version} 后支持` : `supported since ${version}`}
|
{isZhCN ? `自 ${version} 起支持` : `supported since ${version}`}
|
||||||
</Typography.Text>
|
</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 type { CSSProperties } from 'react';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import { Affix, Card, Col, Divider, Flex, Input, Row, Tag, Typography } from 'antd';
|
import { Affix, Card, Col, Divider, Flex, Input, Row, Tag, Typography } from 'antd';
|
||||||
@ -83,7 +83,7 @@ const { Title } = Typography;
|
|||||||
|
|
||||||
const Overview: React.FC = () => {
|
const Overview: React.FC = () => {
|
||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
const { theme } = useContext(SiteContext);
|
const { theme } = React.use(SiteContext);
|
||||||
|
|
||||||
const data = useSidebarData();
|
const data = useSidebarData();
|
||||||
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean>(false);
|
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean>(false);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { LinkOutlined, QuestionCircleOutlined, RightOutlined } from '@ant-design/icons';
|
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 { createStyles, css, useTheme } from 'antd-style';
|
||||||
import { getDesignToken } from 'antd-token-previewer';
|
import { getDesignToken } from 'antd-token-previewer';
|
||||||
import tokenMeta from 'antd/es/version/token-meta.json';
|
import tokenMeta from 'antd/es/version/token-meta.json';
|
||||||
@ -61,16 +61,15 @@ const useStyle = createStyles(({ token }) => ({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
|
gap: ${token.marginXS}px;
|
||||||
`,
|
`,
|
||||||
arrowIcon: css`
|
arrowIcon: css`
|
||||||
font-size: ${token.fontSizeLG}px;
|
font-size: ${token.fontSizeLG}px;
|
||||||
margin-inline-end: ${token.marginXS}px;
|
|
||||||
& svg {
|
& svg {
|
||||||
transition: all ${token.motionDurationSlow};
|
transition: all ${token.motionDurationSlow};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
help: css`
|
help: css`
|
||||||
margin-inline-start: ${token.marginXS}px;
|
|
||||||
font-size: ${token.fontSizeSM}px;
|
font-size: ${token.fontSizeSM}px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: #999;
|
color: #999;
|
||||||
@ -78,6 +77,10 @@ const useStyle = createStyles(({ token }) => ({
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
tokenTitle: css`
|
||||||
|
font-size: ${token.fontSizeLG}px;
|
||||||
|
font-weight: bold;
|
||||||
|
`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface SubTokenTableProps {
|
interface SubTokenTableProps {
|
||||||
@ -153,15 +156,17 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
|
|||||||
<>
|
<>
|
||||||
<div className={styles.tableTitle} onClick={() => setOpen(!open)}>
|
<div className={styles.tableTitle} onClick={() => setOpen(!open)}>
|
||||||
<RightOutlined className={styles.arrowIcon} rotate={open ? 90 : 0} />
|
<RightOutlined className={styles.arrowIcon} rotate={open ? 90 : 0} />
|
||||||
<h3>
|
<Flex className={styles.tokenTitle} gap="small" justify="flex-start" align="center">
|
||||||
{title}
|
{title}
|
||||||
<Popover
|
<Popover
|
||||||
title={null}
|
title={null}
|
||||||
|
destroyOnHidden
|
||||||
styles={{ root: { width: 400 } }}
|
styles={{ root: { width: 400 } }}
|
||||||
content={
|
content={
|
||||||
<Typography>
|
<Typography>
|
||||||
{/* <SourceCode lang="jsx">{code}</SourceCode> */}
|
<pre dir="ltr" style={{ fontSize: 12 }}>
|
||||||
<pre style={{ fontSize: 12 }}>{code}</pre>
|
<code dir="ltr">{code}</code>
|
||||||
|
</pre>
|
||||||
<a href={helpLink} target="_blank" rel="noreferrer">
|
<a href={helpLink} target="_blank" rel="noreferrer">
|
||||||
<LinkOutlined style={{ marginInlineEnd: 4 }} />
|
<LinkOutlined style={{ marginInlineEnd: 4 }} />
|
||||||
{helpText}
|
{helpText}
|
||||||
@ -174,7 +179,7 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
|
|||||||
{helpText}
|
{helpText}
|
||||||
</span>
|
</span>
|
||||||
</Popover>
|
</Popover>
|
||||||
</h3>
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
{open && (
|
{open && (
|
||||||
<ConfigProvider theme={{ token: { borderRadius: 0 } }}>
|
<ConfigProvider theme={{ token: { borderRadius: 0 } }}>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { Suspense, useContext } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons';
|
import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons';
|
||||||
import { ConfigProvider, Tooltip, Button } from 'antd';
|
|
||||||
import { DumiDemoGrid, FormattedMessage, DumiDemo } from 'dumi';
|
|
||||||
import { css, Global } from '@emotion/react';
|
import { css, Global } from '@emotion/react';
|
||||||
|
import { Button, ConfigProvider, Tooltip } from 'antd';
|
||||||
|
import { DumiDemo, DumiDemoGrid, FormattedMessage } from 'dumi';
|
||||||
|
|
||||||
import useLayoutState from '../../../hooks/useLayoutState';
|
import useLayoutState from '../../../hooks/useLayoutState';
|
||||||
import useLocale from '../../../hooks/useLocale';
|
import useLocale from '../../../hooks/useLocale';
|
||||||
@ -21,7 +21,7 @@ const locales = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
|
const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
|
||||||
const { showDebug, setShowDebug } = useContext(DemoContext);
|
const { showDebug, setShowDebug } = React.use(DemoContext);
|
||||||
const [locale] = useLocale(locales);
|
const [locale] = useLocale(locales);
|
||||||
|
|
||||||
const [expandAll, setExpandAll] = useLayoutState(false);
|
const [expandAll, setExpandAll] = useLayoutState(false);
|
||||||
|
@ -44,7 +44,7 @@ const IconSearchFallback: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Skeleton.Button active style={{ margin: '28px 0 10px', width: 100 }} />
|
<Skeleton.Button active style={{ margin: '28px 0 10px', width: 100 }} />
|
||||||
<div className={styles.fallbackWrapper}>
|
<div className={styles.fallbackWrapper}>
|
||||||
{new Array(24).fill(1).map((_, index) => (
|
{Array.from({ length: 24 }).map((_, index) => (
|
||||||
<div key={index} className={styles.skeletonWrapper}>
|
<div key={index} className={styles.skeletonWrapper}>
|
||||||
<Skeleton.Node active style={{ height: 110, width: '100%' }}>
|
<Skeleton.Node active style={{ height: 110, width: '100%' }}>
|
||||||
{' '}
|
{' '}
|
||||||
|
@ -1,41 +1,8 @@
|
|||||||
import React, { Suspense, useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip, App } from 'antd';
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { FormattedMessage } from 'dumi';
|
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 {
|
interface CodeBlockButtonProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
dependencies: Record<PropertyKey, string>;
|
dependencies: Record<PropertyKey, string>;
|
||||||
@ -43,7 +10,8 @@ interface CodeBlockButtonProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies = {}, jsx }) => {
|
const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies = {}, jsx }) => {
|
||||||
const showCodeBlockButton = useShowCodeBlockButton();
|
const { message } = App.useApp();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const codeBlockPrefillConfig = {
|
const codeBlockPrefillConfig = {
|
||||||
title: `${title} - antd@${dependencies.antd}`,
|
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),
|
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" />}>
|
<Tooltip title={<FormattedMessage id="app.demo.codeblock" />}>
|
||||||
<div className="code-box-code-action">
|
<div className="code-box-code-action">
|
||||||
<img
|
{loading ? (
|
||||||
alt="codeblock"
|
<LoadingOutlined className="code-box-codeblock" />
|
||||||
src="https://mdn.alipayobjects.com/huamei_wtld8u/afts/img/A*K8rjSJpTNQ8AAAAAAAAAAAAADhOIAQ/original"
|
) : (
|
||||||
className="code-box-codeblock"
|
<img
|
||||||
onClick={() => {
|
alt="codeblock"
|
||||||
openHituCodeBlock(JSON.stringify(codeBlockPrefillConfig));
|
src="https://mdn.alipayobjects.com/huamei_wtld8u/afts/img/A*K8rjSJpTNQ8AAAAAAAAAAAAADhOIAQ/original"
|
||||||
}}
|
className="code-box-codeblock"
|
||||||
/>
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null;
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (props: CodeBlockButtonProps) => (
|
export default CodeBlockButton;
|
||||||
<Suspense>
|
|
||||||
<CodeBlockButton {...props} />
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
|
@ -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 { LinkOutlined, ThunderboltOutlined, UpOutlined } from '@ant-design/icons';
|
||||||
import type { Project } from '@stackblitz/sdk';
|
import type { Project } from '@stackblitz/sdk';
|
||||||
import stackblitzSdk from '@stackblitz/sdk';
|
import stackblitzSdk from '@stackblitz/sdk';
|
||||||
@ -17,7 +18,6 @@ import CodePenIcon from '../../icons/CodePenIcon';
|
|||||||
import CodeSandboxIcon from '../../icons/CodeSandboxIcon';
|
import CodeSandboxIcon from '../../icons/CodeSandboxIcon';
|
||||||
import ExternalLinkIcon from '../../icons/ExternalLinkIcon';
|
import ExternalLinkIcon from '../../icons/ExternalLinkIcon';
|
||||||
import DemoContext from '../../slots/DemoContext';
|
import DemoContext from '../../slots/DemoContext';
|
||||||
import type { SiteContextProps } from '../../slots/SiteContext';
|
|
||||||
import SiteContext from '../../slots/SiteContext';
|
import SiteContext from '../../slots/SiteContext';
|
||||||
import CodeBlockButton from './CodeBlockButton';
|
import CodeBlockButton from './CodeBlockButton';
|
||||||
import type { AntdPreviewerProps } from './Previewer';
|
import type { AntdPreviewerProps } from './Previewer';
|
||||||
@ -86,7 +86,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
|||||||
clientOnly,
|
clientOnly,
|
||||||
pkgDependencyList,
|
pkgDependencyList,
|
||||||
} = props;
|
} = props;
|
||||||
const { codeType } = useContext(DemoContext);
|
const { codeType } = React.use(DemoContext);
|
||||||
|
|
||||||
const { pkg } = useSiteData();
|
const { pkg } = useSiteData();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -95,7 +95,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
|||||||
|
|
||||||
const entryName = 'index.tsx';
|
const entryName = 'index.tsx';
|
||||||
const entryCode = asset.dependencies[entryName].value;
|
const entryCode = asset.dependencies[entryName].value;
|
||||||
|
|
||||||
const previewDemo = useRef<React.ReactNode>(null);
|
const previewDemo = useRef<React.ReactNode>(null);
|
||||||
const demoContainer = useRef<HTMLElement>(null);
|
const demoContainer = useRef<HTMLElement>(null);
|
||||||
const {
|
const {
|
||||||
@ -110,7 +110,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
|||||||
const codeSandboxIconRef = useRef<HTMLFormElement>(null);
|
const codeSandboxIconRef = useRef<HTMLFormElement>(null);
|
||||||
const codepenIconRef = useRef<HTMLFormElement>(null);
|
const codepenIconRef = useRef<HTMLFormElement>(null);
|
||||||
const [codeExpand, setCodeExpand] = useState<boolean>(false);
|
const [codeExpand, setCodeExpand] = useState<boolean>(false);
|
||||||
const { theme } = useContext<SiteContextProps>(SiteContext);
|
const { theme } = React.use(SiteContext);
|
||||||
|
|
||||||
const { hash, pathname, search } = location;
|
const { hash, pathname, search } = location;
|
||||||
const docsOnlineUrl = `https://ant.design${pathname}${search}#${asset.id}`;
|
const docsOnlineUrl = `https://ant.design${pathname}${search}#${asset.id}`;
|
||||||
@ -140,12 +140,13 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
|||||||
}, [expand]);
|
}, [expand]);
|
||||||
|
|
||||||
const mergedChildren = !iframe && clientOnly ? <ClientOnly>{children}</ClientOnly> : children;
|
const mergedChildren = !iframe && clientOnly ? <ClientOnly>{children}</ClientOnly> : children;
|
||||||
|
const demoUrlWithTheme = `${demoUrl}${theme.includes('dark') ? '?theme=dark' : ''}`;
|
||||||
|
|
||||||
if (!previewDemo.current) {
|
if (!previewDemo.current) {
|
||||||
previewDemo.current = iframe ? (
|
previewDemo.current = iframe ? (
|
||||||
<BrowserFrame>
|
<BrowserFrame>
|
||||||
<iframe
|
<iframe
|
||||||
src={demoUrl}
|
src={demoUrlWithTheme}
|
||||||
height={iframe === true ? undefined : iframe}
|
height={iframe === true ? undefined : iframe}
|
||||||
title="demo"
|
title="demo"
|
||||||
className="iframe-demo"
|
className="iframe-demo"
|
||||||
@ -320,11 +321,18 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
|||||||
const stackblitzPrefillConfig: Project = {
|
const stackblitzPrefillConfig: Project = {
|
||||||
title: `${localizedTitle} - antd@${dependencies.antd}`,
|
title: `${localizedTitle} - antd@${dependencies.antd}`,
|
||||||
template: 'create-react-app',
|
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: '',
|
description: '',
|
||||||
files: {
|
files: {
|
||||||
'index.css': indexCssContent,
|
'index.css': indexCssContent,
|
||||||
[`index.${suffix}`]: indexJsContent,
|
[`index.${suffix}`]: `import '@ant-design/v5-patch-for-react-19';\n${indexJsContent}`,
|
||||||
[`demo.${suffix}`]: demoJsContent,
|
[`demo.${suffix}`]: demoJsContent,
|
||||||
'index.html': html,
|
'index.html': html,
|
||||||
},
|
},
|
||||||
@ -445,7 +453,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
|||||||
aria-label="open in new tab"
|
aria-label="open in new tab"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
href={demoUrl}
|
href={demoUrlWithTheme}
|
||||||
>
|
>
|
||||||
<ExternalLinkIcon className="code-box-separate" />
|
<ExternalLinkIcon className="code-box-separate" />
|
||||||
</a>
|
</a>
|
||||||
@ -515,12 +523,12 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
|||||||
const styleTag = document.createElement('style') as HTMLStyleElement;
|
const styleTag = document.createElement('style') as HTMLStyleElement;
|
||||||
styleTag.type = 'text/css';
|
styleTag.type = 'text/css';
|
||||||
styleTag.innerHTML = style;
|
styleTag.innerHTML = style;
|
||||||
(styleTag as any)['data-demo-url'] = demoUrl;
|
(styleTag as any)['data-demo-url'] = demoUrlWithTheme;
|
||||||
document.head.appendChild(styleTag);
|
document.head.appendChild(styleTag);
|
||||||
return () => {
|
return () => {
|
||||||
document.head.removeChild(styleTag);
|
document.head.removeChild(styleTag);
|
||||||
};
|
};
|
||||||
}, [style, demoUrl]);
|
}, [style, demoUrlWithTheme]);
|
||||||
|
|
||||||
if (version) {
|
if (version) {
|
||||||
return (
|
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 tokenMeta from 'antd/es/version/token-meta.json';
|
||||||
|
|
||||||
import useLocale from '../../../hooks/useLocale';
|
import useLocale from '../../../hooks/useLocale';
|
||||||
|
import BezierVisualizer from '../../common/BezierVisualizer';
|
||||||
import ColorChunk from '../ColorChunk';
|
import ColorChunk from '../ColorChunk';
|
||||||
|
|
||||||
type TokenTableProps = {
|
type TokenTableProps = {
|
||||||
@ -79,7 +80,19 @@ export function useColumns(): Exclude<TableProps<TokenData>['columns'], undefine
|
|||||||
typeof record.value === 'string' &&
|
typeof record.value === 'string' &&
|
||||||
(record.value.startsWith('#') || record.value.startsWith('rgb'));
|
(record.value.startsWith('#') || record.value.startsWith('rgb'));
|
||||||
if (isColor) {
|
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;
|
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 type { ComponentProps } from 'react';
|
||||||
import React, { useContext, useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { Button, Tabs, Typography } from 'antd';
|
import { Button, Tabs, Typography } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import toReactElement from 'jsonml-to-react-element';
|
import toReactElement from 'jsonml-to-react-element';
|
||||||
@ -108,7 +109,7 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
|||||||
initialCodes.style = '';
|
initialCodes.style = '';
|
||||||
}
|
}
|
||||||
const [highlightedCodes, setHighlightedCodes] = React.useState(initialCodes);
|
const [highlightedCodes, setHighlightedCodes] = React.useState(initialCodes);
|
||||||
const { codeType, setCodeType } = useContext(DemoContext);
|
const { codeType, setCodeType } = React.use(DemoContext);
|
||||||
const sourceCodes = {
|
const sourceCodes = {
|
||||||
// omit trailing line break
|
// omit trailing line break
|
||||||
tsx: sourceCode?.trim(),
|
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 { Button, Drawer, Flex, Grid, Popover, Tag, Timeline, Typography } from 'antd';
|
||||||
import type { TimelineItemProps } from 'antd';
|
import type { TimelineItemProps } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import semver from 'semver';
|
|
||||||
|
|
||||||
import deprecatedVersions from '../../../../BUG_VERSIONS.json';
|
|
||||||
import useFetch from '../../../hooks/useFetch';
|
import useFetch from '../../../hooks/useFetch';
|
||||||
import useLocale from '../../../hooks/useLocale';
|
import useLocale from '../../../hooks/useLocale';
|
||||||
import useLocation from '../../../hooks/useLocation';
|
import useLocation from '../../../hooks/useLocation';
|
||||||
|
import { matchDeprecated } from '../../utils';
|
||||||
import Link from '../Link';
|
import Link from '../Link';
|
||||||
|
|
||||||
interface MatchDeprecatedResult {
|
|
||||||
match?: string;
|
|
||||||
reason: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChangelogInfo {
|
interface ChangelogInfo {
|
||||||
version: string;
|
version: string;
|
||||||
changelog: string;
|
changelog: string;
|
||||||
@ -24,17 +18,6 @@ interface ChangelogInfo {
|
|||||||
releaseDate: string;
|
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 }) => ({
|
const useStyle = createStyles(({ token, css }) => ({
|
||||||
listWrap: css`
|
listWrap: css`
|
||||||
> li {
|
> li {
|
||||||
@ -137,9 +120,9 @@ const ParseChangelog: React.FC<{ changelog: string }> = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
let node: React.ReactNode = lastStr;
|
let node: React.ReactNode = lastStr;
|
||||||
if (isQuota) {
|
if (isQuota) {
|
||||||
node = <code>{node}</code>;
|
node = <code key={`code-${i}`}>{node}</code>;
|
||||||
} else if (isBold) {
|
} else if (isBold) {
|
||||||
node = <strong>{node}</strong>;
|
node = <strong key={`strong-${i}`}>{node}</strong>;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
@ -277,7 +260,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
|
|||||||
{version}
|
{version}
|
||||||
{bugVersionInfo.match && (
|
{bugVersionInfo.match && (
|
||||||
<Popover
|
<Popover
|
||||||
destroyTooltipOnHide
|
destroyOnHidden
|
||||||
placement="right"
|
placement="right"
|
||||||
title={<span className={styles.bugReasonTitle}>{locale.bugList}</span>}
|
title={<span className={styles.bugReasonTitle}>{locale.bugList}</span>}
|
||||||
content={
|
content={
|
||||||
@ -327,7 +310,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
|
|||||||
onClick: () => setShow(true),
|
onClick: () => setShow(true),
|
||||||
})}
|
})}
|
||||||
<Drawer
|
<Drawer
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
className={styles.drawerContent}
|
className={styles.drawerContent}
|
||||||
title={locale.changelog}
|
title={locale.changelog}
|
||||||
extra={
|
extra={
|
||||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import ComponentChangelog from './ComponentChangelog';
|
import ComponentChangelog from './ComponentChangelog';
|
||||||
|
|
||||||
const ChangeLog: React.FC<Readonly<React.PropsWithChildren>> = ({ children }) => (
|
const ChangeLog: React.FC<Readonly<React.PropsWithChildren>> = ({ children }) => (
|
||||||
<React.Suspense fallback={null}>
|
<React.Suspense fallback="...">
|
||||||
<ComponentChangelog>{children}</ComponentChangelog>
|
<ComponentChangelog>{children}</ComponentChangelog>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import React, { useContext, useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
import type { GetProp, MenuProps } from 'antd';
|
import type { GetProp, MenuProps } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
@ -7,7 +7,6 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
import useMenu from '../../hooks/useMenu';
|
import useMenu from '../../hooks/useMenu';
|
||||||
import SiteContext from '../slots/SiteContext';
|
import SiteContext from '../slots/SiteContext';
|
||||||
import type { SiteContextProps } from '../slots/SiteContext';
|
|
||||||
|
|
||||||
type MenuItemType = Extract<GetProp<MenuProps, 'items'>[number], { type?: 'item' }>;
|
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 [menuItems, selectedKey] = useMenu({ before, after });
|
||||||
|
|
||||||
const { isMobile } = useContext<SiteContextProps>(SiteContext);
|
const { isMobile } = React.use(SiteContext);
|
||||||
|
|
||||||
const [prev, next] = useMemo(() => {
|
const [prev, next] = useMemo(() => {
|
||||||
const flatMenu = flattenMenu(menuItems);
|
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 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 { createStyles, css } from 'antd-style';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import Prism from 'prismjs';
|
||||||
|
|
||||||
const MARK_BORDER_SIZE = 2;
|
const MARK_BORDER_SIZE = 2;
|
||||||
|
|
||||||
@ -17,6 +22,9 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
|
|||||||
padding: ${token.paddingMD}px;
|
padding: ${token.paddingMD}px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`,
|
`,
|
||||||
|
colWrapPaddingLess: css`
|
||||||
|
padding: 0;
|
||||||
|
`,
|
||||||
listWrap: css`
|
listWrap: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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 {
|
export interface SemanticPreviewProps {
|
||||||
|
componentName: string;
|
||||||
semantics: { name: string; desc: string; version?: string }[];
|
semantics: { name: string; desc: string; version?: string }[];
|
||||||
children: React.ReactElement<any>;
|
children: React.ReactElement<any>;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
padding?: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
||||||
const { semantics = [], children, height } = props;
|
const { semantics = [], children, height, padding, componentName = 'Component' } = props;
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
|
|
||||||
// ======================= Semantic =======================
|
// ======================= Semantic =======================
|
||||||
const getMarkClassName = React.useCallback(
|
const getMarkClassName = React.useCallback(
|
||||||
(semanticKey: string) => `semantic-mark-${semanticKey}`,
|
(semanticKey: string) => `semantic-mark-${semanticKey}`.replace(/\./g, '-'),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const semanticClassNames = React.useMemo<Record<string, string>>(() => {
|
const semanticClassNames = React.useMemo<Record<string, string>>(() => {
|
||||||
const classNames: Record<string, string> = {};
|
let classNames: Record<string, string> = {};
|
||||||
|
|
||||||
semantics.forEach((semantic) => {
|
semantics.forEach((semantic) => {
|
||||||
classNames[semantic.name] = getMarkClassName(semantic.name);
|
const pathCell = getSemanticCells(semantic.name);
|
||||||
|
classNames = set(classNames, pathCell, getMarkClassName(semantic.name));
|
||||||
});
|
});
|
||||||
|
|
||||||
return classNames;
|
return classNames;
|
||||||
}, [semantics]);
|
}, [semantics]);
|
||||||
|
|
||||||
const cloneNode = React.cloneElement(children, {
|
|
||||||
classNames: semanticClassNames,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ======================== Hover =========================
|
// ======================== Hover =========================
|
||||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -134,11 +183,33 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
|||||||
};
|
};
|
||||||
}, [hoverSemantic]);
|
}, [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 ========================
|
// ======================== Render ========================
|
||||||
|
const cloneNode = React.cloneElement(children, {
|
||||||
|
classNames: hoveredSemanticClassNames,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames(styles.container)} ref={containerRef}>
|
<div className={classnames(styles.container)} ref={containerRef}>
|
||||||
<Row style={{ minHeight: height }}>
|
<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>
|
<ConfigProvider theme={{ token: { motion: false } }}>{cloneNode}</ConfigProvider>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
@ -151,11 +222,31 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
|||||||
onMouseLeave={() => setHoverSemantic(null)}
|
onMouseLeave={() => setHoverSemantic(null)}
|
||||||
>
|
>
|
||||||
<Flex vertical gap="small">
|
<Flex vertical gap="small">
|
||||||
<Flex gap="small" align="center">
|
<Flex gap="small" align="center" justify="space-between">
|
||||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
<Flex gap="small" align="center">
|
||||||
{semantic.name}
|
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||||
</Typography.Title>
|
{semantic.name}
|
||||||
{semantic.version && <Tag color="blue">{semantic.version}</Tag>}
|
</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>
|
</Flex>
|
||||||
<Typography.Paragraph style={{ margin: 0, fontSize: token.fontSizeSM }}>
|
<Typography.Paragraph style={{ margin: 0, fontSize: token.fontSizeSM }}>
|
||||||
{semantic.desc}
|
{semantic.desc}
|
@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React, { use, useRef } from 'react';
|
||||||
import { BgColorsOutlined, SmileOutlined } from '@ant-design/icons';
|
import { BgColorsOutlined, LinkOutlined, SmileOutlined, SunOutlined } from '@ant-design/icons';
|
||||||
import { FloatButton } from 'antd';
|
import { Badge, Button, Dropdown } from 'antd';
|
||||||
|
import type { MenuProps } from 'antd';
|
||||||
import { CompactTheme, DarkTheme } from 'antd-token-previewer/es/icons';
|
import { CompactTheme, DarkTheme } from 'antd-token-previewer/es/icons';
|
||||||
// import { Motion } from 'antd-token-previewer/es/icons';
|
|
||||||
import { FormattedMessage, useLocation } from 'dumi';
|
import { FormattedMessage, useLocation } from 'dumi';
|
||||||
|
|
||||||
import useThemeAnimation from '../../../hooks/useThemeAnimation';
|
import useThemeAnimation from '../../../hooks/useThemeAnimation';
|
||||||
|
import type { SiteContextProps } from '../../slots/SiteContext';
|
||||||
|
import SiteContext from '../../slots/SiteContext';
|
||||||
import { getLocalizedPathname, isZhCN } from '../../utils';
|
import { getLocalizedPathname, isZhCN } from '../../utils';
|
||||||
import Link from '../Link';
|
import Link from '../Link';
|
||||||
import ThemeIcon from './ThemeIcon';
|
import ThemeIcon from './ThemeIcon';
|
||||||
@ -14,80 +16,122 @@ export type ThemeName = 'light' | 'dark' | 'compact' | 'motion-off' | 'happy-wor
|
|||||||
|
|
||||||
export interface ThemeSwitchProps {
|
export interface ThemeSwitchProps {
|
||||||
value?: ThemeName[];
|
value?: ThemeName[];
|
||||||
onChange: (value: ThemeName[]) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {
|
const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
|
||||||
const { value = ['light'], onChange } = props;
|
|
||||||
const { pathname, search } = useLocation();
|
const { pathname, search } = useLocation();
|
||||||
|
const { theme, updateSiteConfig } = use<SiteContextProps>(SiteContext);
|
||||||
// const isMotionOff = value.includes('motion-off');
|
|
||||||
const isHappyWork = value.includes('happy-work');
|
|
||||||
const isDark = value.includes('dark');
|
|
||||||
|
|
||||||
const toggleAnimationTheme = useThemeAnimation();
|
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 (
|
return (
|
||||||
<FloatButton.Group
|
<Dropdown menu={{ items, onClick }} arrow={{ pointAtCenter: true }} placement="bottomRight">
|
||||||
trigger="click"
|
<Button type="text" icon={<ThemeIcon />} style={{ fontSize: 16 }} />
|
||||||
icon={<ThemeIcon />}
|
</Dropdown>
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ export default () => {
|
|||||||
> .icon-link::before {
|
> .icon-link::before {
|
||||||
font-size: ${token.fontSizeXL}px;
|
font-size: ${token.fontSizeXL}px;
|
||||||
content: '#';
|
content: '#';
|
||||||
|
color: ${token.colorTextSecondary};
|
||||||
|
font-family: ${token.codeFamily};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,13 +44,24 @@ export default () => {
|
|||||||
html {
|
html {
|
||||||
direction: initial;
|
direction: initial;
|
||||||
|
|
||||||
|
@supports (overflow-x: clip) {
|
||||||
|
overflow-x: clip;
|
||||||
|
}
|
||||||
|
|
||||||
&.rtl {
|
&.rtl {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
overflow-x: hidden;
|
@supports (overflow-x: clip) {
|
||||||
|
overflow-x: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports not (overflow-x: clip) {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
color: ${token.colorText};
|
color: ${token.colorText};
|
||||||
font-size: ${token.fontSize}px;
|
font-size: ${token.fontSize}px;
|
||||||
font-family: ${token.fontFamily};
|
font-family: ${token.fontFamily};
|
||||||
|
@ -3,7 +3,7 @@ import dayjs from 'dayjs';
|
|||||||
|
|
||||||
import 'dayjs/locale/zh-cn';
|
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 ConfigProvider from 'antd/es/config-provider';
|
||||||
import zhCN from 'antd/es/locale/zh_CN';
|
import zhCN from 'antd/es/locale/zh_CN';
|
||||||
import { Helmet, useOutlet, useSiteData } from 'dumi';
|
import { Helmet, useOutlet, useSiteData } from 'dumi';
|
||||||
@ -38,7 +38,7 @@ const DocLayout: React.FC = () => {
|
|||||||
const { pathname, search, hash } = location;
|
const { pathname, search, hash } = location;
|
||||||
const [locale, lang] = useLocale(locales);
|
const [locale, lang] = useLocale(locales);
|
||||||
const timerRef = useRef<ReturnType<typeof setTimeout>>(null!);
|
const timerRef = useRef<ReturnType<typeof setTimeout>>(null!);
|
||||||
const { direction } = useContext(SiteContext);
|
const { direction } = React.use(SiteContext);
|
||||||
const { loading } = useSiteData();
|
const { loading } = useSiteData();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React, { Suspense, useCallback, useEffect } from 'react';
|
// prettier-ignore
|
||||||
import { Monitoring } from 'react-scan/monitoring';
|
import { scan } from 'react-scan'; // import this BEFORE react
|
||||||
|
|
||||||
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
createCache,
|
createCache,
|
||||||
extractStyle,
|
extractStyle,
|
||||||
@ -13,17 +15,10 @@ import { getSandpackCssText } from '@codesandbox/sandpack-react';
|
|||||||
import { theme as antdTheme, App } from 'antd';
|
import { theme as antdTheme, App } from 'antd';
|
||||||
import type { MappingAlgorithm } from 'antd';
|
import type { MappingAlgorithm } from 'antd';
|
||||||
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
|
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
|
||||||
import {
|
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
|
||||||
createSearchParams,
|
|
||||||
useOutlet,
|
|
||||||
useParams,
|
|
||||||
useSearchParams,
|
|
||||||
useServerInsertedHTML,
|
|
||||||
} from 'dumi';
|
|
||||||
|
|
||||||
import { DarkContext } from '../../hooks/useDark';
|
import { DarkContext } from '../../hooks/useDark';
|
||||||
import useLayoutState from '../../hooks/useLayoutState';
|
import useLayoutState from '../../hooks/useLayoutState';
|
||||||
import useLocation from '../../hooks/useLocation';
|
|
||||||
import type { ThemeName } from '../common/ThemeSwitch';
|
import type { ThemeName } from '../common/ThemeSwitch';
|
||||||
import SiteThemeProvider from '../SiteThemeProvider';
|
import SiteThemeProvider from '../SiteThemeProvider';
|
||||||
import type { SiteContextProps } from '../slots/SiteContext';
|
import type { SiteContextProps } from '../slots/SiteContext';
|
||||||
@ -31,8 +26,6 @@ import SiteContext from '../slots/SiteContext';
|
|||||||
|
|
||||||
import '@ant-design/v5-patch-for-react-19';
|
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 Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
|
||||||
type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
|
type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
|
||||||
|
|
||||||
@ -52,6 +45,13 @@ if (typeof window !== 'undefined') {
|
|||||||
location.hash = `#${hashId.replace(/^components-/, '')}`;
|
location.hash = `#${hashId.replace(/^components-/, '')}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
scan({
|
||||||
|
enabled: false,
|
||||||
|
showToolbar: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAlgorithm = (themes: ThemeName[] = []) =>
|
const getAlgorithm = (themes: ThemeName[] = []) =>
|
||||||
@ -69,8 +69,6 @@ const getAlgorithm = (themes: ThemeName[] = []) =>
|
|||||||
|
|
||||||
const GlobalLayout: React.FC = () => {
|
const GlobalLayout: React.FC = () => {
|
||||||
const outlet = useOutlet();
|
const outlet = useOutlet();
|
||||||
const { pathname } = useLocation();
|
|
||||||
const params = useParams();
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [{ theme = [], direction, isMobile, bannerVisible = false }, setSiteState] =
|
const [{ theme = [], direction, isMobile, bannerVisible = false }, setSiteState] =
|
||||||
useLayoutState<SiteState>({
|
useLayoutState<SiteState>({
|
||||||
@ -142,6 +140,12 @@ const GlobalLayout: React.FC = () => {
|
|||||||
// Handle isMobile
|
// Handle isMobile
|
||||||
updateMobileMode();
|
updateMobileMode();
|
||||||
|
|
||||||
|
// 配合 dumi 的 mirror-notify 脚本使用
|
||||||
|
const retrieveMirrorNotification = (window as any)[Symbol.for('antd.mirror-notify')];
|
||||||
|
if (typeof retrieveMirrorNotification === 'function') {
|
||||||
|
retrieveMirrorNotification();
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', updateMobileMode);
|
window.addEventListener('resize', updateMobileMode);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', updateMobileMode);
|
window.removeEventListener('resize', updateMobileMode);
|
||||||
@ -205,49 +209,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 (
|
return (
|
||||||
<DarkContext.Provider value={theme.includes('dark')}>
|
<DarkContext value={theme.includes('dark')}>
|
||||||
<StyleProvider
|
<StyleProvider
|
||||||
cache={styleCache}
|
cache={styleCache}
|
||||||
linters={[legacyNotSelectorLinter, parentSelectorLinter, NaNLinter]}
|
linters={[legacyNotSelectorLinter, parentSelectorLinter, NaNLinter]}
|
||||||
>
|
>
|
||||||
<SiteContext.Provider value={siteContextValue}>
|
<SiteContext value={siteContextValue}>
|
||||||
<SiteThemeProvider theme={themeConfig}>
|
<SiteThemeProvider theme={themeConfig}>
|
||||||
<HappyProvider disabled={!theme.includes('happy-work')}>
|
<HappyProvider disabled={!theme.includes('happy-work')}>
|
||||||
{content}
|
<App>{outlet}</App>
|
||||||
<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}
|
|
||||||
/>
|
|
||||||
</HappyProvider>
|
</HappyProvider>
|
||||||
</SiteThemeProvider>
|
</SiteThemeProvider>
|
||||||
</SiteContext.Provider>
|
</SiteContext>
|
||||||
</StyleProvider>
|
</StyleProvider>
|
||||||
</DarkContext.Provider>
|
</DarkContext>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,10 +4,10 @@ import { ConfigProvider, Layout, Typography } from 'antd';
|
|||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import { FormattedMessage, useRouteMeta } from 'dumi';
|
import { FormattedMessage, useRouteMeta } from 'dumi';
|
||||||
|
|
||||||
import useDark from '../../../hooks/useDark';
|
|
||||||
import CommonHelmet from '../../common/CommonHelmet';
|
import CommonHelmet from '../../common/CommonHelmet';
|
||||||
import EditButton from '../../common/EditButton';
|
import EditButton from '../../common/EditButton';
|
||||||
import Footer from '../../slots/Footer';
|
import Footer from '../../slots/Footer';
|
||||||
|
import { DarkContext } from './../../../hooks/useDark';
|
||||||
import AffixTabs from './AffixTabs';
|
import AffixTabs from './AffixTabs';
|
||||||
|
|
||||||
export type ResourceLayoutProps = PropsWithChildren<NonNullable<any>>;
|
export type ResourceLayoutProps = PropsWithChildren<NonNullable<any>>;
|
||||||
@ -16,85 +16,76 @@ const resourcePadding = 40;
|
|||||||
const articleMaxWidth = 1208;
|
const articleMaxWidth = 1208;
|
||||||
const resourcePaddingXS = 24;
|
const resourcePaddingXS = 24;
|
||||||
|
|
||||||
const useStyle = () => {
|
const useStyle = createStyles(({ token, css }, isDark: boolean) => {
|
||||||
const isRootDark = useDark();
|
return {
|
||||||
|
resourcePage: css`
|
||||||
return createStyles((config) => {
|
footer {
|
||||||
const { token, css } = config;
|
margin-top: 176px;
|
||||||
const { antCls } = token;
|
.rc-footer-container {
|
||||||
|
|
||||||
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;
|
|
||||||
max-width: ${articleMaxWidth}px;
|
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 {
|
@media only screen and (max-width: 767.99px) {
|
||||||
max-width: ${articleMaxWidth}px;
|
& {
|
||||||
margin: 0 auto 56px;
|
article {
|
||||||
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;
|
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 ResourceLayout: React.FC<ResourceLayoutProps> = ({ children }) => {
|
||||||
const { styles } = useStyle();
|
const isDark = React.use(DarkContext);
|
||||||
|
const { styles } = useStyle(isDark);
|
||||||
const meta = useRouteMeta();
|
const meta = useRouteMeta();
|
||||||
const isRootDark = useDark();
|
|
||||||
|
|
||||||
const node = (
|
const node = (
|
||||||
<Layout>
|
<Layout>
|
||||||
<CommonHelmet />
|
<CommonHelmet />
|
||||||
@ -116,7 +107,7 @@ const ResourceLayout: React.FC<ResourceLayoutProps> = ({ children }) => {
|
|||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isRootDark) {
|
if (!isDark) {
|
||||||
return <ConfigProvider theme={{ token: { colorBgLayout: '#fff' } }}>{node}</ConfigProvider>;
|
return <ConfigProvider theme={{ token: { colorBgLayout: '#fff' } }}>{node}</ConfigProvider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,8 +5,7 @@
|
|||||||
"app.theme.switch.compact": "Compact theme",
|
"app.theme.switch.compact": "Compact theme",
|
||||||
"app.theme.switch.motion.on": "Motion On",
|
"app.theme.switch.motion.on": "Motion On",
|
||||||
"app.theme.switch.motion.off": "Motion Off",
|
"app.theme.switch.motion.off": "Motion Off",
|
||||||
"app.theme.switch.happy-work.on": "Happy Work Effect On",
|
"app.theme.switch.happy-work": "Happy Work Effect",
|
||||||
"app.theme.switch.happy-work.off": "Happy Work Effect Off",
|
|
||||||
"app.header.search": "Search...",
|
"app.header.search": "Search...",
|
||||||
"app.header.menu.documentation": "Docs",
|
"app.header.menu.documentation": "Docs",
|
||||||
"app.header.menu.more": "More",
|
"app.header.menu.more": "More",
|
||||||
|
@ -5,8 +5,7 @@
|
|||||||
"app.theme.switch.compact": "紧凑主题",
|
"app.theme.switch.compact": "紧凑主题",
|
||||||
"app.theme.switch.motion.on": "动画开启",
|
"app.theme.switch.motion.on": "动画开启",
|
||||||
"app.theme.switch.motion.off": "动画关闭",
|
"app.theme.switch.motion.off": "动画关闭",
|
||||||
"app.theme.switch.happy-work.on": "快乐工作特效开启",
|
"app.theme.switch.happy-work": "快乐工作特效",
|
||||||
"app.theme.switch.happy-work.off": "快乐工作特效关闭",
|
|
||||||
"app.header.search": "全文本搜索...",
|
"app.header.search": "全文本搜索...",
|
||||||
"app.header.menu.documentation": "文档",
|
"app.header.menu.documentation": "文档",
|
||||||
"app.header.menu.more": "更多",
|
"app.header.menu.more": "更多",
|
||||||
|
@ -4,7 +4,7 @@ import path from 'path';
|
|||||||
import createEmotionServer from '@emotion/server/create-instance';
|
import createEmotionServer from '@emotion/server/create-instance';
|
||||||
import type { IApi, IRoute } from 'dumi';
|
import type { IApi, IRoute } from 'dumi';
|
||||||
import ReactTechStack from 'dumi/dist/techStacks/react';
|
import ReactTechStack from 'dumi/dist/techStacks/react';
|
||||||
import sylvanas from 'sylvanas';
|
import tsToJs from './utils/tsToJs';
|
||||||
|
|
||||||
import { dependencies, devDependencies } from '../../package.json';
|
import { dependencies, devDependencies } from '../../package.json';
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class AntdReactTechStack extends ReactTechStack {
|
|||||||
props.jsx ??= '';
|
props.jsx ??= '';
|
||||||
|
|
||||||
if (opts.type === 'code-block') {
|
if (opts.type === 'code-block') {
|
||||||
props.jsx = opts?.entryPointCode ? sylvanas.parseText(opts.entryPointCode) : '';
|
props.jsx = opts?.entryPointCode ? tsToJs(opts.entryPointCode) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.type === 'external') {
|
if (opts.type === 'external') {
|
||||||
@ -53,7 +53,7 @@ class AntdReactTechStack extends ReactTechStack {
|
|||||||
const codePath = opts.fileAbsPath!.replace(/\.\w+$/, '.tsx');
|
const codePath = opts.fileAbsPath!.replace(/\.\w+$/, '.tsx');
|
||||||
const code = fs.existsSync(codePath) ? fs.readFileSync(codePath, 'utf-8') : '';
|
const code = fs.existsSync(codePath) ? fs.readFileSync(codePath, 'utf-8') : '';
|
||||||
|
|
||||||
props.jsx = sylvanas.parseText(code);
|
props.jsx = tsToJs(code);
|
||||||
|
|
||||||
if (md) {
|
if (md) {
|
||||||
// extract description & css style from md file
|
// extract description & css style from md file
|
||||||
|
@ -130,7 +130,7 @@ const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Card className={card} bordered={false}>
|
<Card className={card} variant="borderless">
|
||||||
<h3 className={bigTitle}>{locale.bigTitle}</h3>
|
<h3 className={bigTitle}>{locale.bigTitle}</h3>
|
||||||
{zhihuLink && (
|
{zhihuLink && (
|
||||||
<>
|
<>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import ContributorsList from '@qixian.cs/github-contributors-list';
|
import ContributorsList from '@qixian.cs/github-contributors-list';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@ -40,7 +40,7 @@ interface ContributorsProps {
|
|||||||
const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
|
const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
const { isMobile } = useContext(SiteContext);
|
const { isMobile } = React.use(SiteContext);
|
||||||
|
|
||||||
if (!filename) {
|
if (!filename) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useContext, useLayoutEffect, useMemo, useState } from 'react';
|
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { Col, Flex, Space, Typography, Skeleton } from 'antd';
|
import { Col, Flex, FloatButton, Skeleton, Space, Typography } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormattedMessage, useRouteMeta } from 'dumi';
|
import { FormattedMessage, useRouteMeta } from 'dumi';
|
||||||
|
|
||||||
@ -9,8 +9,8 @@ import ComponentMeta from '../../builtins/ComponentMeta';
|
|||||||
import type { DemoContextProps } from '../DemoContext';
|
import type { DemoContextProps } from '../DemoContext';
|
||||||
import DemoContext from '../DemoContext';
|
import DemoContext from '../DemoContext';
|
||||||
import SiteContext from '../SiteContext';
|
import SiteContext from '../SiteContext';
|
||||||
import InViewSuspense from './InViewSuspense';
|
|
||||||
import { useStyle } from './DocAnchor';
|
import { useStyle } from './DocAnchor';
|
||||||
|
import InViewSuspense from './InViewSuspense';
|
||||||
|
|
||||||
const Contributors = React.lazy(() => import('./Contributors'));
|
const Contributors = React.lazy(() => import('./Contributors'));
|
||||||
const ColumnCard = React.lazy(() => import('./ColumnCard'));
|
const ColumnCard = React.lazy(() => import('./ColumnCard'));
|
||||||
@ -28,7 +28,7 @@ const AvatarPlaceholder: React.FC<{ num?: number }> = ({ num = 6 }) =>
|
|||||||
const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||||
const meta = useRouteMeta();
|
const meta = useRouteMeta();
|
||||||
const { pathname, hash } = useLocation();
|
const { pathname, hash } = useLocation();
|
||||||
const { direction } = useContext(SiteContext);
|
const { direction } = React.use(SiteContext);
|
||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
|
|
||||||
const [showDebug, setShowDebug] = useLayoutState(false);
|
const [showDebug, setShowDebug] = useLayoutState(false);
|
||||||
@ -52,7 +52,7 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
|||||||
const isRTL = direction === 'rtl';
|
const isRTL = direction === 'rtl';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DemoContext.Provider value={contextValue}>
|
<DemoContext value={contextValue}>
|
||||||
<Col xxl={20} xl={19} lg={18} md={18} sm={24} xs={24}>
|
<Col xxl={20} xl={19} lg={18} md={18} sm={24} xs={24}>
|
||||||
<InViewSuspense fallback={null}>
|
<InViewSuspense fallback={null}>
|
||||||
<DocAnchor showDebug={showDebug} debugDemos={debugDemos} />
|
<DocAnchor showDebug={showDebug} debugDemos={debugDemos} />
|
||||||
@ -89,9 +89,13 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
|||||||
component={meta.frontmatter.title}
|
component={meta.frontmatter.title}
|
||||||
filename={meta.frontmatter.filename}
|
filename={meta.frontmatter.filename}
|
||||||
version={meta.frontmatter.tag}
|
version={meta.frontmatter.tag}
|
||||||
|
designUrl={meta.frontmatter.designUrl}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div style={{ minHeight: 'calc(100vh - 64px)' }}>{children}</div>
|
<div style={{ minHeight: 'calc(100vh - 64px)' }}>
|
||||||
|
{children}
|
||||||
|
<FloatButton.BackTop />
|
||||||
|
</div>
|
||||||
<InViewSuspense fallback={null}>
|
<InViewSuspense fallback={null}>
|
||||||
<ColumnCard
|
<ColumnCard
|
||||||
zhihuLink={meta.frontmatter.zhihu_url}
|
zhihuLink={meta.frontmatter.zhihu_url}
|
||||||
@ -110,7 +114,7 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
|||||||
</InViewSuspense>
|
</InViewSuspense>
|
||||||
<Footer />
|
<Footer />
|
||||||
</Col>
|
</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 {
|
import {
|
||||||
AntDesignOutlined,
|
AntDesignOutlined,
|
||||||
BgColorsOutlined,
|
BgColorsOutlined,
|
||||||
@ -13,7 +14,6 @@ import {
|
|||||||
UsergroupAddOutlined,
|
UsergroupAddOutlined,
|
||||||
ZhihuOutlined,
|
ZhihuOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { FastColor } from '@ant-design/fast-color';
|
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import getAlphaColor from 'antd/es/theme/util/getAlphaColor';
|
import getAlphaColor from 'antd/es/theme/util/getAlphaColor';
|
||||||
import { FormattedMessage, Link } from 'dumi';
|
import { FormattedMessage, Link } from 'dumi';
|
||||||
@ -34,64 +34,57 @@ const locales = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyle = () => {
|
const useStyle = createStyles(({ token, css }, isMobile: boolean) => {
|
||||||
const { isMobile } = useContext(SiteContext);
|
const background = new FastColor(getAlphaColor('#f0f3fa', '#fff'))
|
||||||
return createStyles(({ token, css }) => {
|
.onBackground(token.colorBgContainer)
|
||||||
const background = new FastColor(getAlphaColor('#f0f3fa', '#fff'))
|
.toHexString();
|
||||||
.onBackground(token.colorBgContainer)
|
return {
|
||||||
.toHexString();
|
holder: css`
|
||||||
|
background: ${background};
|
||||||
|
`,
|
||||||
|
|
||||||
return {
|
footer: css`
|
||||||
holder: css`
|
background: ${background};
|
||||||
background: ${background};
|
color: ${token.colorTextSecondary};
|
||||||
`,
|
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
|
||||||
|
|
||||||
footer: css`
|
* {
|
||||||
background: ${background};
|
box-sizing: border-box;
|
||||||
color: ${token.colorTextSecondary};
|
}
|
||||||
|
|
||||||
|
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-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
|
||||||
|
.rc-footer-bottom-container {
|
||||||
* {
|
font-size: ${token.fontSize}px;
|
||||||
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);
|
|
||||||
.rc-footer-bottom-container {
|
|
||||||
font-size: ${token.fontSize}px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
};
|
|
||||||
|
|
||||||
const Footer: React.FC = () => {
|
const Footer: React.FC = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [locale, lang] = useLocale(locales);
|
const [locale, lang] = useLocale(locales);
|
||||||
const { styles } = useStyle();
|
const { isMobile } = React.use(SiteContext);
|
||||||
|
const { styles } = useStyle(isMobile);
|
||||||
|
|
||||||
const { getLink } = location;
|
const { getLink } = location;
|
||||||
|
|
||||||
@ -118,7 +111,9 @@ const Footer: React.FC = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pro Components',
|
title: 'Pro Components',
|
||||||
url: 'https://procomponents.ant.design',
|
url: isZhCN
|
||||||
|
? 'https://pro-components.antdigital.dev'
|
||||||
|
: 'https://procomponents.ant.design',
|
||||||
openExternal: true,
|
openExternal: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip, Button } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import omit from 'rc-util/lib/omit';
|
||||||
export interface LangBtnProps {
|
export interface LangBtnProps {
|
||||||
label1: React.ReactNode;
|
label1: React.ReactNode;
|
||||||
label2: React.ReactNode;
|
label2: React.ReactNode;
|
||||||
@ -12,49 +12,24 @@ export interface LangBtnProps {
|
|||||||
pure?: boolean;
|
pure?: boolean;
|
||||||
onClick?: React.MouseEventHandler;
|
onClick?: React.MouseEventHandler;
|
||||||
'aria-label'?: string;
|
'aria-label'?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BASE_SIZE = '1.2em';
|
const BASE_SIZE = '1.2em';
|
||||||
|
|
||||||
const useStyle = createStyles(({ token, css }) => {
|
const useStyle = createStyles(({ token, css }) => {
|
||||||
const {
|
const { colorText, controlHeight, colorBgContainer, motionDurationMid } = token;
|
||||||
colorText,
|
|
||||||
colorBorder,
|
|
||||||
colorBgContainer,
|
|
||||||
colorBgTextHover,
|
|
||||||
borderRadius,
|
|
||||||
controlHeight,
|
|
||||||
motionDurationMid,
|
|
||||||
} = token;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
btn: css`
|
btn: css`
|
||||||
color: ${colorText};
|
|
||||||
border-color: ${colorBorder};
|
|
||||||
padding: 0 !important;
|
|
||||||
width: ${controlHeight}px;
|
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 {
|
.btn-inner {
|
||||||
transition: all ${motionDurationMid};
|
transition: all ${motionDurationMid};
|
||||||
}
|
}
|
||||||
&:hover {
|
|
||||||
background: ${colorBgTextHover};
|
|
||||||
}
|
|
||||||
img {
|
img {
|
||||||
width: ${BASE_SIZE};
|
width: ${BASE_SIZE};
|
||||||
height: ${BASE_SIZE};
|
height: ${BASE_SIZE};
|
||||||
}
|
}
|
||||||
.anticon {
|
|
||||||
font-size: ${BASE_SIZE};
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
innerDiv: css`
|
innerDiv: css`
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -88,19 +63,19 @@ const useStyle = createStyles(({ token, css }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const LangBtn: React.FC<LangBtnProps> = (props) => {
|
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 {
|
const {
|
||||||
styles: { btn, innerDiv, labelStyle, label1Style, label2Style },
|
styles: { btn, innerDiv, labelStyle, label1Style, label2Style },
|
||||||
} = useStyle();
|
} = useStyle();
|
||||||
|
|
||||||
const node = (
|
const node = (
|
||||||
<button
|
<Button
|
||||||
type="button"
|
type="text"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={btn}
|
className={btn}
|
||||||
key="lang-button"
|
key="lang-button"
|
||||||
aria-label={props['aria-label']}
|
{...omit(rest, ['className'])}
|
||||||
>
|
>
|
||||||
<div className="btn-inner">
|
<div className="btn-inner">
|
||||||
{pure && (value === 1 ? label1 : label2)}
|
{pure && (value === 1 ? label1 : label2)}
|
||||||
@ -115,7 +90,7 @@ const LangBtn: React.FC<LangBtnProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tooltip1 || tooltip2) {
|
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 { 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 { createStyles } from 'antd-style';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@ -8,11 +9,11 @@ import { useLocation, useSiteData } from 'dumi';
|
|||||||
import DumiSearchBar from 'dumi/theme-default/slots/SearchBar';
|
import DumiSearchBar from 'dumi/theme-default/slots/SearchBar';
|
||||||
|
|
||||||
import useLocale from '../../../hooks/useLocale';
|
import useLocale from '../../../hooks/useLocale';
|
||||||
|
import ThemeSwitch from '../../common/ThemeSwitch';
|
||||||
import DirectionIcon from '../../icons/DirectionIcon';
|
import DirectionIcon from '../../icons/DirectionIcon';
|
||||||
import { ANT_DESIGN_NOT_SHOW_BANNER } from '../../layouts/GlobalLayout';
|
import { ANT_DESIGN_NOT_SHOW_BANNER } from '../../layouts/GlobalLayout';
|
||||||
import * as utils from '../../utils';
|
import * as utils from '../../utils';
|
||||||
import { getThemeConfig } from '../../utils';
|
import { getThemeConfig } from '../../utils';
|
||||||
import type { SiteContextProps } from '../SiteContext';
|
|
||||||
import SiteContext from '../SiteContext';
|
import SiteContext from '../SiteContext';
|
||||||
import type { SharedProps } from './interface';
|
import type { SharedProps } from './interface';
|
||||||
import Logo from './Logo';
|
import Logo from './Logo';
|
||||||
@ -168,8 +169,7 @@ const Header: React.FC = () => {
|
|||||||
windowWidth: 1400,
|
windowWidth: 1400,
|
||||||
searching: false,
|
searching: false,
|
||||||
});
|
});
|
||||||
const { direction, isMobile, bannerVisible, updateSiteConfig } =
|
const { direction, isMobile, bannerVisible, updateSiteConfig } = React.use(SiteContext);
|
||||||
useContext<SiteContextProps>(SiteContext);
|
|
||||||
const pingTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const pingTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { pathname, search } = location;
|
const { pathname, search } = location;
|
||||||
@ -302,10 +302,11 @@ const Header: React.FC = () => {
|
|||||||
<Select
|
<Select
|
||||||
key="version"
|
key="version"
|
||||||
size="small"
|
size="small"
|
||||||
|
variant="filled"
|
||||||
className={styles.versionSelect}
|
className={styles.versionSelect}
|
||||||
defaultValue={pkg.version}
|
defaultValue={pkg.version}
|
||||||
onChange={handleVersionChange}
|
onChange={handleVersionChange}
|
||||||
dropdownStyle={getDropdownStyle}
|
styles={{ popup: { root: getDropdownStyle } }}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
getPopupContainer={(trigger) => trigger.parentNode}
|
getPopupContainer={(trigger) => trigger.parentNode}
|
||||||
options={versionOptions}
|
options={versionOptions}
|
||||||
@ -330,13 +331,16 @@ const Header: React.FC = () => {
|
|||||||
pure
|
pure
|
||||||
aria-label="RTL Switch Button"
|
aria-label="RTL Switch Button"
|
||||||
/>,
|
/>,
|
||||||
|
<ThemeSwitch key="theme" />,
|
||||||
<a
|
<a
|
||||||
key="github"
|
key="github"
|
||||||
href="https://github.com/ant-design/ant-design"
|
href="https://github.com/ant-design/ant-design"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
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>,
|
</a>,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import { Col, ConfigProvider, Menu } from 'antd';
|
import { Col, ConfigProvider, Menu } from 'antd';
|
||||||
import { createStyles, useTheme } from 'antd-style';
|
import { createStyles, useTheme } from 'antd-style';
|
||||||
import { useSidebarData } from 'dumi';
|
import { useSidebarData } from 'dumi';
|
||||||
@ -109,7 +109,7 @@ const useStyle = createStyles(({ token, css }) => {
|
|||||||
|
|
||||||
const Sidebar: React.FC = () => {
|
const Sidebar: React.FC = () => {
|
||||||
const sidebarData = useSidebarData();
|
const sidebarData = useSidebarData();
|
||||||
const { isMobile, theme } = useContext(SiteContext);
|
const { isMobile, theme } = React.use(SiteContext);
|
||||||
const { styles } = useStyle();
|
const { styles } = useStyle();
|
||||||
|
|
||||||
const [menuItems, selectedKey] = useMenu();
|
const [menuItems, selectedKey] = useMenu();
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import semver from 'semver';
|
||||||
import flatten from 'lodash/flatten';
|
import flatten from 'lodash/flatten';
|
||||||
import flattenDeep from 'lodash/flattenDeep';
|
import flattenDeep from 'lodash/flattenDeep';
|
||||||
|
import deprecatedVersions from '../../../BUG_VERSIONS.json';
|
||||||
import themeConfig from './themeConfig';
|
import themeConfig from '../themeConfig';
|
||||||
|
|
||||||
interface Meta {
|
interface Meta {
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
@ -24,6 +25,11 @@ interface Orders {
|
|||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MatchDeprecatedResult {
|
||||||
|
match?: string;
|
||||||
|
reason: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export function getMenuItems(
|
export function getMenuItems(
|
||||||
moduleData: ModuleDataItem[],
|
moduleData: ModuleDataItem[],
|
||||||
locale: string,
|
locale: string,
|
||||||
@ -151,28 +157,6 @@ export function getLocalizedPathname(
|
|||||||
return { pathname: fullPath, search };
|
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() {
|
export function isLocalStorageNameSupported() {
|
||||||
const testKey = 'test';
|
const testKey = 'test';
|
||||||
const storage = window.localStorage;
|
const storage = window.localStorage;
|
||||||
@ -223,4 +207,15 @@ export function getMetaDescription(jml?: any[] | null) {
|
|||||||
return paragraph;
|
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;
|
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 os from 'node:os';
|
||||||
|
|
||||||
import rehypeAntd from './.dumi/rehypeAntd';
|
import rehypeAntd from './.dumi/rehypeAntd';
|
||||||
|
import rehypeChangelog from './.dumi/rehypeChangelog';
|
||||||
import remarkAntd from './.dumi/remarkAntd';
|
import remarkAntd from './.dumi/remarkAntd';
|
||||||
|
import remarkAnchor from './.dumi/remarkAnchor';
|
||||||
import { version } from './package.json';
|
import { version } from './package.json';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@ -51,8 +53,8 @@ export default defineConfig({
|
|||||||
// https://github.com/ant-design/ant-design/issues/46628
|
// https://github.com/ant-design/ant-design/issues/46628
|
||||||
'@ant-design/icons$': '@ant-design/icons/lib',
|
'@ant-design/icons$': '@ant-design/icons/lib',
|
||||||
},
|
},
|
||||||
extraRehypePlugins: [rehypeAntd],
|
extraRehypePlugins: [rehypeAntd, rehypeChangelog],
|
||||||
extraRemarkPlugins: [remarkAntd],
|
extraRemarkPlugins: [remarkAntd, remarkAnchor],
|
||||||
metas: [
|
metas: [
|
||||||
{ name: 'theme-color', content: '#1677ff' },
|
{ name: 'theme-color', content: '#1677ff' },
|
||||||
{ name: 'build-time', content: Date.now().toString() },
|
{ name: 'build-time', content: Date.now().toString() },
|
||||||
@ -188,7 +190,7 @@ export default defineConfig({
|
|||||||
{
|
{
|
||||||
async: true,
|
async: true,
|
||||||
content: fs
|
content: fs
|
||||||
.readFileSync(path.join(__dirname, '.dumi', 'scripts', 'mirror-modal.js'))
|
.readFileSync(path.join(__dirname, '.dumi', 'scripts', 'mirror-notify.js'))
|
||||||
.toString(),
|
.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! 😄
|
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.
|
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.
|
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 approve.
|
Your pull requests will be merged after one of the collaborators approves.
|
||||||
Thank you!
|
Thank you!
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -25,6 +25,7 @@ Thank you!
|
|||||||
- [ ] ✅ Test Case
|
- [ ] ✅ Test Case
|
||||||
- [ ] 🔀 Branch merge
|
- [ ] 🔀 Branch merge
|
||||||
- [ ] ⏩ Workflow
|
- [ ] ⏩ Workflow
|
||||||
|
- [ ] ⌨️ Accessibility improvement
|
||||||
- [ ] ❓ Other (about what?)
|
- [ ] ❓ Other (about what?)
|
||||||
|
|
||||||
### 🔗 Related Issues
|
### 🔗 Related Issues
|
||||||
@ -40,7 +41,7 @@ Thank you!
|
|||||||
|
|
||||||
### 📝 Change Log
|
### 📝 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.
|
> - Describe the impact of the changes on developers, not the solution approach.
|
||||||
> - Reference: https://ant.design/changelog
|
> - 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
|
### 🔗 相关 Issue
|
||||||
|
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@ -9,26 +9,22 @@ updates:
|
|||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
|
time: "05:00"
|
||||||
|
timezone: Asia/Shanghai
|
||||||
groups:
|
groups:
|
||||||
rc-component-patch:
|
|
||||||
dependency-type: production
|
|
||||||
patterns:
|
|
||||||
- "rc-*"
|
|
||||||
- "@rc-component*"
|
|
||||||
update-types: [patch]
|
|
||||||
dependencies:
|
dependencies:
|
||||||
dependency-type: production
|
dependency-type: production
|
||||||
exclude-patterns:
|
|
||||||
- "rc-*"
|
|
||||||
- "@rc-component*"
|
|
||||||
update-types: [major, minor]
|
update-types: [major, minor]
|
||||||
dev-dependencies:
|
dev-dependencies:
|
||||||
dependency-type: development
|
dependency-type: development
|
||||||
update-types: [major]
|
update-types: [major]
|
||||||
ignore:
|
ignore:
|
||||||
- dependency-name: "@ant-design/cssinjs"
|
- dependency-name: "rc-*"
|
||||||
|
- dependency-name: "@rc-component*"
|
||||||
|
- dependency-name: "@ant-design*"
|
||||||
- dependency-name: dayjs
|
- dependency-name: dayjs
|
||||||
versions: [1.x]
|
versions: [1.x]
|
||||||
|
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
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;
|
const daysBeforeReminder = 14;
|
||||||
let page = 1;
|
let page = 1;
|
||||||
const perPage = 100;
|
const perPage = 100;
|
||||||
|
|
||||||
const reminderSignature = "This issue has been inactive for more than 14 days";
|
const reminderSignature = "This issue has been inactive for more than 14 days";
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { data: issues } = await github.rest.issues.listForRepo({
|
try {
|
||||||
owner: context.repo.owner,
|
const { data: issues } = await github.rest.issues.listForRepo({
|
||||||
repo: context.repo.repo,
|
|
||||||
state: 'open',
|
|
||||||
assignee: '*',
|
|
||||||
per_page: perPage,
|
|
||||||
page: page,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (issues.length === 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
for (const issue of issues) {
|
|
||||||
if (issue.pull_request) continue;
|
|
||||||
|
|
||||||
const updatedAt = new Date(issue.updated_at);
|
|
||||||
const daysInactive = (now - updatedAt) / (1000 * 60 * 60 * 24);
|
|
||||||
|
|
||||||
const { data: timeline } = await github.rest.issues.listEventsForTimeline({
|
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: issue.number,
|
state: 'open',
|
||||||
|
assignee: '*',
|
||||||
|
per_page: perPage,
|
||||||
|
page: page,
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasLinkedPR = timeline.some(event =>
|
if (issues.length === 0) {
|
||||||
event.event === 'connected' || // PR connected via keywords
|
break;
|
||||||
event.event === 'referenced' && event.commit_id // Connected via commit
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (daysInactive >= daysBeforeReminder && !hasLinkedPR) {
|
const now = new Date();
|
||||||
// get issue comments
|
|
||||||
const { data: comments } = await github.rest.issues.listComments({
|
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,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: issue.number,
|
issue_number: issue.number,
|
||||||
});
|
});
|
||||||
|
|
||||||
// check if reminder has been sent
|
const hasLinkedPR = timeline.some(event =>
|
||||||
const hasReminder = comments.some(comment =>
|
event.event === 'connected' || // PR connected via keywords
|
||||||
comment.body.includes(reminderSignature)
|
event.event === 'referenced' && event.commit_id // Connected via commit
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasReminder) {
|
if (daysInactive >= daysBeforeReminder && !hasLinkedPR) {
|
||||||
const assigneesMentions = issue.assignees
|
// get issue comments
|
||||||
.map(user => `@${user.login}`)
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
.join(' ');
|
|
||||||
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: issue.number,
|
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 }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
body: |
|
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)
|
> [什么是最小化重现,为什么这是必需的?](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:
|
steps:
|
||||||
# We need get PR id first
|
# We need get PR id first
|
||||||
- name: download pr artifact
|
- name: download pr artifact
|
||||||
uses: dawidd6/action-download-artifact@v7
|
uses: dawidd6/action-download-artifact@v9
|
||||||
with:
|
with:
|
||||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
@ -81,7 +81,7 @@ jobs:
|
|||||||
# Download site artifact
|
# Download site artifact
|
||||||
- name: download site artifact
|
- name: download site artifact
|
||||||
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
|
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
|
||||||
uses: dawidd6/action-download-artifact@v7
|
uses: dawidd6/action-download-artifact@v9
|
||||||
with:
|
with:
|
||||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
name: 🐦 Release to Tweet
|
name: 🆇 Release to X
|
||||||
|
|
||||||
on:
|
on:
|
||||||
create
|
create
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tweet:
|
tweet:
|
||||||
if: ${{ github.event.ref_type == 'tag' && !contains(github.event.ref, 'alpha') }}
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.ref_type == 'tag' && !contains(github.event.ref, 'alpha') }}
|
||||||
steps:
|
steps:
|
||||||
- name: Tweet
|
- name: Tweet
|
||||||
uses: nearform-actions/github-action-notify-twitter@v1
|
uses: nearform-actions/github-action-notify-twitter@master
|
||||||
with:
|
with:
|
||||||
message: |
|
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 }}
|
🤖 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-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: ${{ secrets.TWITTER_ACCESS_TOKEN }}
|
||||||
twitter-access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
|
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 ..
|
cd ..
|
||||||
|
|
||||||
- name: Upload to Release
|
- name: Upload to Release
|
||||||
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
|
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
files: website.tar.gz
|
files: website.tar.gz
|
||||||
|
2
.github/workflows/upgrade-deps.yml
vendored
2
.github/workflows/upgrade-deps.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
|||||||
uses: peter-evans/create-pull-request@v7
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }} # Cannot be default!!!
|
token: ${{ secrets.GITHUB_TOKEN }} # Cannot be default!!!
|
||||||
assignees: 'afc163, zombieJ, xrkffgg, MadCcc'
|
assignees: 'afc163, yoyo837, Wxh16144'
|
||||||
title: "chore: upgrade deps"
|
title: "chore: upgrade deps"
|
||||||
commit-message: "chore: upgrade deps"
|
commit-message: "chore: upgrade deps"
|
||||||
body: |
|
body: |
|
||||||
|
1
.github/workflows/verify-files-modify.yml
vendored
1
.github/workflows/verify-files-modify.yml
vendored
@ -9,6 +9,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verify:
|
verify:
|
||||||
|
if: github.event.pull_request.user.login != 'renovate[bot]'
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write # for actions-cool/verify-files-modify to update status of PRs
|
pull-requests: write # for actions-cool/verify-files-modify to update status of PRs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -68,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
# We need get persist-index first
|
# We need get persist-index first
|
||||||
- name: download image snapshot artifact
|
- name: download image snapshot artifact
|
||||||
uses: dawidd6/action-download-artifact@v7
|
uses: dawidd6/action-download-artifact@v9
|
||||||
with:
|
with:
|
||||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
@ -90,7 +90,7 @@ jobs:
|
|||||||
- name: download report artifact
|
- name: download report artifact
|
||||||
id: download_report
|
id: download_report
|
||||||
if: ${{ needs.upstream-workflow-summary.outputs.build-status == 'success' || needs.upstream-workflow-summary.outputs.build-status == 'failure' }}
|
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:
|
with:
|
||||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
@ -166,7 +166,7 @@ jobs:
|
|||||||
});
|
});
|
||||||
const prHeadSha = prResponse.data.head.sha;
|
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 hasVisualDiffSuccess = fullContent.includes('VISUAL_DIFF_SUCCESS');
|
||||||
const hasVisualDiffFailed = fullContent.includes('VISUAL_DIFF_FAILED');
|
const hasVisualDiffFailed = fullContent.includes('VISUAL_DIFF_FAILED');
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ jobs:
|
|||||||
|
|
||||||
# We need get persist key first
|
# We need get persist key first
|
||||||
- name: Download Visual Regression Ref
|
- name: Download Visual Regression Ref
|
||||||
uses: dawidd6/action-download-artifact@v7
|
uses: dawidd6/action-download-artifact@v9
|
||||||
with:
|
with:
|
||||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
@ -79,7 +79,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download Visual-Regression Artifact
|
- name: Download Visual-Regression Artifact
|
||||||
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
|
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
|
||||||
uses: dawidd6/action-download-artifact@v7
|
uses: dawidd6/action-download-artifact@v9
|
||||||
with:
|
with:
|
||||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -36,8 +36,9 @@ config/base.yaml
|
|||||||
yarn.lock
|
yarn.lock
|
||||||
package-lock.json
|
package-lock.json
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
bun.lockb
|
bun.lock*
|
||||||
.pnpm-debug.log
|
.pnpm-debug.log
|
||||||
|
.pnpm-store
|
||||||
components/**/*.js
|
components/**/*.js
|
||||||
components/**/*.jsx
|
components/**/*.jsx
|
||||||
!components/**/__tests__/**/*.js
|
!components/**/__tests__/**/*.js
|
||||||
@ -68,7 +69,6 @@ __image_snapshots__/
|
|||||||
/imageDiffSnapshots
|
/imageDiffSnapshots
|
||||||
/visualRegressionReport*
|
/visualRegressionReport*
|
||||||
|
|
||||||
.devcontainer*
|
|
||||||
.husky/prepare-commit-msg
|
.husky/prepare-commit-msg
|
||||||
|
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
14
.ncurc.js
14
.ncurc.js
@ -18,17 +18,5 @@ module.exports = {
|
|||||||
return check.some((prefix) => name.startsWith(prefix));
|
return check.some((prefix) => name.startsWith(prefix));
|
||||||
},
|
},
|
||||||
// https://github.com/raineorshine/npm-check-updates#target
|
// https://github.com/raineorshine/npm-check-updates#target
|
||||||
target: (name, semver) => {
|
target: () => `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';
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
@ -75,5 +75,7 @@
|
|||||||
"https://github.com/ant-design/ant-design/issues/51420",
|
"https://github.com/ant-design/ant-design/issues/51420",
|
||||||
"https://github.com/ant-design/ant-design/issues/51430"
|
"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,256 @@ tag: vVERSION
|
|||||||
|
|
||||||
#### Release Schedule
|
#### 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.
|
- 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 `destroy*` 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
|
## 5.23.2
|
||||||
|
|
||||||
`2025-01-20`
|
`2025-01-20`
|
||||||
@ -308,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)
|
- 🐞 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)
|
- 🇹🇷 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`
|
`2024-09-22`
|
||||||
|
|
||||||
@ -323,7 +567,7 @@ tag: vVERSION
|
|||||||
<img width="300" alt="float button" src="https://github.com/user-attachments/assets/4b53c0f6-7bdd-4e2a-91cc-2fb804f6e6d3" />
|
<img width="300" alt="float button" src="https://github.com/user-attachments/assets/4b53c0f6-7bdd-4e2a-91cc-2fb804f6e6d3" />
|
||||||
- 🆕 FloatButton supports `htmlType` prop. [#50892](https://github.com/ant-design/ant-design/pull/50892) [@li-jia-nan](https://github.com/li-jia-nan)
|
- 🆕 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)
|
- 💄 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
|
||||||
- 🆕 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)
|
- 🆕 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">
|
<img width="259" alt="menu extra" src="https://github.com/user-attachments/assets/fee57c43-b948-4f98-8a6b-0d94094a8a65">
|
||||||
@ -840,7 +1084,7 @@ tag: vVERSION
|
|||||||
- 🐞 Fix Form's `setFieldsValue` should tread same as `setFields`.
|
- 🐞 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 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 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)
|
- 🐞 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)
|
- 🐞 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)
|
- 🇮🇸 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,251 @@ 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)
|
||||||
|
- 🗑 杂项:废弃多个可开关组件的 `destroy*` 属性,统一为 `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
|
## 5.23.2
|
||||||
|
|
||||||
`2025-01-20`
|
`2025-01-20`
|
||||||
@ -309,7 +554,7 @@ tag: vVERSION
|
|||||||
- 💄 修复 Transfer 在自定义为 TableTransfer 时,宽度不正确的问题。[#50974](https://github.com/ant-design/ant-design/pull/50974) [@zombieJ](https://github.com/zombieJ)
|
- 💄 修复 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)
|
- 🇹🇷 补充 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`
|
`2024-09-22`
|
||||||
|
|
||||||
@ -842,7 +1087,7 @@ tag: vVERSION
|
|||||||
- 🐞 修复 Form 的 `setFieldsValue` 和 `setFields` 的行为应该相同。
|
- 🐞 修复 Form 的 `setFieldsValue` 和 `setFields` 的行为应该相同。
|
||||||
- 🐞 修复 Table 表格列在搜索情况下,国际化失效的问题。[#48126](https://github.com/ant-design/ant-design/pull/48126) [@LingJinT](https://github.com/LingJinT)
|
- 🐞 修复 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)
|
- 🐞 修复 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)
|
- 🐞 修复 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)
|
- 🐞 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)
|
- 🇮🇸 添加冰岛语缺失的 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]
|
[![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]
|
[![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]
|
||||||
|
|
||||||
@ -43,6 +43,8 @@
|
|||||||
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||||
[dumi-url]: https://github.com/umijs/dumi
|
[dumi-url]: https://github.com/umijs/dumi
|
||||||
[github-issues-url]: https://new-issue.ant.design
|
[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>
|
</div>
|
||||||
|
|
||||||
@ -83,6 +85,10 @@ yarn add antd
|
|||||||
pnpm add antd
|
pnpm add antd
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun add antd
|
||||||
|
```
|
||||||
|
|
||||||
## 🔨 示例
|
## 🔨 示例
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
@ -105,7 +111,7 @@ export default App;
|
|||||||
|
|
||||||
### 🛡 TypeScript
|
### 🛡 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>
|
</tr>
|
||||||
</table>
|
</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://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),更好的问题更容易获得帮助。
|
> 强烈推荐阅读 [《提问的智慧》](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 赞助
|
## Issue 赞助
|
||||||
|
|
||||||
我们使用 [Polar.sh](https://polar.sh/ant-design) 和 [Issuehunt](https://issuehunt.io/repos/3452688) 来推动您希望看到的针对 antd 的修复和改进,请查看我们的赞助列表:
|
我们使用 [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>
|
|
||||||
|
|
||||||
[](https://issuehunt.io/repos/34526884)
|
[](https://issuehunt.io/repos/34526884)
|
||||||
|
20
README.md
20
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]
|
[![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]
|
[![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]
|
||||||
|
|
||||||
@ -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-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||||
[dumi-url]: https://github.com/umijs/dumi
|
[dumi-url]: https://github.com/umijs/dumi
|
||||||
[github-issues-url]: https://new-issue.ant.design
|
[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>
|
</div>
|
||||||
|
|
||||||
@ -81,6 +83,10 @@ yarn add antd
|
|||||||
pnpm add antd
|
pnpm add antd
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun add antd
|
||||||
|
```
|
||||||
|
|
||||||
## 🔨 Usage
|
## 🔨 Usage
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
@ -132,7 +138,7 @@ $ npm install
|
|||||||
$ npm start
|
$ 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)
|
## 🤝 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>
|
</tr>
|
||||||
</table>
|
</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.
|
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.
|
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
|
## 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:
|
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:
|
||||||
|
|
||||||
<a href="https://polar.sh/ant-design"><img src="https://polar.sh/embed/fund-our-backlog.svg?org=ant-design" /></a>
|
|
||||||
|
|
||||||
[](https://issuehunt.io/repos/34526884)
|
[](https://issuehunt.io/repos/34526884)
|
||||||
|
@ -41,4 +41,9 @@ describe('unstable', () => {
|
|||||||
expect(document.querySelector('.ant-modal')).toBeTruthy();
|
expect(document.querySelector('.ant-modal')).toBeTruthy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('unstableSetRender without param', async () => {
|
||||||
|
const currentRender = unstableSetRender();
|
||||||
|
expect(currentRender).toBeInstanceOf(Function);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,23 +5,20 @@ import useResponsiveObserver from '../responsiveObserver';
|
|||||||
|
|
||||||
describe('Test ResponsiveObserve', () => {
|
describe('Test ResponsiveObserve', () => {
|
||||||
it('test ResponsiveObserve subscribe and unsubscribe', () => {
|
it('test ResponsiveObserve subscribe and unsubscribe', () => {
|
||||||
let responsiveObserveRef: any;
|
let responsiveRef: any = null;
|
||||||
const Demo = () => {
|
const Demo: React.FC = () => {
|
||||||
const responsiveObserver = useResponsiveObserver();
|
const responsiveObserver = useResponsiveObserver();
|
||||||
responsiveObserveRef = responsiveObserver;
|
responsiveRef = responsiveObserver;
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
render(<Demo />);
|
render(<Demo />);
|
||||||
const subscribeFunc = jest.fn();
|
const subscribeFunc = jest.fn();
|
||||||
const token = responsiveObserveRef.subscribe(subscribeFunc);
|
const token = responsiveRef.subscribe(subscribeFunc);
|
||||||
expect(
|
expect(responsiveRef.matchHandlers[responsiveRef.responsiveMap.xs].mql.matches).toBeTruthy();
|
||||||
responsiveObserveRef.matchHandlers[responsiveObserveRef.responsiveMap.xs].mql.matches,
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(subscribeFunc).toHaveBeenCalledTimes(1);
|
expect(subscribeFunc).toHaveBeenCalledTimes(1);
|
||||||
|
responsiveRef.unsubscribe(token);
|
||||||
responsiveObserveRef.unsubscribe(token);
|
|
||||||
expect(
|
expect(
|
||||||
responsiveObserveRef.matchHandlers[responsiveObserveRef.responsiveMap.xs].mql.removeListener,
|
responsiveRef.matchHandlers[responsiveRef.responsiveMap.xs].mql?.removeEventListener,
|
||||||
).toHaveBeenCalled();
|
).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { waitFakeTimer } from '../../../tests/utils';
|
import { waitFakeTimer } from '../../../tests/utils';
|
||||||
import { isStyleSupport } from '../styleChecker';
|
import { isStyleSupport } from '../styleChecker';
|
||||||
import throttleByAnimationFrame from '../throttleByAnimationFrame';
|
import throttleByAnimationFrame from '../throttleByAnimationFrame';
|
||||||
|
import toList from '../toList';
|
||||||
|
|
||||||
describe('Test utils function', () => {
|
describe('Test utils function', () => {
|
||||||
describe('throttle', () => {
|
describe('throttle', () => {
|
||||||
@ -56,4 +57,12 @@ describe('Test utils function', () => {
|
|||||||
spy.mockRestore();
|
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';
|
// 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;
|
||||||
const extendsObject = <T extends AnyObject = AnyObject>(...list: T[]) => {
|
function mergeProps<A, B, C>(a: A, b: B, c: C): C & B & A;
|
||||||
const result: AnyObject = { ...list[0] };
|
function mergeProps<A, B, C, D>(a: A, b: B, c: C, d: D): D & C & B & A;
|
||||||
|
function mergeProps(...items: any[]) {
|
||||||
for (let i = 1; i < list.length; i++) {
|
const ret: any = {};
|
||||||
const obj = list[i];
|
items.forEach((item) => {
|
||||||
if (obj) {
|
if (item) {
|
||||||
Object.keys(obj).forEach((key) => {
|
Object.keys(item).forEach((key) => {
|
||||||
const val = obj[key];
|
if (item[key] !== undefined) {
|
||||||
if (val !== undefined) {
|
ret[key] = item[key];
|
||||||
result[key] = val;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
export default mergeProps;
|
||||||
};
|
|
||||||
|
|
||||||
export default extendsObject;
|
|
||||||
|
@ -4,6 +4,11 @@ import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
|||||||
import type { DialogProps } from 'rc-dialog';
|
import type { DialogProps } from 'rc-dialog';
|
||||||
import pickAttrs from 'rc-util/lib/pickAttrs';
|
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 ClosableType = DialogProps['closable'];
|
||||||
|
|
||||||
export type BaseContextClosable = { closable?: ClosableType; closeIcon?: ReactNode };
|
export type BaseContextClosable = { closable?: ClosableType; closeIcon?: ReactNode };
|
||||||
@ -58,56 +63,42 @@ function useClosableConfig(closableCollection?: ClosableCollection | null) {
|
|||||||
...closable,
|
...closable,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return closableConfig;
|
return closableConfig;
|
||||||
}, [closable, closeIcon]);
|
}, [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` */
|
/** Collection contains the all the props related with closable. e.g. `closable`, `closeIcon` */
|
||||||
interface ClosableCollection {
|
interface ClosableCollection {
|
||||||
closable?: ClosableType;
|
closable?: ClosableType;
|
||||||
closeIcon?: ReactNode;
|
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 */
|
/** Use same object to support `useMemo` optimization */
|
||||||
const EmptyFallbackCloseCollection: ClosableCollection = {};
|
const EmptyFallbackCloseCollection: FallbackCloseCollection = {};
|
||||||
|
|
||||||
export default function useClosable(
|
export default function useClosable(
|
||||||
propCloseCollection?: ClosableCollection,
|
propCloseCollection?: ClosableCollection,
|
||||||
contextCloseCollection?: ClosableCollection | null,
|
contextCloseCollection?: ClosableCollection | null,
|
||||||
fallbackCloseCollection: ClosableCollection & {
|
fallbackCloseCollection: FallbackCloseCollection = EmptyFallbackCloseCollection,
|
||||||
/**
|
): [
|
||||||
* Some components need to wrap CloseIcon twice,
|
closable: boolean,
|
||||||
* this method will be executed once after the final CloseIcon is calculated
|
closeIcon: React.ReactNode,
|
||||||
*/
|
closeBtnIsDisabled: boolean,
|
||||||
closeIconRender?: (closeIcon: ReactNode) => ReactNode;
|
ariaOrDataProps?: HTMLAriaDataAttributes,
|
||||||
} = EmptyFallbackCloseCollection,
|
] {
|
||||||
): [closable: boolean, closeIcon: React.ReactNode, closeBtnIsDisabled: boolean] {
|
|
||||||
// Align the `props`, `context` `fallback` to config object first
|
// Align the `props`, `context` `fallback` to config object first
|
||||||
const propCloseConfig = useClosableConfig(propCloseCollection);
|
const propCloseConfig = useClosableConfig(propCloseCollection);
|
||||||
const contextCloseConfig = useClosableConfig(contextCloseCollection);
|
const contextCloseConfig = useClosableConfig(contextCloseCollection);
|
||||||
|
|
||||||
|
const [contextLocale] = useLocale('global', defaultLocale.global);
|
||||||
const closeBtnIsDisabled =
|
const closeBtnIsDisabled =
|
||||||
typeof propCloseConfig !== 'boolean' ? !!propCloseConfig?.disabled : false;
|
typeof propCloseConfig !== 'boolean' ? !!propCloseConfig?.disabled : false;
|
||||||
const mergedFallbackCloseCollection = React.useMemo(
|
const mergedFallbackCloseCollection = React.useMemo(
|
||||||
@ -127,11 +118,7 @@ export default function useClosable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (propCloseConfig) {
|
if (propCloseConfig) {
|
||||||
return assignWithoutUndefined(
|
return extendsObject(mergedFallbackCloseCollection, contextCloseConfig, propCloseConfig);
|
||||||
mergedFallbackCloseCollection,
|
|
||||||
contextCloseConfig,
|
|
||||||
propCloseConfig,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============== Context Second ==============
|
// =============== Context Second ==============
|
||||||
@ -141,7 +128,7 @@ export default function useClosable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (contextCloseConfig) {
|
if (contextCloseConfig) {
|
||||||
return assignWithoutUndefined(mergedFallbackCloseCollection, contextCloseConfig);
|
return extendsObject(mergedFallbackCloseCollection, contextCloseConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============= Fallback Default ==============
|
// ============= Fallback Default ==============
|
||||||
@ -151,30 +138,34 @@ export default function useClosable(
|
|||||||
// Calculate the final closeIcon
|
// Calculate the final closeIcon
|
||||||
return React.useMemo(() => {
|
return React.useMemo(() => {
|
||||||
if (mergedClosableConfig === false) {
|
if (mergedClosableConfig === false) {
|
||||||
return [false, null, closeBtnIsDisabled];
|
return [false, null, closeBtnIsDisabled, {}];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { closeIconRender } = mergedFallbackCloseCollection;
|
const { closeIconRender } = mergedFallbackCloseCollection;
|
||||||
const { closeIcon } = mergedClosableConfig;
|
const { closeIcon } = mergedClosableConfig;
|
||||||
|
|
||||||
let mergedCloseIcon: ReactNode = closeIcon;
|
let mergedCloseIcon: ReactNode = closeIcon;
|
||||||
|
|
||||||
|
// Wrap the closeIcon with aria props
|
||||||
|
const ariaOrDataProps = pickAttrs(mergedClosableConfig, true);
|
||||||
|
|
||||||
if (mergedCloseIcon !== null && mergedCloseIcon !== undefined) {
|
if (mergedCloseIcon !== null && mergedCloseIcon !== undefined) {
|
||||||
// Wrap the closeIcon if needed
|
// Wrap the closeIcon if needed
|
||||||
if (closeIconRender) {
|
if (closeIconRender) {
|
||||||
mergedCloseIcon = closeIconRender(closeIcon);
|
mergedCloseIcon = closeIconRender(closeIcon);
|
||||||
}
|
}
|
||||||
|
mergedCloseIcon = React.isValidElement(mergedCloseIcon) ? (
|
||||||
// Wrap the closeIcon with aria props
|
React.cloneElement(mergedCloseIcon, {
|
||||||
const ariaProps = pickAttrs(mergedClosableConfig, true);
|
'aria-label': contextLocale.close,
|
||||||
if (Object.keys(ariaProps).length) {
|
...ariaOrDataProps,
|
||||||
mergedCloseIcon = React.isValidElement(mergedCloseIcon) ? (
|
} as HTMLAriaDataAttributes)
|
||||||
React.cloneElement(mergedCloseIcon, ariaProps)
|
) : (
|
||||||
) : (
|
<span aria-label={contextLocale.close} {...ariaOrDataProps}>
|
||||||
<span {...ariaProps}>{mergedCloseIcon}</span>
|
{mergedCloseIcon}
|
||||||
);
|
</span>
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [true, mergedCloseIcon, closeBtnIsDisabled];
|
return [true, mergedCloseIcon, closeBtnIsDisabled, ariaOrDataProps];
|
||||||
}, [mergedClosableConfig, mergedFallbackCloseCollection]);
|
}, [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';
|
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 type { GlobalToken } from '../theme/internal';
|
||||||
import { useToken } from '../theme/internal';
|
import { useToken } from '../theme/internal';
|
||||||
|
import { addMediaQueryListener, removeMediaQueryListener } from './mediaQueryUtil';
|
||||||
|
|
||||||
export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
||||||
export type BreakpointMap = Record<Breakpoint, string>;
|
export type BreakpointMap = Record<Breakpoint, string>;
|
||||||
@ -28,7 +29,7 @@ const validateBreakpoints = (token: GlobalToken) => {
|
|||||||
const indexableToken: any = token;
|
const indexableToken: any = token;
|
||||||
const revBreakpoints = [...responsiveArray].reverse();
|
const revBreakpoints = [...responsiveArray].reverse();
|
||||||
|
|
||||||
revBreakpoints.forEach((breakpoint: Breakpoint, i: number) => {
|
revBreakpoints.forEach((breakpoint, i) => {
|
||||||
const breakpointUpper = breakpoint.toUpperCase();
|
const breakpointUpper = breakpoint.toUpperCase();
|
||||||
const screenMin = `screen${breakpointUpper}Min`;
|
const screenMin = `screen${breakpointUpper}Min`;
|
||||||
const screen = `screen${breakpointUpper}`;
|
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`;
|
const nextScreenMin = `screen${nextBreakpointUpperMin}Min`;
|
||||||
|
|
||||||
if (!(indexableToken[screenMax] <= indexableToken[nextScreenMin])) {
|
if (!(indexableToken[screenMax] <= indexableToken[nextScreenMin])) {
|
||||||
@ -61,23 +62,45 @@ const validateBreakpoints = (token: GlobalToken) => {
|
|||||||
return token;
|
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 [, token] = useToken();
|
||||||
const responsiveMap: BreakpointMap = getResponsiveMap(validateBreakpoints(token));
|
const responsiveMap = getResponsiveMap(validateBreakpoints(token));
|
||||||
|
|
||||||
// To avoid repeat create instance, we add `useMemo` here.
|
// To avoid repeat create instance, we add `useMemo` here.
|
||||||
return React.useMemo(() => {
|
return React.useMemo<ResponsiveObserverType>(() => {
|
||||||
const subscribers = new Map<number, SubscribeFunc>();
|
const subscribers = new Map<number, SubscribeFunc>();
|
||||||
let subUid = -1;
|
let subUid = -1;
|
||||||
let screens: Partial<Record<Breakpoint, boolean>> = {};
|
let screens: Partial<Record<Breakpoint, boolean>> = {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
matchHandlers: {} as {
|
responsiveMap,
|
||||||
[prop: string]: {
|
matchHandlers: {},
|
||||||
mql: MediaQueryList;
|
|
||||||
listener: (this: MediaQueryList, ev: MediaQueryListEvent) => void;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
dispatch(pointMap: ScreenMap) {
|
dispatch(pointMap: ScreenMap) {
|
||||||
screens = pointMap;
|
screens = pointMap;
|
||||||
subscribers.forEach((func) => func(screens));
|
subscribers.forEach((func) => func(screens));
|
||||||
@ -98,44 +121,26 @@ export default function useResponsiveObserver() {
|
|||||||
this.unregister();
|
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() {
|
register() {
|
||||||
Object.keys(responsiveMap).forEach((screen) => {
|
Object.entries(responsiveMap).forEach(([screen, mediaQuery]) => {
|
||||||
const matchMediaQuery = responsiveMap[screen as Breakpoint];
|
|
||||||
const listener = ({ matches }: { matches: boolean }) => {
|
const listener = ({ matches }: { matches: boolean }) => {
|
||||||
this.dispatch({
|
this.dispatch({ ...screens, [screen]: matches });
|
||||||
...screens,
|
|
||||||
[screen]: matches,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const mql = window.matchMedia(matchMediaQuery);
|
|
||||||
mql.addListener(listener);
|
|
||||||
this.matchHandlers[matchMediaQuery] = {
|
|
||||||
mql,
|
|
||||||
listener,
|
|
||||||
};
|
};
|
||||||
|
const mql = window.matchMedia(mediaQuery);
|
||||||
|
addMediaQueryListener(mql, listener);
|
||||||
|
this.matchHandlers[mediaQuery] = { mql, listener };
|
||||||
listener(mql);
|
listener(mql);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
responsiveMap,
|
unregister() {
|
||||||
|
Object.values(responsiveMap).forEach((mediaQuery) => {
|
||||||
|
const handler = this.matchHandlers[mediaQuery];
|
||||||
|
removeMediaQueryListener(handler?.mql, handler?.listener);
|
||||||
|
});
|
||||||
|
subscribers.clear();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}, [token]);
|
}, [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[] {
|
const toList = <T>(candidate: T | T[], skipEmpty = false): T[] => {
|
||||||
if (skipEmpty && (candidate === undefined || candidate === null)) return [];
|
if (skipEmpty && (candidate === undefined || candidate === null)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return Array.isArray(candidate) ? candidate : [candidate];
|
return Array.isArray(candidate) ? candidate : [candidate];
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default toList;
|
||||||
|
@ -4,7 +4,8 @@ import CSSMotion from 'rc-motion';
|
|||||||
import raf from 'rc-util/lib/raf';
|
import raf from 'rc-util/lib/raf';
|
||||||
import { composeRef } from 'rc-util/lib/ref';
|
import { composeRef } from 'rc-util/lib/ref';
|
||||||
|
|
||||||
import { getReactRender, type UnmountType } from '../../config-provider/UnstableContext';
|
import { unstableSetRender } from '../../config-provider/UnstableContext';
|
||||||
|
import type { UnmountType } from '../../config-provider/UnstableContext';
|
||||||
import { TARGET_CLS } from './interface';
|
import { TARGET_CLS } from './interface';
|
||||||
import type { ShowWaveEffect } from './interface';
|
import type { ShowWaveEffect } from './interface';
|
||||||
import { getTargetWaveColor } from './util';
|
import { getTargetWaveColor } from './util';
|
||||||
@ -40,14 +41,12 @@ const WaveEffect = (props: WaveEffectProps) => {
|
|||||||
const [height, setHeight] = React.useState(0);
|
const [height, setHeight] = React.useState(0);
|
||||||
const [enabled, setEnabled] = React.useState(false);
|
const [enabled, setEnabled] = React.useState(false);
|
||||||
|
|
||||||
const waveStyle = {
|
const waveStyle: React.CSSProperties = {
|
||||||
left,
|
left,
|
||||||
top,
|
top,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
borderRadius: borderRadius.map((radius) => `${radius}px`).join(' '),
|
borderRadius: borderRadius.map((radius) => `${radius}px`).join(' '),
|
||||||
} as React.CSSProperties & {
|
|
||||||
[name: string]: number | string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (color) {
|
if (color) {
|
||||||
@ -161,7 +160,7 @@ const showWaveEffect: ShowWaveEffect = (target, info) => {
|
|||||||
holder.style.top = '0px';
|
holder.style.top = '0px';
|
||||||
target?.insertBefore(holder, target?.firstChild);
|
target?.insertBefore(holder, target?.firstChild);
|
||||||
|
|
||||||
const reactRender = getReactRender();
|
const reactRender = unstableSetRender();
|
||||||
|
|
||||||
let unmountCallback: UnmountType | null = null;
|
let unmountCallback: UnmountType | null = null;
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user