mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 09:26:06 +08:00
Merge branch 'master' into task/add-file-metadata
This commit is contained in:
commit
c4a4cc6241
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 更新
|
||||
- 📦 包体积优化
|
||||
- ⚡️ 性能优化
|
||||
- 🌐 国际化改进
|
||||
- 提供改动背景和解决方案
|
||||
- 更新日志同时提供英文和中文版本
|
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. `destroyOnClose`)
|
||||
* Component use other component config. Naming as component.(e.g. `<Table pagination={{...}} />`)
|
||||
* ClassName: `className`
|
||||
* Additional classes should be merged into `classes` (e.g. `<Button classes={{ inner: 'custom-inner' }} />`)
|
||||
* Format
|
||||
* Not modify value when blur: `preserveInvalidOnBlur`
|
||||
|
||||
## Event
|
||||
* Trigger event: `on` + `EventName`
|
||||
* Trigger sub component event: `on` + `SubComponentName` + `EventName` (e.g.`onSearchChange`)
|
||||
* Trigger prop event: `on` + `PropName` + `EventName` (e.g.`onDragStart`)
|
||||
* Before trigger event: `before` + `EventName`
|
||||
* After trigger event: `after` + `EventName`
|
||||
* After continuous action, such as drag Slider: `on` + `EventName` + `Complete`
|
||||
|
||||
## Reference
|
||||
|
||||
Component should have `ref` prop. Which should provide the structure:
|
||||
|
||||
```tsx
|
||||
ComponentRef {
|
||||
nativeElement: HTMLElement;
|
||||
// Other function
|
||||
focus: VoidFunction;
|
||||
}
|
||||
```
|
||||
|
||||
## Component Token
|
||||
`variant (optional)` + `semantic part` + `semantic part variant (optional)` + `css property` + `size/disabled (optional)`
|
||||
|
||||
All component tokens should follow the structure above, and should not conflict with Global Token.
|
||||
* `variant` means this token only works in certain variant, like `horizontal` `borderless`.
|
||||
* `semantic part` means the typical element of component, like `item` `header`. If there's.
|
||||
* `semantic part status` means the variant of the semantic part before it, like `hover` `selected`.
|
||||
* `css property` means the exact property where the token is used, like `fontSize` `width`.
|
||||
|
||||
For example:
|
||||
| v4 | v5 | Note |
|
||||
| --- | --- | --- |
|
||||
| `@menu-item-color` | `itemColor` | Remove the component prefix |
|
||||
| `@select-item-selected-bg` | `itemSelectedBg` | `selected` is variant of item |
|
||||
| `@select-single-item-height-lg` | `itemHeightLG` | `single` is variant of Select (by default), `LG` is size of Select |
|
||||
|
||||
> Note: If there's no semantic part for one component token, for example, the root borderRadius of Button, it is not suitable to add it. Because we could modify it easily with `className` and `style`.
|
||||
|
||||
## Current listing api & Chinese version
|
||||
ref: [#16048](mdc:https:/github.com/ant-design/ant-design/issues/16048)
|
||||
|
||||
## API standard in the document
|
||||
|
||||
### Examples
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| --------- | ---------------------- | ------------------------------ | ------ |
|
||||
| htmlType | xxx | string | `button ` |
|
||||
| type | xxx | `horizontal ` \| `vertical ` | `horizontal` |
|
||||
| disabled | xxx | boolean | false |
|
||||
| minLength | xxx | number | 0 |
|
||||
| style | xxx | CSSProperties | - |
|
||||
| character | xxx | (props) => ReactNode | - |
|
||||
| offset | xxx| \[number, number] | \[0, 0] |
|
||||
| value | xxx | string \| number | `small` |
|
||||
|
||||
### Promise
|
||||
- When string type, the **Default** use ` `` `.
|
||||
- Can also list string optional values in **Type**.
|
||||
- When boolean type, the **Default** value is true or false.
|
||||
- When number type, the **Default** value use numbers directly.
|
||||
- When function type, use an arrow function expression in **Type**.
|
||||
- No default value use - .
|
||||
- Capitalize the first letter in **Description** apart from `someProp`.
|
||||
- No period at the end of the **Description**.
|
||||
- API order is arranged in alphabetical order, and can be put together under special circumstances (such as: xs sm md).
|
||||
|
||||
ref: [#25066](mdc:https:/github.com/ant-design/ant-design/issues/25066)
|
27
.cursor/rules/project.mdc
Normal file
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,7 +1,9 @@
|
||||
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
|
||||
import React from 'react';
|
||||
import { Col, ConfigProvider, Flex, Row, Tag, theme, Typography } from 'antd';
|
||||
import { Col, ConfigProvider, Flex, Popover, Row, Tag, theme, Typography } from 'antd';
|
||||
import { createStyles, css } from 'antd-style';
|
||||
import classnames from 'classnames';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
|
||||
const MARK_BORDER_SIZE = 2;
|
||||
|
||||
@ -65,13 +67,14 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
|
||||
}));
|
||||
|
||||
export interface SemanticPreviewProps {
|
||||
componentName: string;
|
||||
semantics: { name: string; desc: string; version?: string }[];
|
||||
children: React.ReactElement;
|
||||
children: React.ReactElement<any>;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
||||
const { semantics = [], children, height } = props;
|
||||
const { semantics = [], children, height, componentName = 'Component' } = props;
|
||||
const { token } = theme.useToken();
|
||||
|
||||
// ======================= Semantic =======================
|
||||
@ -97,7 +100,7 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
||||
// ======================== Hover =========================
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>();
|
||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(null);
|
||||
|
||||
const [positionMotion, setPositionMotion] = React.useState<boolean>(false);
|
||||
const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null);
|
||||
@ -111,12 +114,14 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
||||
const targetElement = containerRef.current?.querySelector<HTMLElement>(`.${targetClassName}`);
|
||||
const containerRect = containerRef.current?.getBoundingClientRect();
|
||||
const targetRect = targetElement?.getBoundingClientRect();
|
||||
|
||||
setMarkPos([
|
||||
(targetRect?.left || 0) - (containerRect?.left || 0),
|
||||
(targetRect?.top || 0) - (containerRect?.top || 0),
|
||||
targetRect?.width || 0,
|
||||
targetRect?.height || 0,
|
||||
]);
|
||||
|
||||
timerRef.current = setTimeout(() => {
|
||||
setPositionMotion(true);
|
||||
}, 10);
|
||||
@ -149,11 +154,37 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
||||
onMouseLeave={() => setHoverSemantic(null)}
|
||||
>
|
||||
<Flex vertical gap="small">
|
||||
<Flex gap="small" align="center">
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||
{semantic.name}
|
||||
</Typography.Title>
|
||||
{semantic.version && <Tag color="blue">{semantic.version}</Tag>}
|
||||
<Flex gap="small" align="center" justify="space-between">
|
||||
<Flex gap="small" align="center">
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||
{semantic.name}
|
||||
</Typography.Title>
|
||||
{semantic.version && <Tag color="blue">{semantic.version}</Tag>}
|
||||
</Flex>
|
||||
<Popover
|
||||
content={
|
||||
<Typography style={{ fontSize: 12, minWidth: 300 }}>
|
||||
<pre dir="ltr">
|
||||
<code dir="ltr">
|
||||
{`<${componentName}
|
||||
classNames={{
|
||||
${semantic.name}: 'my-${componentName.toLowerCase()}',
|
||||
}}
|
||||
styles={{
|
||||
${semantic.name}: { color: 'red' },
|
||||
}}
|
||||
>
|
||||
...
|
||||
</${componentName}>`}
|
||||
</code>
|
||||
</pre>
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<InfoCircleOutlined
|
||||
style={{ cursor: 'pointer', color: token.colorTextSecondary }}
|
||||
/>
|
||||
</Popover>
|
||||
</Flex>
|
||||
<Typography.Paragraph style={{ margin: 0, fontSize: token.fontSizeSM }}>
|
||||
{semantic.desc}
|
||||
|
@ -1,30 +0,0 @@
|
||||
function use<T>(promise: PromiseLike<T>): T {
|
||||
const internal: PromiseLike<T> & {
|
||||
status?: 'pending' | 'fulfilled' | 'rejected';
|
||||
value?: T;
|
||||
reason?: any;
|
||||
} = promise;
|
||||
if (internal.status === 'fulfilled') {
|
||||
return internal.value as T;
|
||||
}
|
||||
if (internal.status === 'rejected') {
|
||||
throw internal.reason;
|
||||
}
|
||||
if (internal.status === 'pending') {
|
||||
throw internal;
|
||||
}
|
||||
internal.status = 'pending';
|
||||
internal.then(
|
||||
(result) => {
|
||||
internal.status = 'fulfilled';
|
||||
internal.value = result;
|
||||
},
|
||||
(reason) => {
|
||||
internal.status = 'rejected';
|
||||
internal.reason = reason;
|
||||
},
|
||||
);
|
||||
throw internal;
|
||||
}
|
||||
|
||||
export default use;
|
@ -1,7 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
export const DarkContext = React.createContext(false);
|
||||
|
||||
export default function useDark() {
|
||||
return React.useContext(DarkContext);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import fetch from 'cross-fetch';
|
||||
|
||||
import use from '../use';
|
||||
import FetchCache from './cache';
|
||||
|
||||
const cache = new FetchCache();
|
||||
@ -15,7 +15,7 @@ const useFetch = <T>(options: string | { request: () => PromiseLike<T>; key: str
|
||||
request = options.request;
|
||||
key = options.key;
|
||||
}
|
||||
return use(cache.promise<T>(key, request));
|
||||
return React.use<T>(cache.promise<T>(key, request));
|
||||
};
|
||||
|
||||
export default useFetch;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { Space, Tag, version } from 'antd';
|
||||
import { Flex, Tag, version } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import classnames from 'classnames';
|
||||
import { useFullSidebarData, useSidebarData } from 'dumi';
|
||||
@ -12,6 +12,22 @@ function isVersionNumber(value?: string) {
|
||||
return value && /^\d+\.\d+\.\d+$/.test(value);
|
||||
}
|
||||
|
||||
const getTagColor = (val?: string) => {
|
||||
if (isVersionNumber(val)) {
|
||||
return 'success';
|
||||
}
|
||||
if (val?.toUpperCase() === 'NEW') {
|
||||
return 'success';
|
||||
}
|
||||
if (val?.toUpperCase() === 'UPDATED') {
|
||||
return 'processing';
|
||||
}
|
||||
if (val?.toUpperCase() === 'DEPRECATED') {
|
||||
return 'red';
|
||||
}
|
||||
return 'success';
|
||||
};
|
||||
|
||||
const useStyle = createStyles(({ css, token }) => ({
|
||||
link: css`
|
||||
display: flex;
|
||||
@ -42,20 +58,17 @@ interface MenuItemLabelProps {
|
||||
const MenuItemLabelWithTag: React.FC<MenuItemLabelProps> = (props) => {
|
||||
const { styles } = useStyle();
|
||||
const { before, after, link, title, subtitle, search, tag, className } = props;
|
||||
|
||||
if (!before && !after) {
|
||||
return (
|
||||
<Link to={`${link}${search}`} className={classnames(className, { [styles.link]: tag })}>
|
||||
<Space>
|
||||
<Flex justify="flex-start" align="center" gap="small">
|
||||
<span>{title}</span>
|
||||
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
|
||||
</Space>
|
||||
</Flex>
|
||||
{tag && (
|
||||
<Tag
|
||||
bordered={false}
|
||||
className={classnames(styles.tag)}
|
||||
color={isVersionNumber(tag) || tag === 'New' ? 'success' : 'processing'}
|
||||
>
|
||||
{tag.replace('VERSION', version)}
|
||||
<Tag bordered={false} className={classnames(styles.tag)} color={getTagColor(tag)}>
|
||||
{tag.replace(/VERSION/i, version)}
|
||||
</Tag>
|
||||
)}
|
||||
</Link>
|
||||
|
@ -64,7 +64,6 @@ const useThemeAnimation = () => {
|
||||
event: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||
isDark: boolean,
|
||||
) => {
|
||||
// @ts-ignore
|
||||
if (!(event && typeof document.startViewTransition === 'function')) {
|
||||
return;
|
||||
}
|
||||
@ -85,13 +84,10 @@ const useThemeAnimation = () => {
|
||||
'color-scheme',
|
||||
);
|
||||
document
|
||||
// @ts-ignore
|
||||
.startViewTransition(async () => {
|
||||
// wait for theme change end
|
||||
while (colorBgElevated === animateRef.current.colorBgElevated) {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000 / 60);
|
||||
});
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1000 / 60));
|
||||
}
|
||||
const root = document.documentElement;
|
||||
root.classList.remove(isDark ? 'dark' : 'light');
|
||||
@ -111,7 +107,6 @@ const useThemeAnimation = () => {
|
||||
|
||||
// inject transition style
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
if (typeof document.startViewTransition === 'function') {
|
||||
updateCSS(viewTransitionStyle, 'view-transition-style');
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { Badge, Carousel, Flex, Skeleton, Typography } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import classNames from 'classnames';
|
||||
@ -110,10 +110,10 @@ const RecommendItem: React.FC<RecommendItemProps> = ({ extra, index, icons, clas
|
||||
};
|
||||
|
||||
export const BannerRecommendsFallback: React.FC = () => {
|
||||
const { isMobile } = useContext(SiteContext);
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
const { styles } = useStyle();
|
||||
|
||||
const list = new Array(3).fill(1);
|
||||
const list = Array.from({ length: 3 });
|
||||
|
||||
return isMobile ? (
|
||||
<Carousel className={styles.carousel}>
|
||||
@ -137,11 +137,12 @@ export const BannerRecommendsFallback: React.FC = () => {
|
||||
const BannerRecommends: React.FC = () => {
|
||||
const { styles } = useStyle();
|
||||
const [, lang] = useLocale();
|
||||
const { isMobile } = React.useContext(SiteContext);
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
const data = useSiteData();
|
||||
const extras = data?.extras?.[lang];
|
||||
const icons = data?.icons || [];
|
||||
const first3 = !extras || extras.length === 0 ? new Array(3).fill(null) : extras.slice(0, 3);
|
||||
const first3 =
|
||||
!extras || extras.length === 0 ? Array.from<any>({ length: 3 }) : extras.slice(0, 3);
|
||||
|
||||
if (!data) {
|
||||
return <BannerRecommendsFallback />;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Alert,
|
||||
@ -16,9 +16,9 @@ import { createStyles, css } from 'antd-style';
|
||||
import classNames from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import useDark from '../../../hooks/useDark';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import SiteContext from '../../../theme/slots/SiteContext';
|
||||
import { DarkContext } from './../../../hooks/useDark';
|
||||
import { getCarouselStyle } from './util';
|
||||
|
||||
const { _InternalPanelDoNotUseOrYouWillBeFired: ModalDoNotUseOrYouWillBeFired } = Modal;
|
||||
@ -61,66 +61,62 @@ const locales = {
|
||||
},
|
||||
};
|
||||
|
||||
const useStyle = () => {
|
||||
const isRootDark = useDark();
|
||||
const useStyle = createStyles(({ token }, isDark: boolean) => {
|
||||
const { carousel } = getCarouselStyle();
|
||||
return {
|
||||
card: css`
|
||||
border-radius: ${token.borderRadius}px;
|
||||
border: 1px solid ${isDark ? token.colorBorder : 'transparent'};
|
||||
background-color: ${isDark ? token.colorBgContainer : '#f5f8ff'};
|
||||
padding: ${token.paddingXL}px;
|
||||
flex: none;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
return createStyles(({ token }) => {
|
||||
const { carousel } = getCarouselStyle();
|
||||
|
||||
return {
|
||||
card: css`
|
||||
border-radius: ${token.borderRadius}px;
|
||||
border: 1px solid ${isRootDark ? token.colorBorder : 'transparent'};
|
||||
background: ${isRootDark ? token.colorBgContainer : '#f5f8ff'};
|
||||
padding: ${token.paddingXL}px;
|
||||
> * {
|
||||
flex: none;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
> * {
|
||||
flex: none;
|
||||
}
|
||||
`,
|
||||
cardCircle: css`
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: #1677ff;
|
||||
border-radius: 50%;
|
||||
filter: blur(40px);
|
||||
opacity: 0.1;
|
||||
`,
|
||||
mobileCard: css`
|
||||
height: 395px;
|
||||
`,
|
||||
nodeWrap: css`
|
||||
margin-top: ${token.paddingLG}px;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`,
|
||||
carousel,
|
||||
componentsList: css`
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
`,
|
||||
mobileComponentsList: css`
|
||||
margin: 0 ${token.margin}px;
|
||||
`,
|
||||
};
|
||||
})();
|
||||
};
|
||||
}
|
||||
`,
|
||||
cardCircle: css`
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: #1677ff;
|
||||
border-radius: 50%;
|
||||
filter: blur(40px);
|
||||
opacity: 0.1;
|
||||
`,
|
||||
mobileCard: css`
|
||||
height: 395px;
|
||||
`,
|
||||
nodeWrap: css`
|
||||
margin-top: ${token.paddingLG}px;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`,
|
||||
carousel,
|
||||
componentsList: css`
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
`,
|
||||
mobileComponentsList: css`
|
||||
margin: 0 ${token.margin}px;
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
const ComponentItem: React.FC<ComponentItemProps> = ({ title, node, type, index }) => {
|
||||
const tagColor = type === 'new' ? 'processing' : 'warning';
|
||||
const [locale] = useLocale(locales);
|
||||
const tagText = type === 'new' ? locale.new : locale.update;
|
||||
const { styles } = useStyle();
|
||||
const { isMobile } = useContext(SiteContext);
|
||||
const isDark = React.use(DarkContext);
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
const { styles } = useStyle(isDark);
|
||||
return (
|
||||
<div className={classNames(styles.card, isMobile && styles.mobileCard)}>
|
||||
{/* Decorator */}
|
||||
@ -151,7 +147,7 @@ interface ComponentItemProps {
|
||||
const ComponentsList: React.FC = () => {
|
||||
const { styles } = useStyle();
|
||||
const [locale] = useLocale(locales);
|
||||
const { isMobile } = useContext(SiteContext);
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
const COMPONENTS = React.useMemo<Omit<ComponentItemProps, 'index'>[]>(
|
||||
() => [
|
||||
{
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { Col, Row, Typography } from 'antd';
|
||||
import { createStyles, useTheme } from 'antd-style';
|
||||
import { useLocation } from 'dumi';
|
||||
|
||||
import useDark from '../../../hooks/useDark';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import Link from '../../../theme/common/Link';
|
||||
import SiteContext from '../../../theme/slots/SiteContext';
|
||||
import * as utils from '../../../theme/utils';
|
||||
import { DarkContext } from './../../../hooks/useDark';
|
||||
|
||||
const SECONDARY_LIST = [
|
||||
{
|
||||
@ -63,14 +63,12 @@ const locales = {
|
||||
},
|
||||
};
|
||||
|
||||
const useStyle = () => {
|
||||
const isRootDark = useDark();
|
||||
|
||||
return createStyles(({ token, css }) => ({
|
||||
const useStyle = createStyles(({ token, css }, isDark: boolean) => {
|
||||
return {
|
||||
card: css`
|
||||
padding: ${token.paddingSM}px;
|
||||
border-radius: ${token.borderRadius * 2}px;
|
||||
background: ${isRootDark ? 'rgba(0, 0, 0, 0.45)' : token.colorBgElevated};
|
||||
background: ${isDark ? 'rgba(0, 0, 0, 0.45)' : token.colorBgElevated};
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.03),
|
||||
0 1px 6px -1px rgba(0, 0, 0, 0.02),
|
||||
@ -87,23 +85,25 @@ const useStyle = () => {
|
||||
display: block;
|
||||
border-radius: ${token.borderRadius * 2}px;
|
||||
padding: ${token.paddingMD}px ${token.paddingLG}px;
|
||||
background: ${isRootDark ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.02)'};
|
||||
border: 1px solid ${isRootDark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.06)'};
|
||||
background: ${isDark ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.02)'};
|
||||
border: 1px solid ${isDark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.06)'};
|
||||
|
||||
img {
|
||||
height: 48px;
|
||||
}
|
||||
`,
|
||||
}))();
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
const DesignFramework: React.FC = () => {
|
||||
const [locale] = useLocale(locales);
|
||||
const token = useTheme();
|
||||
const { styles } = useStyle();
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
const isDark = React.use(DarkContext);
|
||||
const { styles } = useStyle(isDark);
|
||||
const { pathname, search } = useLocation();
|
||||
const isZhCN = utils.isZhCN(pathname);
|
||||
const { isMobile } = useContext(SiteContext);
|
||||
|
||||
const colSpan = isMobile ? 24 : 8;
|
||||
|
||||
const MAINLY_LIST = [
|
||||
|
@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { Typography } from 'antd';
|
||||
import { createStyles, useTheme } from 'antd-style';
|
||||
import classNames from 'classnames';
|
||||
@ -40,7 +39,7 @@ const Group: React.FC<React.PropsWithChildren<GroupProps>> = (props) => {
|
||||
const { id, title, titleColor, description, children, decoration, background, collapse } = props;
|
||||
const token = useTheme();
|
||||
const { styles } = useStyle();
|
||||
const { isMobile } = useContext(SiteContext);
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
const childNode = (
|
||||
<>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
|
@ -108,7 +108,7 @@ const ComponentsBlock: React.FC = () => {
|
||||
const { styles } = useStyle();
|
||||
|
||||
return (
|
||||
<Tilt options={{ max: 20, glare: true, scale: 1 }} className={styles.holder}>
|
||||
<Tilt options={{ max: 4, glare: false, scale: 0.98 }} className={styles.holder}>
|
||||
<ModalPanel title="Ant Design 5.0" width="100%">
|
||||
{locale.text}
|
||||
</ModalPanel>
|
||||
@ -119,7 +119,7 @@ const ComponentsBlock: React.FC = () => {
|
||||
<div style={{ flex: 'none' }}>
|
||||
<Dropdown.Button
|
||||
menu={{
|
||||
items: new Array(5).fill(null).map((_, index) => ({
|
||||
items: Array.from({ length: 5 }).map((_, index) => ({
|
||||
key: `opt${index}`,
|
||||
label: `${locale.option} ${index}`,
|
||||
})),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { ConfigProvider, Flex, Typography } from 'antd';
|
||||
import React, { Suspense, use } from 'react';
|
||||
import { Flex, Typography } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import classNames from 'classnames';
|
||||
import { useLocation } from 'dumi';
|
||||
@ -7,9 +7,12 @@ import { useLocation } from 'dumi';
|
||||
import useLocale from '../../../../hooks/useLocale';
|
||||
import LinkButton from '../../../../theme/common/LinkButton';
|
||||
import SiteContext from '../../../../theme/slots/SiteContext';
|
||||
import type { SiteContextProps } from '../../../../theme/slots/SiteContext';
|
||||
import * as utils from '../../../../theme/utils';
|
||||
import GroupMaskLayer from '../GroupMaskLayer';
|
||||
|
||||
import '../SiteContext';
|
||||
|
||||
const ComponentsBlock = React.lazy(() => import('./ComponentsBlock'));
|
||||
|
||||
const locales = {
|
||||
@ -26,105 +29,103 @@ const locales = {
|
||||
},
|
||||
};
|
||||
|
||||
const useStyle = () => {
|
||||
const { direction } = React.useContext(ConfigProvider.ConfigContext);
|
||||
const { isMobile } = React.useContext(SiteContext);
|
||||
const isRTL = direction === 'rtl';
|
||||
return createStyles(({ token, css, cx }) => {
|
||||
const textShadow = `0 0 4px ${token.colorBgContainer}`;
|
||||
const useStyle = createStyles(({ token, css, cx }, siteConfig: SiteContextProps) => {
|
||||
const textShadow = `0 0 4px ${token.colorBgContainer}`;
|
||||
const isDark = siteConfig.theme.includes('dark');
|
||||
const mask = cx(css`
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
backdrop-filter: blur(2px);
|
||||
opacity: 1;
|
||||
background-color: ${isDark ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.2)'};
|
||||
transition: all 1s ease;
|
||||
pointer-events: none;
|
||||
`);
|
||||
|
||||
const mask = cx(css`
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
backdrop-filter: blur(4px);
|
||||
opacity: 1;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
transition: all 1s ease;
|
||||
pointer-events: none;
|
||||
`);
|
||||
const block = cx(css`
|
||||
position: absolute;
|
||||
inset-inline-end: -60px;
|
||||
top: -24px;
|
||||
transition: all 1s cubic-bezier(0.03, 0.98, 0.52, 0.99);
|
||||
`);
|
||||
|
||||
return {
|
||||
holder: css`
|
||||
height: 640px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
perspective: 800px;
|
||||
/* fix safari bug by removing blur style */
|
||||
transform: translateZ(1000px);
|
||||
row-gap: ${token.marginXL}px;
|
||||
return {
|
||||
holder: css`
|
||||
height: 640px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
perspective: 800px;
|
||||
/* fix safari bug by removing blur style */
|
||||
transform: translateZ(1000px);
|
||||
row-gap: ${token.marginXL}px;
|
||||
|
||||
&:hover .${mask} {
|
||||
&:hover {
|
||||
.${mask} {
|
||||
opacity: 0;
|
||||
}
|
||||
`,
|
||||
|
||||
mask,
|
||||
|
||||
typography: css`
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-inline: ${token.paddingXL}px;
|
||||
text-shadow: ${new Array(5)
|
||||
.fill(null)
|
||||
.map(() => textShadow)
|
||||
.join(', ')};
|
||||
|
||||
h1 {
|
||||
font-family: AliPuHui, ${token.fontFamily} !important;
|
||||
font-weight: 900 !important;
|
||||
font-size: ${token.fontSizeHeading2 * 2}px !important;
|
||||
line-height: ${token.lineHeightHeading2} !important;
|
||||
.${block} {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
p {
|
||||
font-size: ${token.fontSizeLG}px !important;
|
||||
font-weight: normal !important;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`,
|
||||
mask,
|
||||
|
||||
block: css`
|
||||
position: absolute;
|
||||
inset-inline-end: 0;
|
||||
top: -38px;
|
||||
transform: ${isRTL ? 'rotate3d(24, 83, -45, 57deg)' : 'rotate3d(24, -83, 45, 57deg)'};
|
||||
`,
|
||||
child: css`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
z-index: 1;
|
||||
`,
|
||||
btnWrap: css`
|
||||
margin-bottom: ${token.marginXL}px;
|
||||
`,
|
||||
bgImg: css`
|
||||
position: absolute;
|
||||
width: 240px;
|
||||
`,
|
||||
bgImgTop: css`
|
||||
top: 0;
|
||||
inset-inline-start: ${isMobile ? '-120px' : 0};
|
||||
`,
|
||||
bgImgBottom: css`
|
||||
bottom: 120px;
|
||||
inset-inline-end: ${isMobile ? 0 : '40%'};
|
||||
`,
|
||||
};
|
||||
})();
|
||||
};
|
||||
typography: css`
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-inline: ${token.paddingXL}px;
|
||||
text-shadow: ${Array.from({ length: 5 }, () => textShadow).join(', ')};
|
||||
h1 {
|
||||
font-family: AliPuHui, ${token.fontFamily} !important;
|
||||
font-weight: 900 !important;
|
||||
font-size: ${token.fontSizeHeading2 * 2}px !important;
|
||||
line-height: ${token.lineHeightHeading2} !important;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: ${token.fontSizeLG}px !important;
|
||||
font-weight: normal !important;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`,
|
||||
block,
|
||||
child: css`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
z-index: 1;
|
||||
`,
|
||||
btnWrap: css`
|
||||
margin-bottom: ${token.marginXL}px;
|
||||
`,
|
||||
bgImg: css`
|
||||
position: absolute;
|
||||
width: 240px;
|
||||
`,
|
||||
bgImgTop: css`
|
||||
top: 0;
|
||||
inset-inline-start: ${siteConfig.isMobile ? '-120px' : 0};
|
||||
`,
|
||||
bgImgBottom: css`
|
||||
bottom: 120px;
|
||||
inset-inline-end: ${siteConfig.isMobile ? 0 : '40%'};
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
const PreviewBanner: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
|
||||
const { children } = props;
|
||||
const [locale] = useLocale(locales);
|
||||
const { styles } = useStyle();
|
||||
const { isMobile } = React.useContext(SiteContext);
|
||||
const siteConfig = use(SiteContext);
|
||||
const { styles } = useStyle(siteConfig);
|
||||
const { pathname, search } = useLocation();
|
||||
const isZhCN = utils.isZhCN(pathname);
|
||||
|
||||
@ -148,7 +149,7 @@ const PreviewBanner: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
|
||||
<div className={styles.holder}>
|
||||
{/* Mobile not show the component preview */}
|
||||
<Suspense fallback={null}>
|
||||
{isMobile ? null : (
|
||||
{siteConfig.isMobile ? null : (
|
||||
<div className={styles.block}>
|
||||
<ComponentsBlock />
|
||||
</div>
|
||||
|
@ -4,8 +4,6 @@ export interface SiteContextProps {
|
||||
isMobile: boolean;
|
||||
}
|
||||
|
||||
const SiteContext = React.createContext<SiteContextProps>({
|
||||
isMobile: false,
|
||||
});
|
||||
const SiteContext = React.createContext<SiteContextProps>({ isMobile: false });
|
||||
|
||||
export default SiteContext;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ColorPicker, Flex, Input } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import type { ColorPickerProps, GetProp } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { generateColor } from 'antd/es/color-picker/util';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@ -61,7 +61,7 @@ const DebouncedColorPicker: React.FC<React.PropsWithChildren<ThemeColorPickerPro
|
||||
<ColorPicker
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
presets={[{ label: 'PresetColors', colors: PRESET_COLORS }]}
|
||||
presets={[{ label: 'PresetColors', key: 'PresetColors', colors: PRESET_COLORS }]}
|
||||
>
|
||||
{children}
|
||||
</ColorPicker>
|
||||
|
@ -1,12 +1,13 @@
|
||||
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
|
||||
import * as React from 'react';
|
||||
import { defaultAlgorithm, defaultTheme } from '@ant-design/compatible';
|
||||
import { FastColor } from '@ant-design/fast-color';
|
||||
import {
|
||||
BellOutlined,
|
||||
FolderOutlined,
|
||||
HomeOutlined,
|
||||
QuestionCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import type { ColorPickerProps, GetProp, MenuProps, ThemeConfig } from 'antd';
|
||||
import {
|
||||
Breadcrumb,
|
||||
@ -25,13 +26,13 @@ import { generateColor } from 'antd/es/color-picker/util';
|
||||
import classNames from 'classnames';
|
||||
import { useLocation } from 'dumi';
|
||||
|
||||
import useDark from '../../../../hooks/useDark';
|
||||
import useLocale from '../../../../hooks/useLocale';
|
||||
import LinkButton from '../../../../theme/common/LinkButton';
|
||||
import SiteContext from '../../../../theme/slots/SiteContext';
|
||||
import { getLocalizedPathname } from '../../../../theme/utils';
|
||||
import Group from '../Group';
|
||||
import { getCarouselStyle } from '../util';
|
||||
import { DarkContext } from './../../../../hooks/useDark';
|
||||
import BackgroundImage from './BackgroundImage';
|
||||
import ColorPicker from './ColorPicker';
|
||||
import { DEFAULT_COLOR, getAvatarURL, getClosetColor, PINK_COLOR } from './colorUtil';
|
||||
@ -324,7 +325,7 @@ const ThemesInfo: Record<THEME, Partial<ThemeData>> = {
|
||||
const normalize = (value: number) => value / 255;
|
||||
|
||||
function rgbToColorMatrix(color: string) {
|
||||
const rgb = new TinyColor(color).toRgb();
|
||||
const rgb = new FastColor(color).toRgb();
|
||||
const { r, g, b } = rgb;
|
||||
|
||||
const invertValue = normalize(r) * 100;
|
||||
@ -360,7 +361,7 @@ const Theme: React.FC = () => {
|
||||
const { compact, themeType, colorPrimary, ...themeToken } = themeData;
|
||||
const isLight = themeType !== 'dark';
|
||||
const [form] = Form.useForm();
|
||||
const { isMobile } = React.useContext(SiteContext);
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
const colorPrimaryValue = React.useMemo(
|
||||
() => (typeof colorPrimary === 'string' ? colorPrimary : colorPrimary.toHexString()),
|
||||
[colorPrimary],
|
||||
@ -393,11 +394,11 @@ const Theme: React.FC = () => {
|
||||
form.setFieldsValue(mergedData);
|
||||
}, [themeType]);
|
||||
|
||||
const isRootDark = useDark();
|
||||
const isDark = React.use(DarkContext);
|
||||
|
||||
React.useEffect(() => {
|
||||
onThemeChange({}, { ...themeData, themeType: isRootDark ? 'dark' : 'default' });
|
||||
}, [isRootDark]);
|
||||
onThemeChange({}, { ...themeData, themeType: isDark ? 'dark' : 'default' });
|
||||
}, [isDark]);
|
||||
|
||||
// ================================ Tokens ================================
|
||||
const closestColor = getClosetColor(colorPrimaryValue);
|
||||
|
@ -2,8 +2,8 @@ import React, { Suspense } from 'react';
|
||||
import { ConfigProvider, theme } from 'antd';
|
||||
import { createStyles, css } from 'antd-style';
|
||||
|
||||
import useDark from '../../hooks/useDark';
|
||||
import useLocale from '../../hooks/useLocale';
|
||||
import { DarkContext } from './../../hooks/useDark';
|
||||
import BannerRecommends from './components/BannerRecommends';
|
||||
import Group from './components/Group';
|
||||
import PreviewBanner from './components/PreviewBanner';
|
||||
@ -41,7 +41,7 @@ const Homepage: React.FC = () => {
|
||||
const { styles } = useStyle();
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const isRootDark = useDark();
|
||||
const isDark = React.use(DarkContext);
|
||||
|
||||
return (
|
||||
<section>
|
||||
@ -78,7 +78,7 @@ const Homepage: React.FC = () => {
|
||||
<Group
|
||||
title={locale.designTitle}
|
||||
description={locale.designDesc}
|
||||
background={isRootDark ? '#393F4A' : '#F5F8FF'}
|
||||
background={isDark ? '#393F4A' : '#F5F8FF'}
|
||||
decoration={
|
||||
<img
|
||||
draggable={false}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
|
||||
import React, { Suspense, useEffect } from 'react';
|
||||
import { Button, App, Skeleton } from 'antd';
|
||||
import { App, Button, Skeleton } from 'antd';
|
||||
import { enUS, zhCN } from 'antd-token-previewer';
|
||||
import type { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
import { Helmet } from 'dumi';
|
||||
|
86
.dumi/remarkAnchor.ts
Normal file
86
.dumi/remarkAnchor.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { unistUtilVisit } from 'dumi';
|
||||
import type { UnifiedTransformer } from 'dumi';
|
||||
|
||||
let toSlug: typeof import('github-slugger').slug;
|
||||
|
||||
// workaround to import pure esm module
|
||||
(async () => {
|
||||
({ slug: toSlug } = await import('github-slugger'));
|
||||
})();
|
||||
|
||||
const isNil = (value: any) => value == null;
|
||||
|
||||
const toArr = <T>(value: T | T[]) => {
|
||||
if (isNil(value)) return [];
|
||||
return Array.isArray(value) ? value : [value];
|
||||
};
|
||||
|
||||
const patch = (context: Record<string, any>, key: string, value: any) => {
|
||||
if (!context[key]) {
|
||||
context[key] = value;
|
||||
}
|
||||
return context[key];
|
||||
};
|
||||
|
||||
interface Options {
|
||||
level?: number;
|
||||
}
|
||||
|
||||
const remarkAnchor = (opt: Options = {}): UnifiedTransformer<any> => {
|
||||
// https://regex101.com/r/WDjkK0/1
|
||||
const RE = /\s*\{#([^}]+)\}$/;
|
||||
|
||||
const realOpt = {
|
||||
level: [1, 2, 3, 4, 5, 6],
|
||||
...opt,
|
||||
};
|
||||
|
||||
return function transformer(tree) {
|
||||
const ids = new Set();
|
||||
|
||||
unistUtilVisit.visit(tree, 'heading', (node) => {
|
||||
if (toArr(realOpt.level).indexOf(node.depth) === -1) {
|
||||
return unistUtilVisit.CONTINUE;
|
||||
}
|
||||
|
||||
const lastChild = node.children.at(-1);
|
||||
|
||||
if (lastChild?.type === 'text') {
|
||||
const text = lastChild.value;
|
||||
const match = text.match(RE);
|
||||
if (match) {
|
||||
const id = match[1];
|
||||
if (id !== toSlug(id)) {
|
||||
throw new Error(
|
||||
`Expected header ID to be a valid slug. You specified: {#${id}}. Replace it with: {#${toSlug(id)}}`,
|
||||
);
|
||||
}
|
||||
|
||||
node.data ??= {};
|
||||
node.data.hProperties = { ...node.data.hProperties, id };
|
||||
|
||||
lastChild.value = text.replace(RE, '');
|
||||
|
||||
if (lastChild.value === '') {
|
||||
node.children.pop();
|
||||
}
|
||||
if (ids.has(id)) {
|
||||
throw new Error(`Cannot have a duplicate header with id "${id}" on the page.
|
||||
Rename the section or give it an explicit unique ID. For example: #### Arguments {#setstate-arguments}`);
|
||||
}
|
||||
|
||||
ids.add(id);
|
||||
|
||||
const data = patch(node, 'data', {});
|
||||
patch(data, 'id', id);
|
||||
patch(data, 'htmlAttributes', {});
|
||||
patch(data, 'hProperties', {});
|
||||
patch(data.htmlAttributes, 'id', id);
|
||||
patch(data.hProperties, 'id', id);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export default remarkAnchor;
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { theme as antdTheme, ConfigProvider } from 'antd';
|
||||
import type { ThemeConfig } from 'antd';
|
||||
import type { ThemeProviderProps } from 'antd-style';
|
||||
@ -31,10 +31,10 @@ const headerHeight = 64;
|
||||
const bannerHeight = 38;
|
||||
|
||||
const SiteThemeProvider: React.FC<ThemeProviderProps<any>> = ({ children, theme, ...rest }) => {
|
||||
const { getPrefixCls, iconPrefixCls } = useContext(ConfigProvider.ConfigContext);
|
||||
const { getPrefixCls, iconPrefixCls } = React.use(ConfigProvider.ConfigContext);
|
||||
const rootPrefixCls = getPrefixCls();
|
||||
const { token } = antdTheme.useToken();
|
||||
const { bannerVisible } = useContext(SiteContext);
|
||||
const { bannerVisible } = React.use(SiteContext);
|
||||
React.useEffect(() => {
|
||||
// 需要注意与 components/config-provider/demo/holderRender.tsx 配置冲突
|
||||
ConfigProvider.config({ theme: theme as ThemeConfig });
|
||||
|
23
.dumi/theme/builtins/Badge/index.tsx
Normal file
23
.dumi/theme/builtins/Badge/index.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import { Tag, TagProps } from 'antd';
|
||||
|
||||
// https://github.com/umijs/dumi/blob/master/src/client/theme-default/builtins/Badge/index.tsx
|
||||
interface BadgeProps extends TagProps {
|
||||
type: 'info' | 'warning' | 'error' | 'success';
|
||||
}
|
||||
|
||||
const colorMap = {
|
||||
info: 'blue',
|
||||
warning: 'orange',
|
||||
error: 'red',
|
||||
success: 'green',
|
||||
};
|
||||
|
||||
export default ({ type = 'info', ...props }: BadgeProps) => (
|
||||
<Tag
|
||||
bordered={false}
|
||||
color={colorMap[type]}
|
||||
{...props}
|
||||
style={{ verticalAlign: 'top', ...props.style }}
|
||||
/>
|
||||
);
|
@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import type { ColorInput } from '@ctrl/tinycolor';
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import { FastColor } from '@ant-design/fast-color';
|
||||
import type { ColorInput } from '@ant-design/fast-color';
|
||||
import { Popover } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => ({
|
||||
@ -22,24 +23,47 @@ const useStyle = createStyles(({ token, css }) => ({
|
||||
}));
|
||||
|
||||
interface ColorChunkProps {
|
||||
value?: ColorInput;
|
||||
value: ColorInput;
|
||||
enablePopover?: boolean;
|
||||
}
|
||||
|
||||
const ColorChunk: React.FC<React.PropsWithChildren<ColorChunkProps>> = (props) => {
|
||||
const { styles } = useStyle();
|
||||
const { value, children } = props;
|
||||
const { styles, theme } = useStyle();
|
||||
const { value, children, enablePopover } = props;
|
||||
|
||||
const dotColor = React.useMemo(() => {
|
||||
const _color = new TinyColor(value).toHex8String();
|
||||
return _color.endsWith('ff') ? _color.slice(0, -2) : _color;
|
||||
}, [value]);
|
||||
const dotColor = React.useMemo(() => new FastColor(value).toHexString(), [value]);
|
||||
|
||||
return (
|
||||
let dotNode = (
|
||||
<span className={styles.codeSpan}>
|
||||
<span className={styles.dot} style={{ backgroundColor: dotColor }} />
|
||||
{children ?? dotColor}
|
||||
</span>
|
||||
);
|
||||
|
||||
if (enablePopover) {
|
||||
dotNode = (
|
||||
<Popover
|
||||
placement="left"
|
||||
content={<div hidden />}
|
||||
styles={{
|
||||
body: {
|
||||
backgroundColor: dotColor,
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: theme.borderRadiusLG,
|
||||
},
|
||||
root: {
|
||||
'--antd-arrow-background-color': dotColor,
|
||||
backgroundColor: 'transparent',
|
||||
} as React.CSSProperties,
|
||||
}}
|
||||
>
|
||||
{dotNode}
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
return dotNode;
|
||||
};
|
||||
|
||||
export default ColorChunk;
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { EditOutlined, GithubOutlined, HistoryOutlined } from '@ant-design/icons';
|
||||
import { EditOutlined, GithubOutlined, HistoryOutlined, CompassOutlined } from '@ant-design/icons';
|
||||
import type { GetProp } from 'antd';
|
||||
import { Descriptions, Flex, theme, Tooltip, Typography } from 'antd';
|
||||
import { createStyles, css } from 'antd-style';
|
||||
import kebabCase from 'lodash/kebabCase';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import Link from '../../common/Link';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import ComponentChangelog from '../../common/ComponentChangelog';
|
||||
@ -18,6 +19,7 @@ const locales = {
|
||||
docs: '文档',
|
||||
edit: '编辑此页',
|
||||
changelog: '更新日志',
|
||||
design: '设计指南',
|
||||
version: '版本',
|
||||
},
|
||||
en: {
|
||||
@ -28,6 +30,7 @@ const locales = {
|
||||
docs: 'Docs',
|
||||
edit: 'Edit this page',
|
||||
changelog: 'Changelog',
|
||||
design: 'Design',
|
||||
version: 'Version',
|
||||
},
|
||||
};
|
||||
@ -57,24 +60,8 @@ const useStyle = createStyles(({ token }) => ({
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
`,
|
||||
import: css`
|
||||
color: ${token.magenta8};
|
||||
`,
|
||||
component: css`
|
||||
color: ${token.colorText};
|
||||
`,
|
||||
from: css`
|
||||
color: ${token.magenta8};
|
||||
margin-inline-end: 0.5em;
|
||||
`,
|
||||
antd: css`
|
||||
color: ${token.green8};
|
||||
`,
|
||||
semicolon: css`
|
||||
color: ${token.colorText};
|
||||
`,
|
||||
icon: css`
|
||||
margin-inline-end: ${token.marginXXS}px;
|
||||
margin-inline-end: 3px;
|
||||
`,
|
||||
}));
|
||||
|
||||
@ -83,10 +70,11 @@ export interface ComponentMetaProps {
|
||||
source: string | true;
|
||||
filename?: string;
|
||||
version?: string;
|
||||
designUrl?: string;
|
||||
}
|
||||
|
||||
const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
const { component, source, filename, version } = props;
|
||||
const { component, source, filename, version, designUrl } = props;
|
||||
const { token } = theme.useToken();
|
||||
const [locale, lang] = useLocale(locales);
|
||||
const isZhCN = lang === 'cn';
|
||||
@ -130,23 +118,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
};
|
||||
|
||||
// ======================== Render ========================
|
||||
const importList = [
|
||||
<span key="import" className={styles.import}>
|
||||
import
|
||||
</span>,
|
||||
<span key="component" className={styles.component}>{`{ ${transformComponentName(
|
||||
component,
|
||||
)} }`}</span>,
|
||||
<span key="from" className={styles.from}>
|
||||
from
|
||||
</span>,
|
||||
<span key="antd" className={styles.antd}>
|
||||
{`"antd"`}
|
||||
</span>,
|
||||
<span key="semicolon" className={styles.semicolon}>
|
||||
;
|
||||
</span>,
|
||||
];
|
||||
const importList = `import { ${transformComponentName(component)} } from "antd";`;
|
||||
|
||||
return (
|
||||
<Descriptions
|
||||
@ -154,7 +126,9 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
colon={false}
|
||||
column={1}
|
||||
style={{ marginTop: token.margin }}
|
||||
labelStyle={{ paddingInlineEnd: token.padding, width: 56 }}
|
||||
styles={{
|
||||
label: { paddingInlineEnd: token.padding, width: 56 },
|
||||
}}
|
||||
items={
|
||||
[
|
||||
{
|
||||
@ -185,7 +159,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
filename && {
|
||||
label: locale.docs,
|
||||
children: (
|
||||
<Flex justify="flex-start" align="center" gap="middle">
|
||||
<Flex justify="flex-start" align="center" gap="small">
|
||||
<Typography.Link
|
||||
className={styles.code}
|
||||
href={`${branchUrl}${filename}`}
|
||||
@ -194,6 +168,12 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
<EditOutlined className={styles.icon} />
|
||||
<span>{locale.edit}</span>
|
||||
</Typography.Link>
|
||||
{designUrl && (
|
||||
<Link className={styles.code} to={designUrl}>
|
||||
<CompassOutlined className={styles.icon} />
|
||||
<span>{locale.design}</span>
|
||||
</Link>
|
||||
)}
|
||||
<ComponentChangelog>
|
||||
<Typography.Link className={styles.code}>
|
||||
<HistoryOutlined className={styles.icon} />
|
||||
@ -207,7 +187,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
label: locale.version,
|
||||
children: (
|
||||
<Typography.Text className={styles.code}>
|
||||
{isZhCN ? `自 ${version} 后支持` : `supported since ${version}`}
|
||||
{isZhCN ? `自 ${version} 起支持` : `supported since ${version}`}
|
||||
</Typography.Text>
|
||||
),
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { memo, useContext, useMemo, useRef, useState } from 'react';
|
||||
import React, { memo, useMemo, useRef, useState } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import { Affix, Card, Col, Divider, Flex, Input, Row, Tag, Typography } from 'antd';
|
||||
@ -83,7 +83,7 @@ const { Title } = Typography;
|
||||
|
||||
const Overview: React.FC = () => {
|
||||
const { styles } = useStyle();
|
||||
const { theme } = useContext(SiteContext);
|
||||
const { theme } = React.use(SiteContext);
|
||||
|
||||
const data = useSidebarData();
|
||||
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean>(false);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { LinkOutlined, QuestionCircleOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { ConfigProvider, Popover, Table, Typography } from 'antd';
|
||||
import { ConfigProvider, Flex, Popover, Table, Typography } from 'antd';
|
||||
import { createStyles, css, useTheme } from 'antd-style';
|
||||
import { getDesignToken } from 'antd-token-previewer';
|
||||
import tokenMeta from 'antd/es/version/token-meta.json';
|
||||
@ -61,16 +61,15 @@ const useStyle = createStyles(({ token }) => ({
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
line-height: 40px;
|
||||
gap: ${token.marginXS}px;
|
||||
`,
|
||||
arrowIcon: css`
|
||||
font-size: ${token.fontSizeLG}px;
|
||||
margin-inline-end: ${token.marginXS}px;
|
||||
& svg {
|
||||
transition: all ${token.motionDurationSlow};
|
||||
}
|
||||
`,
|
||||
help: css`
|
||||
margin-inline-start: ${token.marginXS}px;
|
||||
font-size: ${token.fontSizeSM}px;
|
||||
font-weight: normal;
|
||||
color: #999;
|
||||
@ -78,6 +77,10 @@ const useStyle = createStyles(({ token }) => ({
|
||||
color: #999;
|
||||
}
|
||||
`,
|
||||
tokenTitle: css`
|
||||
font-size: ${token.fontSizeLG}px;
|
||||
font-weight: bold;
|
||||
`,
|
||||
}));
|
||||
|
||||
interface SubTokenTableProps {
|
||||
@ -153,15 +156,17 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
|
||||
<>
|
||||
<div className={styles.tableTitle} onClick={() => setOpen(!open)}>
|
||||
<RightOutlined className={styles.arrowIcon} rotate={open ? 90 : 0} />
|
||||
<h3>
|
||||
<Flex className={styles.tokenTitle} gap="small" justify="flex-start" align="center">
|
||||
{title}
|
||||
<Popover
|
||||
title={null}
|
||||
overlayStyle={{ width: 400 }}
|
||||
destroyTooltipOnHide
|
||||
styles={{ root: { width: 400 } }}
|
||||
content={
|
||||
<Typography>
|
||||
{/* <SourceCode lang="jsx">{code}</SourceCode> */}
|
||||
<pre style={{ fontSize: 12 }}>{code}</pre>
|
||||
<pre dir="ltr" style={{ fontSize: 12 }}>
|
||||
<code dir="ltr">{code}</code>
|
||||
</pre>
|
||||
<a href={helpLink} target="_blank" rel="noreferrer">
|
||||
<LinkOutlined style={{ marginInlineEnd: 4 }} />
|
||||
{helpText}
|
||||
@ -174,7 +179,7 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
|
||||
{helpText}
|
||||
</span>
|
||||
</Popover>
|
||||
</h3>
|
||||
</Flex>
|
||||
</div>
|
||||
{open && (
|
||||
<ConfigProvider theme={{ token: { borderRadius: 0 } }}>
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React, { Suspense, useContext } from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons';
|
||||
import { ConfigProvider, Tooltip, Button } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { DumiDemoGrid, FormattedMessage } from 'dumi';
|
||||
import { css, Global } from '@emotion/react';
|
||||
import { Button, ConfigProvider, Tooltip } from 'antd';
|
||||
import { DumiDemo, DumiDemoGrid, FormattedMessage } from 'dumi';
|
||||
|
||||
import useLayoutState from '../../../hooks/useLayoutState';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import DemoContext from '../../slots/DemoContext';
|
||||
import DemoFallback from '../Previewer/DemoFallback';
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
@ -21,7 +21,7 @@ const locales = {
|
||||
};
|
||||
|
||||
const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
|
||||
const { showDebug, setShowDebug } = useContext(DemoContext);
|
||||
const { showDebug, setShowDebug } = React.use(DemoContext);
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
const [expandAll, setExpandAll] = useLayoutState(false);
|
||||
@ -41,13 +41,16 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
|
||||
|
||||
const demos = React.useMemo(
|
||||
() =>
|
||||
items.map((item: any) => {
|
||||
items.reduce<typeof items>((acc, item) => {
|
||||
const { previewerProps } = item;
|
||||
const { debug } = previewerProps;
|
||||
return {
|
||||
if (debug && !showDebug) {
|
||||
return acc;
|
||||
}
|
||||
return acc.concat({
|
||||
...item,
|
||||
previewerProps: {
|
||||
...item.previewerProps,
|
||||
...previewerProps,
|
||||
expand: expandAll,
|
||||
// always override debug property, because dumi will hide debug demo in production
|
||||
debug: false,
|
||||
@ -57,17 +60,13 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
|
||||
*/
|
||||
originDebug: debug,
|
||||
},
|
||||
};
|
||||
}),
|
||||
});
|
||||
}, []),
|
||||
[expandAll, showDebug],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('demo-wrapper', {
|
||||
'demo-wrapper-show-debug': showDebug,
|
||||
})}
|
||||
>
|
||||
<div className="demo-wrapper">
|
||||
<Global
|
||||
styles={css`
|
||||
:root {
|
||||
@ -113,9 +112,14 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
|
||||
</Tooltip>
|
||||
</span>
|
||||
<ConfigProvider theme={{ cssVar: enableCssVar, hashed: !enableCssVar }}>
|
||||
<Suspense>
|
||||
<DumiDemoGrid items={demos} />
|
||||
</Suspense>
|
||||
<DumiDemoGrid
|
||||
items={demos}
|
||||
demoRender={(item) => (
|
||||
<Suspense key={item.demo.id} fallback={<DemoFallback />}>
|
||||
<DumiDemo {...item} />
|
||||
</Suspense>
|
||||
)}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
</div>
|
||||
);
|
||||
|
@ -44,7 +44,7 @@ const IconSearchFallback: React.FC = () => {
|
||||
</div>
|
||||
<Skeleton.Button active style={{ margin: '28px 0 10px', width: 100 }} />
|
||||
<div className={styles.fallbackWrapper}>
|
||||
{new Array(24).fill(1).map((_, index) => (
|
||||
{Array.from({ length: 24 }).map((_, index) => (
|
||||
<div key={index} className={styles.skeletonWrapper}>
|
||||
<Skeleton.Node active style={{ height: 110, width: '100%' }}>
|
||||
{' '}
|
||||
|
80
.dumi/theme/builtins/Previewer/CodeBlockButton.tsx
Normal file
80
.dumi/theme/builtins/Previewer/CodeBlockButton.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import React, { Suspense, useEffect, useState } from 'react';
|
||||
import { Tooltip } from 'antd';
|
||||
import { FormattedMessage } from 'dumi';
|
||||
|
||||
import { ping } from '../../utils';
|
||||
|
||||
let pingDeferrer: PromiseLike<boolean>;
|
||||
|
||||
const codeBlockJs =
|
||||
'https://renderoffice.a' +
|
||||
'lipay' +
|
||||
'objects.com/p' +
|
||||
'/yuyan/180020010001206410/parseFileData-v1.0.1.js';
|
||||
|
||||
function useShowCodeBlockButton() {
|
||||
const [showCodeBlockButton, setShowCodeBlockButton] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
pingDeferrer ??= new Promise<boolean>((resolve) => {
|
||||
ping((status) => {
|
||||
if (status !== 'timeout' && status !== 'error') {
|
||||
// Async insert `codeBlockJs` into body end
|
||||
const script = document.createElement('script');
|
||||
script.src = codeBlockJs;
|
||||
script.async = true;
|
||||
document.body.appendChild(script);
|
||||
|
||||
return resolve(true);
|
||||
}
|
||||
return resolve(false);
|
||||
});
|
||||
});
|
||||
pingDeferrer.then(setShowCodeBlockButton);
|
||||
}, []);
|
||||
|
||||
return showCodeBlockButton;
|
||||
}
|
||||
|
||||
interface CodeBlockButtonProps {
|
||||
title?: string;
|
||||
dependencies: Record<PropertyKey, string>;
|
||||
jsx: string;
|
||||
}
|
||||
|
||||
const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies = {}, jsx }) => {
|
||||
const showCodeBlockButton = useShowCodeBlockButton();
|
||||
|
||||
const codeBlockPrefillConfig = {
|
||||
title: `${title} - antd@${dependencies.antd}`,
|
||||
js: `${
|
||||
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
|
||||
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
|
||||
/export default/,
|
||||
'const ComponentDemo =',
|
||||
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
|
||||
css: '',
|
||||
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
|
||||
};
|
||||
|
||||
return showCodeBlockButton ? (
|
||||
<Tooltip title={<FormattedMessage id="app.demo.codeblock" />}>
|
||||
<div className="code-box-code-action">
|
||||
<img
|
||||
alt="codeblock"
|
||||
src="https://mdn.alipayobjects.com/huamei_wtld8u/afts/img/A*K8rjSJpTNQ8AAAAAAAAAAAAADhOIAQ/original"
|
||||
className="code-box-codeblock"
|
||||
onClick={() => {
|
||||
openHituCodeBlock(JSON.stringify(codeBlockPrefillConfig));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default (props: CodeBlockButtonProps) => (
|
||||
<Suspense>
|
||||
<CodeBlockButton {...props} />
|
||||
</Suspense>
|
||||
);
|
@ -1,4 +1,5 @@
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { LinkOutlined, ThunderboltOutlined, UpOutlined } from '@ant-design/icons';
|
||||
import type { Project } from '@stackblitz/sdk';
|
||||
import stackblitzSdk from '@stackblitz/sdk';
|
||||
@ -16,11 +17,9 @@ import EditButton from '../../common/EditButton';
|
||||
import CodePenIcon from '../../icons/CodePenIcon';
|
||||
import CodeSandboxIcon from '../../icons/CodeSandboxIcon';
|
||||
import ExternalLinkIcon from '../../icons/ExternalLinkIcon';
|
||||
import RiddleIcon from '../../icons/RiddleIcon';
|
||||
import DemoContext from '../../slots/DemoContext';
|
||||
import type { SiteContextProps } from '../../slots/SiteContext';
|
||||
import SiteContext from '../../slots/SiteContext';
|
||||
import { ping } from '../../utils';
|
||||
import CodeBlockButton from './CodeBlockButton';
|
||||
import type { AntdPreviewerProps } from './Previewer';
|
||||
|
||||
const { ErrorBoundary } = Alert;
|
||||
@ -39,27 +38,6 @@ const track = ({ type, demo }: { type: string; demo: string }) => {
|
||||
window.gtag('event', 'demo', { event_category: type, event_label: demo });
|
||||
};
|
||||
|
||||
let pingDeferrer: PromiseLike<boolean>;
|
||||
|
||||
function useShowRiddleButton() {
|
||||
const [showRiddleButton, setShowRiddleButton] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
pingDeferrer ??= new Promise<boolean>((resolve) => {
|
||||
ping((status) => {
|
||||
if (status !== 'timeout' && status !== 'error') {
|
||||
return resolve(true);
|
||||
}
|
||||
|
||||
return resolve(false);
|
||||
});
|
||||
});
|
||||
pingDeferrer.then(setShowRiddleButton);
|
||||
}, []);
|
||||
|
||||
return showRiddleButton;
|
||||
}
|
||||
|
||||
const useStyle = createStyles(({ token }) => {
|
||||
const { borderRadius } = token;
|
||||
return {
|
||||
@ -98,7 +76,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
title,
|
||||
description,
|
||||
originDebug,
|
||||
jsx,
|
||||
jsx = '',
|
||||
style,
|
||||
compact,
|
||||
background,
|
||||
@ -108,7 +86,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
clientOnly,
|
||||
pkgDependencyList,
|
||||
} = props;
|
||||
const { codeType } = useContext(DemoContext);
|
||||
const { codeType } = React.use(DemoContext);
|
||||
|
||||
const { pkg } = useSiteData();
|
||||
const location = useLocation();
|
||||
@ -117,7 +95,6 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
|
||||
const entryName = 'index.tsx';
|
||||
const entryCode = asset.dependencies[entryName].value;
|
||||
const showRiddleButton = useShowRiddleButton();
|
||||
|
||||
const previewDemo = useRef<React.ReactNode>(null);
|
||||
const demoContainer = useRef<HTMLElement>(null);
|
||||
@ -127,14 +104,13 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
setSource: setLiveDemoSource,
|
||||
} = useLiveDemo(asset.id, {
|
||||
iframe: Boolean(iframe),
|
||||
containerRef: demoContainer,
|
||||
containerRef: demoContainer as React.RefObject<HTMLElement>,
|
||||
});
|
||||
const anchorRef = useRef<HTMLAnchorElement>(null);
|
||||
const codeSandboxIconRef = useRef<HTMLFormElement>(null);
|
||||
const riddleIconRef = useRef<HTMLFormElement>(null);
|
||||
const codepenIconRef = useRef<HTMLFormElement>(null);
|
||||
const [codeExpand, setCodeExpand] = useState<boolean>(false);
|
||||
const { theme } = useContext<SiteContextProps>(SiteContext);
|
||||
const { theme } = React.use(SiteContext);
|
||||
|
||||
const { hash, pathname, search } = location;
|
||||
const docsOnlineUrl = `https://ant.design${pathname}${search}#${asset.id}`;
|
||||
@ -164,12 +140,13 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
}, [expand]);
|
||||
|
||||
const mergedChildren = !iframe && clientOnly ? <ClientOnly>{children}</ClientOnly> : children;
|
||||
const demoUrlWithTheme = `${demoUrl}${theme.includes('dark') ? '?theme=dark' : ''}`;
|
||||
|
||||
if (!previewDemo.current) {
|
||||
previewDemo.current = iframe ? (
|
||||
<BrowserFrame>
|
||||
<iframe
|
||||
src={demoUrl}
|
||||
src={demoUrlWithTheme}
|
||||
height={iframe === true ? undefined : iframe}
|
||||
title="demo"
|
||||
className="iframe-demo"
|
||||
@ -278,18 +255,6 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
js_pre_processor: 'typescript',
|
||||
};
|
||||
|
||||
const riddlePrefillConfig = {
|
||||
title: `${localizedTitle} - antd@${dependencies.antd}`,
|
||||
js: `${
|
||||
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
|
||||
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
|
||||
/export default/,
|
||||
'const ComponentDemo =',
|
||||
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
|
||||
css: '',
|
||||
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
|
||||
};
|
||||
|
||||
// Reorder source code
|
||||
let parsedSourceCode = suffix === 'tsx' ? entryCode : jsx;
|
||||
let importReactContent = "import React from 'react';";
|
||||
@ -356,11 +321,18 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
||||
const stackblitzPrefillConfig: Project = {
|
||||
title: `${localizedTitle} - antd@${dependencies.antd}`,
|
||||
template: 'create-react-app',
|
||||
dependencies,
|
||||
dependencies:{
|
||||
...dependencies,
|
||||
react: '^19.0.0',
|
||||
'react-dom': '^19.0.0',
|
||||
'@types/react': '^19.0.0',
|
||||
'@types/react-dom': '^19.0.0',
|
||||
'@ant-design/v5-patch-for-react-19': '^1.0.3'
|
||||
},
|
||||
description: '',
|
||||
files: {
|
||||
'index.css': indexCssContent,
|
||||
[`index.${suffix}`]: indexJsContent,
|
||||
[`index.${suffix}`]: `import '@ant-design/v5-patch-for-react-19';\n${indexJsContent}`,
|
||||
[`demo.${suffix}`]: demoJsContent,
|
||||
'index.html': html,
|
||||
},
|
||||
@ -440,24 +412,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
||||
<CodeSandboxIcon className="code-box-codesandbox" />
|
||||
</Tooltip>
|
||||
</form>
|
||||
{showRiddleButton ? (
|
||||
<form
|
||||
className="code-box-code-action"
|
||||
action="//riddle.alibaba-inc.com/riddles/define"
|
||||
method="POST"
|
||||
target="_blank"
|
||||
ref={riddleIconRef}
|
||||
onClick={() => {
|
||||
track({ type: 'riddle', demo: asset.id });
|
||||
riddleIconRef.current?.submit();
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="data" value={JSON.stringify(riddlePrefillConfig)} />
|
||||
<Tooltip title={<FormattedMessage id="app.demo.riddle" />}>
|
||||
<RiddleIcon className="code-box-riddle" />
|
||||
</Tooltip>
|
||||
</form>
|
||||
) : null}
|
||||
<CodeBlockButton title={localizedTitle} dependencies={dependencies} jsx={jsx} />
|
||||
<Tooltip title={<FormattedMessage id="app.demo.stackblitz" />}>
|
||||
<span
|
||||
className="code-box-code-action"
|
||||
@ -498,7 +453,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
||||
aria-label="open in new tab"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={demoUrl}
|
||||
href={demoUrlWithTheme}
|
||||
>
|
||||
<ExternalLinkIcon className="code-box-separate" />
|
||||
</a>
|
||||
@ -568,12 +523,12 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
||||
const styleTag = document.createElement('style') as HTMLStyleElement;
|
||||
styleTag.type = 'text/css';
|
||||
styleTag.innerHTML = style;
|
||||
(styleTag as any)['data-demo-url'] = demoUrl;
|
||||
(styleTag as any)['data-demo-url'] = demoUrlWithTheme;
|
||||
document.head.appendChild(styleTag);
|
||||
return () => {
|
||||
document.head.removeChild(styleTag);
|
||||
};
|
||||
}, [style, demoUrl]);
|
||||
}, [style, demoUrlWithTheme]);
|
||||
|
||||
if (version) {
|
||||
return (
|
||||
|
27
.dumi/theme/builtins/Previewer/DemoFallback.tsx
Normal file
27
.dumi/theme/builtins/Previewer/DemoFallback.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Skeleton } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => ({
|
||||
skeletonWrapper: css`
|
||||
width: 100% !important;
|
||||
height: 250px;
|
||||
margin-bottom: ${token.margin}px;
|
||||
border-radius: ${token.borderRadiusLG}px;
|
||||
`,
|
||||
}));
|
||||
|
||||
const DemoFallback = () => {
|
||||
const { styles } = useStyle();
|
||||
return (
|
||||
<Skeleton.Node
|
||||
active
|
||||
className={styles.skeletonWrapper}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
>
|
||||
{' '}
|
||||
</Skeleton.Node>
|
||||
);
|
||||
};
|
||||
|
||||
export default DemoFallback;
|
@ -7,6 +7,7 @@ import DesignPreviewer from './DesignPreviewer';
|
||||
|
||||
export interface AntdPreviewerProps extends IPreviewerProps {
|
||||
originDebug?: IPreviewerProps['debug'];
|
||||
jsx?: string;
|
||||
}
|
||||
|
||||
const Previewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
|
91
.dumi/theme/builtins/Previewer/RiddleButton.tsx
Normal file
91
.dumi/theme/builtins/Previewer/RiddleButton.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import React, { Suspense, useEffect, useRef, useState } from 'react';
|
||||
import { Tooltip } from 'antd';
|
||||
import { FormattedMessage } from 'dumi';
|
||||
|
||||
import type { IPreviewerProps } from 'dumi';
|
||||
|
||||
import RiddleIcon from '../../icons/RiddleIcon';
|
||||
import { ping } from '../../utils';
|
||||
|
||||
let pingDeferrer: PromiseLike<boolean>;
|
||||
|
||||
function useShowRiddleButton() {
|
||||
const [showRiddleButton, setShowRiddleButton] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
pingDeferrer ??= new Promise<boolean>((resolve) => {
|
||||
ping((status) => {
|
||||
if (status !== 'timeout' && status !== 'error') {
|
||||
return resolve(true);
|
||||
}
|
||||
|
||||
return resolve(false);
|
||||
});
|
||||
});
|
||||
pingDeferrer.then(setShowRiddleButton);
|
||||
}, []);
|
||||
|
||||
return showRiddleButton;
|
||||
}
|
||||
|
||||
interface RiddleButtonProps {
|
||||
title?: string;
|
||||
dependencies: Record<PropertyKey, string>;
|
||||
jsx: string;
|
||||
track: ({
|
||||
type,
|
||||
demo,
|
||||
}: {
|
||||
type: string;
|
||||
demo: string;
|
||||
}) => void;
|
||||
asset: IPreviewerProps['asset'];
|
||||
}
|
||||
|
||||
const RiddleButton: React.FC<RiddleButtonProps> = ({
|
||||
title,
|
||||
dependencies = {},
|
||||
jsx,
|
||||
track,
|
||||
asset,
|
||||
}) => {
|
||||
const riddleIconRef = useRef<HTMLFormElement>(null);
|
||||
const showRiddleButton = useShowRiddleButton();
|
||||
|
||||
const riddlePrefillConfig = {
|
||||
title: `${title} - antd@${dependencies.antd}`,
|
||||
js: `${
|
||||
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
|
||||
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
|
||||
/export default/,
|
||||
'const ComponentDemo =',
|
||||
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
|
||||
css: '',
|
||||
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
|
||||
};
|
||||
|
||||
return showRiddleButton ? (
|
||||
<form
|
||||
className="code-box-code-action"
|
||||
action="//riddle.alibaba-inc.com/riddles/define"
|
||||
method="POST"
|
||||
target="_blank"
|
||||
ref={riddleIconRef}
|
||||
onClick={() => {
|
||||
track({ type: 'riddle', demo: asset.id });
|
||||
riddleIconRef.current?.submit();
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="data" value={JSON.stringify(riddlePrefillConfig)} />
|
||||
<Tooltip title={<FormattedMessage id="app.demo.riddle" />}>
|
||||
<RiddleIcon className="code-box-riddle" />
|
||||
</Tooltip>
|
||||
</form>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default (props: RiddleButtonProps) => (
|
||||
<Suspense>
|
||||
<RiddleButton {...props} />
|
||||
</Suspense>
|
||||
);
|
@ -1,39 +1,17 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { Alert, Skeleton } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { Alert } from 'antd';
|
||||
import Previewer from './Previewer';
|
||||
import DemoFallback from './DemoFallback';
|
||||
import type { IPreviewerProps } from 'dumi';
|
||||
|
||||
const { ErrorBoundary } = Alert;
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => ({
|
||||
skeletonWrapper: css`
|
||||
width: 100% !important;
|
||||
height: 250px;
|
||||
margin-bottom: ${token.margin}px;
|
||||
border-radius: ${token.borderRadiusLG}px;
|
||||
`,
|
||||
}));
|
||||
|
||||
const PreviewerSuspense: React.FC<IPreviewerProps> = (props) => {
|
||||
const { styles } = useStyle();
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Suspense
|
||||
fallback={
|
||||
<Skeleton.Node
|
||||
active
|
||||
className={styles.skeletonWrapper}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
>
|
||||
{' '}
|
||||
</Skeleton.Node>
|
||||
}
|
||||
>
|
||||
<Previewer {...props} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
const PreviewerSuspense: React.FC<IPreviewerProps> = (props) => (
|
||||
<ErrorBoundary>
|
||||
<Suspense fallback={<DemoFallback />}>
|
||||
<Previewer {...props} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
export default PreviewerSuspense;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// 用于 color.md 中的颜色对比
|
||||
import React from 'react';
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import { FastColor } from '@ant-design/fast-color';
|
||||
import { Flex, theme } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import tokenMeta from 'antd/es/version/token-meta.json';
|
||||
@ -55,7 +55,7 @@ const useStyle = createStyles(({ token, css }) => {
|
||||
});
|
||||
|
||||
function color2Rgba(color: string) {
|
||||
return `#${new TinyColor(color).toHex8().toUpperCase()}`;
|
||||
return `#${new FastColor(color).toHexString().toUpperCase()}`;
|
||||
}
|
||||
|
||||
interface ColorCircleProps {
|
||||
|
@ -7,6 +7,7 @@ import { getDesignToken } from 'antd-token-previewer';
|
||||
import tokenMeta from 'antd/es/version/token-meta.json';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import BezierVisualizer from '../../common/BezierVisualizer';
|
||||
import ColorChunk from '../ColorChunk';
|
||||
|
||||
type TokenTableProps = {
|
||||
@ -79,7 +80,19 @@ export function useColumns(): Exclude<TableProps<TokenData>['columns'], undefine
|
||||
typeof record.value === 'string' &&
|
||||
(record.value.startsWith('#') || record.value.startsWith('rgb'));
|
||||
if (isColor) {
|
||||
return <ColorChunk value={record.value}>{record.value}</ColorChunk>;
|
||||
return (
|
||||
<ColorChunk value={record.value} enablePopover>
|
||||
{record.value}
|
||||
</ColorChunk>
|
||||
);
|
||||
}
|
||||
|
||||
const isBezier =
|
||||
typeof record.value === 'string' &&
|
||||
record.value.toLowerCase().trim().startsWith('cubic-bezier');
|
||||
|
||||
if (isBezier) {
|
||||
return <BezierVisualizer value={record.value} />;
|
||||
}
|
||||
return typeof record.value !== 'string' ? JSON.stringify(record.value) : record.value;
|
||||
},
|
||||
@ -99,7 +112,7 @@ const TokenTable: FC<TokenTableProps> = ({ type }) => {
|
||||
name: token,
|
||||
desc: lang === 'cn' ? meta.desc : meta.descEn,
|
||||
type: meta.type,
|
||||
value: defaultToken[token],
|
||||
value: defaultToken[token as keyof typeof defaultToken],
|
||||
})),
|
||||
[type, lang],
|
||||
);
|
||||
|
87
.dumi/theme/common/BezierVisualizer/Visualizer.tsx
Normal file
87
.dumi/theme/common/BezierVisualizer/Visualizer.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React, { useId } from 'react';
|
||||
import { theme } from 'antd';
|
||||
|
||||
export interface VisualizerProps {
|
||||
/**
|
||||
* 控制点坐标
|
||||
* @description 控制点坐标范围 [0, 1]
|
||||
* @example [0.78, 0.14, 0.15, 0.86]
|
||||
*/
|
||||
controls: [number, number, number, number];
|
||||
width?: number;
|
||||
height?: number;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
const Visualizer: React.FC<VisualizerProps> = (props) => {
|
||||
const {
|
||||
controls: [x1, y1, x2, y2],
|
||||
width = 180,
|
||||
height = width,
|
||||
} = props;
|
||||
const { token } = theme.useToken();
|
||||
|
||||
// 坐标转换到SVG视图
|
||||
const scale = (val: number, axis: 'x' | 'y') =>
|
||||
axis === 'x' ? val * width : height - val * height;
|
||||
|
||||
const gridStep = width / 5; // 网格步长
|
||||
const patternId = useId(); // 生成唯一ID
|
||||
|
||||
return (
|
||||
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
|
||||
<title>Cubic Bezier Visualizer</title>
|
||||
{/* 背景 */}
|
||||
<rect width="100%" height="100%" fill={token.colorBgContainer} />
|
||||
|
||||
{/* 修正后的网格 */}
|
||||
<pattern id={patternId} width={gridStep} height={gridStep} patternUnits="userSpaceOnUse">
|
||||
<path
|
||||
d={`
|
||||
M 0 0 H ${gridStep}
|
||||
M 0 0 V ${gridStep}
|
||||
M ${gridStep} 0 V ${gridStep}
|
||||
M 0 ${gridStep} H ${gridStep}
|
||||
`}
|
||||
stroke={token.colorBorderSecondary}
|
||||
strokeWidth={token.controlOutlineWidth}
|
||||
shapeRendering="crispEdges"
|
||||
/>
|
||||
</pattern>
|
||||
<rect width="100%" height="100%" fill={`url(#${patternId})`} />
|
||||
|
||||
{/* 贝塞尔曲线路径 */}
|
||||
<path
|
||||
d={`
|
||||
M 0 ${height}
|
||||
C ${scale(x1, 'x')} ${scale(y1, 'y')},
|
||||
${scale(x2, 'x')} ${scale(y2, 'y')},
|
||||
${width} 0
|
||||
`}
|
||||
fill="none"
|
||||
stroke={token.colorPrimary}
|
||||
strokeWidth={token.controlOutlineWidth * 2}
|
||||
/>
|
||||
|
||||
{/* 控制点连线 */}
|
||||
<path
|
||||
d={`
|
||||
M 0 ${height}
|
||||
L ${scale(x1, 'x')} ${scale(y1, 'y')}
|
||||
L ${scale(x2, 'x')} ${scale(y2, 'y')}
|
||||
L ${width} 0
|
||||
`}
|
||||
fill="none"
|
||||
stroke={token.colorPrimaryActive}
|
||||
strokeDasharray="4 2"
|
||||
strokeWidth={token.controlOutlineWidth}
|
||||
/>
|
||||
|
||||
{/* 控制点 */}
|
||||
<circle cx={scale(x1, 'x')} cy={scale(y1, 'y')} r="5" fill={token['red-6']} />
|
||||
<circle cx={scale(x2, 'x')} cy={scale(y2, 'y')} r="5" fill={token['green-6']} />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default Visualizer;
|
55
.dumi/theme/common/BezierVisualizer/index.tsx
Normal file
55
.dumi/theme/common/BezierVisualizer/index.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Button, Flex, Tooltip, Typography } from 'antd';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import ExternalLinkIcon from '../../icons/ExternalLinkIcon';
|
||||
import Visualizer from './Visualizer';
|
||||
|
||||
export interface BezierVisualizerProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
const RE = /^cubic-bezier\((.*)\)$/;
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
open: '在 cubic-bezier.com 中打开',
|
||||
},
|
||||
en: {
|
||||
open: 'Open in cubic-bezier.com',
|
||||
},
|
||||
};
|
||||
|
||||
const BezierVisualizer = (props: BezierVisualizerProps) => {
|
||||
const { value } = props;
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
const controls = useMemo(() => {
|
||||
const m = RE.exec(value.toLowerCase().trim());
|
||||
if (m) {
|
||||
return m[1].split(',').map((v) => parseFloat(v.trim())) as [number, number, number, number];
|
||||
}
|
||||
return null;
|
||||
}, [value]);
|
||||
|
||||
if (!controls) return null;
|
||||
|
||||
return (
|
||||
<Flex vertical gap="small">
|
||||
<Visualizer controls={controls} />
|
||||
<Flex align="center">
|
||||
<Typography.Text>{value}</Typography.Text>
|
||||
<Tooltip title={locale.open}>
|
||||
<Button
|
||||
type="link"
|
||||
href={`https://cubic-bezier.com/#${controls.join(',')}`}
|
||||
target="_blank"
|
||||
icon={<ExternalLinkIcon />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default BezierVisualizer;
|
@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
|
||||
import type { ComponentProps } from 'react';
|
||||
import React, { useContext, useEffect, useMemo } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { Button, Tabs, Typography } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import toReactElement from 'jsonml-to-react-element';
|
||||
@ -108,7 +109,7 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
||||
initialCodes.style = '';
|
||||
}
|
||||
const [highlightedCodes, setHighlightedCodes] = React.useState(initialCodes);
|
||||
const { codeType, setCodeType } = useContext(DemoContext);
|
||||
const { codeType, setCodeType } = React.use(DemoContext);
|
||||
const sourceCodes = {
|
||||
// omit trailing line break
|
||||
tsx: sourceCode?.trim(),
|
||||
|
@ -20,6 +20,7 @@ interface ChangelogInfo {
|
||||
version: string;
|
||||
changelog: string;
|
||||
refs: string[];
|
||||
contributors: string[];
|
||||
releaseDate: string;
|
||||
}
|
||||
|
||||
@ -136,9 +137,9 @@ const ParseChangelog: React.FC<{ changelog: string }> = (props) => {
|
||||
} else {
|
||||
let node: React.ReactNode = lastStr;
|
||||
if (isQuota) {
|
||||
node = <code>{node}</code>;
|
||||
node = <code key={`code-${i}`}>{node}</code>;
|
||||
} else if (isBold) {
|
||||
node = <strong>{node}</strong>;
|
||||
node = <strong key={`strong-${i}`}>{node}</strong>;
|
||||
}
|
||||
|
||||
nodes.push(node);
|
||||
@ -160,14 +161,30 @@ const ParseChangelog: React.FC<{ changelog: string }> = (props) => {
|
||||
return <span>{parsedChangelog}</span>;
|
||||
};
|
||||
|
||||
const RefLinks: React.FC<{ refs: string[] }> = ({ refs }) => {
|
||||
const RefLinks: React.FC<{ refs: string[]; contributors: string[] }> = ({ refs, contributors }) => {
|
||||
const { styles } = useStyle();
|
||||
|
||||
return (
|
||||
<>
|
||||
{refs?.map((ref) => (
|
||||
<a className={styles.linkRef} key={ref} href={ref} target="_blank" rel="noreferrer">
|
||||
#{ref.match(/^.*\/(\d+)$/)?.[1]}
|
||||
</a>
|
||||
<React.Fragment key={ref}>
|
||||
<a className={styles.linkRef} key={ref} href={ref} target="_blank" rel="noreferrer">
|
||||
#{ref.match(/[^/]+$/)?.[0]}
|
||||
</a>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{contributors?.map((contributor) => (
|
||||
<React.Fragment key={contributor}>
|
||||
<a
|
||||
className={styles.linkRef}
|
||||
key={contributor}
|
||||
href={`https://github.com/${contributor}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
@{contributor}
|
||||
</a>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
@ -178,7 +195,7 @@ const RenderChangelogList: React.FC<{ changelogList: ChangelogInfo[] }> = ({ cha
|
||||
const { styles } = useStyle();
|
||||
const len = changelogList.length;
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
const { refs, changelog } = changelogList[i];
|
||||
const { refs, changelog, contributors } = changelogList[i];
|
||||
// Check if the next line is an image link and append it to the current line
|
||||
if (i + 1 < len && changelogList[i + 1].changelog.trim().startsWith('<img')) {
|
||||
const imgDom = new DOMParser().parseFromString(changelogList[i + 1].changelog, 'text/html');
|
||||
@ -186,7 +203,7 @@ const RenderChangelogList: React.FC<{ changelogList: ChangelogInfo[] }> = ({ cha
|
||||
elements.push(
|
||||
<li key={i}>
|
||||
<ParseChangelog changelog={changelog} />
|
||||
<RefLinks refs={refs} />
|
||||
<RefLinks refs={refs} contributors={contributors} />
|
||||
<br />
|
||||
<img
|
||||
src={imgElement?.getAttribute('src') || ''}
|
||||
@ -200,7 +217,7 @@ const RenderChangelogList: React.FC<{ changelogList: ChangelogInfo[] }> = ({ cha
|
||||
elements.push(
|
||||
<li key={i}>
|
||||
<ParseChangelog changelog={changelog} />
|
||||
<RefLinks refs={refs} />
|
||||
<RefLinks refs={refs} contributors={contributors} />
|
||||
</li>,
|
||||
);
|
||||
}
|
||||
@ -306,7 +323,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
|
||||
return (
|
||||
<>
|
||||
{isValidElement(children) &&
|
||||
cloneElement(children as React.ReactElement, {
|
||||
cloneElement(children as React.ReactElement<any>, {
|
||||
onClick: () => setShow(true),
|
||||
})}
|
||||
<Drawer
|
||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||
import ComponentChangelog from './ComponentChangelog';
|
||||
|
||||
const ChangeLog: React.FC<Readonly<React.PropsWithChildren>> = ({ children }) => (
|
||||
<React.Suspense fallback={null}>
|
||||
<React.Suspense fallback="...">
|
||||
<ComponentChangelog>{children}</ComponentChangelog>
|
||||
</React.Suspense>
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import type { GetProp, MenuProps } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
@ -7,7 +7,6 @@ import classNames from 'classnames';
|
||||
|
||||
import useMenu from '../../hooks/useMenu';
|
||||
import SiteContext from '../slots/SiteContext';
|
||||
import type { SiteContextProps } from '../slots/SiteContext';
|
||||
|
||||
type MenuItemType = Extract<GetProp<MenuProps, 'items'>[number], { type?: 'item' }>;
|
||||
|
||||
@ -115,7 +114,7 @@ const PrevAndNext: React.FC<{ rtl?: boolean }> = ({ rtl }) => {
|
||||
|
||||
const [menuItems, selectedKey] = useMenu({ before, after });
|
||||
|
||||
const { isMobile } = useContext<SiteContextProps>(SiteContext);
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
|
||||
const [prev, next] = useMemo(() => {
|
||||
const flatMenu = flattenMenu(menuItems);
|
||||
@ -141,13 +140,23 @@ const PrevAndNext: React.FC<{ rtl?: boolean }> = ({ rtl }) => {
|
||||
return (
|
||||
<section className={styles.prevNextNav}>
|
||||
{prev &&
|
||||
React.cloneElement(prev.label as ReactElement, {
|
||||
className: classNames(styles.pageNav, styles.prevNav, prev.className),
|
||||
})}
|
||||
React.cloneElement(
|
||||
prev.label as ReactElement<{
|
||||
className: string;
|
||||
}>,
|
||||
{
|
||||
className: classNames(styles.pageNav, styles.prevNav, prev.className),
|
||||
},
|
||||
)}
|
||||
{next &&
|
||||
React.cloneElement(next.label as ReactElement, {
|
||||
className: classNames(styles.pageNav, styles.nextNav, next.className),
|
||||
})}
|
||||
React.cloneElement(
|
||||
next.label as ReactElement<{
|
||||
className: string;
|
||||
}>,
|
||||
{
|
||||
className: classNames(styles.pageNav, styles.nextNav, next.className),
|
||||
},
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
import { BgColorsOutlined, SmileOutlined } from '@ant-design/icons';
|
||||
import { FloatButton } from 'antd';
|
||||
import React, { use } from 'react';
|
||||
import { BgColorsOutlined, LinkOutlined, SmileOutlined, SunOutlined } from '@ant-design/icons';
|
||||
import { Badge, Button, Dropdown } from 'antd';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { CompactTheme, DarkTheme } from 'antd-token-previewer/es/icons';
|
||||
// import { Motion } from 'antd-token-previewer/es/icons';
|
||||
import { FormattedMessage, useLocation } from 'dumi';
|
||||
|
||||
import useThemeAnimation from '../../../hooks/useThemeAnimation';
|
||||
import type { SiteContextProps } from '../../slots/SiteContext';
|
||||
import SiteContext from '../../slots/SiteContext';
|
||||
import { getLocalizedPathname, isZhCN } from '../../utils';
|
||||
import Link from '../Link';
|
||||
import ThemeIcon from './ThemeIcon';
|
||||
@ -14,80 +16,119 @@ export type ThemeName = 'light' | 'dark' | 'compact' | 'motion-off' | 'happy-wor
|
||||
|
||||
export interface ThemeSwitchProps {
|
||||
value?: ThemeName[];
|
||||
onChange: (value: ThemeName[]) => void;
|
||||
}
|
||||
|
||||
const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {
|
||||
const { value = ['light'], onChange } = props;
|
||||
const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
|
||||
const { pathname, search } = useLocation();
|
||||
|
||||
// const isMotionOff = value.includes('motion-off');
|
||||
const isHappyWork = value.includes('happy-work');
|
||||
const isDark = value.includes('dark');
|
||||
|
||||
const { theme, updateSiteConfig } = use<SiteContextProps>(SiteContext);
|
||||
const toggleAnimationTheme = useThemeAnimation();
|
||||
|
||||
return (
|
||||
<FloatButton.Group
|
||||
trigger="click"
|
||||
icon={<ThemeIcon />}
|
||||
aria-label="Theme Switcher"
|
||||
badge={{ dot: true }}
|
||||
>
|
||||
<Link
|
||||
to={getLocalizedPathname('/theme-editor', isZhCN(pathname), search)}
|
||||
style={{ display: 'block' }}
|
||||
>
|
||||
<FloatButton
|
||||
icon={<BgColorsOutlined />}
|
||||
tooltip={<FormattedMessage id="app.footer.theme" />}
|
||||
/>
|
||||
</Link>
|
||||
<FloatButton
|
||||
icon={<DarkTheme />}
|
||||
type={isDark ? 'primary' : 'default'}
|
||||
onClick={(e) => {
|
||||
// Toggle animation when switch theme
|
||||
toggleAnimationTheme(e, isDark);
|
||||
const badge = <Badge color="blue" style={{ marginTop: -1 }} />;
|
||||
|
||||
if (isDark) {
|
||||
onChange(value.filter((theme) => theme !== 'dark'));
|
||||
} else {
|
||||
onChange([...value, 'dark']);
|
||||
}
|
||||
}}
|
||||
tooltip={<FormattedMessage id="app.theme.switch.dark" />}
|
||||
/>
|
||||
<FloatButton
|
||||
icon={<CompactTheme />}
|
||||
type={value.includes('compact') ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
if (value.includes('compact')) {
|
||||
onChange(value.filter((theme) => theme !== 'compact'));
|
||||
} else {
|
||||
onChange([...value, 'compact']);
|
||||
}
|
||||
}}
|
||||
tooltip={<FormattedMessage id="app.theme.switch.compact" />}
|
||||
/>
|
||||
<FloatButton
|
||||
badge={{ dot: true }}
|
||||
icon={<SmileOutlined />}
|
||||
type={isHappyWork ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
if (isHappyWork) {
|
||||
onChange(value.filter((theme) => theme !== 'happy-work'));
|
||||
} else {
|
||||
onChange([...value, 'happy-work']);
|
||||
}
|
||||
}}
|
||||
tooltip={
|
||||
<FormattedMessage
|
||||
id={isHappyWork ? 'app.theme.switch.happy-work.off' : 'app.theme.switch.happy-work.on'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</FloatButton.Group>
|
||||
// 主题选项配置
|
||||
const themeOptions = [
|
||||
{
|
||||
id: 'app.theme.switch.default',
|
||||
icon: <SunOutlined />,
|
||||
key: 'light',
|
||||
showBadge: () => theme.includes('light') || theme.length === 0,
|
||||
},
|
||||
{
|
||||
id: 'app.theme.switch.dark',
|
||||
icon: <DarkTheme />,
|
||||
key: 'dark',
|
||||
showBadge: () => theme.includes('dark'),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
id: 'app.theme.switch.compact',
|
||||
icon: <CompactTheme />,
|
||||
key: 'compact',
|
||||
showBadge: () => theme.includes('compact'),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
id: 'app.theme.switch.happy-work',
|
||||
icon: <SmileOutlined />,
|
||||
key: 'happy-work',
|
||||
showBadge: () => theme.includes('happy-work'),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
id: 'app.footer.theme',
|
||||
icon: <BgColorsOutlined />,
|
||||
key: 'theme-editor',
|
||||
extra: <LinkOutlined />,
|
||||
isLink: true,
|
||||
linkPath: '/theme-editor',
|
||||
},
|
||||
];
|
||||
|
||||
// 构建下拉菜单项
|
||||
const items = themeOptions.map((option, i) => {
|
||||
if (option.type === 'divider') {
|
||||
return { type: 'divider' as const, key: `divider-${i}` };
|
||||
}
|
||||
|
||||
const { id, icon, key, showBadge, extra, isLink, linkPath } = option;
|
||||
|
||||
return {
|
||||
label: isLink ? (
|
||||
<Link to={getLocalizedPathname(linkPath!, isZhCN(pathname), search)}>
|
||||
<FormattedMessage id={id} />
|
||||
</Link>
|
||||
) : (
|
||||
<FormattedMessage id={id} />
|
||||
),
|
||||
icon,
|
||||
key: key || i,
|
||||
extra: showBadge ? (showBadge() ? badge : null) : extra,
|
||||
};
|
||||
});
|
||||
|
||||
// 处理主题切换
|
||||
const handleThemeChange = (key: string, domEvent: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
// 主题编辑器特殊处理
|
||||
if (key === 'theme-editor') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 亮色/暗色模式切换时应用动画效果
|
||||
if (key === 'dark' || key === 'light') {
|
||||
toggleAnimationTheme(domEvent, theme.includes('dark'));
|
||||
}
|
||||
|
||||
const themeKey = key as ThemeName;
|
||||
|
||||
// 亮色/暗色模式是互斥的
|
||||
if (['light', 'dark'].includes(key)) {
|
||||
const filteredTheme = theme.filter((t) => !['light', 'dark'].includes(t));
|
||||
updateSiteConfig({
|
||||
theme: [...filteredTheme, themeKey],
|
||||
});
|
||||
} else {
|
||||
// 其他主题选项是开关式的
|
||||
const hasTheme = theme.includes(themeKey);
|
||||
updateSiteConfig({
|
||||
theme: hasTheme ? theme.filter((t) => t !== themeKey) : [...theme, themeKey],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onClick: MenuProps['onClick'] = ({ key, domEvent }) => {
|
||||
handleThemeChange(key, domEvent as React.MouseEvent<HTMLElement, MouseEvent>);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown menu={{ items, onClick }} arrow={{ pointAtCenter: true }} placement="bottomRight">
|
||||
<Button type="text" icon={<ThemeIcon />} style={{ fontSize: 16 }} />
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -282,12 +282,13 @@ const GlobalDemoStyles: React.FC = () => {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-riddle {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
&-codeblock {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
&-codesandbox {
|
||||
@ -324,7 +325,6 @@ const GlobalDemoStyles: React.FC = () => {
|
||||
|
||||
&-debug {
|
||||
border-color: ${token.purple3};
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-debug &-title a {
|
||||
@ -334,10 +334,6 @@ const GlobalDemoStyles: React.FC = () => {
|
||||
|
||||
.demo-wrapper {
|
||||
position: relative;
|
||||
|
||||
&-show-debug .code-box-debug {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.all-code-box-controls {
|
||||
|
@ -34,6 +34,8 @@ export default () => {
|
||||
> .icon-link::before {
|
||||
font-size: ${token.fontSizeXL}px;
|
||||
content: '#';
|
||||
color: ${token.colorTextSecondary};
|
||||
font-family: ${token.codeFamily};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import { FastColor } from '@ant-design/fast-color';
|
||||
import { css, Global } from '@emotion/react';
|
||||
import { useTheme } from 'antd-style';
|
||||
|
||||
@ -410,7 +410,7 @@ const GlobalStyle: React.FC = () => {
|
||||
background: ${demoGridColor};
|
||||
|
||||
&:nth-child(2n + 1) {
|
||||
background: ${new TinyColor(demoGridColor).setAlpha(0.75).toHex8String()};
|
||||
background: ${new FastColor(demoGridColor).setA(0.75).toHexString()};
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,12 +426,12 @@ const GlobalStyle: React.FC = () => {
|
||||
}
|
||||
|
||||
${antCls}-row .demo-col-1 {
|
||||
background: ${new TinyColor(demoGridColor).setAlpha(0.75).toHexString()};
|
||||
background: ${new FastColor(demoGridColor).setA(0.75).toHexString()};
|
||||
}
|
||||
|
||||
${antCls}-row .demo-col-2,
|
||||
.code-box-demo ${antCls}-row .demo-col-2 {
|
||||
background: ${new TinyColor(demoGridColor).setAlpha(0.75).toHexString()};
|
||||
background: ${new FastColor(demoGridColor).setA(0.75).toHexString()};
|
||||
}
|
||||
|
||||
${antCls}-row .demo-col-3,
|
||||
@ -442,7 +442,7 @@ const GlobalStyle: React.FC = () => {
|
||||
|
||||
${antCls}-row .demo-col-4,
|
||||
.code-box-demo ${antCls}-row .demo-col-4 {
|
||||
background: ${new TinyColor(demoGridColor).setAlpha(0.6).toHexString()};
|
||||
background: ${new FastColor(demoGridColor).setA(0.6).toHexString()};
|
||||
}
|
||||
|
||||
${antCls}-row .demo-col-5,
|
||||
|
@ -3,7 +3,7 @@ import dayjs from 'dayjs';
|
||||
|
||||
import 'dayjs/locale/zh-cn';
|
||||
|
||||
import React, { useContext, useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import React, { useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import ConfigProvider from 'antd/es/config-provider';
|
||||
import zhCN from 'antd/es/locale/zh_CN';
|
||||
import { Helmet, useOutlet, useSiteData } from 'dumi';
|
||||
@ -37,8 +37,8 @@ const DocLayout: React.FC = () => {
|
||||
const location = useLocation();
|
||||
const { pathname, search, hash } = location;
|
||||
const [locale, lang] = useLocale(locales);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
const { direction } = useContext(SiteContext);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout>>(null!);
|
||||
const { direction } = React.use(SiteContext);
|
||||
const { loading } = useSiteData();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
|
@ -1,4 +1,7 @@
|
||||
import React, { Suspense, useCallback, useEffect } from 'react';
|
||||
// prettier-ignore
|
||||
import { scan } from 'react-scan'; // import this BEFORE react
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import {
|
||||
createCache,
|
||||
extractStyle,
|
||||
@ -16,13 +19,12 @@ import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML }
|
||||
|
||||
import { DarkContext } from '../../hooks/useDark';
|
||||
import useLayoutState from '../../hooks/useLayoutState';
|
||||
import useLocation from '../../hooks/useLocation';
|
||||
import type { ThemeName } from '../common/ThemeSwitch';
|
||||
import SiteThemeProvider from '../SiteThemeProvider';
|
||||
import type { SiteContextProps } from '../slots/SiteContext';
|
||||
import SiteContext from '../slots/SiteContext';
|
||||
|
||||
const ThemeSwitch = React.lazy(() => import('../common/ThemeSwitch'));
|
||||
import '@ant-design/v5-patch-for-react-19';
|
||||
|
||||
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
|
||||
type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
|
||||
@ -43,6 +45,13 @@ if (typeof window !== 'undefined') {
|
||||
location.hash = `#${hashId.replace(/^components-/, '')}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
scan({
|
||||
enabled: false,
|
||||
showToolbar: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const getAlgorithm = (themes: ThemeName[] = []) =>
|
||||
@ -60,7 +69,6 @@ const getAlgorithm = (themes: ThemeName[] = []) =>
|
||||
|
||||
const GlobalLayout: React.FC = () => {
|
||||
const outlet = useOutlet();
|
||||
const { pathname } = useLocation();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [{ theme = [], direction, isMobile, bannerVisible = false }, setSiteState] =
|
||||
useLayoutState<SiteState>({
|
||||
@ -70,6 +78,9 @@ const GlobalLayout: React.FC = () => {
|
||||
bannerVisible: false,
|
||||
});
|
||||
|
||||
// TODO: This can be remove in v6
|
||||
const useCssVar = searchParams.get('cssVar') !== 'false';
|
||||
|
||||
const updateSiteConfig = useCallback(
|
||||
(props: SiteState) => {
|
||||
setSiteState((prev) => ({ ...prev, ...props }));
|
||||
@ -150,8 +161,8 @@ const GlobalLayout: React.FC = () => {
|
||||
() => ({
|
||||
algorithm: getAlgorithm(theme),
|
||||
token: { motion: !theme.includes('motion-off') },
|
||||
cssVar: true,
|
||||
hashed: false,
|
||||
cssVar: useCssVar,
|
||||
hashed: !useCssVar,
|
||||
}),
|
||||
[theme],
|
||||
);
|
||||
@ -192,39 +203,21 @@ const GlobalLayout: React.FC = () => {
|
||||
/>
|
||||
));
|
||||
|
||||
const demoPage = pathname.startsWith('/~demos');
|
||||
|
||||
// ============================ Render ============================
|
||||
let content: React.ReactNode = outlet;
|
||||
|
||||
// Demo page should not contain App component
|
||||
if (!demoPage) {
|
||||
content = (
|
||||
<App>
|
||||
{outlet}
|
||||
<Suspense>
|
||||
<ThemeSwitch
|
||||
value={theme}
|
||||
onChange={(nextTheme) => updateSiteConfig({ theme: nextTheme })}
|
||||
/>
|
||||
</Suspense>
|
||||
</App>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DarkContext.Provider value={theme.includes('dark')}>
|
||||
<DarkContext value={theme.includes('dark')}>
|
||||
<StyleProvider
|
||||
cache={styleCache}
|
||||
linters={[legacyNotSelectorLinter, parentSelectorLinter, NaNLinter]}
|
||||
>
|
||||
<SiteContext.Provider value={siteContextValue}>
|
||||
<SiteContext value={siteContextValue}>
|
||||
<SiteThemeProvider theme={themeConfig}>
|
||||
<HappyProvider disabled={!theme.includes('happy-work')}>{content}</HappyProvider>
|
||||
<HappyProvider disabled={!theme.includes('happy-work')}>
|
||||
<App>{outlet}</App>
|
||||
</HappyProvider>
|
||||
</SiteThemeProvider>
|
||||
</SiteContext.Provider>
|
||||
</SiteContext>
|
||||
</StyleProvider>
|
||||
</DarkContext.Provider>
|
||||
</DarkContext>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -4,10 +4,10 @@ import { ConfigProvider, Layout, Typography } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { FormattedMessage, useRouteMeta } from 'dumi';
|
||||
|
||||
import useDark from '../../../hooks/useDark';
|
||||
import CommonHelmet from '../../common/CommonHelmet';
|
||||
import EditButton from '../../common/EditButton';
|
||||
import Footer from '../../slots/Footer';
|
||||
import { DarkContext } from './../../../hooks/useDark';
|
||||
import AffixTabs from './AffixTabs';
|
||||
|
||||
export type ResourceLayoutProps = PropsWithChildren<NonNullable<any>>;
|
||||
@ -16,85 +16,76 @@ const resourcePadding = 40;
|
||||
const articleMaxWidth = 1208;
|
||||
const resourcePaddingXS = 24;
|
||||
|
||||
const useStyle = () => {
|
||||
const isRootDark = useDark();
|
||||
|
||||
return createStyles((config) => {
|
||||
const { token, css } = config;
|
||||
const { antCls } = token;
|
||||
|
||||
return {
|
||||
resourcePage: css`
|
||||
footer {
|
||||
margin-top: 176px;
|
||||
|
||||
.rc-footer-container {
|
||||
max-width: ${articleMaxWidth}px;
|
||||
margin: 0 auto;
|
||||
padding-inline-end: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
`,
|
||||
resourceContent: css`
|
||||
padding: 0 ${resourcePadding}px;
|
||||
max-width: ${articleMaxWidth}px;
|
||||
margin: 0 auto;
|
||||
box-sizing: content-box;
|
||||
min-height: 100vh;
|
||||
|
||||
@media only screen and (max-width: 767.99px) {
|
||||
& {
|
||||
article {
|
||||
padding: 0 ${resourcePaddingXS}px;
|
||||
}
|
||||
${antCls}-col {
|
||||
padding-top: ${token.padding}px !important;
|
||||
padding-bottom: ${token.padding}px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
banner: css`
|
||||
padding: 0 ${resourcePadding}px;
|
||||
overflow: hidden;
|
||||
${
|
||||
isRootDark
|
||||
? ``
|
||||
: `background: url('https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*y_r7RogIG1wAAAAAAAAAAABkARQnAQ');`
|
||||
}
|
||||
background-size: cover;
|
||||
|
||||
h1 {
|
||||
box-sizing: content-box;
|
||||
const useStyle = createStyles(({ token, css }, isDark: boolean) => {
|
||||
return {
|
||||
resourcePage: css`
|
||||
footer {
|
||||
margin-top: 176px;
|
||||
.rc-footer-container {
|
||||
max-width: ${articleMaxWidth}px;
|
||||
margin: 56px auto 16px;
|
||||
margin: 0 auto;
|
||||
padding-inline-end: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
`,
|
||||
resourceContent: css`
|
||||
padding: 0 ${resourcePadding}px;
|
||||
max-width: ${articleMaxWidth}px;
|
||||
margin: 0 auto;
|
||||
box-sizing: content-box;
|
||||
min-height: 100vh;
|
||||
|
||||
section {
|
||||
max-width: ${articleMaxWidth}px;
|
||||
margin: 0 auto 56px;
|
||||
font-weight: 200;
|
||||
font-size: ${token.fontSizeLG}px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767.99px) {
|
||||
& {
|
||||
margin: 0 -${resourcePaddingXS}px;
|
||||
@media only screen and (max-width: 767.99px) {
|
||||
& {
|
||||
article {
|
||||
padding: 0 ${resourcePaddingXS}px;
|
||||
}
|
||||
${token.antCls}-col {
|
||||
padding-top: ${token.padding}px !important;
|
||||
padding-bottom: ${token.padding}px !important;
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
})();
|
||||
};
|
||||
}
|
||||
`,
|
||||
banner: css`
|
||||
padding: 0 ${resourcePadding}px;
|
||||
overflow: hidden;
|
||||
${
|
||||
isDark
|
||||
? ``
|
||||
: `background: url('https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*y_r7RogIG1wAAAAAAAAAAABkARQnAQ');`
|
||||
}
|
||||
background-size: cover;
|
||||
|
||||
h1 {
|
||||
box-sizing: content-box;
|
||||
max-width: ${articleMaxWidth}px;
|
||||
margin: 56px auto 16px;
|
||||
}
|
||||
|
||||
section {
|
||||
max-width: ${articleMaxWidth}px;
|
||||
margin: 0 auto 56px;
|
||||
font-weight: 200;
|
||||
font-size: ${token.fontSizeLG}px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767.99px) {
|
||||
& {
|
||||
margin: 0 -${resourcePaddingXS}px;
|
||||
padding: 0 ${resourcePaddingXS}px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
const ResourceLayout: React.FC<ResourceLayoutProps> = ({ children }) => {
|
||||
const { styles } = useStyle();
|
||||
const isDark = React.use(DarkContext);
|
||||
const { styles } = useStyle(isDark);
|
||||
const meta = useRouteMeta();
|
||||
const isRootDark = useDark();
|
||||
|
||||
const node = (
|
||||
<Layout>
|
||||
<CommonHelmet />
|
||||
@ -116,7 +107,7 @@ const ResourceLayout: React.FC<ResourceLayoutProps> = ({ children }) => {
|
||||
</Layout>
|
||||
);
|
||||
|
||||
if (!isRootDark) {
|
||||
if (!isDark) {
|
||||
return <ConfigProvider theme={{ token: { colorBgLayout: '#fff' } }}>{node}</ConfigProvider>;
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,7 @@
|
||||
"app.theme.switch.compact": "Compact theme",
|
||||
"app.theme.switch.motion.on": "Motion On",
|
||||
"app.theme.switch.motion.off": "Motion Off",
|
||||
"app.theme.switch.happy-work.on": "Happy Work Effect On",
|
||||
"app.theme.switch.happy-work.off": "Happy Work Effect Off",
|
||||
"app.theme.switch.happy-work": "Happy Work Effect",
|
||||
"app.header.search": "Search...",
|
||||
"app.header.menu.documentation": "Docs",
|
||||
"app.header.menu.more": "More",
|
||||
@ -35,7 +34,7 @@
|
||||
"app.demo.codepen": "Open in CodePen",
|
||||
"app.demo.codesandbox": "Open in CodeSandbox",
|
||||
"app.demo.stackblitz": "Open in Stackblitz",
|
||||
"app.demo.riddle": "Open in Riddle",
|
||||
"app.demo.codeblock": "Open in Hitu",
|
||||
"app.demo.separate": "Open in a new window",
|
||||
"app.demo.online": "Online Address",
|
||||
"app.home.introduce": "A design system for enterprise-level products. Create an efficient and enjoyable work experience.",
|
||||
|
@ -5,8 +5,7 @@
|
||||
"app.theme.switch.compact": "紧凑主题",
|
||||
"app.theme.switch.motion.on": "动画开启",
|
||||
"app.theme.switch.motion.off": "动画关闭",
|
||||
"app.theme.switch.happy-work.on": "快乐工作特效开启",
|
||||
"app.theme.switch.happy-work.off": "快乐工作特效关闭",
|
||||
"app.theme.switch.happy-work": "快乐工作特效",
|
||||
"app.header.search": "全文本搜索...",
|
||||
"app.header.menu.documentation": "文档",
|
||||
"app.header.menu.more": "更多",
|
||||
@ -35,7 +34,7 @@
|
||||
"app.demo.codepen": "在 CodePen 中打开",
|
||||
"app.demo.codesandbox": "在 CodeSandbox 中打开",
|
||||
"app.demo.stackblitz": "在 Stackblitz 中打开",
|
||||
"app.demo.riddle": "在 Riddle 中打开",
|
||||
"app.demo.codeblock": "在海兔中打开",
|
||||
"app.demo.separate": "在新窗口打开",
|
||||
"app.demo.online": "线上地址",
|
||||
"app.home.introduce": "企业级产品设计体系,创造高效愉悦的工作体验",
|
||||
|
@ -2,10 +2,9 @@ import { createHash } from 'crypto';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import createEmotionServer from '@emotion/server/create-instance';
|
||||
import chalk from 'chalk';
|
||||
import type { IApi, IRoute } from 'dumi';
|
||||
import ReactTechStack from 'dumi/dist/techStacks/react';
|
||||
import sylvanas from 'sylvanas';
|
||||
import tsToJs from './utils/tsToJs';
|
||||
|
||||
import { dependencies, devDependencies } from '../../package.json';
|
||||
|
||||
@ -42,7 +41,7 @@ class AntdReactTechStack extends ReactTechStack {
|
||||
props.jsx ??= '';
|
||||
|
||||
if (opts.type === 'code-block') {
|
||||
props.jsx = opts?.entryPointCode ? sylvanas.parseText(opts.entryPointCode) : '';
|
||||
props.jsx = opts?.entryPointCode ? tsToJs(opts.entryPointCode) : '';
|
||||
}
|
||||
|
||||
if (opts.type === 'external') {
|
||||
@ -54,7 +53,7 @@ class AntdReactTechStack extends ReactTechStack {
|
||||
const codePath = opts.fileAbsPath!.replace(/\.\w+$/, '.tsx');
|
||||
const code = fs.existsSync(codePath) ? fs.readFileSync(codePath, 'utf-8') : '';
|
||||
|
||||
props.jsx = sylvanas.parseText(code);
|
||||
props.jsx = tsToJs(code);
|
||||
|
||||
if (md) {
|
||||
// extract description & css style from md file
|
||||
@ -126,7 +125,8 @@ class AntdReactTechStack extends ReactTechStack {
|
||||
|
||||
const resolve = (p: string): string => require.resolve(p);
|
||||
|
||||
const RoutesPlugin = (api: IApi) => {
|
||||
const RoutesPlugin = async (api: IApi) => {
|
||||
const chalk = await import('chalk').then((m) => m.default);
|
||||
// const ssrCssFileName = `ssr-${Date.now()}.css`;
|
||||
|
||||
const writeCSSFile = (key: string, hashKey: string, cssString: string) => {
|
||||
|
@ -130,7 +130,7 @@ const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Card className={card} bordered={false}>
|
||||
<Card className={card} variant="borderless">
|
||||
<h3 className={bigTitle}>{locale.bigTitle}</h3>
|
||||
{zhihuLink && (
|
||||
<>
|
||||
|
@ -1,19 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { AvatarListItem } from '@qixian.cs/github-contributors-list/dist/AvatarList';
|
||||
import { Avatar, Skeleton, Tooltip } from 'antd';
|
||||
|
||||
const AvatarPlaceholder: React.FC<{ num?: number }> = ({ num = 3 }) => (
|
||||
<li>
|
||||
{Array.from({ length: num }).map<React.ReactNode>((_, i) => (
|
||||
<Skeleton.Avatar
|
||||
size="small"
|
||||
active
|
||||
key={i}
|
||||
style={{ marginInlineStart: i === 0 ? 0 : -8 }}
|
||||
/>
|
||||
))}
|
||||
</li>
|
||||
);
|
||||
import { Avatar, Tooltip } from 'antd';
|
||||
|
||||
interface ContributorAvatarProps {
|
||||
loading?: boolean;
|
||||
@ -23,11 +10,7 @@ interface ContributorAvatarProps {
|
||||
const ContributorAvatar: React.FC<ContributorAvatarProps> = (props) => {
|
||||
const {
|
||||
item: { username, url } = {},
|
||||
loading,
|
||||
} = props;
|
||||
if (loading) {
|
||||
return <AvatarPlaceholder />;
|
||||
}
|
||||
if (username?.includes('github-actions')) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import ContributorsList from '@qixian.cs/github-contributors-list';
|
||||
import { createStyles } from 'antd-style';
|
||||
import classNames from 'classnames';
|
||||
@ -40,7 +40,7 @@ interface ContributorsProps {
|
||||
const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { styles } = useStyle();
|
||||
const { isMobile } = useContext(SiteContext);
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
|
||||
if (!filename) {
|
||||
return null;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useContext, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { Col, Flex, Space, Typography } from 'antd';
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { Col, Flex, Skeleton, Space, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage, useRouteMeta } from 'dumi';
|
||||
|
||||
@ -9,8 +9,8 @@ import ComponentMeta from '../../builtins/ComponentMeta';
|
||||
import type { DemoContextProps } from '../DemoContext';
|
||||
import DemoContext from '../DemoContext';
|
||||
import SiteContext from '../SiteContext';
|
||||
import InViewSuspense from './InViewSuspense';
|
||||
import { useStyle } from './DocAnchor';
|
||||
import InViewSuspense from './InViewSuspense';
|
||||
|
||||
const Contributors = React.lazy(() => import('./Contributors'));
|
||||
const ColumnCard = React.lazy(() => import('./ColumnCard'));
|
||||
@ -20,10 +20,15 @@ const Footer = React.lazy(() => import('../Footer'));
|
||||
const PrevAndNext = React.lazy(() => import('../../common/PrevAndNext'));
|
||||
const EditButton = React.lazy(() => import('../../common/EditButton'));
|
||||
|
||||
const AvatarPlaceholder: React.FC<{ num?: number }> = ({ num = 6 }) =>
|
||||
Array.from({ length: num }).map<React.ReactNode>((_, i) => (
|
||||
<Skeleton.Avatar size="small" active key={i} style={{ marginInlineStart: i === 0 ? 0 : -8 }} />
|
||||
));
|
||||
|
||||
const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const meta = useRouteMeta();
|
||||
const { pathname, hash } = useLocation();
|
||||
const { direction } = useContext(SiteContext);
|
||||
const { direction } = React.use(SiteContext);
|
||||
const { styles } = useStyle();
|
||||
|
||||
const [showDebug, setShowDebug] = useLayoutState(false);
|
||||
@ -47,7 +52,7 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const isRTL = direction === 'rtl';
|
||||
|
||||
return (
|
||||
<DemoContext.Provider value={contextValue}>
|
||||
<DemoContext value={contextValue}>
|
||||
<Col xxl={20} xl={19} lg={18} md={18} sm={24} xs={24}>
|
||||
<InViewSuspense fallback={null}>
|
||||
<DocAnchor showDebug={showDebug} debugDemos={debugDemos} />
|
||||
@ -84,10 +89,11 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
component={meta.frontmatter.title}
|
||||
filename={meta.frontmatter.filename}
|
||||
version={meta.frontmatter.tag}
|
||||
designUrl={meta.frontmatter.designUrl}
|
||||
/>
|
||||
)}
|
||||
<div style={{ minHeight: 'calc(100vh - 64px)' }}>{children}</div>
|
||||
<InViewSuspense>
|
||||
<InViewSuspense fallback={null}>
|
||||
<ColumnCard
|
||||
zhihuLink={meta.frontmatter.zhihu_url}
|
||||
yuqueLink={meta.frontmatter.yuque_url}
|
||||
@ -95,7 +101,7 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
/>
|
||||
</InViewSuspense>
|
||||
<div style={{ marginTop: 120 }}>
|
||||
<InViewSuspense fallback={<div style={{ height: 50 }} />}>
|
||||
<InViewSuspense fallback={<AvatarPlaceholder />}>
|
||||
<Contributors filename={meta.frontmatter.filename} />
|
||||
</InViewSuspense>
|
||||
</div>
|
||||
@ -105,7 +111,7 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
</InViewSuspense>
|
||||
<Footer />
|
||||
</Col>
|
||||
</DemoContext.Provider>
|
||||
</DemoContext>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { FastColor } from '@ant-design/fast-color';
|
||||
import {
|
||||
AntDesignOutlined,
|
||||
BgColorsOutlined,
|
||||
@ -13,7 +14,6 @@ import {
|
||||
UsergroupAddOutlined,
|
||||
ZhihuOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import { createStyles } from 'antd-style';
|
||||
import getAlphaColor from 'antd/es/theme/util/getAlphaColor';
|
||||
import { FormattedMessage, Link } from 'dumi';
|
||||
@ -34,64 +34,57 @@ const locales = {
|
||||
},
|
||||
};
|
||||
|
||||
const useStyle = () => {
|
||||
const { isMobile } = useContext(SiteContext);
|
||||
return createStyles(({ token, css }) => {
|
||||
const background = new TinyColor(getAlphaColor('#f0f3fa', '#fff'))
|
||||
.onBackground(token.colorBgContainer)
|
||||
.toHexString();
|
||||
const useStyle = createStyles(({ token, css }, isMobile: boolean) => {
|
||||
const background = new FastColor(getAlphaColor('#f0f3fa', '#fff'))
|
||||
.onBackground(token.colorBgContainer)
|
||||
.toHexString();
|
||||
return {
|
||||
holder: css`
|
||||
background: ${background};
|
||||
`,
|
||||
|
||||
return {
|
||||
holder: css`
|
||||
background: ${background};
|
||||
`,
|
||||
footer: css`
|
||||
background: ${background};
|
||||
color: ${token.colorTextSecondary};
|
||||
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
|
||||
|
||||
footer: css`
|
||||
background: ${background};
|
||||
color: ${token.colorTextSecondary};
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h2,
|
||||
a {
|
||||
color: ${token.colorText};
|
||||
}
|
||||
.rc-footer-column {
|
||||
margin-bottom: ${isMobile ? 60 : 0}px;
|
||||
:last-child {
|
||||
margin-bottom: ${isMobile ? 20 : 0}px;
|
||||
}
|
||||
}
|
||||
.rc-footer-item-icon {
|
||||
top: -1.5px;
|
||||
}
|
||||
.rc-footer-container {
|
||||
max-width: 1208px;
|
||||
margin-inline: auto;
|
||||
padding-inline: ${token.marginXXL}px;
|
||||
}
|
||||
.rc-footer-bottom {
|
||||
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
.rc-footer-bottom-container {
|
||||
font-size: ${token.fontSize}px;
|
||||
}
|
||||
|
||||
h2,
|
||||
a {
|
||||
color: ${token.colorText};
|
||||
}
|
||||
|
||||
.rc-footer-column {
|
||||
margin-bottom: ${isMobile ? 60 : 0}px;
|
||||
:last-child {
|
||||
margin-bottom: ${isMobile ? 20 : 0}px;
|
||||
}
|
||||
}
|
||||
|
||||
.rc-footer-item-icon {
|
||||
top: -1.5px;
|
||||
}
|
||||
|
||||
.rc-footer-container {
|
||||
max-width: 1208px;
|
||||
margin-inline: auto;
|
||||
padding-inline: ${token.marginXXL}px;
|
||||
}
|
||||
|
||||
.rc-footer-bottom {
|
||||
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
|
||||
.rc-footer-bottom-container {
|
||||
font-size: ${token.fontSize}px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
})();
|
||||
};
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
const location = useLocation();
|
||||
const [locale, lang] = useLocale(locales);
|
||||
const { styles } = useStyle();
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
const { styles } = useStyle(isMobile);
|
||||
|
||||
const { getLink } = location;
|
||||
|
||||
@ -118,7 +111,9 @@ const Footer: React.FC = () => {
|
||||
},
|
||||
{
|
||||
title: 'Pro Components',
|
||||
url: 'https://procomponents.ant.design',
|
||||
url: isZhCN
|
||||
? 'https://pro-components.antdigital.dev'
|
||||
: 'https://procomponents.ant.design',
|
||||
openExternal: true,
|
||||
},
|
||||
{
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Tooltip } from 'antd';
|
||||
import { Tooltip, Button } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import omit from 'rc-util/lib/omit';
|
||||
export interface LangBtnProps {
|
||||
label1: React.ReactNode;
|
||||
label2: React.ReactNode;
|
||||
@ -12,49 +12,24 @@ export interface LangBtnProps {
|
||||
pure?: boolean;
|
||||
onClick?: React.MouseEventHandler;
|
||||
'aria-label'?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const BASE_SIZE = '1.2em';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => {
|
||||
const {
|
||||
colorText,
|
||||
colorBorder,
|
||||
colorBgContainer,
|
||||
colorBgTextHover,
|
||||
borderRadius,
|
||||
controlHeight,
|
||||
motionDurationMid,
|
||||
} = token;
|
||||
const { colorText, controlHeight, colorBgContainer, motionDurationMid } = token;
|
||||
|
||||
return {
|
||||
btn: css`
|
||||
color: ${colorText};
|
||||
border-color: ${colorBorder};
|
||||
padding: 0 !important;
|
||||
width: ${controlHeight}px;
|
||||
height: ${controlHeight}px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: ${borderRadius}px;
|
||||
transition: all ${motionDurationMid};
|
||||
cursor: pointer;
|
||||
.btn-inner {
|
||||
transition: all ${motionDurationMid};
|
||||
}
|
||||
&:hover {
|
||||
background: ${colorBgTextHover};
|
||||
}
|
||||
img {
|
||||
width: ${BASE_SIZE};
|
||||
height: ${BASE_SIZE};
|
||||
}
|
||||
.anticon {
|
||||
font-size: ${BASE_SIZE};
|
||||
}
|
||||
`,
|
||||
innerDiv: css`
|
||||
position: relative;
|
||||
@ -88,19 +63,19 @@ const useStyle = createStyles(({ token, css }) => {
|
||||
});
|
||||
|
||||
const LangBtn: React.FC<LangBtnProps> = (props) => {
|
||||
const { label1, label2, tooltip1, tooltip2, value, pure, onClick } = props;
|
||||
const { label1, label2, tooltip1, tooltip2, value, pure, onClick, ...rest } = props;
|
||||
|
||||
const {
|
||||
styles: { btn, innerDiv, labelStyle, label1Style, label2Style },
|
||||
} = useStyle();
|
||||
|
||||
const node = (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
type="text"
|
||||
onClick={onClick}
|
||||
className={btn}
|
||||
key="lang-button"
|
||||
aria-label={props['aria-label']}
|
||||
{...omit(rest, ['className'])}
|
||||
>
|
||||
<div className="btn-inner">
|
||||
{pure && (value === 1 ? label1 : label2)}
|
||||
@ -115,7 +90,7 @@ const LangBtn: React.FC<LangBtnProps> = (props) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (tooltip1 || tooltip2) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { GithubOutlined, MenuOutlined } from '@ant-design/icons';
|
||||
import { Alert, Col, ConfigProvider, Popover, Row, Select } from 'antd';
|
||||
import { Alert, Button, Col, ConfigProvider, Popover, Row, Select, Tooltip } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import classNames from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
@ -8,11 +9,11 @@ import { useLocation, useSiteData } from 'dumi';
|
||||
import DumiSearchBar from 'dumi/theme-default/slots/SearchBar';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import ThemeSwitch from '../../common/ThemeSwitch';
|
||||
import DirectionIcon from '../../icons/DirectionIcon';
|
||||
import { ANT_DESIGN_NOT_SHOW_BANNER } from '../../layouts/GlobalLayout';
|
||||
import * as utils from '../../utils';
|
||||
import { getThemeConfig } from '../../utils';
|
||||
import type { SiteContextProps } from '../SiteContext';
|
||||
import SiteContext from '../SiteContext';
|
||||
import type { SharedProps } from './interface';
|
||||
import Logo from './Logo';
|
||||
@ -168,8 +169,7 @@ const Header: React.FC = () => {
|
||||
windowWidth: 1400,
|
||||
searching: false,
|
||||
});
|
||||
const { direction, isMobile, bannerVisible, updateSiteConfig } =
|
||||
useContext<SiteContextProps>(SiteContext);
|
||||
const { direction, isMobile, bannerVisible, updateSiteConfig } = React.use(SiteContext);
|
||||
const pingTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const location = useLocation();
|
||||
const { pathname, search } = location;
|
||||
@ -302,6 +302,7 @@ const Header: React.FC = () => {
|
||||
<Select
|
||||
key="version"
|
||||
size="small"
|
||||
variant="filled"
|
||||
className={styles.versionSelect}
|
||||
defaultValue={pkg.version}
|
||||
onChange={handleVersionChange}
|
||||
@ -330,13 +331,16 @@ const Header: React.FC = () => {
|
||||
pure
|
||||
aria-label="RTL Switch Button"
|
||||
/>,
|
||||
<ThemeSwitch key="theme" />,
|
||||
<a
|
||||
key="github"
|
||||
href="https://github.com/ant-design/ant-design"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<SwitchBtn value={1} label1={<GithubOutlined />} tooltip1="Github" label2={null} pure />
|
||||
<Tooltip title="GitHub" destroyTooltipOnHide>
|
||||
<Button type="text" icon={<GithubOutlined />} style={{ fontSize: 16 }} />
|
||||
</Tooltip>
|
||||
</a>,
|
||||
];
|
||||
|
||||
@ -357,7 +361,7 @@ const Header: React.FC = () => {
|
||||
<header className={headerClassName}>
|
||||
{isMobile && (
|
||||
<Popover
|
||||
overlayClassName={styles.popoverMenu}
|
||||
classNames={{ root: styles.popoverMenu }}
|
||||
placement="bottomRight"
|
||||
content={menu}
|
||||
trigger="click"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { Col, ConfigProvider, Menu } from 'antd';
|
||||
import { createStyles, useTheme } from 'antd-style';
|
||||
import { useSidebarData } from 'dumi';
|
||||
@ -109,7 +109,7 @@ const useStyle = createStyles(({ token, css }) => {
|
||||
|
||||
const Sidebar: React.FC = () => {
|
||||
const sidebarData = useSidebarData();
|
||||
const { isMobile, theme } = useContext(SiteContext);
|
||||
const { isMobile, theme } = React.use(SiteContext);
|
||||
const { styles } = useStyle();
|
||||
|
||||
const [menuItems, selectedKey] = useMenu();
|
||||
|
107
.dumi/theme/utils/__tests__/runTests.ts
Normal file
107
.dumi/theme/utils/__tests__/runTests.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import tsToJs from '../tsToJs';
|
||||
|
||||
// 简单测试用例:基本的 TypeScript 到 JavaScript 转换
|
||||
console.log('测试 1: 基本 TypeScript 转换');
|
||||
const tsInput = `
|
||||
interface Person {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
function greet(person: Person): string {
|
||||
return \`Hello, \${person.name}!\`;
|
||||
}
|
||||
|
||||
const john: Person = { name: 'John', age: 30 };
|
||||
greet(john);
|
||||
`;
|
||||
|
||||
const jsOutput = tsToJs(tsInput);
|
||||
console.log('输入:', tsInput);
|
||||
console.log('输出:', jsOutput);
|
||||
console.log('检查点:');
|
||||
console.log('- interface 被移除:', !jsOutput.includes('interface'));
|
||||
console.log('- 类型注解被移除:', !jsOutput.includes(': string') && !jsOutput.includes(': Person'));
|
||||
console.log('- 函数定义正确:', jsOutput.includes('function greet(person)'));
|
||||
console.log('- 对象定义正确:', jsOutput.includes('const john = { name:'));
|
||||
|
||||
// 测试用例 2: JSX 转换
|
||||
console.log('\n测试 2: JSX 转换');
|
||||
const tsxInput = `
|
||||
import React, { FC } from 'react';
|
||||
|
||||
interface ButtonProps {
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const Button: FC<ButtonProps> = ({ text, onClick }) => {
|
||||
return (
|
||||
<button
|
||||
className="primary-button"
|
||||
onClick={onClick}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
`;
|
||||
|
||||
const jsxOutput = tsToJs(tsxInput);
|
||||
console.log('输入:', tsxInput);
|
||||
console.log('输出:', jsxOutput);
|
||||
console.log('检查点:');
|
||||
console.log('- interface 被移除:', !jsxOutput.includes('interface ButtonProps'));
|
||||
console.log('- 类型注解被移除:', !jsxOutput.includes(': FC<ButtonProps>'));
|
||||
console.log('- JSX 被保留:', jsxOutput.includes('<button') && jsxOutput.includes('</button>'));
|
||||
console.log(
|
||||
'- 属性被保留:',
|
||||
jsxOutput.includes('className="primary-button"') && jsxOutput.includes('onClick={onClick}'),
|
||||
);
|
||||
|
||||
// 测试用例 3: 类型导入处理
|
||||
console.log('\n测试 3: 类型导入处理');
|
||||
const typeImportInput = `
|
||||
import React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
|
||||
const MyButton = (props: ButtonProps) => {
|
||||
return <Button {...props} />;
|
||||
};
|
||||
`;
|
||||
|
||||
const typeImportOutput = tsToJs(typeImportInput);
|
||||
console.log('输入:', typeImportInput);
|
||||
console.log('输出:', typeImportOutput);
|
||||
console.log('检查点:');
|
||||
console.log('- 类型导入被移除:', !typeImportOutput.includes('import type'));
|
||||
console.log('- ReactNode 被移除:', !typeImportOutput.includes('ReactNode'));
|
||||
console.log('- ButtonProps 被移除:', !typeImportOutput.includes('ButtonProps'));
|
||||
console.log(
|
||||
'- 普通导入被保留:',
|
||||
typeImportOutput.includes("import React from 'react'") &&
|
||||
typeImportOutput.includes("import { Button } from 'antd'"),
|
||||
);
|
||||
|
||||
// 总结测试结果
|
||||
console.log('\n测试总结:');
|
||||
const test1Pass =
|
||||
!jsOutput.includes('interface') &&
|
||||
!jsOutput.includes(': string') &&
|
||||
jsOutput.includes('function greet(person)');
|
||||
const test2Pass =
|
||||
!jsxOutput.includes('interface ButtonProps') &&
|
||||
jsxOutput.includes('<button') &&
|
||||
jsxOutput.includes('</button>');
|
||||
const test3Pass =
|
||||
!typeImportOutput.includes('import type') &&
|
||||
typeImportOutput.includes("import React from 'react'");
|
||||
|
||||
console.log('测试 1 (基本 TypeScript 转换):', test1Pass ? '通过' : '失败');
|
||||
console.log('测试 2 (JSX 转换):', test2Pass ? '通过' : '失败');
|
||||
console.log('测试 3 (类型导入处理):', test3Pass ? '通过' : '失败');
|
||||
console.log('所有测试:', test1Pass && test2Pass && test3Pass ? '通过' : '失败');
|
93
.dumi/theme/utils/__tests__/tsToJs.test.ts
Normal file
93
.dumi/theme/utils/__tests__/tsToJs.test.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import tsToJs from '../tsToJs';
|
||||
|
||||
describe('tsToJs', () => {
|
||||
it('应该将基本的 TypeScript 转换为 JavaScript', () => {
|
||||
const tsInput = `
|
||||
interface Person {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
function greet(person: Person): string {
|
||||
return \`Hello, \${person.name}!\`;
|
||||
}
|
||||
|
||||
const john: Person = { name: 'John', age: 30 };
|
||||
greet(john);
|
||||
`;
|
||||
|
||||
const jsOutput = tsToJs(tsInput);
|
||||
|
||||
// 检查结果中不应包含 TypeScript 特有的语法
|
||||
expect(jsOutput).not.toContain('interface');
|
||||
expect(jsOutput).not.toContain(': string');
|
||||
expect(jsOutput).not.toContain(': Person');
|
||||
expect(jsOutput).not.toContain(': number');
|
||||
|
||||
// 检查结果应包含 JavaScript 代码
|
||||
expect(jsOutput).toContain('function greet(person)');
|
||||
expect(jsOutput).toContain('return');
|
||||
expect(jsOutput).toContain('Hello');
|
||||
expect(jsOutput).toContain("const john = { name: 'John', age: 30 }");
|
||||
});
|
||||
|
||||
it('应该保留 JSX 语法', () => {
|
||||
const tsxInput = `
|
||||
import React, { FC } from 'react';
|
||||
|
||||
interface ButtonProps {
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const Button: FC<ButtonProps> = ({ text, onClick }) => {
|
||||
return (
|
||||
<button
|
||||
className="primary-button"
|
||||
onClick={onClick}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
`;
|
||||
|
||||
const jsxOutput = tsToJs(tsxInput);
|
||||
|
||||
// 检查结果中不应包含 TypeScript 特有的语法
|
||||
expect(jsxOutput).not.toContain('interface ButtonProps');
|
||||
expect(jsxOutput).not.toContain(': FC<ButtonProps>');
|
||||
|
||||
// 检查结果应保留 JSX 语法
|
||||
expect(jsxOutput).toContain('<button');
|
||||
expect(jsxOutput).toContain('className="primary-button"');
|
||||
expect(jsxOutput).toContain('</button>');
|
||||
expect(jsxOutput).toContain('onClick={onClick}');
|
||||
});
|
||||
|
||||
it('应该删除类型导入', () => {
|
||||
const tsInput = `
|
||||
import React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import type { ButtonProps } from 'antd/es/button';
|
||||
|
||||
const MyButton = (props: ButtonProps) => {
|
||||
return <Button {...props} />;
|
||||
};
|
||||
`;
|
||||
|
||||
const jsOutput = tsToJs(tsInput);
|
||||
|
||||
// 检查结果中不应包含类型导入
|
||||
expect(jsOutput).not.toContain('import type');
|
||||
expect(jsOutput).not.toContain('ReactNode');
|
||||
expect(jsOutput).not.toContain('ButtonProps');
|
||||
|
||||
// 保留普通导入
|
||||
expect(jsOutput).toContain("import React from 'react'");
|
||||
expect(jsOutput).toContain("import { Button } from 'antd'");
|
||||
});
|
||||
});
|
273
.dumi/theme/utils/tsToJs.test.ts
Normal file
273
.dumi/theme/utils/tsToJs.test.ts
Normal file
@ -0,0 +1,273 @@
|
||||
import { parseText } from './tsToJs';
|
||||
|
||||
// 简单 TypeScript 代码示例
|
||||
const tsCode = `
|
||||
interface Person {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
class Employee implements Person {
|
||||
name: string;
|
||||
age: number;
|
||||
department: string;
|
||||
|
||||
constructor(name: string, age: number, department: string) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
this.department = department;
|
||||
}
|
||||
|
||||
getInfo(): string {
|
||||
return \`\${this.name}, \${this.age}, \${this.department}\`;
|
||||
}
|
||||
}
|
||||
|
||||
const employee: Employee = new Employee('张三', 30, '研发部');
|
||||
console.log(employee.getInfo());
|
||||
`;
|
||||
|
||||
// 包含 JSX 的 TypeScript 代码示例
|
||||
const tsxCode = `
|
||||
import React, { FC, useState } from 'react';
|
||||
import { Button } from 'antd';
|
||||
|
||||
interface CounterProps {
|
||||
initialCount?: number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const Counter: FC<CounterProps> = ({ initialCount = 0, label }) => {
|
||||
const [count, setCount] = useState<number>(initialCount);
|
||||
|
||||
const increment = (): void => {
|
||||
setCount(count + 1);
|
||||
};
|
||||
|
||||
const decrement = (): void => {
|
||||
setCount(count - 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="counter">
|
||||
<h3>{label}: {count}</h3>
|
||||
<Button type="primary" onClick={increment}>+</Button>
|
||||
<Button onClick={decrement}>-</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Counter;
|
||||
`;
|
||||
|
||||
// 复杂 TypeScript 代码示例,包含泛型、类型导入、类型别名等
|
||||
const complexTsCode = `
|
||||
import React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Table } from 'antd';
|
||||
import type { TableProps, TableColumnType } from 'antd/es/table';
|
||||
|
||||
// 类型别名
|
||||
type Status = 'pending' | 'processing' | 'success' | 'failed';
|
||||
|
||||
// 泛型接口
|
||||
interface DataItem<T = string> {
|
||||
id: number;
|
||||
name: string;
|
||||
status: Status;
|
||||
details: T;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// 类型映射和条件类型
|
||||
type ReadonlyDataItem<T> = {
|
||||
readonly [K in keyof DataItem<T>]: DataItem<T>[K];
|
||||
};
|
||||
|
||||
// 工具类型
|
||||
type OptionalId<T> = Omit<T, 'id'> & { id?: number };
|
||||
|
||||
// 类型断言函数
|
||||
function assertIsDataItem<T>(item: any): asserts item is DataItem<T> {
|
||||
if (!item || typeof item.id !== 'number') {
|
||||
throw new Error('Invalid DataItem: missing or invalid id');
|
||||
}
|
||||
}
|
||||
|
||||
// 使用泛型组件
|
||||
const DataTable = <T extends string>(props: {
|
||||
data: DataItem<T>[];
|
||||
renderDetails?: (details: T) => ReactNode;
|
||||
}) => {
|
||||
const { data, renderDetails } = props;
|
||||
|
||||
// 定义表格列
|
||||
const columns: TableColumnType<DataItem<T>>[] = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (status: Status) => {
|
||||
const statusColors = {
|
||||
pending: 'blue',
|
||||
processing: 'orange',
|
||||
success: 'green',
|
||||
failed: 'red',
|
||||
};
|
||||
return <span style={{ color: statusColors[status] }}>{status}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '详情',
|
||||
dataIndex: 'details',
|
||||
key: 'details',
|
||||
render: (details: T) => renderDetails ? renderDetails(details) : details,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
render: (date: Date) => date.toLocaleString(),
|
||||
},
|
||||
];
|
||||
|
||||
return <Table<DataItem<T>> columns={columns} dataSource={data} rowKey="id" />;
|
||||
};
|
||||
|
||||
// 使用工具类型创建数据
|
||||
const createDataItem = <T extends string>(item: OptionalId<DataItem<T>>): DataItem<T> => {
|
||||
return {
|
||||
id: item.id ?? Math.floor(Math.random() * 1000),
|
||||
name: item.name,
|
||||
status: item.status,
|
||||
details: item.details,
|
||||
createdAt: item.createdAt || new Date(),
|
||||
};
|
||||
};
|
||||
|
||||
// 示例数据
|
||||
const exampleData: DataItem<string>[] = [
|
||||
createDataItem({
|
||||
name: '项目 A',
|
||||
status: 'success',
|
||||
details: '项目顺利完成',
|
||||
createdAt: new Date(2023, 0, 15),
|
||||
}),
|
||||
createDataItem({
|
||||
name: '项目 B',
|
||||
status: 'processing',
|
||||
details: '正在进行中...',
|
||||
createdAt: new Date(2023, 2, 10),
|
||||
}),
|
||||
];
|
||||
|
||||
// 渲染组件
|
||||
const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>数据表格示例</h1>
|
||||
<DataTable
|
||||
data={exampleData}
|
||||
renderDetails={(details) => <em>{details}</em>}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
`;
|
||||
|
||||
// 加入Jest测试用例
|
||||
describe('tsToJs函数测试', () => {
|
||||
// 转换普通 TypeScript 代码
|
||||
it('应该能正确转换普通TypeScript代码', () => {
|
||||
const jsCode = parseText(tsCode);
|
||||
|
||||
// 验证类型注解被移除
|
||||
expect(jsCode).not.toContain('interface Person');
|
||||
expect(jsCode).not.toContain(': string');
|
||||
expect(jsCode).not.toContain(': number');
|
||||
|
||||
// 验证类实现被保留
|
||||
expect(jsCode).toContain('class Employee');
|
||||
expect(jsCode).toContain('constructor(name, age, department)');
|
||||
expect(jsCode).toContain('getInfo()');
|
||||
|
||||
// 验证实例创建
|
||||
expect(jsCode).toContain("new Employee('张三', 30, '研发部')");
|
||||
|
||||
console.log('转换前的 TypeScript 代码:');
|
||||
console.log(tsCode);
|
||||
console.log('\n转换后的 JavaScript 代码:');
|
||||
console.log(jsCode);
|
||||
});
|
||||
|
||||
// 转换包含 JSX 的 TypeScript 代码
|
||||
it('应该能正确转换TSX代码', () => {
|
||||
const jsxCode = parseText(tsxCode);
|
||||
|
||||
// 验证React导入被保留
|
||||
expect(jsxCode).toContain('import React');
|
||||
|
||||
// 验证类型注解和类型导入被移除
|
||||
expect(jsxCode).not.toContain('FC<');
|
||||
expect(jsxCode).not.toContain('interface CounterProps');
|
||||
expect(jsxCode).not.toContain('<number>');
|
||||
expect(jsxCode).not.toContain(': void');
|
||||
|
||||
// 验证JSX结构被保留
|
||||
expect(jsxCode).toContain('<div className="counter">');
|
||||
expect(jsxCode).toContain('<Button type="primary"');
|
||||
|
||||
// 验证默认参数被保留
|
||||
expect(jsxCode).toContain('initialCount = 0');
|
||||
|
||||
console.log('\n\n转换前的 TSX 代码:');
|
||||
console.log(tsxCode);
|
||||
console.log('\n转换后的 JSX 代码:');
|
||||
console.log(jsxCode);
|
||||
});
|
||||
|
||||
// 转换复杂 TypeScript 代码
|
||||
it('应该能正确转换复杂TypeScript代码', () => {
|
||||
const complexJsCode = parseText(complexTsCode);
|
||||
|
||||
// 验证类型导入被移除
|
||||
expect(complexJsCode).not.toContain('import type');
|
||||
|
||||
// 验证泛型被移除
|
||||
expect(complexJsCode).not.toContain('<T>');
|
||||
expect(complexJsCode).not.toContain('<T extends string>');
|
||||
|
||||
// 验证类型别名和接口被移除
|
||||
expect(complexJsCode).not.toContain('type Status');
|
||||
expect(complexJsCode).not.toContain('interface DataItem');
|
||||
|
||||
// 验证函数和组件结构被保留
|
||||
expect(complexJsCode).toContain('function assertIsDataItem');
|
||||
expect(complexJsCode).toContain('const DataTable = ');
|
||||
expect(complexJsCode).toContain('const createDataItem = ');
|
||||
|
||||
// 验证JSX结构被保留
|
||||
expect(complexJsCode).toContain('<Table');
|
||||
expect(complexJsCode).toContain('<span style=');
|
||||
|
||||
// 验证空值合并运算符的处理
|
||||
expect(complexJsCode).toContain('_a = item.id'); // TypeScript会将 ?? 转换为更兼容的语法
|
||||
|
||||
console.log('\n\n转换前的复杂 TypeScript 代码:');
|
||||
console.log(complexTsCode);
|
||||
console.log('\n转换后的 JavaScript 代码:');
|
||||
console.log(complexJsCode);
|
||||
});
|
||||
});
|
75
.dumi/theme/utils/tsToJs.ts
Normal file
75
.dumi/theme/utils/tsToJs.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import * as ts from 'typescript';
|
||||
import { format } from '@prettier/sync';
|
||||
|
||||
/**
|
||||
* TypeScript 到 JavaScript 代码转换工具
|
||||
*
|
||||
* 这个模块用于将 TypeScript 代码(包括 TSX)转换为 JavaScript 代码(包括 JSX)
|
||||
* 主要用于替代 sylvanas 库的功能,用于文档站点中的代码示例转换
|
||||
*
|
||||
* 实现原理:使用 TypeScript 编译器 API 将 TS 代码转换为 JS 代码
|
||||
*
|
||||
* 特性:
|
||||
* 1. 删除所有类型注解
|
||||
* 2. 保留 JSX 语法
|
||||
* 3. 删除类型导入
|
||||
* 4. 转换 ES6+ 语法为更兼容的语法(如空值合并运算符)
|
||||
* 5. 保留原始代码风格和注释
|
||||
* 6. 使用 Prettier 格式化输出代码,提高可读性
|
||||
* 7. 处理 React 组件和 hooks 的转换
|
||||
* 8. 支持 TypeScript 特有语法(如装饰器、枚举等)的转换
|
||||
*
|
||||
* @param tsCode TypeScript 代码字符串
|
||||
* @returns 转换后的 JavaScript 代码
|
||||
*/
|
||||
export default function (tsCode: string): string {
|
||||
// 设置编译器选项,保留 JSX 语法
|
||||
const compilerOptions: ts.CompilerOptions = {
|
||||
target: ts.ScriptTarget.ES2016, // 目标 ECMAScript 版本
|
||||
module: ts.ModuleKind.ESNext, // 使用 ES 模块
|
||||
jsx: ts.JsxEmit.Preserve, // 保留 JSX 语法
|
||||
esModuleInterop: true, // 启用 ES 模块互操作性
|
||||
removeComments: false, // 保留注释
|
||||
isolatedModules: true, // 将每个文件视为单独模块
|
||||
declaration: false, // 不生成类型声明文件
|
||||
};
|
||||
|
||||
// 直接使用 TypeScript 编译器 API 进行转换
|
||||
const result = ts.transpileModule(tsCode, { compilerOptions });
|
||||
|
||||
try {
|
||||
// 使用 Prettier 同步格式化代码
|
||||
const formatted = format(result.outputText, {
|
||||
// Prettier 格式化选项
|
||||
parser: 'babel',
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
jsxSingleQuote: false,
|
||||
trailingComma: 'all',
|
||||
bracketSpacing: true,
|
||||
jsxBracketSameLine: false,
|
||||
arrowParens: 'avoid',
|
||||
});
|
||||
|
||||
return formatted;
|
||||
} catch (error) {
|
||||
// 如果格式化出错,返回未格式化的代码
|
||||
console.warn('Prettier 格式化出错:', error);
|
||||
return result.outputText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 TypeScript 代码转换为 JavaScript 代码
|
||||
*
|
||||
* 这是一个公开的 API,供测试和外部调用使用
|
||||
*
|
||||
* @param tsCode TypeScript 代码字符串
|
||||
* @returns 转换后的 JavaScript 代码
|
||||
*/
|
||||
export function parseText(tsCode: string): string {
|
||||
return exports.default(tsCode);
|
||||
}
|
@ -5,6 +5,7 @@ import os from 'node:os';
|
||||
|
||||
import rehypeAntd from './.dumi/rehypeAntd';
|
||||
import remarkAntd from './.dumi/remarkAntd';
|
||||
import remarkAnchor from './.dumi/remarkAnchor';
|
||||
import { version } from './package.json';
|
||||
|
||||
export default defineConfig({
|
||||
@ -52,7 +53,7 @@ export default defineConfig({
|
||||
'@ant-design/icons$': '@ant-design/icons/lib',
|
||||
},
|
||||
extraRehypePlugins: [rehypeAntd],
|
||||
extraRemarkPlugins: [remarkAntd],
|
||||
extraRemarkPlugins: [remarkAntd, remarkAnchor],
|
||||
metas: [
|
||||
{ name: 'theme-color', content: '#1677ff' },
|
||||
{ name: 'build-time', content: Date.now().toString() },
|
||||
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -25,6 +25,7 @@ Thank you!
|
||||
- [ ] ✅ Test Case
|
||||
- [ ] 🔀 Branch merge
|
||||
- [ ] ⏩ Workflow
|
||||
- [ ] ⌨️ Accessibility improvement
|
||||
- [ ] ❓ Other (about what?)
|
||||
|
||||
### 🔗 Related Issues
|
||||
|
1
.github/PULL_REQUEST_TEMPLATE_CN.md
vendored
1
.github/PULL_REQUEST_TEMPLATE_CN.md
vendored
@ -25,6 +25,7 @@
|
||||
- [ ] ✅ 测试用例
|
||||
- [ ] 🔀 分支合并
|
||||
- [ ] ⏩ 工作流程
|
||||
- [ ] ⌨️ 无障碍改进
|
||||
- [ ] ❓ 其他改动(是关于什么的改动?)
|
||||
|
||||
### 🔗 相关 Issue
|
||||
|
79
.github/workflows/auto-unassign.yml
vendored
79
.github/workflows/auto-unassign.yml
vendored
@ -1,79 +0,0 @@
|
||||
name: Auto Unassign
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # Run at 00:00 every day
|
||||
|
||||
permissions:
|
||||
issues: write # Need write permission to modify issue assignees
|
||||
|
||||
jobs:
|
||||
unassign_job:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Unassign inactive issues
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const daysBeforeUnassign = 14;
|
||||
let page = 1;
|
||||
const perPage = 100;
|
||||
|
||||
while (true) {
|
||||
const { data: issues } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
assignee: '*', // Filter assigned issues
|
||||
per_page: perPage,
|
||||
page: page,
|
||||
});
|
||||
|
||||
if (issues.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
|
||||
for (const issue of issues) {
|
||||
if (issue.pull_request) continue;
|
||||
|
||||
const updatedAt = new Date(issue.updated_at);
|
||||
const daysInactive = (now - updatedAt) / (1000 * 60 * 60 * 24);
|
||||
|
||||
const { data: timeline } = await github.rest.issues.listEventsForTimeline({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
});
|
||||
|
||||
const hasLinkedPR = timeline.some(event =>
|
||||
event.event === 'cross-referenced' && event.source?.issue?.pull_request || // PR referenced this issue
|
||||
event.event === 'connected' || // PR connected via keywords
|
||||
event.event === 'referenced' && event.commit_id // Connected via commit
|
||||
);
|
||||
|
||||
if (daysInactive >= daysBeforeUnassign && !hasLinkedPR) {
|
||||
const assigneesMentions = issue.assignees
|
||||
.map(user => `@${user.login}`)
|
||||
.join(' ');
|
||||
|
||||
// Remove assignees
|
||||
await github.rest.issues.removeAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
assignees: issue.assignees.map(user => user.login),
|
||||
});
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: `${assigneesMentions} Assignees have been automatically removed since no PR was linked to this issue within 14 days. Please contact maintainers for reassignment if you wish to continue working on this issue.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
page += 1;
|
||||
}
|
91
.github/workflows/issue-inactivity-reminder.yml
vendored
Normal file
91
.github/workflows/issue-inactivity-reminder.yml
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
name: Issue Inactivity Reminder
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # Run at 00:00 every day
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
reminder_job:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send reminders for inactive issues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const daysBeforeReminder = 14;
|
||||
let page = 1;
|
||||
const perPage = 100;
|
||||
|
||||
const reminderSignature = "This issue has been inactive for more than 14 days";
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const { data: issues } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
assignee: '*',
|
||||
per_page: perPage,
|
||||
page: page,
|
||||
});
|
||||
|
||||
if (issues.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
|
||||
for (const issue of issues) {
|
||||
if (issue.pull_request) continue;
|
||||
|
||||
const updatedAt = new Date(issue.updated_at);
|
||||
const daysInactive = (now - updatedAt) / (1000 * 60 * 60 * 24);
|
||||
|
||||
const { data: timeline } = await github.rest.issues.listEventsForTimeline({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
});
|
||||
|
||||
const hasLinkedPR = timeline.some(event =>
|
||||
event.event === 'connected' || // PR connected via keywords
|
||||
event.event === 'referenced' && event.commit_id // Connected via commit
|
||||
);
|
||||
|
||||
if (daysInactive >= daysBeforeReminder && !hasLinkedPR) {
|
||||
// get issue comments
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
});
|
||||
|
||||
// check if reminder has been sent
|
||||
const hasReminder = comments.some(comment =>
|
||||
comment.body.includes(reminderSignature)
|
||||
);
|
||||
|
||||
if (!hasReminder) {
|
||||
const assigneesMentions = issue.assignees
|
||||
.map(user => `@${user.login}`)
|
||||
.join(' ');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: `${assigneesMentions} 这个 issue 已经超过 14 天没有更新或关联 PR。如果您仍在处理这个 issue,请更新进度;如果您无法继续处理,请联系维护者重新分配。\n\nThis issue has been inactive for more than 14 days without any updates or linked PR. If you are still working on this issue, please provide a progress update. If you are unable to continue, please contact the maintainers for reassignment.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
page += 1;
|
||||
} catch (error) {
|
||||
console.error(`Error processing page ${page}:`, error);
|
||||
break;
|
||||
}
|
||||
}
|
4
.github/workflows/issue-labeled.yml
vendored
4
.github/workflows/issue-labeled.yml
vendored
@ -38,9 +38,9 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}. Please provide a online reproduction by forking [this one](https://u.ant.design/reproduce) or provide a minimal GitHub repository like [create-react-app-antd](https://github.com/ant-design/create-react-app-antd). Issues labeled by `Need Reproduce` will be closed if no activities in 3 days.
|
||||
Hello @${{ github.event.issue.user.login }}, We need you to provide an online reproduction example to help us investigate the issue. You can either fork this [online reproduction template](https://u.ant.design/reproduce) or create a minimal project repository using our [Scaffolding Guide](https://u.ant.design/guide). This issue will be automatically closed if no follow-up is made within 3 days.
|
||||
|
||||
你好 @${{ github.event.issue.user.login }},我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过 fork 这个[在线重现案例](https://u.ant.design/reproduce) ,或者提供一个最小化的 GitHub 仓库(类似 [create-react-app-antd](https://github.com/ant-design/create-react-app-antd))。3 天内未跟进此 issue 将会被自动关闭。
|
||||
你好 @${{ github.event.issue.user.login }},我们需要你提供一个在线的重现示例以便于我们帮你排查问题。你可以通过 fork 这个[在线重现模板](https://u.ant.design/reproduce),或者基于我们的[脚手架指南](https://u.ant.design/guide) 新建一个最小化项目仓库。3 天内未跟进此 issue 将会被自动关闭。
|
||||
|
||||
> [什么是最小化重现,为什么这是必需的?](https://github.com/ant-design/ant-design/wiki/%E4%BB%80%E4%B9%88%E6%98%AF%E6%9C%80%E5%B0%8F%E5%8C%96%E9%87%8D%E7%8E%B0%EF%BC%8C%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%99%E6%98%AF%E5%BF%85%E9%9C%80%E7%9A%84%EF%BC%9F)
|
||||
|
||||
|
2
.github/workflows/pr-check-merge.yml
vendored
2
.github/workflows/pr-check-merge.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
issues: write # for actions-cool/issues-helper to update issues
|
||||
pull-requests: write # for actions-cool/issues-helper to update PRs
|
||||
runs-on: ubuntu-latest
|
||||
if: (github.event.pull_request.head.ref == 'feature' || github.event.pull_request.head.ref == 'master') && github.event.pull_request.head.user.login == 'ant-design'
|
||||
if: (github.event.pull_request.head.ref == 'next' || github.event.pull_request.head.ref == 'feature' || github.event.pull_request.head.ref == 'master') && github.event.pull_request.head.user.login == 'ant-design'
|
||||
steps:
|
||||
- uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
|
4
.github/workflows/preview-deploy.yml
vendored
4
.github/workflows/preview-deploy.yml
vendored
@ -61,7 +61,7 @@ jobs:
|
||||
steps:
|
||||
# We need get PR id first
|
||||
- name: download pr artifact
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
@ -81,7 +81,7 @@ jobs:
|
||||
# Download site artifact
|
||||
- name: download site artifact
|
||||
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
@ -1,22 +1,19 @@
|
||||
name: 🐦 Release to Tweet
|
||||
name: 🆇 Release to X
|
||||
|
||||
on:
|
||||
create
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
tweet:
|
||||
if: ${{ github.event.ref_type == 'tag' && !contains(github.event.ref, 'alpha') }}
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.ref_type == 'tag' && !contains(github.event.ref, 'alpha') }}
|
||||
steps:
|
||||
- name: Tweet
|
||||
uses: nearform-actions/github-action-notify-twitter@v1
|
||||
uses: nearform-actions/github-action-notify-twitter@master
|
||||
with:
|
||||
message: |
|
||||
🤖 Ant Design just released antd@${{ github.event.ref }} ✨🎊✨ Check out the full release note: https://github.com/ant-design/ant-design/releases/tag/${{ github.event.ref }}
|
||||
twitter-app-key: ${{ secrets.TWITTER_API_KEY }}
|
||||
twitter-app-secret: ${{ secrets.TWITTER_API_SECRET_KEY }}
|
||||
twitter-app-secret: ${{ secrets.TWITTER_API_SECRET }}
|
||||
twitter-access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
|
||||
twitter-access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
|
2
.github/workflows/site-deploy.yml
vendored
2
.github/workflows/site-deploy.yml
vendored
@ -117,7 +117,7 @@ jobs:
|
||||
cd ..
|
||||
|
||||
- name: Upload to Release
|
||||
uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
|
||||
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
files: website.tar.gz
|
||||
|
233
.github/workflows/test-v6.yml
vendored
Normal file
233
.github/workflows/test-v6.yml
vendored
Normal file
@ -0,0 +1,233 @@
|
||||
# Origin Source
|
||||
# https://github.com/ant-design/ant-design/blob/79f566b7f8abb1012ef55b0d2793bfdf5595b85d/.github/workflows/test.yml
|
||||
name: ✅ test v6
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [next]
|
||||
pull_request:
|
||||
branches: [next]
|
||||
|
||||
# Cancel prev CI if new commit come
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- run: bun run lint
|
||||
|
||||
################################ Test ################################
|
||||
test-react-legacy:
|
||||
name: test-react-legacy
|
||||
strategy:
|
||||
matrix:
|
||||
react: ['18']
|
||||
shard: [1/2, 2/2]
|
||||
env:
|
||||
REACT: ${{ matrix.react }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- name: install react 18
|
||||
if: ${{ matrix.react == '18' }}
|
||||
run: bun run bun-install-react-18
|
||||
# dom test
|
||||
- name: dom test
|
||||
run: bun run test -- --maxWorkers=2 --shard=${{matrix.shard}}
|
||||
|
||||
test-node:
|
||||
name: test-node
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- run: bun run test:node
|
||||
|
||||
test-react-latest:
|
||||
name: test-react-latest
|
||||
strategy:
|
||||
matrix:
|
||||
module: [dom]
|
||||
shard: [1/2, 2/2]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
|
||||
# dom test
|
||||
- name: dom test
|
||||
run: bun run test -- --maxWorkers=2 --shard=${{matrix.shard}} --coverage
|
||||
|
||||
- name: persist coverages
|
||||
run: |
|
||||
mkdir persist-coverage
|
||||
mv coverage/coverage-final.json persist-coverage/react-test-${{matrix.module}}-${{strategy.job-index}}.json
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: upload coverages
|
||||
with:
|
||||
name: coverage-artifacts-${{ matrix.module }}-${{ strategy.job-index }}
|
||||
path: persist-coverage/
|
||||
|
||||
test-react-latest-dist:
|
||||
name: test-react-latest-dist
|
||||
strategy:
|
||||
matrix:
|
||||
module: [dist, dist-min]
|
||||
shard: [1/2, 2/2]
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
|
||||
- name: restore cache from dist
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: dist
|
||||
key: dist-${{ github.sha }}
|
||||
|
||||
- name: dist-min test
|
||||
if: ${{ matrix.module == 'dist-min' }}
|
||||
run: bun run test
|
||||
env:
|
||||
LIB_DIR: dist-min
|
||||
|
||||
- name: dist test
|
||||
if: ${{ matrix.module == 'dist' }}
|
||||
run: bun run test
|
||||
env:
|
||||
LIB_DIR: dist
|
||||
|
||||
############################ Test Coverage ###########################
|
||||
upload-test-coverage:
|
||||
name: test-coverage
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-react-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: coverage-artifacts-*
|
||||
merge-multiple: true
|
||||
path: persist-coverage
|
||||
- name: Merge Code Coverage
|
||||
run: |
|
||||
bunx nyc merge persist-coverage/ coverage/coverage-final.json
|
||||
bunx nyc report --reporter text -t coverage --report-dir coverage
|
||||
rm -rf persist-coverage
|
||||
- name: Upload coverage to codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
# use own token to upload coverage reports
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
########################### Compile & Test ###########################
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
|
||||
- name: cache lib
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: lib
|
||||
key: lib-${{ github.sha }}
|
||||
|
||||
- name: cache es
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: es
|
||||
key: es-${{ github.sha }}
|
||||
|
||||
- name: compile
|
||||
run: bun run compile
|
||||
|
||||
- name: cache dist
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: dist
|
||||
key: dist-${{ github.sha }}
|
||||
|
||||
- name: dist
|
||||
run: bun run dist
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=4096
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
CI: 1
|
||||
|
||||
- name: check build files
|
||||
run: bun run test:dekko
|
||||
|
||||
# Artifact build files
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
with:
|
||||
name: build artifacts
|
||||
path: |
|
||||
dist
|
||||
locale
|
||||
es
|
||||
lib
|
||||
|
||||
- name: zip builds
|
||||
if: github.repository == 'ant-design/ant-design' && github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
env:
|
||||
ALI_OSS_AK_ID: ${{ secrets.ALI_OSS_AK_ID }}
|
||||
ALI_OSS_AK_SECRET: ${{ secrets.ALI_OSS_AK_SECRET }}
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
zip -r oss-artifacts.zip dist locale es lib
|
||||
echo "🤖 Uploading"
|
||||
node scripts/visual-regression/upload.js ./oss-artifacts.zip --ref=$HEAD_SHA
|
||||
|
||||
test-lib-es:
|
||||
name: test lib/es module
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
module: [lib, es]
|
||||
shard: [1/2, 2/2]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
|
||||
- name: restore cache from ${{ matrix.module }}
|
||||
# lib only run in master branch not in pull request
|
||||
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ matrix.module }}
|
||||
key: ${{ matrix.module }}-${{ github.sha }}
|
||||
|
||||
- name: compile
|
||||
# lib only run in master branch not in pull request
|
||||
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
|
||||
run: bun run compile
|
||||
|
||||
- name: test
|
||||
# lib only run in master branch not in pull request
|
||||
if: ${{ github.event_name != 'pull_request' || matrix.module != 'lib' }}
|
||||
run: bun run test -- --maxWorkers=2 --shard=${{matrix.shard}}
|
||||
env:
|
||||
LIB_DIR: ${{ matrix.module }}
|
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@ -2,7 +2,11 @@
|
||||
# https://github.com/ant-design/ant-design/blob/79f566b7f8abb1012ef55b0d2793bfdf5595b85d/.github/workflows/test.yml
|
||||
name: ✅ test
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches: [master, feature]
|
||||
pull_request:
|
||||
branches: [master, feature]
|
||||
|
||||
# Cancel prev CI if new commit come
|
||||
concurrency:
|
||||
@ -175,7 +179,7 @@ jobs:
|
||||
CI: 1
|
||||
|
||||
- name: check build files
|
||||
run: node ./tests/dekko/index.test.js
|
||||
run: bun run test:dekko
|
||||
|
||||
# Artifact build files
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
1
.github/workflows/verify-files-modify.yml
vendored
1
.github/workflows/verify-files-modify.yml
vendored
@ -9,6 +9,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
if: github.event.pull_request.user.login != 'renovate[bot]'
|
||||
permissions:
|
||||
pull-requests: write # for actions-cool/verify-files-modify to update status of PRs
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -4,7 +4,7 @@ name: 👀 Visual Regression Diff Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master, feature]
|
||||
branches: [master, feature, next]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
# Cancel prev CI if new commit come
|
||||
|
@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
# We need get persist-index first
|
||||
- name: download image snapshot artifact
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
@ -90,7 +90,7 @@ jobs:
|
||||
- name: download report artifact
|
||||
id: download_report
|
||||
if: ${{ needs.upstream-workflow-summary.outputs.build-status == 'success' || needs.upstream-workflow-summary.outputs.build-status == 'failure' }}
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
@ -166,7 +166,7 @@ jobs:
|
||||
});
|
||||
const prHeadSha = prResponse.data.head.sha;
|
||||
|
||||
const fullContent = `${{ toJSON(steps.report.outputs.content) }}`;
|
||||
const fullContent = ${{ toJSON(steps.report.outputs.content) }};
|
||||
const hasVisualDiffSuccess = fullContent.includes('VISUAL_DIFF_SUCCESS');
|
||||
const hasVisualDiffFailed = fullContent.includes('VISUAL_DIFF_FAILED');
|
||||
|
||||
|
@ -8,7 +8,7 @@ name: 👀 Visual Regression Diff Start
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [master, feature]
|
||||
branches: [master, feature, next]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
|
@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
# We need get persist key first
|
||||
- name: Download Visual Regression Ref
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
- name: Download Visual-Regression Artifact
|
||||
if: ${{ fromJSON(needs.upstream-workflow-summary.outputs.build-success) }}
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
@ -87,7 +87,7 @@ jobs:
|
||||
path: ./tmp
|
||||
|
||||
- name: Persist Image Snapshot to OSS
|
||||
if: github.repository == 'ant-design/ant-design' && github.event.workflow_run.event == 'push' && (github.event.workflow_run.head_branch == 'master' || github.event.workflow_run.head_branch == 'feature')
|
||||
if: github.repository == 'ant-design/ant-design' && github.event.workflow_run.event == 'push' && (github.event.workflow_run.head_branch == 'master' || github.event.workflow_run.head_branch == 'feature' || github.event.workflow_run.head_branch == 'next')
|
||||
env:
|
||||
ALI_OSS_AK_ID: ${{ secrets.ALI_OSS_AK_ID }}
|
||||
ALI_OSS_AK_SECRET: ${{ secrets.ALI_OSS_AK_SECRET }}
|
||||
|
@ -7,6 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- feature
|
||||
- next
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -68,7 +68,6 @@ __image_snapshots__/
|
||||
/imageDiffSnapshots
|
||||
/visualRegressionReport*
|
||||
|
||||
.devcontainer*
|
||||
.husky/prepare-commit-msg
|
||||
|
||||
.eslintcache
|
||||
|
2
.husky/pre-commit
Executable file → Normal file
2
.husky/pre-commit
Executable file → Normal file
@ -1 +1 @@
|
||||
lint-staged
|
||||
lint-staged
|
1
.jest.js
1
.jest.js
@ -5,6 +5,7 @@ const compileModules = [
|
||||
'@ant-design',
|
||||
'countup.js',
|
||||
'.pnpm',
|
||||
'@asamuzakjp/css-color',
|
||||
];
|
||||
|
||||
const ignoreList = [];
|
||||
|
14
.ncurc.js
14
.ncurc.js
@ -18,17 +18,5 @@ module.exports = {
|
||||
return check.some((prefix) => name.startsWith(prefix));
|
||||
},
|
||||
// https://github.com/raineorshine/npm-check-updates#target
|
||||
target: (name, semver) => {
|
||||
const { operator } = semver[0] ?? {};
|
||||
|
||||
// rc-component
|
||||
if (rcOrg.some((prefix) => name.startsWith(prefix))) {
|
||||
// `^` always upgrade latest, otherwise follow semver.
|
||||
if (operator === '^') {
|
||||
return 'latest';
|
||||
}
|
||||
}
|
||||
|
||||
return 'semver';
|
||||
},
|
||||
target: () => `semver`,
|
||||
};
|
||||
|
@ -74,5 +74,6 @@
|
||||
"5.22.1": [
|
||||
"https://github.com/ant-design/ant-design/issues/51420",
|
||||
"https://github.com/ant-design/ant-design/issues/51430"
|
||||
]
|
||||
],
|
||||
"5.22.6": ["https://github.com/ant-design/ant-design/issues/52124"]
|
||||
}
|
||||
|
@ -15,6 +15,253 @@ tag: vVERSION
|
||||
|
||||
---
|
||||
|
||||
## 5.24.7
|
||||
|
||||
`2025-04-14`
|
||||
|
||||
- 🐞 Fix Input causing incorrect Popover positioning when a `suffix` is present. [#53475](https://github.com/ant-design/ant-design/pull/53475)
|
||||
- 🐞 Fix Table filter menu selection state loss when `column.filterDropdown` is set to `undefined`. [#53421](https://github.com/ant-design/ant-design/pull/53421)
|
||||
- 🇨🇳 ColorPicker add `zh_HK` `zh_TW` locales. [#53440](https://github.com/ant-design/ant-design/pull/53440) [@mjsong07](https://github.com/mjsong07)
|
||||
|
||||
## 5.24.6
|
||||
|
||||
`2025-04-01`
|
||||
|
||||
- 🐞 Fix Modal show loading with async call, still can close with click outer space. [#53227](https://github.com/ant-design/ant-design/pull/53227) [@jin19980928](https://github.com/jin19980928)
|
||||
- 🐞 Fix when Table `size` is `small`, the theme config will take effect on the sub Pagination. [#52829](https://github.com/ant-design/ant-design/pull/52829) [@Can-Chen](https://github.com/Can-Chen)
|
||||
|
||||
## 5.24.5
|
||||
|
||||
`2025-03-24`
|
||||
|
||||
- 🐞 Fixed the issue that the suffix of InputNumber moves left after the mouse enters when it is disabled. [#53184](https://github.com/ant-design/ant-design/pull/53184) [@yellowryan](https://github.com/yellowryan)
|
||||
- 💄 Fix Form syntax errors of style selector. [#53236](https://github.com/ant-design/ant-design/pull/53236) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 💄 Refactor TextArea resize logic when set `resize: both` style to fit with React life cycle. [#53235](https://github.com/ant-design/ant-design/pull/53235) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🇮🇷 Add missing translations and fix typos for Farsi language (fa_IR). [#53251](https://github.com/ant-design/ant-design/pull/53251) [@AliReza-Kamkar](https://github.com/AliReza-Kamkar)
|
||||
|
||||
## 5.24.4
|
||||
|
||||
`2025-03-17`
|
||||
|
||||
- 🐞 Fix Input.TextArea width synchronization issue during resizing. [#53024](https://github.com/ant-design/ant-design/pull/53024) [@triyys](https://github.com/triyys)
|
||||
- 🐞 Fix Typography type color not follow `color[Status]Text` instead of `color[Status]`. [#53086](https://github.com/ant-design/ant-design/pull/53086) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Fix Affix abnormal `onChange` event behavior in React versions below 18. [#53038](https://github.com/ant-design/ant-design/pull/53038) [@waiter](https://github.com/waiter)
|
||||
- 🐞 Fix Form `requiredMark` not working when component is false. [#52950](https://github.com/ant-design/ant-design/pull/52950) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 🇹🇷 Add Turkish (tr_TR) localization support for the Tour component. [#53117](https://github.com/ant-design/ant-design/pull/53117) [@hakankosdag](https://github.com/hakankosdag)
|
||||
|
||||
## 5.24.3
|
||||
|
||||
`2025-03-05`
|
||||
|
||||
- Input
|
||||
- 🐞 Fix the next element was not correctly selected after pressing the Tab key when `allowClear` was turned on for Input. [#52977](https://github.com/ant-design/ant-design/pull/52977) [@wanpan11](https://github.com/wanpan11)
|
||||
- 💄 Fix the border display issue when hovering in the `disabled` state when Input has `variant="underlined"` turned on. [#52959](https://github.com/ant-design/ant-design/pull/52959) [@ustcfury](https://github.com/ustcfury)
|
||||
- 💄 Fix DatePicker header buttons misalignment caused by unexpected spacing. [#53007](https://github.com/ant-design/ant-design/pull/53007) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 💄 Fix AutoComplete input text not centered when `size="large"`. [#52819](https://github.com/ant-design/ant-design/pull/52819) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🇩🇪 Add missing `de_DE` translations for Transfer. [#53047](https://github.com/ant-design/ant-design/pull/53047) [@chrisinick](https://github.com/chrisinick)
|
||||
|
||||
## 5.24.2
|
||||
|
||||
`2025-02-24`
|
||||
|
||||
- Input
|
||||
- 🐞 Fix Input with component token `inputFontSize` breaks the height of `controlHeight`. [#52865](https://github.com/ant-design/ant-design/pull/52865) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Fix Input.Search has a border that is not aligned with the bottom of the search button when configure `variable` as `underlined`. [#52861](https://github.com/ant-design/ant-design/pull/52861) [@ustcfury](https://github.com/ustcfury)
|
||||
- 🛠 Improve Input.OTP logic for create default state. [#52878](https://github.com/ant-design/ant-design/pull/52878) [@Dandelion-F](https://github.com/Dandelion-F)
|
||||
- 🛠 Improve Input.OTP implementation for render separator. [#52841](https://github.com/ant-design/ant-design/pull/52841) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- Watermark
|
||||
- 🐞 Fix Watermark may cause page unresponsive when re-rendering. [#52897](https://github.com/ant-design/ant-design/pull/52897) [@765477020](https://github.com/765477020)
|
||||
- 🆕 Improve Watermark rendering logic to avoid disable it via developer tools and `hidden` attribute. [#52891](https://github.com/ant-design/ant-design/pull/52891) [@arronlai](https://github.com/arronlai)
|
||||
- 🐞 Fix DatePicker.RangePicker arrow position not correctly when sometime reopened. [#52854](https://github.com/ant-design/ant-design/pull/52854) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Fix Layout.Sider content overflow issue when `collapsedWidth={0}`. [#52862](https://github.com/ant-design/ant-design/pull/52862) [@afc163](https://github.com/afc163)
|
||||
- 🛠 Refactor Grid internal useBreakpoint logic to be same as other component, this will not affect usage. [#52870](https://github.com/ant-design/ant-design/pull/52870) [@zombieJ](https://github.com/zombieJ)
|
||||
- 💄 Fix Button styles for hyperlink mode. [#52888](https://github.com/ant-design/ant-design/pull/52888) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 💄 Fix Table sortable column headers could not wrap automatically. [#52899](https://github.com/ant-design/ant-design/pull/52899) [@765477020](https://github.com/765477020)
|
||||
- ⚡️ Improve Menu re-rendering performance when pass function to `expandIcon` property. [#52863](https://github.com/ant-design/ant-design/pull/52863) [@wanpan11](https://github.com/wanpan11)
|
||||
- ⚡️ Improve Carousel indicator animation performance. [#52881](https://github.com/ant-design/ant-design/pull/52881) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- RTL
|
||||
- 💄 Fix DatePicker wrong icon direction for RTL mode. [#52896](https://github.com/ant-design/ant-design/pull/52896) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 💄 Fix Dropdown wrong arrow direction of multi-level menu for RTL mode. [#52885](https://github.com/ant-design/ant-design/pull/52885) [@yellowryan](https://github.com/yellowryan)
|
||||
|
||||
## 5.24.1
|
||||
|
||||
`2025-02-17`
|
||||
|
||||
- 🐞 Fix Button with `color` to be `primary` and `variant` to be `text` or `link` will not use correct color. [#52812](https://github.com/ant-design/ant-design/pull/52812) [@zombieJ](https://github.com/zombieJ)
|
||||
- 💄 Fix Input.Group & Input.OTP style issues caused by undefined CSS variables. [#52799](https://github.com/ant-design/ant-design/pull/52799) [@afc163](https://github.com/afc163)
|
||||
- 🐞 Fix DatePicker with long content `prefix` breaks the layout. [#52776](https://github.com/ant-design/ant-design/pull/52776) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🐞 Fix Table title missing `aria-label` when sorting. [#52772](https://github.com/ant-design/ant-design/pull/52772) [@mellis481](https://github.com/mellis481)
|
||||
- 🐞 Fix Alert.ErrorBoundary type error when used as a JSX component with `@types/react@18.x`. [#52766](https://github.com/ant-design/ant-design/pull/52766) [@afc163](https://github.com/afc163)
|
||||
- 💄 Fix Segmented `shape` not working with `size`. [#52757](https://github.com/ant-design/ant-design/pull/52757) [@yellowryan](https://github.com/yellowryan)
|
||||
|
||||
## 5.24.0
|
||||
|
||||
`2025-02-11`
|
||||
|
||||
- 🆕 Notification support `actions` prop and deprecated `btn` prop. [#52703](https://github.com/ant-design/ant-design/pull/52703) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 Carousel support show dot duration. [#52672](https://github.com/ant-design/ant-design/pull/52672) [@yellowryan](https://github.com/yellowryan)
|
||||
- 🆕 Input.OTP support `separator` prop. [#52668](https://github.com/ant-design/ant-design/pull/52668) [@HaceraI](https://github.com/HaceraI)
|
||||
- 🆕 Descriptions add `labelColor` component token. [#52700](https://github.com/ant-design/ant-design/pull/52700) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🆕 Segmented supports `shape="round"`. [#52685](https://github.com/ant-design/ant-design/pull/52685) [@afc163](https://github.com/afc163)
|
||||
- 🆕 ConfigProvider support `variant` for Card. [#52552](https://github.com/ant-design/ant-design/pull/52552) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 Progress/Step supports custom rounding with `rounding` prop. [#52017](https://github.com/ant-design/ant-design/pull/52017) [@yanghoxom](https://github.com/yanghoxom)
|
||||
- 🆕 Divider `orientation` support `start` and `end`. [#52567](https://github.com/ant-design/ant-design/pull/52567) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🆕 Add `underlined` to `variant` of Input, InputNumber, Mentions, Form, Select, Cascader, TreeSelect, DatePicker and TimePicker. [#52546](https://github.com/ant-design/ant-design/pull/52546) [@ustcfury](https://github.com/ustcfury)
|
||||
- 🆕 ConfigProvider support global config of Modal `centered` . [#52343](https://github.com/ant-design/ant-design/pull/52343) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🆕 Add `label` class name for Checkbox and Radio. [#52322](https://github.com/ant-design/ant-design/pull/52322) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🐞 Fix Tooltip/Popover/Popconfirm/Dropdown misaligned popup positions with custom children in React 19. [react-component/util#623](https://github.com/react-component/util/pull/623) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Fix DatePicker.RangePicker arrow position when popup auto adjust position. [#52719](https://github.com/ant-design/ant-design/pull/52719) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Update locale `filterCheckall` to `filterCheckAll`. [#52517](https://github.com/ant-design/ant-design/pull/52517) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🐞 Fix Form that `scrollToField` and `scrollToFirstError` cannot focus components of antd. [#52726](https://github.com/ant-design/ant-design/pull/52726) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 💄 Fix Button shadow color appearing awkward on dark backgrounds. [#52701](https://github.com/ant-design/ant-design/pull/52701) [@afc163](https://github.com/afc163)
|
||||
- 💄 Fixed the unnatural animation transition effect of Segmented component in dark mode. [#52708](https://github.com/ant-design/ant-design/pull/52708) [@yellowryan](https://github.com/yellowryan)
|
||||
- 💄 Separate style of Input and TextArea. [#52570](https://github.com/ant-design/ant-design/pull/52570) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 💄 Fix Input and Select style issue under css var mode. [#52554](https://github.com/ant-design/ant-design/pull/52554) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- ⌨️ Remove role="alert" from Form field error to improve screen reader experience. [#52661](https://github.com/ant-design/ant-design/pull/52661) [@mellis481](https://github.com/mellis481)
|
||||
- ⌨️ Improve accessibility by adding localized labels for empty table header and panel buttons. [#52689](https://github.com/ant-design/ant-design/pull/52689) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- ⌨️ Improve Tabs accessibility by fixing error `Certain ARIA roles must contain particular children`. [#52677](https://github.com/ant-design/ant-design/pull/52677) [@afc163](https://github.com/afc163)
|
||||
- ⌨️ Add screen reader support for Tooltip. [#52293](https://github.com/ant-design/ant-design/pull/52293) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- TypeScript
|
||||
- 🤖 Separate type of Button `onClick` event by `href`. [#52654](https://github.com/ant-design/ant-design/pull/52654) [@Brew-Brew](https://github.com/Brew-Brew)
|
||||
- 🤖 Deprecate Button.Group, prefer Space.Compact. [#52572](https://github.com/ant-design/ant-design/pull/52572) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🤖 Deprecate Input.Group, prefer Space.Compact. [#52571](https://github.com/ant-design/ant-design/pull/52571) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🤖 Tooltip export TooltipRef type. [#49230](https://github.com/ant-design/ant-design/pull/49230) [@nuintun](https://github.com/nuintun)
|
||||
|
||||
## 5.23.4
|
||||
|
||||
`2025-02-05`
|
||||
|
||||
First release in the Year of the Snake, wishing you a prosperous start! 🐍
|
||||
|
||||
- 🐞 Fixed Pagination accessibility issue by supplementing missing ARIA attributes support. [#52616](https://github.com/ant-design/ant-design/pull/52616) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🐞 Added TextArea component support in `Space.Compact`. [#52639](https://github.com/ant-design/ant-design/pull/52639) [@Can-Chen](https://github.com/Can-Chen)
|
||||
- 🐞 Fixed Menu with `theme="dark"` and `mode="horizontal"` identical text/background color issue. [#52617](https://github.com/ant-design/ant-design/pull/52617) [@afc163](https://github.com/afc163)
|
||||
- 🇦🇪 Add Tour Arabic translation. [#52642](https://github.com/ant-design/ant-design/pull/52642) [@Sagie501](https://github.com/Sagie501)
|
||||
- 🇮🇱 Add Tour Hebrew translation. [#52641](https://github.com/ant-design/ant-design/pull/52641) [@Sagie501](https://github.com/Sagie501)
|
||||
|
||||
## 5.23.3
|
||||
|
||||
`2025-01-28`
|
||||
|
||||
Last version of the Dragon Year, Happy Chinese New Year! 🐲
|
||||
|
||||
- ⌨️ MISC: Add accessibility tests for all component demos to ensure compliance with accessibility standards. Optimize accessibility support for some components, improving compatibility with screen readers and keyboard operations. [#51372](https://github.com/ant-design/ant-design/pull/51372) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🐞 MISC: Fix importing `antd/dist/reset.css` file issue. [#52559](https://github.com/ant-design/ant-design/pull/52559) [@CaptainVolcom](https://github.com/CaptainVolcom)
|
||||
- 🐞 Fix Button throwing error when `loading` is `null`. [#52508](https://github.com/ant-design/ant-design/pull/52508) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🐞 Fix Table last row border color transition issue. [#52549](https://github.com/ant-design/ant-design/pull/52549) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 💄 Fix Cascader checkbox cursor style in disabled state. [#52539](https://github.com/ant-design/ant-design/pull/52539) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 💄 Fix ConfigProvider not correctly modifying icon style priority when StyleProvider configures `layer`. [#52533](https://github.com/ant-design/ant-design/pull/52533) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Fix Layout sidebar toggle button style missing in non-cssVar mode. [#52537](https://github.com/ant-design/ant-design/pull/52537) [@afc163](https://github.com/afc163)
|
||||
- 🐞 Fix Tree checkbox cursor style in disabled state. [#52525](https://github.com/ant-design/ant-design/pull/52525) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- notification
|
||||
- 🐞 Fix notification `useNotification` `closeIcon` configuration not working. [#52516](https://github.com/ant-design/ant-design/pull/52516) [@typenoob](https://github.com/typenoob)
|
||||
- 🐞 Fix notification component display flicker issue under App component. [#52499](https://github.com/ant-design/ant-design/pull/52499) [@afc163](https://github.com/afc163)
|
||||
- RTL
|
||||
- 🐞 Fix Splitter abnormal collapse behavior in RTL mode. [#52501](https://github.com/ant-design/ant-design/pull/52501) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 💄 Fix Spin style issue in RTL mode. [#52538](https://github.com/ant-design/ant-design/pull/52538) [@afc163](https://github.com/afc163)
|
||||
|
||||
## 5.23.2
|
||||
|
||||
`2025-01-20`
|
||||
|
||||
- 🐞 Fix Space.Compact throwing `Should not use more than one & in a selector` warning. [#52489](https://github.com/ant-design/ant-design/pull/52489)
|
||||
- 💄 Fix the Layout switching sidebar button style was lost. [#52477](https://github.com/ant-design/ant-design/pull/52477)
|
||||
- 💄 Fix the scroll bar height is not 0 when the first row of the virtual scroll Table is collapsed. [#52447](https://github.com/ant-design/ant-design/pull/52447) [@LeeSSHH](https://github.com/LeeSSHH)
|
||||
- 💄 Fix the last item in Descriptions did not correctly fill the remaining space. [#52410](https://github.com/ant-design/ant-design/pull/52410) [@anyuxuan](https://github.com/anyuxuan)
|
||||
- 💄 Fix extra margin for the last item in Radio. [#52433](https://github.com/ant-design/ant-design/pull/52433)
|
||||
- 💄 Fix the Input/Mentions clear button padding was incorrect. [#52407](https://github.com/ant-design/ant-design/pull/52407) [@ustcfury](https://github.com/ustcfury)
|
||||
- 💄 Fix rounded corners of `addonAfter` in Input compact mode. [#52490](https://github.com/ant-design/ant-design/pull/52490) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 💄 Fix Menu.Item links were still clickable and lacked disabled styles when in disabled state. [#52402](https://github.com/ant-design/ant-design/pull/52402) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- TypeScript
|
||||
- 🤖 MISC: Optimize PurePanel to use React.ComponentType type. [#52480](https://github.com/ant-design/ant-design/pull/52480)
|
||||
- 🤖 Fix missing token type for Skeleton and Rate. [#52406](https://github.com/ant-design/ant-design/pull/52406) [@coding-ice](https://github.com/coding-ice)
|
||||
|
||||
## 5.23.1
|
||||
|
||||
`2025-01-13`
|
||||
|
||||
- 🆕 Add Tree leaf node className for differentiate node type. [#52274](https://github.com/ant-design/ant-design/pull/52274) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
|
||||
- 🐞 Fix DatePicker switch buttons is not hidden when `superPrevIcon/superNextIcon/prevIcon/nextIcon` is null. [#52327](https://github.com/ant-design/ant-design/pull/52327) [@afc163](https://github.com/afc163)
|
||||
- 🐞 Fix Select throws `error not a valid selector` in Jest tests. [#51844](https://github.com/ant-design/ant-design/pull/51844) [@renovate](https://github.com/renovate)
|
||||
- 🐞 Fix Layout.Sider under ConfigProvider directly, the `theme` not working. [#52302](https://github.com/ant-design/ant-design/pull/52302) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Fix Splitter lost previous state when re-expanding. [#52222](https://github.com/ant-design/ant-design/pull/52222) [@jjlstruggle](https://github.com/jjlstruggle)
|
||||
- 🐞 Fix Table unexpected row selections when set `checkStrictly` to false in tree mode. [#52338](https://github.com/ant-design/ant-design/pull/52338) [@LeeSSHH](https://github.com/LeeSSHH)
|
||||
- Button
|
||||
- 🐞 Fix Button alignment and icon centering by adjusting the icon size for icon-only Buttons. [#52353](https://github.com/ant-design/ant-design/pull/52353) [@afc163](https://github.com/afc163)
|
||||
- 💄 Fix Button missing `box-shadow` style. [#52304](https://github.com/ant-design/ant-design/pull/52304) [@zombieJ](https://github.com/zombieJ)
|
||||
- RTL
|
||||
- 💄 Fix Collapse arrow direction in RTL mode. [#52374](https://github.com/ant-design/ant-design/pull/52374) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 💄 Fix Layout.Sider arrow direction in RTL mode. [#52374](https://github.com/ant-design/ant-design/pull/52374) [@aojunhao123](https://github.com/aojunhao123)
|
||||
|
||||
## 5.23.0
|
||||
|
||||
`2025-01-06`
|
||||
|
||||
- 🔥 TreeSelect support `maxCount` to limit the maximum number of selections. [#51759](https://github.com/ant-design/ant-design/pull/51759) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🔥 Modal `width` support responsive size. [#51653](https://github.com/ant-design/ant-design/pull/51653) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🔥 Splitter support `lazy` mode. [#51557](https://github.com/ant-design/ant-design/pull/51557) [@OysterD3](https://github.com/OysterD3)
|
||||
- Button
|
||||
- 🔥 Button `color` support full color palette. [#51550](https://github.com/ant-design/ant-design/pull/51550) [@OysterD3](https://github.com/OysterD3)
|
||||
<img width="520" alt="Button Colors" src="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*ApyYQpXQQfgAAAAAAAAAAAAADgCCAQ/original">
|
||||
- 🆕 Button support `loading={{ icon: ReactNode }}` to customize loading icon. [#51758](https://github.com/ant-design/ant-design/pull/51758) [@zhangchao-wooc](https://github.com/zhangchao-wooc)
|
||||
- Menu
|
||||
- 🐞 Fix Menu `extra` font size and vertical align issue. [#52217](https://github.com/ant-design/ant-design/pull/52217) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🆕 Menu add token `subMenuItemSelectedColor` to resolve submenu title color being overrided by `itemSelectedColor`. [#52182](https://github.com/ant-design/ant-design/pull/52182) [@afc163](https://github.com/afc163)
|
||||
- 🆕 Semantic Props
|
||||
- 🆕 ConfigProvider support Empty semantic props `classNames` and `styles`. [#52208](https://github.com/ant-design/ant-design/pull/52208) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ConfigProvider support Popconfirm semantic props `classNames` and `styles`. [#52126](https://github.com/ant-design/ant-design/pull/52126) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ConfigProvider support Popover semantic props `classNames` and `styles`. [#52110](https://github.com/ant-design/ant-design/pull/52110) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ConfigProvider support Tooltip semantic props `classNames` and `styles`. [#51872](https://github.com/ant-design/ant-design/pull/51872) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ConfigProvider support Descriptions semantic props `classNames` and `styles`. [#52120](https://github.com/ant-design/ant-design/pull/52120) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ConfigProvider support Slider semantic props `classNames` and `styles`. [#52185](https://github.com/ant-design/ant-design/pull/52185) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 Transfer support `showSearch` config `defaultValue` & `placeholder`. [#52125](https://github.com/ant-design/ant-design/pull/52125) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
|
||||
- 🆕 Calendar now supports `showWeek` prop. [#52072](https://github.com/ant-design/ant-design/pull/52072) [@afc163](https://github.com/afc163)
|
||||
- 🆕 Mentions support `onPopupScroll` props. [#51858](https://github.com/ant-design/ant-design/pull/51858) [@OysterD3](https://github.com/OysterD3)
|
||||
- 🆕 Card support `bodyPaddingSM`, `headerPaddingSM`, `bodyPadding`, `headerPadding` component token. [#51762](https://github.com/ant-design/ant-design/pull/51762) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ColorPicker `presets` support `key` prop. [#51794](https://github.com/ant-design/ant-design/pull/51794) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🆕 Cascader support `optionSelectedColor` token. [#51769](https://github.com/ant-design/ant-design/pull/51769) [@thinkasany](https://github.com/thinkasany)
|
||||
- Tree
|
||||
- 🛠 Refactor Tree part code to Function Component for React 19 perf preparing. [#52209](https://github.com/ant-design/ant-design/pull/52209) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 💄 Optimize Tree `disabled` & `selected` node display style. [#52173](https://github.com/ant-design/ant-design/pull/52173) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
|
||||
- 🐞 Fix Slider crash when `tipFormatter` is undefined. [#52184](https://github.com/ant-design/ant-design/pull/52184) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🐞 Fix Layout.Sider `trigger` style not correct. [#46a8eff](https://github.com/ant-design/ant-design/commit/46a8eff) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- Table
|
||||
- 🐞 Fix Table `fixed:right` is not working in `expandable`. [#52176](https://github.com/ant-design/ant-design/pull/52176) [@afc163](https://github.com/afc163)
|
||||
- 🐞 Fix Table sticky scrollbar not working in rtl direction. [#52176](https://github.com/ant-design/ant-design/pull/52176) [@afc163](https://github.com/afc163)
|
||||
- 💄 Optimize Flex to always reset `margin` & `padding` for customize component. [#52170](https://github.com/ant-design/ant-design/pull/52170) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🐞 Fix DatePicker.RangePicker `needConfirm` sometime can switch panel without confirm. [#52102](https://github.com/ant-design/ant-design/pull/52102) [@Zyf665](https://github.com/Zyf665)
|
||||
- 💄 Optimize Collapse focus styles and items border radius. [#52086](https://github.com/ant-design/ant-design/pull/52086) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- ⌨️ Add Radio.Group default `name` prop to improve a11y. [#52076](https://github.com/ant-design/ant-design/pull/52076) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- ⌨️ Input.Search add `type=search` by default. [#52083](https://github.com/ant-design/ant-design/pull/52083) [@Kaikiat1126](https://github.com/Kaikiat1126)
|
||||
- ⌨️ Improve Tabs focus style for keyboard operation. [#52002](https://github.com/ant-design/ant-design/pull/52002) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- Segmented
|
||||
- ⌨️ Optimize Segmented focus style to improve a11y. [#51934](https://github.com/ant-design/ant-design/pull/51934) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- ⌨️ Segmented support `name` prop to improve a11y. [#51725](https://github.com/ant-design/ant-design/pull/51725) [@thinkasany](https://github.com/thinkasany)
|
||||
- 📦 MISC: Reduce bundle size by replacing `@ctrl/tinycolor` with `@ant-design/fast-color`. [#52190](https://github.com/ant-design/ant-design/pull/52190) [#52157](https://github.com/ant-design/ant-design/pull/52157) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- ⌨️ Adjust Input, InputNumber, Mentions, Textarea clear icon from `span` to `button` to improve a11y. [#52180](https://github.com/ant-design/ant-design/pull/52180) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🐞 MISC: Fix build error when using React 19. [#52168](https://github.com/ant-design/ant-design/pull/52168) [@zombieJ](https://github.com/zombieJ)
|
||||
- TypeScript
|
||||
- 🤖 Adjust Table `ref` type to React.Ref. [#52205](https://github.com/ant-design/ant-design/pull/52205) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🤖 Calendar export CalendarMode type. [#52160](https://github.com/ant-design/ant-design/pull/52160) [@Kaikiat1126](https://github.com/Kaikiat1126)
|
||||
|
||||
## 5.22.7
|
||||
|
||||
`2024-12-27`
|
||||
|
||||
- 🐞 Fix Button text and icon not align. [#52132](https://github.com/ant-design/ant-design/pull/52132) [@afc163](https://github.com/afc163)
|
||||
- 🐞 Fix Button throws `reactRender is not a function` under React 19. [#52105](https://github.com/ant-design/ant-design/pull/52105) [@afc163](https://github.com/afc163)
|
||||
- TypeScript
|
||||
- 🤖 Fix Menu interface type error from external module. [#51715](https://github.com/ant-design/ant-design/pull/51715) [@msyavuz](https://github.com/msyavuz)
|
||||
|
||||
## 5.22.6
|
||||
|
||||
`2024-12-23`
|
||||
|
||||
- 🐞 Align Button with and without icons consistently. [#52070](https://github.com/ant-design/ant-design/pull/52070)
|
||||
- 🐞 Fix Splitter collapsible icon `z-index` too low. [#52065](https://github.com/ant-design/ant-design/pull/52065) [@wanpan11](https://github.com/wanpan11)
|
||||
- 🐞 Fix Button motion not smooth when set `loading`. [#52059](https://github.com/ant-design/ant-design/pull/52059) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Fix Button issue where solid default button text disappears on hover in dark mode. [#52024](https://github.com/ant-design/ant-design/pull/52024) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
|
||||
## 5.22.5
|
||||
|
||||
`2024-12-15`
|
||||
@ -2016,7 +2263,7 @@ tag: vVERSION
|
||||
- 💄 Adjust Select, TreeSelect, Cascader always show the `arrow` by default when multiple. [#41028](https://github.com/ant-design/ant-design/pull/41028)
|
||||
- 🐞 Fix Form `Form.Item.useStatus` problem with sever-side-rendering. [#40977](https://github.com/ant-design/ant-design/pull/40977) [@AndyBoat](https://github.com/AndyBoat)
|
||||
- 🐞 MISC: Fix arrow shape in some components. [#40971](https://github.com/ant-design/ant-design/pull/40971)
|
||||
- 🐞 Fix Layout throw `React does not recognize the `suffixCls` prop on a DOM element` warning. [#40969](https://github.com/ant-design/ant-design/pull/40969)
|
||||
- 🐞 Fix Layout throw "React does not recognize the `suffixCls` prop on a DOM element" warning. [#40969](https://github.com/ant-design/ant-design/pull/40969)
|
||||
- 🐞 Fix Watermark that text will be displayed when the picture loads abnormally. [#40770](https://github.com/ant-design/ant-design/pull/40770) [@OriginRing](https://github.com/OriginRing)
|
||||
- 🐞 Image support flip function in preview mode. Fix Image `fallback` when used in ssr. [#40660](https://github.com/ant-design/ant-design/pull/40660)
|
||||
- 🐞 Fix Typography component is not centered in the Select component. [#40422](https://github.com/ant-design/ant-design/pull/40422) [@Yuiai01](https://github.com/Yuiai01)
|
||||
|
@ -15,6 +15,254 @@ tag: vVERSION
|
||||
|
||||
---
|
||||
|
||||
## 5.24.7
|
||||
|
||||
`2025-04-14`
|
||||
|
||||
- 🐞 修复 Input 存在 `suffix` 的情况下 Popover 弹出位置错乱的问题。[#53475](https://github.com/ant-design/ant-design/pull/53475)
|
||||
- 🐞 修复 Table `column.filterDropdown` 指定为 `undefined` 导致筛选菜单选中状态丢失的问题。[#53421](https://github.com/ant-design/ant-design/pull/53421)
|
||||
- 🇨🇳 补充 ColorPicker 的 `zh_HK` `zh_TW` 本地化文案。[#53440](https://github.com/ant-design/ant-design/pull/53440) [@mjsong07](https://github.com/mjsong07)
|
||||
|
||||
## 5.24.6
|
||||
|
||||
`2025-04-01`
|
||||
|
||||
- 🐞 修复 Modal 通过异步方法显示 loading 态时,点击外侧仍能触发关闭的问题。[#53227](https://github.com/ant-design/ant-design/pull/53227) [@jin19980928](https://github.com/jin19980928)
|
||||
- 🐞 修复 Table 在 `size` 为 `small` 时,主题配置 Pagination 无效的问题。[#52829](https://github.com/ant-design/ant-design/pull/52829) [@Can-Chen](https://github.com/Can-Chen)
|
||||
|
||||
## 5.24.5
|
||||
|
||||
`2025-03-24`
|
||||
|
||||
- 🐞 修复 InputNumber 在禁用状态下鼠标划入后 `suffix` 出现左移的问题。[#53184](https://github.com/ant-design/ant-design/pull/53184) [@yellowryan](https://github.com/yellowryan)
|
||||
- 💄 修复 Form 组件样式选择器语法错误。[#53236](https://github.com/ant-design/ant-design/pull/53236) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 💄 重构 TextArea 对 `resize: both` 时处理尺寸的逻辑以更符合 React 生命周期。[#53235](https://github.com/ant-design/ant-design/pull/53235) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🇮🇷 添加缺失的波斯语 (fa_IR) 翻译并修正拼写错误。[#53251](https://github.com/ant-design/ant-design/pull/53251) [@AliReza-Kamkar](https://github.com/AliReza-Kamkar)
|
||||
|
||||
## 5.24.4
|
||||
|
||||
`2025-03-17`
|
||||
|
||||
- 🐞 修复 Input.TextArea 调整大小时宽度同步问题。[#53024](https://github.com/ant-design/ant-design/pull/53024) [@triyys](https://github.com/triyys)
|
||||
- 🐞 修复 Typography `type` 颜色没有跟随 token `color[Status]Text` 而是 `color[Status]` 的问题。[#53086](https://github.com/ant-design/ant-design/pull/53086) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修复 Affix 组件在 React 18 以下版本中 `onChange` 参数值异常的问题。[#53038](https://github.com/ant-design/ant-design/pull/53038) [@waiter](https://github.com/waiter)
|
||||
- 🐞 修复 Form `requiredMark` 在 `component=false` 时不起作用的问题。[#52950](https://github.com/ant-design/ant-design/pull/52950) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 🇹🇷 新增 Tour 组件的土耳其语(tr_TR)本地化支持。[#53117](https://github.com/ant-design/ant-design/pull/53117) [@hakankosdag](https://github.com/hakankosdag)
|
||||
|
||||
## 5.24.3
|
||||
|
||||
`2025-03-05`
|
||||
|
||||
- Input
|
||||
- 🐞 修复 Input 开启 `allowClear` 后按下 Tab 键后没有正确选中下个元素的问题。[#52977](https://github.com/ant-design/ant-design/pull/52977) [@wanpan11](https://github.com/wanpan11)
|
||||
- 💄 修复 Input 开启 `variant="underlined"` 时 `disabled` 状态下 hover 时边框显示问题。[#52959](https://github.com/ant-design/ant-design/pull/52959) [@ustcfury](https://github.com/ustcfury)
|
||||
- 💄 修复 DatePicker 头部按钮意外间距导致的未对齐问题。[#53007](https://github.com/ant-design/ant-design/pull/53007) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 💄 修复 AutoComplete 在 `size="large"` 时文字未居中对齐的问题。[#52819](https://github.com/ant-design/ant-design/pull/52819) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🇩🇪 完善 `de_DE` Transfer 本地化. [#53047](https://github.com/ant-design/ant-design/pull/53047) [@chrisinick](https://github.com/chrisinick)
|
||||
|
||||
## 5.24.2
|
||||
|
||||
`2025-02-24`
|
||||
|
||||
- Input
|
||||
- 🐞 修复 Input 配置 `inputFontSize` component token 时,`controlHeight` 会不生效的问题。[#52865](https://github.com/ant-design/ant-design/pull/52865) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修复 Input.Search 在设置 `variant` 为 `underlined` 时下边框与搜索按钮底部没对齐的问题。[#52861](https://github.com/ant-design/ant-design/pull/52861) [@ustcfury](https://github.com/ustcfury)
|
||||
- 🛠 优化 Input.OTP 的默认状态创建逻辑。[#52878](https://github.com/ant-design/ant-design/pull/52878) [@Dandelion-F](https://github.com/Dandelion-F)
|
||||
- 🛠 优化 Input.OTP 的分隔符渲染实现。[#52841](https://github.com/ant-design/ant-design/pull/52841) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- Watermark
|
||||
- 🐞 修复 Watermark 重新渲染时可能导致页面卡死的问题。[#52897](https://github.com/ant-design/ant-design/pull/52897) [@765477020](https://github.com/765477020)
|
||||
- 🆕 调整 Watermark 渲染逻辑,防止通过开发者工具添加 `hidden` 属性来去掉水印。[#52891](https://github.com/ant-design/ant-design/pull/52891) [@arronlai](https://github.com/arronlai)
|
||||
- 🐞 修复 DatePicker.RangePicker 在弹层重新打开的时候,有可能出现箭头位置不正确的问题。[#52854](https://github.com/ant-design/ant-design/pull/52854) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修复 Layout.Sider 当 `collapsedWidth={0}` 时的内容溢出的问题。[#52862](https://github.com/ant-design/ant-design/pull/52862) [@afc163](https://github.com/afc163)
|
||||
- 🛠 重构 Grid 内部响应式逻辑以复用其他组件类似的逻辑,该更新不会于使用上有所变化。[#52870](https://github.com/ant-design/ant-design/pull/52870) [@zombieJ](https://github.com/zombieJ)
|
||||
- 💄 修复 Button 超链接模式的样式。[#52888](https://github.com/ant-design/ant-design/pull/52888) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 💄 修复 Table 可排序列头不自动换行的问题。[#52899](https://github.com/ant-design/ant-design/pull/52899) [@765477020](https://github.com/765477020)
|
||||
- ⚡️ 优化 Menu 在 `expandIcon` 属性传入函数时重新渲染的性能。[#52863](https://github.com/ant-design/ant-design/pull/52863) [@wanpan11](https://github.com/wanpan11)
|
||||
- ⚡️ 优化 Carousel 指示器的动画性能。[#52881](https://github.com/ant-design/ant-design/pull/52881) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- RTL
|
||||
- 💄 修复 DatePicker 在 RTL 模式下图标方向错误的问题。[#52896](https://github.com/ant-design/ant-design/pull/52896) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 💄 修复 Dropdown 在 RTL 模式下多级菜单箭头方向错误的问题。[#52885](https://github.com/ant-design/ant-design/pull/52885) [@yellowryan](https://github.com/yellowryan)
|
||||
|
||||
## 5.24.1
|
||||
|
||||
`2025-02-17`
|
||||
|
||||
- 🐞 修复 Button 配置 `color` 为 `primary` 并且 `variant` 为 `text` 或 `link` 时,没有取用正确的色板的问题。[#52812](https://github.com/ant-design/ant-design/pull/52812) [@zombieJ](https://github.com/zombieJ)
|
||||
- 💄 修复 Input.Group 与 Input.OTP 由于 css 变量未定义导致样式异常的问题。[#52799](https://github.com/ant-design/ant-design/pull/52799) [@afc163](https://github.com/afc163)
|
||||
- 🐞 修复 DatePicker 的 `prefix` 内容多时会换行的问题。[#52776](https://github.com/ant-design/ant-design/pull/52776) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🐞 修复 Table 列标题在排序时丢失 `aria-label` 的问题。[#52772](https://github.com/ant-design/ant-design/pull/52772) [@mellis481](https://github.com/mellis481)
|
||||
- 🐞 修复 Alert.ErrorBoundary 在 `@types/react@18.x` 中无法作为 JSX 组件使用的类型错误问题。[#52766](https://github.com/ant-design/ant-design/pull/52766) [@afc163](https://github.com/afc163)
|
||||
- 💄 修复 Segmented 设置 `size` 时,`shape` 不生效的问题。[#52757](https://github.com/ant-design/ant-design/pull/52757) [@yellowryan](https://github.com/yellowryan)
|
||||
|
||||
## 5.24.0
|
||||
|
||||
`2025-02-11`
|
||||
|
||||
- 🆕 Notification 支持 `actions` 属性并废弃 `btn` 属性。[#52703](https://github.com/ant-design/ant-design/pull/52703) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 Carousel 支持展示指示点进度。[#52672](https://github.com/ant-design/ant-design/pull/52672) [@yellowryan](https://github.com/yellowryan)
|
||||
- 🆕 Input.OTP 支持 `separator` 属性。[#52668](https://github.com/ant-design/ant-design/pull/52668) [@HaceraI](https://github.com/HaceraI)
|
||||
- 🆕 Descriptions 增加 `labelColor` 组件 token。[#52700](https://github.com/ant-design/ant-design/pull/52700) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🆕 Segmented 支持 `shape="round"` 的胶囊形状的样式。[#52685](https://github.com/ant-design/ant-design/pull/52685) [@afc163](https://github.com/afc163)
|
||||
- 🆕 ConfigProvider 支持 Card 组件的 `variant` 配置。[#52552](https://github.com/ant-design/ant-design/pull/52552) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 Progress/Step 支持使用 `rounding` 属性自定义取整方法。[#52017](https://github.com/ant-design/ant-design/pull/52017) [@yanghoxom](https://github.com/yanghoxom)
|
||||
- 🆕 Divider 的 `orientation` 属性支持 `start` 和 `end`.[#52567](https://github.com/ant-design/ant-design/pull/52567) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🆕 为 Input、InputNumber、Mentions、Form、Select、Cascader、TreeSelect、DatePicker、TimePicker 组件的 `variant` 添加 `underlined` 属性。[#52546](https://github.com/ant-design/ant-design/pull/52546) [@ustcfury](https://github.com/ustcfury)
|
||||
- 🆕 ConfigProvider 支持 Modal `centered` 全局配置。[#52343](https://github.com/ant-design/ant-design/pull/52343) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🆕 为 Checkbox 和 Radio 增加 `label` 类名。[#52322](https://github.com/ant-design/ant-design/pull/52322) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🐞 修复 Tooltip/Popover/Popconfirm/Dropdown 在 React 19 下 children 为自定义组件时弹层位置错乱的问题。[react-component/util#623](https://github.com/react-component/util/pull/623) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修复 DatePicker.RangePicker 弹出窗体自动调整位置时,面板的箭头位置不正确的问题。[#52719](https://github.com/ant-design/ant-design/pull/52719) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修正 locale 中 `filterCheckall` 为 `filterCheckAll`。[#52517](https://github.com/ant-design/ant-design/pull/52517) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🐞 修复 Form `scrollToField` 和 `scrollToFirstError` 无法聚焦 antd 组件的问题。[#52726](https://github.com/ant-design/ant-design/pull/52726) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 💄 修复 Button 预设值按钮的阴影色在暗色背景下显示突兀的问题。[#52701](https://github.com/ant-design/ant-design/pull/52701) [@afc163](https://github.com/afc163)
|
||||
- 💄 修复 Segmented 组件在暗黑模式下的动画过渡效果不自然的问题。[#52708](https://github.com/ant-design/ant-design/pull/52708) [@yellowryan](https://github.com/yellowryan)
|
||||
- 💄 拆分 Input 和 TextArea 样式。[#52570](https://github.com/ant-design/ant-design/pull/52570) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 💄 修复 Input 和 Select 在 css var 模式下的样式问题。[#52554](https://github.com/ant-design/ant-design/pull/52554) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- ⌨️ Form field error 移除 role="alert" 以提升可访问性。[#52661](https://github.com/ant-design/ant-design/pull/52661) [@mellis481](https://github.com/mellis481)
|
||||
- ⌨️ 优化无障碍体验,为空表头和面板按钮添加本地化标签。[#52689](https://github.com/ant-design/ant-design/pull/52689) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- ⌨️ 优化 Tabs 组件的可访问性,修复 `Certain ARIA roles must contain particular children` 的报错。[#52677](https://github.com/ant-design/ant-design/pull/52677) [@afc163](https://github.com/afc163)
|
||||
- ⌨️ 为 Tooltip 添加读屏器支持。[#52293](https://github.com/ant-design/ant-design/pull/52293) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- TypeScript
|
||||
- 🤖 Button 根据 `href` 属性区分 `onClick` 事件类型。[#52654](https://github.com/ant-design/ant-design/pull/52654) [@Brew-Brew](https://github.com/Brew-Brew)
|
||||
- 🤖 废弃 Button.Group, 推荐 Space.Compact。[#52572](https://github.com/ant-design/ant-design/pull/52572) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🤖 废弃 Input.Group,推荐 Space.Compact。[#52571](https://github.com/ant-design/ant-design/pull/52571) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🤖 Tooltip 导出 TooltipRef 类型。[#49230](https://github.com/ant-design/ant-design/pull/49230) [@nuintun](https://github.com/nuintun)
|
||||
|
||||
## 5.23.4
|
||||
|
||||
`2025-02-05`
|
||||
|
||||
蛇年第一个版本,开工大吉!🐍
|
||||
|
||||
- 🐞 修复 Pagination 可访问性问题,补充缺失的 ARIA 属性支持。[#52616](https://github.com/ant-design/ant-design/pull/52616) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🐞 Space.Compact 支持 textarea 组件。[#52639](https://github.com/ant-design/ant-design/pull/52639) [@Can-Chen](https://github.com/Can-Chen)
|
||||
- 🐞 修复 Menu `theme="dark"` 时水平菜单的文字色和背景色同色的问题。[#52617](https://github.com/ant-design/ant-design/pull/52617) [@afc163](https://github.com/afc163)
|
||||
- 🇪🇬 Tour 增加阿拉伯文(埃及) (ar_EG) 的翻译。 [#52642](https://github.com/ant-design/ant-design/pull/52642) [@Sagie501](https://github.com/Sagie501)
|
||||
- 🇮🇱 Tour 增加以色列的国际化翻译。[#52641](https://github.com/ant-design/ant-design/pull/52641) [@Sagie501](https://github.com/Sagie501)
|
||||
|
||||
|
||||
## 5.23.3
|
||||
|
||||
`2025-01-28`
|
||||
|
||||
龙年最后一版,祝各位新春愉快!🐲
|
||||
|
||||
- ⌨️ MISC: 为所有组件的示例添加了可访问性测试,确保符合无障碍标准。并优化了部分组件的可访问性支持,改进了对屏幕阅读器和键盘操作的兼容性。[#51372](https://github.com/ant-design/ant-design/pull/51372) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🐞 MISC: 修复导入 `antd/dist/reset.css` 文件的问题。[#52559](https://github.com/ant-design/ant-design/pull/52559) [@CaptainVolcom](https://github.com/CaptainVolcom)
|
||||
- 🐞 修复 Button `loading` 为 `null` 时抛错的问题。[#52508](https://github.com/ant-design/ant-design/pull/52508) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🐞 修复 Table 最后一行边框颜色过渡问题。[#52549](https://github.com/ant-design/ant-design/pull/52549) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 💄 修复 Cascader 组件禁用状态下复选框的鼠标指针样式问题。[#52539](https://github.com/ant-design/ant-design/pull/52539) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 💄 修复 ConfigProvider 在 StyleProvider 配置 `layer` 时不会正确修改图标对应样式优先级的问题。[#52533](https://github.com/ant-design/ant-design/pull/52533) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修复 Layout 切换侧边栏按钮在非 cssVar 模式下样式丢失的问题。. [#52537](https://github.com/ant-design/ant-design/pull/52537) [@afc163](https://github.com/afc163)
|
||||
- 🐞 修复 Tree 组件禁用状态下复选框的鼠标指针样式问题。[#52525](https://github.com/ant-design/ant-design/pull/52525) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- notification
|
||||
- 🐞 修复 notification `useNotification` 中 `closeIcon` 配置无效的问题。[#52516](https://github.com/ant-design/ant-design/pull/52516) [@typenoob](https://github.com/typenoob)
|
||||
- 🐞 修复 notification 组件在 App 组件下的显示闪烁问题。[#52499](https://github.com/ant-design/ant-design/pull/52499) [@afc163](https://github.com/afc163)
|
||||
- RTL
|
||||
- 🐞 修复 Splitter 在 rtl 模式下折叠行为异常的问题。[#52501](https://github.com/ant-design/ant-design/pull/52501) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 💄 修复 Spin 在 rtl 模式下的样式问题。[#52538](https://github.com/ant-design/ant-design/pull/52538) [@afc163](https://github.com/afc163)
|
||||
|
||||
## 5.23.2
|
||||
|
||||
`2025-01-20`
|
||||
|
||||
- 🐞 修复 Space.Compact 抛出 `Should not use more than one & in a selector` 警告信息的问题。[#52489](https://github.com/ant-design/ant-design/pull/52489)
|
||||
- 💄 修复 Layout 切换侧边栏按钮样式丢失的问题。[#52477](https://github.com/ant-design/ant-design/pull/52477)
|
||||
- 💄 修复 Table 收起虚拟滚动表格第一行时滚动条高度不为 0 的问题。[#52447](https://github.com/ant-design/ant-design/pull/52447) [@LeeSSHH](https://github.com/LeeSSHH)
|
||||
- 💄 修复 Descriptions 最后一项未正确填充满剩余空间的问题。[#52410](https://github.com/ant-design/ant-design/pull/52410) [@anyuxuan](https://github.com/anyuxuan)
|
||||
- 💄 修复 Radio.Group 最后一项多余 margin 的问题。[#52433](https://github.com/ant-design/ant-design/pull/52433)
|
||||
- 💄 修复 Input/Mentions 清除按钮 padding 不正确的问题。[#52407](https://github.com/ant-design/ant-design/pull/52407) [@ustcfury](https://github.com/ustcfury)
|
||||
- 💄 修复 Input 紧凑模式中 `addonAfter` 的圆角问题。[#52490](https://github.com/ant-design/ant-design/pull/52490) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 💄 修复 Menu.Item 在禁用状态下链接仍可点击且缺少禁用样式的问题。[#52402](https://github.com/ant-design/ant-design/pull/52402) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- TypeScript
|
||||
- 🤖 MISC: 优化 PurePanel 使用 React.ComponentType 类型。[#52480](https://github.com/ant-design/ant-design/pull/52480)
|
||||
- 🤖 修复 Skeleton 和 Rate 缺失的 token 类型。[#52406](https://github.com/ant-design/ant-design/pull/52406) [@coding-ice](https://github.com/coding-ice)
|
||||
|
||||
## 5.23.1
|
||||
|
||||
`2025-01-13`
|
||||
|
||||
- 🆕 新增 Tree 组件叶子节点的 className 用于区分节点类型。[#52274](https://github.com/ant-design/ant-design/pull/52274) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
|
||||
- 🐞 修复 DatePicker `superPrevIcon/superNextIcon/prevIcon/nextIcon` 设置为 null 时切换按钮依旧存在的问题。[#52327](https://github.com/ant-design/ant-design/pull/52327) [@afc163](https://github.com/afc163)
|
||||
- 🐞 修复 Select 组件在 jest 测试中报错 `not a valid selector` 的问题。[#51844](https://github.com/ant-design/ant-design/pull/51844) [@renovate](https://github.com/renovate)
|
||||
- 🐞 修复 Layout.Sider 直接嵌套在 ConfigProvider 下时,`theme` 配置无效的问题。[#52302](https://github.com/ant-design/ant-design/pull/52302) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修复 Splitter 二次展开时丢失上一次状态的问题。[#52222](https://github.com/ant-design/ant-design/pull/52222) [@jjlstruggle](https://github.com/jjlstruggle)
|
||||
- 🐞 修复 Table 树形展示且设置 `checkStrictly` 为 false 时,某些行被错误选中的问题。[#52338](https://github.com/ant-design/ant-design/pull/52338) [@LeeSSHH](https://github.com/LeeSSHH)
|
||||
- Button
|
||||
- 🐞 调整 Button 纯图标的大小从而修复按钮对齐和图标居中问题。[#52353](https://github.com/ant-design/ant-design/pull/52353) [@afc163](https://github.com/afc163)
|
||||
- 💄 修复 Button 丢失阴影样式的问题。[#52304](https://github.com/ant-design/ant-design/pull/52304) [@zombieJ](https://github.com/zombieJ)
|
||||
- RTL
|
||||
- 💄 修复 Collapse 在 RTL 模式下的箭头方向。[#52374](https://github.com/ant-design/ant-design/pull/52374) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 💄 修复 Layout.Sider 在 RTL 模式下的箭头方向。[#52374](https://github.com/ant-design/ant-design/pull/52374) [@aojunhao123](https://github.com/aojunhao123)
|
||||
|
||||
## 5.23.0
|
||||
|
||||
`2025-01-06`
|
||||
|
||||
- 🔥 TreeSelect 新增 `maxCount` 属性以限制最大选择数量。[#51759](https://github.com/ant-design/ant-design/pull/51759) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🔥 Modal `width` 支持响应式尺寸。[#51653](https://github.com/ant-design/ant-design/pull/51653) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🔥 Splitter 增加 `lazy` 模式。[#51557](https://github.com/ant-design/ant-design/pull/51557) [@OysterD3](https://github.com/OysterD3)
|
||||
- Button
|
||||
- 🔥 Button `color` 属性支持完整色板。[#51550](https://github.com/ant-design/ant-design/pull/51550) [@OysterD3](https://github.com/OysterD3)
|
||||
<img width="520" alt="Button Colors" src="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*ApyYQpXQQfgAAAAAAAAAAAAADgCCAQ/original">
|
||||
- 🆕 Button 组件新增 `loading={{ icon: ReactNode }}` 以自定义加载图标。[#51758](https://github.com/ant-design/ant-design/pull/51758) [@zhangchao-wooc](https://github.com/zhangchao-wooc)
|
||||
- Menu
|
||||
- 🆕 Menu 新增 token `subMenuItemSelectedColor`,避免 `itemSelectedColor` 覆盖子菜单标题样式。[#52182](https://github.com/ant-design/ant-design/pull/52182) [@afc163](https://github.com/afc163)
|
||||
- 🐞 修复 Menu `extra` 字体大小和垂直居中对齐问题。[#52217](https://github.com/ant-design/ant-design/pull/52217) [@guoyunhe](https://github.com/guoyunhe)
|
||||
- 🆕 语义化
|
||||
- 🆕 ConfigProvider 支持 Empty 组件语义化 `classNames` 和 `styles`。[#52208](https://github.com/ant-design/ant-design/pull/52208) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ConfigProvider 支持 Slider 组件语义化 `classNames` 和 `styles`。[#52185](https://github.com/ant-design/ant-design/pull/52185) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ConfigProvider 支持 Popconfirm 组件语义化 `classNames` 和 `styles`。[#52126](https://github.com/ant-design/ant-design/pull/52126) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ConfigProvider 支持 Popover 组件语义化 `classNames` 和 `styles`。[#52110](https://github.com/ant-design/ant-design/pull/52110) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ConfigProvider 支持 Tooltip 组件语义化 `classNames` 和 `styles`。[#51872](https://github.com/ant-design/ant-design/pull/51872) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ConfigProvider 支持 Descriptions 组件语义化 `classNames` 和 `styles`。[#52120](https://github.com/ant-design/ant-design/pull/52120) [@thinkasany](https://github.com/thinkasany)
|
||||
- Tree
|
||||
- 🛠 重构 Tree 部分代码为 Function Component 以为 React 19 做更好性能准备。[#52209](https://github.com/ant-design/ant-design/pull/52209) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 💄 优化 Tree `disabled` 与 `selected` 节点状态下的颜色展示。[#52173](https://github.com/ant-design/ant-design/pull/52173) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
|
||||
- 🆕 Transfer 支持 `showSearch` 配置 `defaultValue` 和 `placeholder`。[#52125](https://github.com/ant-design/ant-design/pull/52125) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
|
||||
- 🆕 Calendar 支持 `showWeek` 属性用于显示周数列。[#52072](https://github.com/ant-design/ant-design/pull/52072) [@afc163](https://github.com/afc163)
|
||||
- 🆕 Mentions 新增 `onPopupScroll` 属性。[#51858](https://github.com/ant-design/ant-design/pull/51858) [@OysterD3](https://github.com/OysterD3)
|
||||
- 🆕 Card 增加 `bodyPaddingSM`、`headerPaddingSM`、`bodyPadding`、`headerPadding` 组件 token。[#51762](https://github.com/ant-design/ant-design/pull/51762) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🆕 ColorPicker `presets` 支持传入 `key`。[#51794](https://github.com/ant-design/ant-design/pull/51794) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🆕 Cascader 新增 `optionSelectedColor` token。[#51769](https://github.com/ant-design/ant-design/pull/51769) [@thinkasany](https://github.com/thinkasany)
|
||||
- 🐞 修复 Layout.Sider `trigger` 样式不正确的问题。[#46a8eff](https://github.com/ant-design/ant-design/commit/46a8eff) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- Table
|
||||
- 🐞 修复 Table `expandable` 中设置 `fixed:right` 不生效的问题。[#52176](https://github.com/ant-design/ant-design/pull/52176) [@afc163](https://github.com/afc163)
|
||||
- 🐞 修复 Table `sticky` 模式下水平固定滚动条在 rtl 模式下不生效的问题。[#52176](https://github.com/ant-design/ant-design/pull/52176) [@afc163](https://github.com/afc163)
|
||||
- 💄 优化 Flex 使其在自定义渲染组件时总是重置 `margin`、`padding` 样式。[#52170](https://github.com/ant-design/ant-design/pull/52170) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🐞 修复 DatePicker.RangePicker `needConfirm` 模式偶尔在不确认仍然可以切换面板的问题。[#52102](https://github.com/ant-design/ant-design/pull/52102) [@Zyf665](https://github.com/Zyf665)
|
||||
- 🐞 修复 Slider 当 `tipFormatter` 未定义时导致崩溃的问题。[#52184](https://github.com/ant-design/ant-design/pull/52184) [@thinkasany](https://github.com/thinkasany)
|
||||
- 💄 优化 Collapse 聚焦样式以及折叠项圆角。[#52086](https://github.com/ant-design/ant-design/pull/52086) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- ⌨️ 为 Radio.Group 添加默认 `name` 属性以提升无障碍体验。[#52076](https://github.com/ant-design/ant-design/pull/52076) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- ⌨️ Input.Search 添加默认 `type=search` 类型。[#52083](https://github.com/ant-design/ant-design/pull/52083) [@Kaikiat1126](https://github.com/Kaikiat1126)
|
||||
- ⌨️ 优化 Tabs 键盘操作时的焦点样式。[#52002](https://github.com/ant-design/ant-design/pull/52002) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- Segmented
|
||||
- ⌨️ 优化 Segmented 聚焦样式以提升无障碍体验。[#51934](https://github.com/ant-design/ant-design/pull/51934) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- ⌨️ Segmented 支持 `name` 属性以提升无障碍体验。[#51725](https://github.com/ant-design/ant-design/pull/51725) [@thinkasany](https://github.com/thinkasany)
|
||||
- 📦 MISC: 用 `@ant-design/fast-color` 替换 `@ctrl/tinycolor` 以降低打包体积。[#52190](https://github.com/ant-design/ant-design/pull/52190) [#52157](https://github.com/ant-design/ant-design/pull/52157) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- ⌨️ 调整 Input、InputNumber、Mentions、Textarea 组件清除图标从 `span` 元素更改为 `button` 元素,提高了可访问性和交互性。[#52180](https://github.com/ant-design/ant-design/pull/52180) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🐞 MISC: 修复 React 19 下构建报错的问题。[#52168](https://github.com/ant-design/ant-design/pull/52168) [@zombieJ](https://github.com/zombieJ)
|
||||
- TypeScript
|
||||
- 🤖 调整 Table `ref` 类型为 React.Ref。[#52205](https://github.com/ant-design/ant-design/pull/52205) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🤖 Calendar 导出 CalendarMode 类型。[#52160](https://github.com/ant-design/ant-design/pull/52160) [@Kaikiat1126](https://github.com/Kaikiat1126)
|
||||
|
||||
## 5.22.7
|
||||
|
||||
`2024-12-27`
|
||||
|
||||
- 🐞 修复 Button 文字和图标不对齐的问题。[#52132](https://github.com/ant-design/ant-design/pull/52132) [@afc163](https://github.com/afc163)
|
||||
- 🐞 修复在 React 19 下点击 Button 时抛出 `reactRender is not a function` 错误的问题。[#52105](https://github.com/ant-design/ant-design/pull/52105) [@afc163](https://github.com/afc163)
|
||||
- TypeScript
|
||||
- 🤖 修复 Menu `component` 属性类型抛错。[#51715](https://github.com/ant-design/ant-design/pull/51715) [@msyavuz](https://github.com/msyavuz)
|
||||
|
||||
## 5.22.6
|
||||
|
||||
`2024-12-23`
|
||||
|
||||
- 🐞 修复 Button 有图标和无图标按钮对齐差一像素的问题。[#52070](https://github.com/ant-design/ant-design/pull/52070)
|
||||
- 🐞 修复 Splitter 组件折叠图标 `z-index` 层级过低问题。[#52065](https://github.com/ant-design/ant-design/pull/52065) [@wanpan11](https://github.com/wanpan11)
|
||||
- 🐞 修复 Button 启用 `loading` 时,动画不够顺滑的问题。[#52059](https://github.com/ant-design/ant-design/pull/52059) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修复 Button 暗色模式下默认填充按钮文本在悬停时消失的问题。[#52024](https://github.com/ant-design/ant-design/pull/52024) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
|
||||
## 5.22.5
|
||||
|
||||
`2024-12-15`
|
||||
@ -92,7 +340,7 @@ tag: vVERSION
|
||||
|
||||
- Form
|
||||
- 🆕 Form.Item 支持隐藏 label。[#51524](https://github.com/ant-design/ant-design/pull/51524) [@crazyair](https://github.com/crazyair)
|
||||
- 🐞 Form 移除了用于撑开 error 高度的 div,将 errorDom 和 extraDom 用一个 div 包裹,并为该 div 设置了最小高度。[#51254](https://github.com/ant-design/ant-design/pull/51254) [@hongzzz](https://github.com/hongzzz)
|
||||
- 🐞 Form 移除了用于撑开 error 高度的 div,将 errorDom 和 extraDom 用一个 div 包裹,并为该 div 设置了最小高度。[#51254](https://github.com/ant-design/ant-design/pull/51254) [@hongzzz](https://github.com/hongzzz)
|
||||
- 🐞 修复 Form 在字段触发 change 但是值没有变化时,`onValuesChange` 仍然会触发的问题。[#51437](https://github.com/ant-design/ant-design/pull/51437) [@crazyair](https://github.com/crazyair)
|
||||
- 🆕 Form 支持在表单验证失败时,scrollToFirstError 中的 focus 属性。[#51231](https://github.com/ant-design/ant-design/pull/51231) [@nathanlao](https://github.com/nathanlao)
|
||||
- Table
|
||||
@ -166,6 +414,7 @@ tag: vVERSION
|
||||
- 💄 修改 Button `textHoverBg` 在悬浮状态下的背景色为 `colorFillTertiary`。[#51187](https://github.com/ant-design/ant-design/pull/51187) [@coding-ice](https://github.com/coding-ice)
|
||||
- TypeScript
|
||||
- 🤖 优化 Switch `eventHandler` 类型。[#51165](https://github.com/ant-design/ant-design/pull/51165) [@thinkasany](https://github.com/thinkasany)
|
||||
|
||||
## 5.21.3
|
||||
|
||||
`2024-10-09`
|
||||
@ -214,17 +463,17 @@ tag: vVERSION
|
||||
<img width="520" alt="Splitter" src="https://github.com/user-attachments/assets/25fc4e3c-1aa5-41bb-8f39-f34f7149e0f6">
|
||||
- Button
|
||||
- 🔥 Button 支持 `variant` 变体和 `color` 颜色属性,以支持更多组合样式。[#50051](https://github.com/ant-design/ant-design/pull/50051) [@coding-ice](https://github.com/coding-ice)
|
||||
<img width="420" alt="Button" src="https://github.com/user-attachments/assets/cd5cb7fb-25e8-445f-b217-7fdd4ae0f9b4">
|
||||
<img width="420" alt="Button" src="https://github.com/user-attachments/assets/cd5cb7fb-25e8-445f-b217-7fdd4ae0f9b4">
|
||||
- 💄 Button 添加 `textColor`、`textHoverColor` 和 `textActiveColor` 三个 token。[#47870](https://github.com/ant-design/ant-design/pull/47870) [@madocto](https://github.com/madocto)
|
||||
- FloatButton
|
||||
- 🆕 FloatButton 组件支持 `placement` 属性,支持从四个方向弹出菜单。(实现方式改为 `position: absolute` + flex 布局,可能会对你现有的布局造成 breaking change,请注意兼容)[#50407](https://github.com/ant-design/ant-design/pull/50407) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
<img width="300" alt="float button" src="https://github.com/user-attachments/assets/4b53c0f6-7bdd-4e2a-91cc-2fb804f6e6d3" />
|
||||
<img width="300" alt="float button" src="https://github.com/user-attachments/assets/4b53c0f6-7bdd-4e2a-91cc-2fb804f6e6d3" />
|
||||
- 💄 统一 FloatButton 和 FloatButton.Group 的按钮圆角。[#50513](https://github.com/ant-design/ant-design/pull/50513) [@Layouwen](https://github.com/Layouwen)
|
||||
- 💄 FloatButton 组件的 `z-index` 加入 `useZIndex` 管理,兼容弹层类组件。[#50311](https://github.com/ant-design/ant-design/pull/50311) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🆕 FloatButton 支持传入 `htmlType` 属性。[#50892](https://github.com/ant-design/ant-design/pull/50892) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- Menu
|
||||
- 🆕 Menu.Item 和 Dropdown 的 menu 支持 `extra` 属性。[#50431](https://github.com/ant-design/ant-design/pull/50431) [@coding-ice](https://github.com/coding-ice)
|
||||
<img width="259" alt="menu extra" src="https://github.com/user-attachments/assets/fee57c43-b948-4f98-8a6b-0d94094a8a65">
|
||||
<img width="259" alt="menu extra" src="https://github.com/user-attachments/assets/fee57c43-b948-4f98-8a6b-0d94094a8a65">
|
||||
- 🐞 修复 Menu `popupStyle` 在 SubMenu 上失效的问题。[#50922](https://github.com/ant-design/ant-design/pull/50922) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- Table
|
||||
- 🆕 Table 列支持配置 `minWidth` 属性。[#50416](https://github.com/ant-design/ant-design/pull/50416) [@linxianxi](https://github.com/linxianxi)
|
||||
@ -2016,7 +2265,7 @@ tag: vVERSION
|
||||
- 💄 调整 Select, TreeSelect, Cascader 在多选时总是默认显示下拉箭头。[#41028](https://github.com/ant-design/ant-design/pull/41028)
|
||||
- 🐞 修复 Form 组件 `Form.Item.useStatus` 导致的服务端渲染问题。[#40977](https://github.com/ant-design/ant-design/pull/40977) [@AndyBoat](https://github.com/AndyBoat)
|
||||
- 🐞 杂项:修复部分组件箭头形状问题。[#40971](https://github.com/ant-design/ant-design/pull/40971)
|
||||
- 🐞 修复 Layout 报错 `React does not recognize the `suffixCls` prop on a DOM element` 的问题。[#40969](https://github.com/ant-design/ant-design/pull/40969)
|
||||
- 🐞 修复 Layout 报错 "React does not recognize the `suffixCls` prop on a DOM element" 的问题。[#40969](https://github.com/ant-design/ant-design/pull/40969)
|
||||
- 🐞 修复 Watermark 组件图片加载异常时的问题,默认展示文字。[#40770](https://github.com/ant-design/ant-design/pull/40770) [@OriginRing](https://github.com/OriginRing)
|
||||
- 🐞 Image 预览新增图片翻转功能。并修复 Image `fallback` 在 ssr 下失效的问题。[#40660](https://github.com/ant-design/ant-design/pull/40660)
|
||||
- 🐞 修复 Select 中使用 Typography 不居中的问题。[#40422](https://github.com/ant-design/ant-design/pull/40422) [@Yuiai01](https://github.com/Yuiai01)
|
||||
|
@ -20,8 +20,8 @@
|
||||
|
||||
[npm-image]: https://img.shields.io/npm/v/antd.svg?style=flat-square
|
||||
[npm-url]: https://npmjs.org/package/antd
|
||||
[github-action-image]: https://github.com/ant-design/ant-design/workflows/%E2%9C%85%20test/badge.svg
|
||||
[github-action-url]: https://github.com/ant-design/ant-design/actions?query=workflow%3A%22%E2%9C%85+test%22
|
||||
[github-action-image]: https://github.com/ant-design/ant-design/actions/workflows/test.yml/badge.svg
|
||||
[github-action-url]: https://github.com/ant-design/ant-design/actions/workflows/test.yml
|
||||
[codecov-image]: https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square
|
||||
[codecov-url]: https://codecov.io/gh/ant-design/ant-design/branch/master
|
||||
[download-image]: https://img.shields.io/npm/dm/antd.svg?style=flat-square
|
||||
@ -83,6 +83,10 @@ yarn add antd
|
||||
pnpm add antd
|
||||
```
|
||||
|
||||
```bash
|
||||
bun add antd
|
||||
```
|
||||
|
||||
## 🔨 示例
|
||||
|
||||
```tsx
|
||||
@ -105,7 +109,7 @@ export default App;
|
||||
|
||||
### 🛡 TypeScript
|
||||
|
||||
`antd` 使用 TypeScript 编写,具有完整的类型定义,参考 [在 create-react-app 中使用](https://ant.design/docs/react/use-with-create-react-app-cn)。
|
||||
`antd` 使用 TypeScript 编写,具有完整的类型定义,参考 [在 Next.js 中使用](https://ant.design/docs/react/use-with-next-cn)。
|
||||
|
||||
## 🌍 国际化
|
||||
|
||||
|
@ -20,8 +20,8 @@ An enterprise-class UI design language and React UI library.
|
||||
|
||||
[npm-image]: https://img.shields.io/npm/v/antd.svg?style=flat-square
|
||||
[npm-url]: https://npmjs.org/package/antd
|
||||
[github-action-image]: https://github.com/ant-design/ant-design/workflows/%E2%9C%85%20test/badge.svg
|
||||
[github-action-url]: https://github.com/ant-design/ant-design/actions?query=workflow%3A%22%E2%9C%85+test%22
|
||||
[github-action-image]: https://github.com/ant-design/ant-design/actions/workflows/test.yml/badge.svg
|
||||
[github-action-url]: https://github.com/ant-design/ant-design/actions/workflows/test.yml
|
||||
[codecov-image]: https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square
|
||||
[codecov-url]: https://codecov.io/gh/ant-design/ant-design/branch/master
|
||||
[download-image]: https://img.shields.io/npm/dm/antd.svg?style=flat-square
|
||||
@ -81,6 +81,10 @@ yarn add antd
|
||||
pnpm add antd
|
||||
```
|
||||
|
||||
```bash
|
||||
bun add antd
|
||||
```
|
||||
|
||||
## 🔨 Usage
|
||||
|
||||
```tsx
|
||||
|
@ -74,6 +74,7 @@ exports[`antd exports modules correctly 1`] = `
|
||||
"message",
|
||||
"notification",
|
||||
"theme",
|
||||
"unstableSetRender",
|
||||
"version",
|
||||
]
|
||||
`;
|
||||
|
44
components/__tests__/unstable.test.ts
Normal file
44
components/__tests__/unstable.test.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Modal, unstableSetRender } from 'antd';
|
||||
|
||||
import { waitFakeTimer19 } from '../../tests/utils';
|
||||
|
||||
// TODO: Remove this. Mock for React 19
|
||||
jest.mock('react-dom', () => {
|
||||
const realReactDOM = jest.requireActual('react-dom');
|
||||
|
||||
if (realReactDOM.version.startsWith('19')) {
|
||||
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||
}
|
||||
|
||||
return realReactDOM;
|
||||
});
|
||||
|
||||
describe('unstable', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('unstableSetRender', async () => {
|
||||
if (ReactDOM.version.startsWith('19')) {
|
||||
unstableSetRender((node, container) => {
|
||||
const root = (ReactDOM as any).createRoot(container);
|
||||
root.render(node);
|
||||
return async () => {
|
||||
root.unmount();
|
||||
};
|
||||
});
|
||||
|
||||
Modal.info({ content: 'unstableSetRender' });
|
||||
|
||||
await waitFakeTimer19();
|
||||
|
||||
expect(document.querySelector('.ant-modal')).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user