mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 17:44:35 +08:00
Merge branch 'master' into fix-form-vertical-offset
This commit is contained in:
commit
7979593ca2
94
.cursor/rules/locale.mdc
Normal file
94
.cursor/rules/locale.mdc
Normal file
@ -0,0 +1,94 @@
|
||||
---
|
||||
description: 本地化规范文档
|
||||
globs: ["components/locale/*_*.ts", "components/locale/index.tsx"]
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# 本地化规范
|
||||
|
||||
antd 中所有的本地化配置都应该在 `components/locale` 目录中完成定义,主要分为两步:类型定义和本地化配置
|
||||
|
||||
## 类型定义
|
||||
|
||||
antd 的本地化配置的类型定义的入口文件是 `components/locale/index.tsx`, 当需要添加新的本地化配置时,需要检查对应组件或全局配置的类型是否存在,如果不存在,则需要增加相应的类型描述。
|
||||
如果新增或修改的本地化配置时组件配置,那么具体的本地化类型应该在相应的组件目录定义,定义好后在 `components/locale/index.tsx` 引入对应组件的类型定义。
|
||||
|
||||
## 本地化配置
|
||||
|
||||
### 纯字符串配置
|
||||
|
||||
antd 中的本地化配置文件命名规则是:`*_*.ts`,如:`zh_CN.ts`,文件默认导出一个 `Locale` 类型对象。
|
||||
通常在为 antd 添加后修改某一项本地化配置时,如无特殊说明,需要同时修改所有语言的本地化配置。
|
||||
|
||||
本地化配置文件列表如下(包括但不限于):
|
||||
|
||||
```json
|
||||
["components/locale/ar_EG.ts","components/locale/az_AZ.ts","components/locale/bg_BG.ts","components/locale/bn_BD.ts","components/locale/by_BY.ts","components/locale/ca_ES.ts","components/locale/cs_CZ.ts","components/locale/da_DK.ts","components/locale/de_DE.ts","components/locale/el_GR.ts","components/locale/en_GB.ts","components/locale/en_US.ts","components/locale/es_ES.ts","components/locale/et_EE.ts","components/locale/eu_ES.ts","components/locale/fa_IR.ts","components/locale/fi_FI.ts","components/locale/fr_BE.ts","components/locale/fr_CA.ts","components/locale/fr_FR.ts","components/locale/ga_IE.ts","components/locale/gl_ES.ts","components/locale/he_IL.ts","components/locale/hi_IN.ts","components/locale/hr_HR.ts","components/locale/hu_HU.ts","components/locale/hy_AM.ts","components/locale/id_ID.ts","components/locale/is_IS.ts","components/locale/it_IT.ts","components/locale/ja_JP.ts","components/locale/ka_GE.ts","components/locale/kk_KZ.ts","components/locale/km_KH.ts","components/locale/kmr_IQ.ts","components/locale/kn_IN.ts","components/locale/ko_KR.ts","components/locale/ku_IQ.ts","components/locale/lt_LT.ts","components/locale/lv_LV.ts","components/locale/mk_MK.ts","components/locale/ml_IN.ts","components/locale/mn_MN.ts","components/locale/ms_MY.ts","components/locale/my_MM.ts","components/locale/nb_NO.ts","components/locale/ne_NP.ts","components/locale/nl_BE.ts","components/locale/nl_NL.ts","components/locale/pl_PL.ts","components/locale/pt_BR.ts","components/locale/pt_PT.ts","components/locale/ro_RO.ts","components/locale/ru_RU.ts","components/locale/si_LK.ts","components/locale/sk_SK.ts","components/locale/sl_SI.ts","components/locale/sr_RS.ts","components/locale/sv_SE.ts","components/locale/ta_IN.ts","components/locale/th_TH.ts","components/locale/tk_TK.ts","components/locale/tr_TR.ts","components/locale/uk_UA.ts","components/locale/ur_PK.ts","components/locale/uz_UZ.ts","components/locale/vi_VN.ts","components/locale/zh_CN.ts","components/locale/zh_HK.ts","components/locale/zh_TW.ts"]
|
||||
```
|
||||
|
||||
本地化配置的内容通常是纯字符串,如:
|
||||
|
||||
```typescript
|
||||
{
|
||||
// ...
|
||||
Modal: {
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
justOkText: '知道了',
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 字符串模版配置
|
||||
|
||||
当然,也有一些需要配置需要再运行时实时替换变量的模版配置,带有 `${}` 的变量将在实际使用的地方被实时替换成对应的变量内容:
|
||||
```typescript
|
||||
{
|
||||
// ...
|
||||
date: {
|
||||
format: '${label}日期格式无效',
|
||||
parse: '${label}不能转换为日期',
|
||||
invalid: '${label}是一个无效日期',
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 全局配置
|
||||
|
||||
如果某个本地化配置不独属于某个组件,而是数据全局的本地化配置,此时应该在 `global` 中添加相关属性,如:
|
||||
|
||||
```typescript
|
||||
{
|
||||
// ...
|
||||
// locales for all components
|
||||
global: {
|
||||
placeholder: '请选择',
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
# 使用本地化
|
||||
|
||||
antd 中具体使用本地化配置时,可以使用 `components/locale/index.tsx` 文件中导出的 `useLocale` 获取全局上下文中配置的本地化,并跟组件属性中传入的本地化配置合并后得到最完整的本地化配置,如:
|
||||
|
||||
```tsx
|
||||
import { useLocale } from "../locale";
|
||||
import enUS from '../locale/en_US';
|
||||
|
||||
export function TestComp(props) {
|
||||
const { locale: propLocale } = props;
|
||||
const [contextLocale] = useLocale("TestComp", enUs);
|
||||
|
||||
const locale = {...contextLocale, ...propLocale};
|
||||
|
||||
return (
|
||||
<div title={locale?.title}>
|
||||
{locale?.text}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
Basically, antd naming requires **FULL NAME** instead of Abbreviation.
|
||||
@ -31,7 +31,7 @@ Basically, antd naming requires **FULL NAME** instead of Abbreviation.
|
||||
* Multiple icons: `FunctionName` + `Icon`
|
||||
* Trigger: `trigger`
|
||||
* Sub function trigger: `Sub Function` + `Trigger`
|
||||
* Trigger on the time point: `xxx` + `On` + `EventName` (e.g. `destroyOnClose`)
|
||||
* Trigger on the time point: `xxx` + `On` + `EventName` (e.g. `destroyOnHidden`)
|
||||
* Component use other component config. Naming as component.(e.g. `<Table pagination={{...}} />`)
|
||||
* ClassName: `className`
|
||||
* Additional classes should be merged into `classes` (e.g. `<Button classes={{ inner: 'custom-inner' }} />`)
|
||||
@ -62,15 +62,15 @@ ComponentRef {
|
||||
`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`.
|
||||
* `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`.
|
||||
* `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 |
|
||||
| `@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 |
|
||||
|
||||
@ -86,13 +86,13 @@ ref: [#16048](mdc:https:/github.com/ant-design/ant-design/issues/16048)
|
||||
| Property | Description | Type | Default |
|
||||
| --------- | ---------------------- | ------------------------------ | ------ |
|
||||
| htmlType | xxx | string | `button ` |
|
||||
| type | xxx | `horizontal ` \| `vertical ` | `horizontal` |
|
||||
| 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` |
|
||||
| value | xxx | string \| number | `small` |
|
||||
|
||||
### Promise
|
||||
- When string type, the **Default** use ` `` `.
|
||||
@ -105,4 +105,4 @@ ref: [#16048](mdc:https:/github.com/ant-design/ant-design/issues/16048)
|
||||
- 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)
|
||||
ref: [#25066](mdc:https:/github.com/ant-design/ant-design/issues/25066)
|
||||
|
89
.dumi/rehypeChangelog.ts
Normal file
89
.dumi/rehypeChangelog.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import type { UnifiedTransformer } from 'dumi';
|
||||
import { unistUtilVisit } from 'dumi';
|
||||
import set from 'lodash/set';
|
||||
let hastToString: typeof import('hast-util-to-string').toString;
|
||||
|
||||
// workaround to import pure esm module
|
||||
(async () => {
|
||||
({ toString: hastToString } = await import('hast-util-to-string'));
|
||||
})();
|
||||
|
||||
const COMPONENT_NAME = 'RefinedChangelog';
|
||||
|
||||
function rehypeChangelog(): UnifiedTransformer<any> {
|
||||
return (tree, vFile) => {
|
||||
const { filename } = vFile.data.frontmatter as any;
|
||||
|
||||
// 只处理 changelog 文件
|
||||
if (!/^changelog\.\S+\.md$/i.test(filename)) return;
|
||||
|
||||
const nodesToWrap: { parent: any; startIdx: number }[] = [];
|
||||
const WRAPPER_FLAG = 'data-changelog-wrapped'; // 包裹容器唯一标识
|
||||
|
||||
unistUtilVisit.visit(tree, 'element', (node, idx, parent) => {
|
||||
if (node.properties?.[WRAPPER_FLAG]) return unistUtilVisit.SKIP;
|
||||
|
||||
if (
|
||||
idx !== undefined &&
|
||||
parent &&
|
||||
idx! + 2 < parent.children.length &&
|
||||
node.tagName === 'h2' &&
|
||||
parent.children[idx! + 1].tagName === 'p' &&
|
||||
parent.children[idx! + 2].tagName === 'ul'
|
||||
) {
|
||||
nodesToWrap.push({ parent, startIdx: idx! });
|
||||
}
|
||||
});
|
||||
|
||||
nodesToWrap.reverse().forEach(({ parent, startIdx }) => {
|
||||
const [heading, date, list] = parent.children.splice(startIdx, 3);
|
||||
|
||||
const version = hastToString(heading);
|
||||
const dateStr = hastToString(date);
|
||||
|
||||
const headingWrap = {
|
||||
type: 'element',
|
||||
tagName: `${COMPONENT_NAME}.Version`,
|
||||
// 为标签添加语义化 className (下面同理)
|
||||
children: [set(heading, 'properties.className', 'changelog-version')],
|
||||
};
|
||||
|
||||
const dateWrap = {
|
||||
type: 'element',
|
||||
tagName: `${COMPONENT_NAME}.Date`,
|
||||
children: [set(date, 'properties.className', 'changelog-date')],
|
||||
};
|
||||
|
||||
const listWrap = {
|
||||
type: 'element',
|
||||
tagName: `${COMPONENT_NAME}.Details`,
|
||||
children: [set(list, 'properties.className', 'changelog-details')],
|
||||
};
|
||||
|
||||
const wrapper = {
|
||||
type: 'element',
|
||||
tagName: COMPONENT_NAME,
|
||||
properties: {
|
||||
[WRAPPER_FLAG]: true,
|
||||
},
|
||||
JSXAttributes: [
|
||||
{
|
||||
type: 'JSXAttribute',
|
||||
name: 'version',
|
||||
value: JSON.stringify(version),
|
||||
},
|
||||
{
|
||||
type: 'JSXAttribute',
|
||||
name: 'date',
|
||||
value: JSON.stringify(dateStr),
|
||||
},
|
||||
],
|
||||
children: [headingWrap, dateWrap, listWrap],
|
||||
};
|
||||
|
||||
parent.children.splice(startIdx, 0, wrapper);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default rehypeChangelog;
|
@ -1,176 +0,0 @@
|
||||
(function createMirrorModal() {
|
||||
if (
|
||||
(navigator.languages.includes('zh') || navigator.languages.includes('zh-CN')) &&
|
||||
/-cn\/?$/.test(window.location.pathname) &&
|
||||
!['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(window.location.hostname) &&
|
||||
!window.location.host.includes('surge') &&
|
||||
window.location.hostname !== 'localhost'
|
||||
) {
|
||||
const ANTD_DOT_NOT_SHOW_MIRROR_MODAL = 'ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL';
|
||||
|
||||
const lastShowTime = window.localStorage.getItem(ANTD_DOT_NOT_SHOW_MIRROR_MODAL);
|
||||
if (
|
||||
lastShowTime &&
|
||||
lastShowTime !== 'true' &&
|
||||
Date.now() - new Date(lastShowTime).getTime() < 7 * 24 * 60 * 60 * 1000
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
@keyframes mirror-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mirror-zoom-in {
|
||||
from {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.mirror-modal-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
z-index: 9999;
|
||||
animation: mirror-fade-in 0.3s forwards;
|
||||
}
|
||||
|
||||
.mirror-modal-dialog {
|
||||
position: fixed;
|
||||
top: 120px;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
margin: 0 auto;
|
||||
width: 420px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #eee;
|
||||
background: #fff;
|
||||
padding: 20px 24px;
|
||||
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
animation: mirror-zoom-in 0.3s forwards;
|
||||
box-sizing: border-box;
|
||||
max-width: 100vw;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.mirror-modal-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
align-self: flex-start;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mirror-modal-content {
|
||||
font-size: 14px;
|
||||
align-self: flex-start;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.mirror-modal-btns {
|
||||
align-self: flex-end;
|
||||
margin-top: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mirror-modal-btn {
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
padding: 4px 16px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.mirror-modal-confirm-btn {
|
||||
background: #1677ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mirror-modal-confirm-btn:hover {
|
||||
background: #4096ff;
|
||||
}
|
||||
|
||||
.mirror-modal-confirm-btn:active {
|
||||
background: #0958d9;
|
||||
}
|
||||
|
||||
.mirror-modal-cancel-btn {
|
||||
border: 1px solid #eee;
|
||||
color: #000;
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
|
||||
.mirror-modal-cancel-btn:hover {
|
||||
border-color: #4096ff;
|
||||
color: #4096ff
|
||||
}
|
||||
|
||||
.mirror-modal-cancel-btn:active {
|
||||
border-color: #0958d9;
|
||||
color: #0958d9;
|
||||
}
|
||||
`;
|
||||
document.head.append(style);
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'mirror-modal-mask';
|
||||
|
||||
const dialog = document.createElement('div');
|
||||
dialog.className = 'mirror-modal-dialog';
|
||||
modal.append(dialog);
|
||||
|
||||
const title = document.createElement('div');
|
||||
title.className = 'mirror-modal-title';
|
||||
title.textContent = '提示';
|
||||
dialog.append(title);
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'mirror-modal-content';
|
||||
content.textContent = '🚀 国内用户推荐访问国内镜像以获得极速体验~';
|
||||
dialog.append(content);
|
||||
|
||||
const btnWrapper = document.createElement('div');
|
||||
btnWrapper.className = 'mirror-modal-btns';
|
||||
dialog.append(btnWrapper);
|
||||
|
||||
const cancelBtn = document.createElement('a');
|
||||
cancelBtn.className = 'mirror-modal-cancel-btn mirror-modal-btn';
|
||||
cancelBtn.textContent = '7 天内不再显示';
|
||||
btnWrapper.append(cancelBtn);
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
window.localStorage.setItem(ANTD_DOT_NOT_SHOW_MIRROR_MODAL, new Date().toISOString());
|
||||
document.body.removeChild(modal);
|
||||
document.head.removeChild(style);
|
||||
document.body.style.overflow = '';
|
||||
});
|
||||
|
||||
const confirmBtn = document.createElement('a');
|
||||
confirmBtn.className = 'mirror-modal-confirm-btn mirror-modal-btn';
|
||||
confirmBtn.href = window.location.href.replace(window.location.host, 'ant-design.antgroup.com');
|
||||
confirmBtn.textContent = '🚀 立刻前往';
|
||||
btnWrapper.append(confirmBtn);
|
||||
|
||||
document.body.append(modal);
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
})();
|
230
.dumi/scripts/mirror-notify.js
Normal file
230
.dumi/scripts/mirror-notify.js
Normal file
@ -0,0 +1,230 @@
|
||||
(function createMirrorModal() {
|
||||
const SIGN = Symbol.for('antd.mirror-notify');
|
||||
const always = window.localStorage.getItem('DEBUG') === 'antd';
|
||||
const officialChinaMirror = 'https://ant-design.antgroup.com';
|
||||
|
||||
const enabledCondition = [
|
||||
// Check if the browser language is Chinese
|
||||
navigator.languages.includes('zh') || navigator.languages.includes('zh-CN'),
|
||||
// Check if the URL path ends with -cn
|
||||
/-cn\/?$/.test(window.location.pathname),
|
||||
// chinese mirror URL
|
||||
!['ant-design.gitee.io', new URL(officialChinaMirror).hostname].includes(
|
||||
window.location.hostname,
|
||||
),
|
||||
// PR review URL
|
||||
!window.location.host.includes('surge'),
|
||||
// development mode
|
||||
!['127.0.0.1', 'localhost'].includes(window.location.hostname),
|
||||
];
|
||||
|
||||
const isEnabled = always || enabledCondition.every(Boolean);
|
||||
|
||||
if (!isEnabled) return;
|
||||
|
||||
const prefixCls = 'antd-mirror-notify';
|
||||
const primaryColor = '#1677ff';
|
||||
|
||||
function insertCss() {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
transform: translate3d(100%, 0, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.${prefixCls} {
|
||||
position: fixed;
|
||||
inset-inline-end: 12px;
|
||||
inset-block-start: 12px;
|
||||
z-index: 9999;
|
||||
width: 360px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
animation: slideInRight 0.3s ease-in-out;
|
||||
}
|
||||
.${prefixCls}-content {
|
||||
padding: 16px;
|
||||
}
|
||||
.${prefixCls}-content a {
|
||||
color: ${primaryColor};
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.${prefixCls}-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-block-end: 8px;
|
||||
}
|
||||
.${prefixCls}-message {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
line-height: 1.57;
|
||||
}
|
||||
.${prefixCls}-footer {
|
||||
display: none;
|
||||
margin-block-start: 16px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.${prefixCls}-progress {
|
||||
position: relative;
|
||||
inset-inline-end: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.${prefixCls}-progress::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: var(--progress, 0%);
|
||||
background-color: ${primaryColor};
|
||||
transition: width 0.05s linear; /* Adjusted for smoother animation matching refreshRate */
|
||||
}
|
||||
.${prefixCls}-close {
|
||||
all: unset;
|
||||
position: absolute;
|
||||
inset-inline-end: 2px;
|
||||
inset-block-start: 2px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.${prefixCls}-close:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.${prefixCls}-action {
|
||||
all: unset;
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
background-color: ${primaryColor};
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.append(style);
|
||||
}
|
||||
|
||||
function createNotification() {
|
||||
insertCss();
|
||||
|
||||
const notify = document.createElement('div');
|
||||
notify.className = `${prefixCls} slideInRight`;
|
||||
notify.innerHTML = `
|
||||
<div class="${prefixCls}-content">
|
||||
<div class="${prefixCls}-title">🇨🇳 访问不畅?试试国内镜像</div>
|
||||
<div class="${prefixCls}-message">
|
||||
国内镜像站点可以帮助您更快地访问文档和资源。<br>
|
||||
请尝试访问 <a href="${officialChinaMirror}">国内镜像站点</a>,以获得更好的体验。
|
||||
</div>
|
||||
<div class="${prefixCls}-footer">
|
||||
<button class="${prefixCls}-action">🚀 立即前往</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="${prefixCls}-close">X</button>
|
||||
<div class="${prefixCls}-progress" style="--progress: 100%;"></div>
|
||||
`;
|
||||
document.body.appendChild(notify);
|
||||
|
||||
notify.querySelector(`.${prefixCls}-close`).addEventListener('click', () => {
|
||||
removeNotify();
|
||||
});
|
||||
|
||||
notify.querySelector(`.${prefixCls}-action`).addEventListener('click', () => {
|
||||
window.location.href = officialChinaMirror;
|
||||
removeNotify();
|
||||
});
|
||||
|
||||
const refreshRate = 50; // ms
|
||||
const duration = 10; // s
|
||||
const step = 100 / ((duration * 1000) / refreshRate);
|
||||
let progressInterval = -1;
|
||||
|
||||
function removeNotify() {
|
||||
clearInterval(progressInterval);
|
||||
notify.remove();
|
||||
}
|
||||
|
||||
const progressEl = notify.querySelector(`.${prefixCls}-progress`);
|
||||
let currentProgressValue = 100;
|
||||
|
||||
const progress = {
|
||||
get value() {
|
||||
return currentProgressValue;
|
||||
},
|
||||
set value(val) {
|
||||
currentProgressValue = Math.max(0, Math.min(100, val));
|
||||
progressEl.style.setProperty('--progress', `${currentProgressValue}%`);
|
||||
},
|
||||
};
|
||||
|
||||
function startProgressTimer() {
|
||||
if (progressInterval !== -1) {
|
||||
clearInterval(progressInterval);
|
||||
}
|
||||
progressInterval = setInterval(() => {
|
||||
if (progress.value <= 0) {
|
||||
removeNotify();
|
||||
} else {
|
||||
progress.value -= step;
|
||||
}
|
||||
}, refreshRate);
|
||||
}
|
||||
|
||||
startProgressTimer();
|
||||
|
||||
notify.addEventListener('mouseenter', () => {
|
||||
clearInterval(progressInterval);
|
||||
});
|
||||
|
||||
notify.addEventListener('mouseleave', () => {
|
||||
startProgressTimer();
|
||||
});
|
||||
}
|
||||
|
||||
// 断定网络不畅阈值(秒)
|
||||
const delayDuration = 3;
|
||||
|
||||
const reactTimeoutId = setTimeout(() => {
|
||||
if (typeof window[SIGN]?.YES === 'undefined') {
|
||||
console.error(
|
||||
`antd.mirror-notify: 页面加载超过 ${delayDuration} 秒,可能是网络不畅。\n请尝试访问国内镜像站点。%c${officialChinaMirror}`,
|
||||
`color: ${primaryColor}; font-weight: bold;`,
|
||||
);
|
||||
createNotification();
|
||||
}
|
||||
}, delayDuration * 1000);
|
||||
|
||||
// 交给 React effect 清理
|
||||
window[SIGN] = function stopMirrorNotify() {
|
||||
window[SIGN].YES = Date.now();
|
||||
clearTimeout(reactTimeoutId);
|
||||
};
|
||||
})();
|
@ -160,7 +160,7 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
|
||||
{title}
|
||||
<Popover
|
||||
title={null}
|
||||
destroyTooltipOnHide
|
||||
destroyOnHidden
|
||||
styles={{ root: { width: 400 } }}
|
||||
content={
|
||||
<Typography>
|
||||
|
@ -1,41 +1,8 @@
|
||||
import React, { Suspense, useEffect, useState } from 'react';
|
||||
import { Tooltip } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { Tooltip, App } from 'antd';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { FormattedMessage } from 'dumi';
|
||||
|
||||
import { ping } from '../../utils';
|
||||
|
||||
let pingDeferrer: PromiseLike<boolean>;
|
||||
|
||||
const codeBlockJs =
|
||||
'https://renderoffice.a' +
|
||||
'lipay' +
|
||||
'objects.com/p' +
|
||||
'/yuyan/180020010001206410/parseFileData-v1.0.1.js';
|
||||
|
||||
function useShowCodeBlockButton() {
|
||||
const [showCodeBlockButton, setShowCodeBlockButton] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
pingDeferrer ??= new Promise<boolean>((resolve) => {
|
||||
ping((status) => {
|
||||
if (status !== 'timeout' && status !== 'error') {
|
||||
// Async insert `codeBlockJs` into body end
|
||||
const script = document.createElement('script');
|
||||
script.src = codeBlockJs;
|
||||
script.async = true;
|
||||
document.body.appendChild(script);
|
||||
|
||||
return resolve(true);
|
||||
}
|
||||
return resolve(false);
|
||||
});
|
||||
});
|
||||
pingDeferrer.then(setShowCodeBlockButton);
|
||||
}, []);
|
||||
|
||||
return showCodeBlockButton;
|
||||
}
|
||||
|
||||
interface CodeBlockButtonProps {
|
||||
title?: string;
|
||||
dependencies: Record<PropertyKey, string>;
|
||||
@ -43,7 +10,8 @@ interface CodeBlockButtonProps {
|
||||
}
|
||||
|
||||
const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies = {}, jsx }) => {
|
||||
const showCodeBlockButton = useShowCodeBlockButton();
|
||||
const { message } = App.useApp();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const codeBlockPrefillConfig = {
|
||||
title: `${title} - antd@${dependencies.antd}`,
|
||||
@ -57,24 +25,56 @@ const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies =
|
||||
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
|
||||
};
|
||||
|
||||
return showCodeBlockButton ? (
|
||||
const openHituCodeBlockFn = () => {
|
||||
setLoading(false);
|
||||
// @ts-ignore
|
||||
if (window.openHituCodeBlock) {
|
||||
// @ts-ignore
|
||||
window.openHituCodeBlock(JSON.stringify(codeBlockPrefillConfig));
|
||||
} else {
|
||||
message.error('此功能仅在内网环境可用');
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
const scriptId = 'hitu-code-block-js';
|
||||
const existScript = document.getElementById(scriptId) as HTMLScriptElement | null;
|
||||
// @ts-ignore
|
||||
if (existScript?.dataset.loaded) {
|
||||
openHituCodeBlockFn();
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://renderoffice.alipayobjects.com/p/yuyan/180020010001206410/parseFileData-v1.0.1.js?t=${Date.now()}`;
|
||||
script.async = true;
|
||||
script.id = scriptId;
|
||||
script.onload = () => {
|
||||
script.dataset.loaded = 'true';
|
||||
openHituCodeBlockFn();
|
||||
};
|
||||
script.onerror = () => {
|
||||
openHituCodeBlockFn();
|
||||
};
|
||||
document.body.appendChild(script);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip title={<FormattedMessage id="app.demo.codeblock" />}>
|
||||
<div className="code-box-code-action">
|
||||
<img
|
||||
alt="codeblock"
|
||||
src="https://mdn.alipayobjects.com/huamei_wtld8u/afts/img/A*K8rjSJpTNQ8AAAAAAAAAAAAADhOIAQ/original"
|
||||
className="code-box-codeblock"
|
||||
onClick={() => {
|
||||
openHituCodeBlock(JSON.stringify(codeBlockPrefillConfig));
|
||||
}}
|
||||
/>
|
||||
{loading ? (
|
||||
<LoadingOutlined className="code-box-codeblock" />
|
||||
) : (
|
||||
<img
|
||||
alt="codeblock"
|
||||
src="https://mdn.alipayobjects.com/huamei_wtld8u/afts/img/A*K8rjSJpTNQ8AAAAAAAAAAAAADhOIAQ/original"
|
||||
className="code-box-codeblock"
|
||||
onClick={handleClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : null;
|
||||
);
|
||||
};
|
||||
|
||||
export default (props: CodeBlockButtonProps) => (
|
||||
<Suspense>
|
||||
<CodeBlockButton {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
export default CodeBlockButton;
|
||||
|
@ -1,91 +0,0 @@
|
||||
import React, { Suspense, useEffect, useRef, useState } from 'react';
|
||||
import { Tooltip } from 'antd';
|
||||
import { FormattedMessage } from 'dumi';
|
||||
|
||||
import type { IPreviewerProps } from 'dumi';
|
||||
|
||||
import RiddleIcon from '../../icons/RiddleIcon';
|
||||
import { ping } from '../../utils';
|
||||
|
||||
let pingDeferrer: PromiseLike<boolean>;
|
||||
|
||||
function useShowRiddleButton() {
|
||||
const [showRiddleButton, setShowRiddleButton] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
pingDeferrer ??= new Promise<boolean>((resolve) => {
|
||||
ping((status) => {
|
||||
if (status !== 'timeout' && status !== 'error') {
|
||||
return resolve(true);
|
||||
}
|
||||
|
||||
return resolve(false);
|
||||
});
|
||||
});
|
||||
pingDeferrer.then(setShowRiddleButton);
|
||||
}, []);
|
||||
|
||||
return showRiddleButton;
|
||||
}
|
||||
|
||||
interface RiddleButtonProps {
|
||||
title?: string;
|
||||
dependencies: Record<PropertyKey, string>;
|
||||
jsx: string;
|
||||
track: ({
|
||||
type,
|
||||
demo,
|
||||
}: {
|
||||
type: string;
|
||||
demo: string;
|
||||
}) => void;
|
||||
asset: IPreviewerProps['asset'];
|
||||
}
|
||||
|
||||
const RiddleButton: React.FC<RiddleButtonProps> = ({
|
||||
title,
|
||||
dependencies = {},
|
||||
jsx,
|
||||
track,
|
||||
asset,
|
||||
}) => {
|
||||
const riddleIconRef = useRef<HTMLFormElement>(null);
|
||||
const showRiddleButton = useShowRiddleButton();
|
||||
|
||||
const riddlePrefillConfig = {
|
||||
title: `${title} - antd@${dependencies.antd}`,
|
||||
js: `${
|
||||
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
|
||||
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
|
||||
/export default/,
|
||||
'const ComponentDemo =',
|
||||
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
|
||||
css: '',
|
||||
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
|
||||
};
|
||||
|
||||
return showRiddleButton ? (
|
||||
<form
|
||||
className="code-box-code-action"
|
||||
action="//riddle.alibaba-inc.com/riddles/define"
|
||||
method="POST"
|
||||
target="_blank"
|
||||
ref={riddleIconRef}
|
||||
onClick={() => {
|
||||
track({ type: 'riddle', demo: asset.id });
|
||||
riddleIconRef.current?.submit();
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="data" value={JSON.stringify(riddlePrefillConfig)} />
|
||||
<Tooltip title={<FormattedMessage id="app.demo.riddle" />}>
|
||||
<RiddleIcon className="code-box-riddle" />
|
||||
</Tooltip>
|
||||
</form>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default (props: RiddleButtonProps) => (
|
||||
<Suspense>
|
||||
<RiddleButton {...props} />
|
||||
</Suspense>
|
||||
);
|
131
.dumi/theme/builtins/RefinedChangelog/index.tsx
Normal file
131
.dumi/theme/builtins/RefinedChangelog/index.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import * as React from 'react';
|
||||
import { BugOutlined } from '@ant-design/icons';
|
||||
import { Button, Flex, Popover, theme } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import { matchDeprecated } from '../../utils';
|
||||
|
||||
interface RefinedChangelogProps {
|
||||
version?: string;
|
||||
date?: string;
|
||||
}
|
||||
|
||||
interface ContextProps {
|
||||
version: string;
|
||||
date?: Dayjs;
|
||||
isDeprecated?: boolean;
|
||||
reason?: string[];
|
||||
}
|
||||
|
||||
const ChangelogContext = React.createContext<ContextProps>({
|
||||
version: '0.0.0',
|
||||
});
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
deprecatedTitle: '🚨 该版本存在缺陷, 请升级至下一个新版本',
|
||||
},
|
||||
en: {
|
||||
deprecatedTitle: '🚨 This version has defects, please upgrade to the next version',
|
||||
},
|
||||
};
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => ({
|
||||
container: css`
|
||||
margin-block: ${token.margin}px;
|
||||
padding: ${token.padding}px;
|
||||
|
||||
.changelog-version {
|
||||
line-height: ${token.lineHeight} !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
`,
|
||||
isDeprecated: css``,
|
||||
}));
|
||||
|
||||
function RefinedChangelog(props: React.PropsWithChildren<RefinedChangelogProps>) {
|
||||
const { version, date, children } = props;
|
||||
|
||||
const { styles, cx } = useStyle();
|
||||
|
||||
const memoizedValue = React.useMemo(() => {
|
||||
const realVersion = version || '0.0.0';
|
||||
const bugVersionInfo = matchDeprecated(realVersion);
|
||||
return {
|
||||
version: realVersion,
|
||||
isDeprecated: !!bugVersionInfo?.match,
|
||||
reason: bugVersionInfo?.reason,
|
||||
date: date ? dayjs(date) : undefined,
|
||||
};
|
||||
}, [version, date]);
|
||||
|
||||
return (
|
||||
<ChangelogContext.Provider value={memoizedValue}>
|
||||
<div
|
||||
className={cx('refined-changelog', styles.container, {
|
||||
[styles.isDeprecated]: memoizedValue.isDeprecated,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</ChangelogContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Version({ children }: React.PropsWithChildren) {
|
||||
const { isDeprecated, reason } = React.use(ChangelogContext);
|
||||
const { token } = theme.useToken();
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
if (!isDeprecated) {
|
||||
return children;
|
||||
}
|
||||
|
||||
const reasonContent = (
|
||||
<Flex vertical align="start">
|
||||
{reason?.map((item, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
type="link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={item}
|
||||
icon={<BugOutlined />}
|
||||
>
|
||||
{item}
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex align="center" gap="small">
|
||||
{children}
|
||||
<Popover placement="right" title={locale.deprecatedTitle} content={reasonContent}>
|
||||
<BugOutlined
|
||||
style={{
|
||||
lineHeight: token.lineHeight,
|
||||
fontSize: token.fontSize,
|
||||
color: token.colorErrorText,
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
function DateComp(props: React.PropsWithChildren) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
function Details(props: React.PropsWithChildren) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
export default Object.assign(RefinedChangelog, {
|
||||
Version,
|
||||
Date: DateComp,
|
||||
Details,
|
||||
});
|
@ -3,19 +3,13 @@ import { BugOutlined } from '@ant-design/icons';
|
||||
import { Button, Drawer, Flex, Grid, Popover, Tag, Timeline, Typography } from 'antd';
|
||||
import type { TimelineItemProps } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import semver from 'semver';
|
||||
|
||||
import deprecatedVersions from '../../../../BUG_VERSIONS.json';
|
||||
import useFetch from '../../../hooks/useFetch';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import useLocation from '../../../hooks/useLocation';
|
||||
import { matchDeprecated } from '../../utils';
|
||||
import Link from '../Link';
|
||||
|
||||
interface MatchDeprecatedResult {
|
||||
match?: string;
|
||||
reason: string[];
|
||||
}
|
||||
|
||||
interface ChangelogInfo {
|
||||
version: string;
|
||||
changelog: string;
|
||||
@ -24,17 +18,6 @@ interface ChangelogInfo {
|
||||
releaseDate: string;
|
||||
}
|
||||
|
||||
function matchDeprecated(v: string): MatchDeprecatedResult {
|
||||
const match = Object.keys(deprecatedVersions).find((depreciated) =>
|
||||
semver.satisfies(v, depreciated),
|
||||
);
|
||||
const reason = deprecatedVersions[match as keyof typeof deprecatedVersions] || [];
|
||||
return {
|
||||
match,
|
||||
reason: Array.isArray(reason) ? reason : [reason],
|
||||
};
|
||||
}
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => ({
|
||||
listWrap: css`
|
||||
> li {
|
||||
@ -277,7 +260,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
|
||||
{version}
|
||||
{bugVersionInfo.match && (
|
||||
<Popover
|
||||
destroyTooltipOnHide
|
||||
destroyOnHidden
|
||||
placement="right"
|
||||
title={<span className={styles.bugReasonTitle}>{locale.bugList}</span>}
|
||||
content={
|
||||
@ -327,7 +310,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
|
||||
onClick: () => setShow(true),
|
||||
})}
|
||||
<Drawer
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
className={styles.drawerContent}
|
||||
title={locale.changelog}
|
||||
extra={
|
||||
|
87
.dumi/theme/common/SelectSemanticTemplate.tsx
Normal file
87
.dumi/theme/common/SelectSemanticTemplate.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
|
||||
import useLocale from '../../hooks/useLocale';
|
||||
import SemanticPreview from './SemanticPreview';
|
||||
|
||||
export const locales = {
|
||||
cn: {
|
||||
root: '根元素',
|
||||
'popup.root': '弹出菜单元素',
|
||||
},
|
||||
en: {
|
||||
root: 'Root element',
|
||||
'popup.root': 'Popup element',
|
||||
},
|
||||
};
|
||||
|
||||
interface BlockProps {
|
||||
component: React.ComponentType<any>;
|
||||
options?: { value: string; label: string }[];
|
||||
defaultValue?: string;
|
||||
style?: React.CSSProperties;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const Block: React.FC<BlockProps> = ({ component: Component, options, defaultValue, ...props }) => {
|
||||
const divRef = React.useRef<HTMLDivElement>(null);
|
||||
return (
|
||||
<div ref={divRef} style={{ position: 'absolute', marginBottom: 80 }}>
|
||||
<Component
|
||||
{...props}
|
||||
open
|
||||
placement="bottomLeft"
|
||||
defaultValue={defaultValue}
|
||||
getPopupContainer={() => divRef.current}
|
||||
options={options}
|
||||
styles={{
|
||||
popup: { zIndex: 1 },
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface SelectSemanticTemplateProps {
|
||||
component: React.ComponentType<any>;
|
||||
componentName: string;
|
||||
defaultValue?: string;
|
||||
options?: { value: string; label: string }[];
|
||||
height?: number;
|
||||
onSearch?: (text: string) => void;
|
||||
placeholder?: string;
|
||||
style?: React.CSSProperties;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const SelectSemanticTemplate: React.FC<SelectSemanticTemplateProps> = ({
|
||||
component,
|
||||
defaultValue,
|
||||
options,
|
||||
height,
|
||||
style,
|
||||
componentName,
|
||||
...restProps
|
||||
}) => {
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
return (
|
||||
<SemanticPreview
|
||||
componentName={componentName}
|
||||
semantics={[
|
||||
{ name: 'root', desc: locale.root, version: '5.25.0' },
|
||||
{ name: 'popup.root', desc: locale['popup.root'], version: '5.25.0' },
|
||||
]}
|
||||
height={height}
|
||||
>
|
||||
<Block
|
||||
component={component}
|
||||
defaultValue={defaultValue}
|
||||
options={options}
|
||||
style={style}
|
||||
{...restProps}
|
||||
/>
|
||||
</SemanticPreview>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectSemanticTemplate;
|
@ -1,9 +1,12 @@
|
||||
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
|
||||
import React from 'react';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import get from 'rc-util/lib/utils/get';
|
||||
import set from 'rc-util/lib/utils/set';
|
||||
import { Col, ConfigProvider, Flex, Popover, Row, Tag, theme, Typography } from 'antd';
|
||||
import { createStyles, css } from 'antd-style';
|
||||
import classnames from 'classnames';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import Prism from 'prismjs';
|
||||
|
||||
const MARK_BORDER_SIZE = 2;
|
||||
|
||||
@ -19,6 +22,9 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
|
||||
padding: ${token.paddingMD}px;
|
||||
overflow: hidden;
|
||||
`,
|
||||
colWrapPaddingLess: css`
|
||||
padding: 0;
|
||||
`,
|
||||
listWrap: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -66,37 +72,77 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
|
||||
`,
|
||||
}));
|
||||
|
||||
function getSemanticCells(semanticPath: string) {
|
||||
return semanticPath.split('.');
|
||||
}
|
||||
|
||||
function HighlightExample(props: { componentName: string; semanticName: string }) {
|
||||
const { componentName, semanticName } = props;
|
||||
|
||||
const highlightCode = React.useMemo(() => {
|
||||
const classNames = set({}, getSemanticCells(semanticName), `my-classname`);
|
||||
const styles = set({}, getSemanticCells(semanticName), { color: 'red' });
|
||||
|
||||
function format(obj: object) {
|
||||
const str = JSON.stringify(obj, null, 2);
|
||||
return (
|
||||
str
|
||||
// Add space
|
||||
.split('\n')
|
||||
.map((line) => ` ${line}`)
|
||||
.join('\n')
|
||||
.trim()
|
||||
// Replace quotes
|
||||
.replace(/"/g, "'")
|
||||
// Remove key quotes
|
||||
.replace(/'([^']+)':/g, '$1:')
|
||||
);
|
||||
}
|
||||
|
||||
const code = `
|
||||
<${componentName}
|
||||
classNames={${format(classNames)}}
|
||||
styles={${format(styles)}}
|
||||
/>`.trim();
|
||||
|
||||
return Prism.highlight(code, Prism.languages.javascript, 'jsx');
|
||||
}, [componentName, semanticName]);
|
||||
|
||||
return (
|
||||
// biome-ignore lint: lint/security/noDangerouslySetInnerHtml
|
||||
<div dangerouslySetInnerHTML={{ __html: highlightCode }} />
|
||||
);
|
||||
}
|
||||
|
||||
export interface SemanticPreviewProps {
|
||||
componentName: string;
|
||||
semantics: { name: string; desc: string; version?: string }[];
|
||||
children: React.ReactElement<any>;
|
||||
height?: number;
|
||||
padding?: false;
|
||||
}
|
||||
|
||||
const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
||||
const { semantics = [], children, height, componentName = 'Component' } = props;
|
||||
const { semantics = [], children, height, padding, componentName = 'Component' } = props;
|
||||
const { token } = theme.useToken();
|
||||
|
||||
// ======================= Semantic =======================
|
||||
const getMarkClassName = React.useCallback(
|
||||
(semanticKey: string) => `semantic-mark-${semanticKey}`,
|
||||
(semanticKey: string) => `semantic-mark-${semanticKey}`.replace(/\./g, '-'),
|
||||
[],
|
||||
);
|
||||
|
||||
const semanticClassNames = React.useMemo<Record<string, string>>(() => {
|
||||
const classNames: Record<string, string> = {};
|
||||
let classNames: Record<string, string> = {};
|
||||
|
||||
semantics.forEach((semantic) => {
|
||||
classNames[semantic.name] = getMarkClassName(semantic.name);
|
||||
const pathCell = getSemanticCells(semantic.name);
|
||||
classNames = set(classNames, pathCell, getMarkClassName(semantic.name));
|
||||
});
|
||||
|
||||
return classNames;
|
||||
}, [semantics]);
|
||||
|
||||
const cloneNode = React.cloneElement(children, {
|
||||
classNames: semanticClassNames,
|
||||
});
|
||||
|
||||
// ======================== Hover =========================
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -137,11 +183,33 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
||||
};
|
||||
}, [hoverSemantic]);
|
||||
|
||||
const hoveredSemanticClassNames = React.useMemo(() => {
|
||||
if (!hoverSemantic) {
|
||||
return semanticClassNames;
|
||||
}
|
||||
|
||||
const hoverCell = getSemanticCells(hoverSemantic);
|
||||
const clone = set(
|
||||
semanticClassNames,
|
||||
hoverCell,
|
||||
classnames(get(semanticClassNames, hoverCell), getMarkClassName('active')),
|
||||
);
|
||||
|
||||
return clone;
|
||||
}, [semanticClassNames, hoverSemantic]);
|
||||
|
||||
// ======================== Render ========================
|
||||
const cloneNode = React.cloneElement(children, {
|
||||
classNames: hoveredSemanticClassNames,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classnames(styles.container)} ref={containerRef}>
|
||||
<Row style={{ minHeight: height }}>
|
||||
<Col span={16} className={classnames(styles.colWrap)}>
|
||||
<Col
|
||||
span={16}
|
||||
className={classnames(styles.colWrap, padding === false && styles.colWrapPaddingLess)}
|
||||
>
|
||||
<ConfigProvider theme={{ token: { motion: false } }}>{cloneNode}</ConfigProvider>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
@ -166,16 +234,10 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
||||
<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}>`}
|
||||
<HighlightExample
|
||||
componentName={componentName}
|
||||
semanticName={semantic.name}
|
||||
/>
|
||||
</code>
|
||||
</pre>
|
||||
</Typography>
|
@ -1,4 +1,4 @@
|
||||
import React, { use } from 'react';
|
||||
import React, { use, useRef } from 'react';
|
||||
import { BgColorsOutlined, LinkOutlined, SmileOutlined, SunOutlined } from '@ant-design/icons';
|
||||
import { Badge, Button, Dropdown } from 'antd';
|
||||
import type { MenuProps } from 'antd';
|
||||
@ -22,6 +22,7 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
|
||||
const { pathname, search } = useLocation();
|
||||
const { theme, updateSiteConfig } = use<SiteContextProps>(SiteContext);
|
||||
const toggleAnimationTheme = useThemeAnimation();
|
||||
const lastThemeKey = useRef<string>(theme.includes('dark') ? 'dark' : 'light');
|
||||
|
||||
const badge = <Badge color="blue" style={{ marginTop: -1 }} />;
|
||||
|
||||
@ -95,10 +96,12 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
|
||||
// 处理主题切换
|
||||
const handleThemeChange = (key: string, domEvent: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
// 主题编辑器特殊处理
|
||||
if (key === 'theme-editor') {
|
||||
if (key === 'theme-editor' || key === lastThemeKey.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastThemeKey.current = key;
|
||||
|
||||
// 亮色/暗色模式切换时应用动画效果
|
||||
if (key === 'dark' || key === 'light') {
|
||||
toggleAnimationTheme(domEvent, theme.includes('dark'));
|
||||
|
@ -44,13 +44,24 @@ export default () => {
|
||||
html {
|
||||
direction: initial;
|
||||
|
||||
@supports (overflow-x: clip) {
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
&.rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
@supports (overflow-x: clip) {
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
@supports not (overflow-x: clip) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
color: ${token.colorText};
|
||||
font-size: ${token.fontSize}px;
|
||||
font-family: ${token.fontFamily};
|
||||
|
@ -140,6 +140,12 @@ const GlobalLayout: React.FC = () => {
|
||||
// Handle isMobile
|
||||
updateMobileMode();
|
||||
|
||||
// 配合 dumi 的 mirror-notify 脚本使用
|
||||
const retrieveMirrorNotification = (window as any)[Symbol.for('antd.mirror-notify')];
|
||||
if (typeof retrieveMirrorNotification === 'function') {
|
||||
retrieveMirrorNotification();
|
||||
}
|
||||
|
||||
window.addEventListener('resize', updateMobileMode);
|
||||
return () => {
|
||||
window.removeEventListener('resize', updateMobileMode);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { Col, Flex, Skeleton, Space, Typography } from 'antd';
|
||||
import { Col, Flex, FloatButton, Skeleton, Space, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage, useRouteMeta } from 'dumi';
|
||||
|
||||
@ -92,7 +92,10 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
designUrl={meta.frontmatter.designUrl}
|
||||
/>
|
||||
)}
|
||||
<div style={{ minHeight: 'calc(100vh - 64px)' }}>{children}</div>
|
||||
<div style={{ minHeight: 'calc(100vh - 64px)' }}>
|
||||
{children}
|
||||
<FloatButton.BackTop />
|
||||
</div>
|
||||
<InViewSuspense fallback={null}>
|
||||
<ColumnCard
|
||||
zhihuLink={meta.frontmatter.zhihu_url}
|
||||
|
@ -306,7 +306,7 @@ const Header: React.FC = () => {
|
||||
className={styles.versionSelect}
|
||||
defaultValue={pkg.version}
|
||||
onChange={handleVersionChange}
|
||||
dropdownStyle={getDropdownStyle}
|
||||
styles={{ popup: { root: getDropdownStyle } }}
|
||||
popupMatchSelectWidth={false}
|
||||
getPopupContainer={(trigger) => trigger.parentNode}
|
||||
options={versionOptions}
|
||||
@ -338,7 +338,7 @@ const Header: React.FC = () => {
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Tooltip title="GitHub" destroyTooltipOnHide>
|
||||
<Tooltip title="GitHub" destroyOnHidden>
|
||||
<Button type="text" icon={<GithubOutlined />} style={{ fontSize: 16 }} />
|
||||
</Tooltip>
|
||||
</a>,
|
||||
|
@ -1,107 +0,0 @@
|
||||
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 ? '通过' : '失败');
|
@ -1,93 +0,0 @@
|
||||
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'");
|
||||
});
|
||||
});
|
@ -1,7 +1,8 @@
|
||||
import semver from 'semver';
|
||||
import flatten from 'lodash/flatten';
|
||||
import flattenDeep from 'lodash/flattenDeep';
|
||||
|
||||
import themeConfig from './themeConfig';
|
||||
import deprecatedVersions from '../../../BUG_VERSIONS.json';
|
||||
import themeConfig from '../themeConfig';
|
||||
|
||||
interface Meta {
|
||||
skip?: boolean;
|
||||
@ -24,6 +25,11 @@ interface Orders {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
interface MatchDeprecatedResult {
|
||||
match?: string;
|
||||
reason: string[];
|
||||
}
|
||||
|
||||
export function getMenuItems(
|
||||
moduleData: ModuleDataItem[],
|
||||
locale: string,
|
||||
@ -151,28 +157,6 @@ export function getLocalizedPathname(
|
||||
return { pathname: fullPath, search };
|
||||
}
|
||||
|
||||
export function ping(callback: (status: string) => void) {
|
||||
const url =
|
||||
'https://private-a' +
|
||||
'lipay' +
|
||||
'objects.alip' +
|
||||
'ay.com/alip' +
|
||||
'ay-rmsdeploy-image/rmsportal/RKuAiriJqrUhyqW.png';
|
||||
const img = new Image();
|
||||
let done: boolean;
|
||||
const finish = (status: string) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
img.src = '';
|
||||
callback(status);
|
||||
}
|
||||
};
|
||||
img.onload = () => finish('responded');
|
||||
img.onerror = () => finish('error');
|
||||
img.src = url;
|
||||
return setTimeout(() => finish('timeout'), 1500);
|
||||
}
|
||||
|
||||
export function isLocalStorageNameSupported() {
|
||||
const testKey = 'test';
|
||||
const storage = window.localStorage;
|
||||
@ -223,4 +207,15 @@ export function getMetaDescription(jml?: any[] | null) {
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
export function matchDeprecated(v: string): MatchDeprecatedResult {
|
||||
const match = Object.keys(deprecatedVersions).find((depreciated) =>
|
||||
semver.satisfies(v, depreciated),
|
||||
);
|
||||
const reason = deprecatedVersions[match as keyof typeof deprecatedVersions] || [];
|
||||
return {
|
||||
match,
|
||||
reason: Array.isArray(reason) ? reason : [reason],
|
||||
};
|
||||
}
|
||||
|
||||
export const getThemeConfig = () => themeConfig;
|
@ -1,273 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
@ -2,27 +2,16 @@ import * as ts from 'typescript';
|
||||
import { format } from '@prettier/sync';
|
||||
|
||||
/**
|
||||
* TypeScript 到 JavaScript 代码转换工具
|
||||
* 将 TypeScript 代码(含 TSX)转换为 JavaScript 代码(含 JSX)。
|
||||
*
|
||||
* 这个模块用于将 TypeScript 代码(包括 TSX)转换为 JavaScript 代码(包括 JSX)
|
||||
* 主要用于替代 sylvanas 库的功能,用于文档站点中的代码示例转换
|
||||
* 使用 TypeScript 编译器 API 移除类型注解和类型导入,保留 JSX 语法和注释,并将代码转换为 JavaScript。转换结果会通过 Prettier 进行格式化,提升可读性。适用于文档示例、代码展示等场景。
|
||||
*
|
||||
* 实现原理:使用 TypeScript 编译器 API 将 TS 代码转换为 JS 代码
|
||||
* @param tsCode - 输入的 TypeScript 代码字符串。
|
||||
* @returns 转换并格式化后的 JavaScript 代码字符串。
|
||||
*
|
||||
* 特性:
|
||||
* 1. 删除所有类型注解
|
||||
* 2. 保留 JSX 语法
|
||||
* 3. 删除类型导入
|
||||
* 4. 转换 ES6+ 语法为更兼容的语法(如空值合并运算符)
|
||||
* 5. 保留原始代码风格和注释
|
||||
* 6. 使用 Prettier 格式化输出代码,提高可读性
|
||||
* 7. 处理 React 组件和 hooks 的转换
|
||||
* 8. 支持 TypeScript 特有语法(如装饰器、枚举等)的转换
|
||||
*
|
||||
* @param tsCode TypeScript 代码字符串
|
||||
* @returns 转换后的 JavaScript 代码
|
||||
* @remark 若 Prettier 格式化失败,将返回未格式化的转换结果。
|
||||
*/
|
||||
export default function (tsCode: string): string {
|
||||
export default function tsToJs(tsCode: string): string {
|
||||
// 设置编译器选项,保留 JSX 语法
|
||||
const compilerOptions: ts.CompilerOptions = {
|
||||
target: ts.ScriptTarget.ES2016, // 目标 ECMAScript 版本
|
||||
@ -61,15 +50,3 @@ export default function (tsCode: string): string {
|
||||
return result.outputText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 TypeScript 代码转换为 JavaScript 代码
|
||||
*
|
||||
* 这是一个公开的 API,供测试和外部调用使用
|
||||
*
|
||||
* @param tsCode TypeScript 代码字符串
|
||||
* @returns 转换后的 JavaScript 代码
|
||||
*/
|
||||
export function parseText(tsCode: string): string {
|
||||
return exports.default(tsCode);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import * as fs from 'fs-extra';
|
||||
import os from 'node:os';
|
||||
|
||||
import rehypeAntd from './.dumi/rehypeAntd';
|
||||
import rehypeChangelog from './.dumi/rehypeChangelog';
|
||||
import remarkAntd from './.dumi/remarkAntd';
|
||||
import remarkAnchor from './.dumi/remarkAnchor';
|
||||
import { version } from './package.json';
|
||||
@ -52,7 +53,7 @@ export default defineConfig({
|
||||
// https://github.com/ant-design/ant-design/issues/46628
|
||||
'@ant-design/icons$': '@ant-design/icons/lib',
|
||||
},
|
||||
extraRehypePlugins: [rehypeAntd],
|
||||
extraRehypePlugins: [rehypeAntd, rehypeChangelog],
|
||||
extraRemarkPlugins: [remarkAntd, remarkAnchor],
|
||||
metas: [
|
||||
{ name: 'theme-color', content: '#1677ff' },
|
||||
@ -189,7 +190,7 @@ export default defineConfig({
|
||||
{
|
||||
async: true,
|
||||
content: fs
|
||||
.readFileSync(path.join(__dirname, '.dumi', 'scripts', 'mirror-modal.js'))
|
||||
.readFileSync(path.join(__dirname, '.dumi', 'scripts', 'mirror-notify.js'))
|
||||
.toString(),
|
||||
},
|
||||
{
|
||||
|
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,8 +1,8 @@
|
||||
<!--
|
||||
First of all, thank you for your contribution! 😄
|
||||
For requesting to pull a new feature or bugfix, please send it from a feature/bugfix branch based on the `master` branch.
|
||||
Before submitting your pull request, please make sure the checklist below is confirmed.
|
||||
Your pull requests will be merged after one of the collaborators approve.
|
||||
Before submitting your pull request, please make sure the checklist below is filled out.
|
||||
Your pull requests will be merged after one of the collaborators approves.
|
||||
Thank you!
|
||||
-->
|
||||
|
||||
@ -41,7 +41,7 @@ Thank you!
|
||||
|
||||
### 📝 Change Log
|
||||
|
||||
> - Read [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) like a cat tracks a laser pointer.
|
||||
> - Read [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)! Track your changes, like a cat tracks a laser pointer.
|
||||
> - Describe the impact of the changes on developers, not the solution approach.
|
||||
> - Reference: https://ant.design/changelog
|
||||
|
||||
|
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@ -9,26 +9,22 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "05:00"
|
||||
timezone: Asia/Shanghai
|
||||
groups:
|
||||
rc-component-patch:
|
||||
dependency-type: production
|
||||
patterns:
|
||||
- "rc-*"
|
||||
- "@rc-component*"
|
||||
update-types: [patch]
|
||||
dependencies:
|
||||
dependency-type: production
|
||||
exclude-patterns:
|
||||
- "rc-*"
|
||||
- "@rc-component*"
|
||||
update-types: [major, minor]
|
||||
dev-dependencies:
|
||||
dependency-type: development
|
||||
update-types: [major]
|
||||
ignore:
|
||||
- dependency-name: "@ant-design/cssinjs"
|
||||
- dependency-name: "rc-*"
|
||||
- dependency-name: "@rc-component*"
|
||||
- dependency-name: "@ant-design*"
|
||||
- dependency-name: dayjs
|
||||
versions: [1.x]
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
|
2
.github/workflows/upgrade-deps.yml
vendored
2
.github/workflows/upgrade-deps.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }} # Cannot be default!!!
|
||||
assignees: 'afc163, zombieJ, xrkffgg, MadCcc'
|
||||
assignees: 'afc163, yoyo837, Wxh16144'
|
||||
title: "chore: upgrade deps"
|
||||
commit-message: "chore: upgrade deps"
|
||||
body: |
|
||||
|
@ -75,5 +75,7 @@
|
||||
"https://github.com/ant-design/ant-design/issues/51420",
|
||||
"https://github.com/ant-design/ant-design/issues/51430"
|
||||
],
|
||||
"5.22.6": ["https://github.com/ant-design/ant-design/issues/52124"]
|
||||
"5.22.6": ["https://github.com/ant-design/ant-design/issues/52124"],
|
||||
"5.24.8": ["https://github.com/ant-design/ant-design/issues/53652"],
|
||||
"5.25.0": ["https://github.com/ant-design/ant-design/issues/53764"]
|
||||
}
|
||||
|
@ -9,12 +9,89 @@ tag: vVERSION
|
||||
|
||||
#### Release Schedule
|
||||
|
||||
- Weekly release: patch version at the end of every week for routine bugfix (anytime for urgent bugfix).
|
||||
- Weekly release: patch version at the end of every week for routine bugfixes (anytime for an urgent bugfix).
|
||||
- Monthly release: minor version at the end of every month for new features.
|
||||
- Major version release is not included in this schedule for breaking change and new features.
|
||||
- Major version release is not included in this schedule for breaking changes and new features.
|
||||
|
||||
---
|
||||
|
||||
## 5.25.3
|
||||
|
||||
`2025-05-26`
|
||||
|
||||
- 🐞 Fix Typography.Text `delete` property not updating. [#53861](https://github.com/ant-design/ant-design/pull/53861) [@codingories](https://github.com/codingories)
|
||||
- 🐞 Fix the Statistic.Timer as a subcomponent of Tooltip could not display text prompts properly. [#53888](https://github.com/ant-design/ant-design/pull/53888) [@jin19980928](https://github.com/jin19980928)
|
||||
- 🐞 Fix the `style` setting of the Upload component did not take effect in more types. [#53877](https://github.com/ant-design/ant-design/pull/53877) [@QuentinHsu](https://github.com/QuentinHsu)
|
||||
- 💄 Fix the residual focus style after clicking Tabs. [#53901](https://github.com/ant-design/ant-design/pull/53901)
|
||||
|
||||
## 5.25.2
|
||||
|
||||
`2025-05-19`
|
||||
|
||||
- 🐞 Fix AutoComplete `onPaste` event callback not working problem on inside Input. [#53839](https://github.com/ant-design/ant-design/issues/53839) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 🐞 Fix ColorPicker cannot input for hex value. [#53814](https://github.com/ant-design/ant-design/pull/53814) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 🐞 Fix Statistic.Timer ssr hydrate issue. [#53817](https://github.com/ant-design/ant-design/pull/53817) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Fix Table header blink issue when sticky is enable. [#53803](https://github.com/ant-design/ant-design/pull/53803) [@afc163](https://github.com/afc163)
|
||||
- 💄 Fix Input.Search `variant="filled"` broken UI. [#53787](https://github.com/ant-design/ant-design/pull/53787) [@afc163](https://github.com/afc163)
|
||||
- TypeScript
|
||||
- 🤖 Fix Upload.Dragger does not accept generic parameter problem. [#53842](https://github.com/ant-design/ant-design/pull/53842) [@fnoopv](https://github.com/fnoopv)
|
||||
- 🤖 Remove Modal invalid properties type definition. [#53808](https://github.com/ant-design/ant-design/pull/53808) [@wanpan11](https://github.com/wanpan11)
|
||||
|
||||
## 5.25.1
|
||||
|
||||
`2025-05-09`
|
||||
|
||||
- 🐞 Splitter fix screen frozen when drag finished. [#53767](https://github.com/ant-design/ant-design/pull/53767) [@wanpan11](https://github.com/wanpan11)
|
||||
- 🌐 Image support Hebrew locale. [#53771](https://github.com/ant-design/ant-design/pull/53771) [@Sagie501](https://github.com/Sagie501)
|
||||
|
||||
## 5.25.0
|
||||
|
||||
`2025-05-07`
|
||||
|
||||
- 🔥 New component Statistic.Timer, supporting both counting up and down. [#53401](https://github.com/ant-design/ant-design/pull/53401) [@lcgash](https://github.com/lcgash)
|
||||
- 🆕 Tour add `actionsRender` prop to custom action button. [#53067](https://github.com/ant-design/ant-design/pull/53067) [@dengfuping](https://github.com/dengfuping)
|
||||
- 🆕 Add `size` prop to Divider. [#53570](https://github.com/ant-design/ant-design/pull/53570) [@coding-ice](https://github.com/coding-ice)
|
||||
- Collapse
|
||||
- 🆕 Collapse add `borderlessContentPadding` component token. [#52858](https://github.com/ant-design/ant-design/pull/52858) [@coding-ice](https://github.com/coding-ice)
|
||||
- 🆕 Collapse add `borderlessContentBg` component token. [#50902](https://github.com/ant-design/ant-design/pull/50902) [@coding-ice](https://github.com/coding-ice)
|
||||
- 🆕 Upload supports paste upload via the `pastable` property. [#53463](https://github.com/ant-design/ant-design/pull/53463) [@madocto](https://github.com/madocto)
|
||||
- 🆕 AutoComplete component adds `popup` semantic node with support for customizing dropdown menu via `classNames.popup`, `styles.popup`, `popupRender` and `onOpenChange`, while deprecating legacy `popupClassName`, `dropdownClassName`, `dropdownStyle`, `dropdownRender` and `onDropdownVisibleChange` APIs. [#53257](https://github.com/ant-design/ant-design/pull/53257) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🆕 Cascader component adds `popup` semantic node, and deprecated some api. [#53311](https://github.com/ant-design/ant-design/pull/53311) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🆕 ConfigProvider support setting the `variant` and `color` props of Button. [#53165](https://github.com/ant-design/ant-design/pull/53165) [@yellowryan](https://github.com/yellowryan)
|
||||
- 🆕 TreeSelect component adds `popup` semantic node, and deprecated some api. [#53285](https://github.com/ant-design/ant-design/pull/53285) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🆕 DatePicker and TimePicker add `popup` semantic node, and deprecated some api. [#53718](https://github.com/ant-design/ant-design/pull/53718) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🆕 Select component adds `popup` semantic node with support for customizing dropdown menu via `classNames.popup`, `styles.popup`, `popupRender` and `onOpenChange`, while deprecating legacy `popupClassName`, `dropdownClassName`, `dropdownStyle`, `dropdownRender` and `onDropdownVisibleChange` APIs. [#53243](https://github.com/ant-design/ant-design/pull/53243) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🆕 FloatButton supports `tooltip` props. [#53138](https://github.com/ant-design/ant-design/pull/53138) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 🆕 Table `rowSelection` support `align` prop. [#53127](https://github.com/ant-design/ant-design/pull/53127) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🆕 `options` prop of Radio.Group and Checkbox.Group support `classNames`. [#52917](https://github.com/ant-design/ant-design/pull/52917) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- ⚡️ Optimize ColorPicker components to use derived state pattern instead of setState in useEffect. [#53701](https://github.com/ant-design/ant-design/pull/53701) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 🐞 Fix Checkbox that render empty dom when `children` is `null`. [#53723](https://github.com/ant-design/ant-design/pull/53723) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🐞 Fix Anchor that would refresh the page after clicking the anchor point. [#53687](https://github.com/ant-design/ant-design/pull/53687) [@765477020](https://github.com/765477020)
|
||||
- Splitter
|
||||
- 🐞 Fix Splitter that multiple calls to `onResizeEnd` in lazy mode. [#53708](https://github.com/ant-design/ant-design/pull/53708) [@wanpan11](https://github.com/wanpan11)
|
||||
- 🐞 Fix Splitter to use minimum value as fallback when historical value out of bound. [#53703](https://github.com/ant-design/ant-design/pull/53703) [@jjlstruggle](https://github.com/jjlstruggle)
|
||||
- 💄 Fix Form that label is not middle align with input when not required and wrapped. [#53552](https://github.com/ant-design/ant-design/pull/53552) [@pre1ude](https://github.com/pre1ude)
|
||||
- 🐞 Fix Tabs card type height not working correctly when using `cardHeight` token. [#52837](https://github.com/ant-design/ant-design/pull/52837) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🛠 MISC:Refactor compatible code, use standard web API first, and downgrade to deprecated API when not compatibale [#53107](https://github.com/ant-design/ant-design/pull/53107) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- ⌨️ Opt Tour's `aria-*` props. [#53345](https://github.com/ant-design/ant-design/pull/53345) [@kiner-tang](https://github.com/kiner-tang)
|
||||
- ⌨️ MISC: Optimized closable component's aria props. [#53410](https://github.com/ant-design/ant-design/pull/53410) [@kiner-tang](https://github.com/kiner-tang)
|
||||
- 🗑 MISC: Deprecate `destroy*` of some components for `destroyOnHidden` prop. [#53739](https://github.com/ant-design/ant-design/pull/53739) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🗑 Deprecate `dropdownRender` of Dropdown for `popupRender` [#53263](https://github.com/ant-design/ant-design/pull/53263) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🗑 Deprecated `dropdown*` props in Cascader [#53133](https://github.com/ant-design/ant-design/pull/53133) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🇨🇿 Add cs_CZ locale for QRCode and ColorPicker. [#53741](https://github.com/ant-design/ant-design/pull/53741) [@malda26](https://github.com/malda26)
|
||||
|
||||
## 5.24.9
|
||||
|
||||
`2025-04-29`
|
||||
|
||||
- 🐞 Fix Splitter mask not hiding correctly in lazy mode. [#53653](https://github.com/ant-design/ant-design/pull/53653) [@wanpan11](https://github.com/wanpan11)
|
||||
- 🐞 Fix issue when modifying `offsetBottom` and `offsetTop` of the Affix does not take effect. [#53607](https://github.com/ant-design/ant-design/pull/53607) [@yellowryan](https://github.com/yellowryan)
|
||||
- ⚡️ Fix Select keeps showing `clearIcon` when it has a value on mobile devices. [#53576](https://github.com/ant-design/ant-design/pull/53576) [@EmilyLiu](https://github.com/EmilyLiu)
|
||||
- 🐞 Fix Slider formatter returns fixed content, Tooltip position abnormal after dragging. [#53460](https://github.com/ant-design/ant-design/pull/53460) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
|
||||
- 🐞 Fix Tabs keyboard operation not working. [#53692](https://github.com/ant-design/ant-design/pull/53692) [@afc163](https://github.com/afc163)
|
||||
- RTL
|
||||
- 💄 Fix counter element direction of Image in RTL mode. [#53593](https://github.com/ant-design/ant-design/pull/53593) [@aojunhao123](https://github.com/aojunhao123)
|
||||
|
||||
## 5.24.8
|
||||
|
||||
`2025-04-21`
|
||||
@ -475,7 +552,7 @@ Last version of the Dragon Year, Happy Chinese New Year! 🐲
|
||||
- 🐞 Fix Transfer width issue when customized as TableTransfer. [#50974](https://github.com/ant-design/ant-design/pull/50974) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🇹🇷 Add Turkish text for `filterCheckall` in Table component. [#51000](https://github.com/ant-design/ant-design/pull/51000) [@ytahirkose](https://github.com/ytahirkose)
|
||||
|
||||
## 5.21.0 🔥
|
||||
## 5.21.0
|
||||
|
||||
`2024-09-22`
|
||||
|
||||
|
@ -15,6 +15,83 @@ tag: vVERSION
|
||||
|
||||
---
|
||||
|
||||
## 5.25.3
|
||||
|
||||
`2025-05-26`
|
||||
|
||||
- 🐞 修复 Typography.Text `delete` 属性无法更新的问题。[#53861](https://github.com/ant-design/ant-design/pull/53861) [@codingories](https://github.com/codingories)
|
||||
- 🐞 修复 Statistic.Timer 组件作为 Tooltip 的子组件无法正常展示文字提示的问题。[#53888](https://github.com/ant-design/ant-design/pull/53888) [@jin19980928](https://github.com/jin19980928)
|
||||
- 🐞 修复 Upload 组件在更多类型时 `style` 设置未生效的问题。[#53877](https://github.com/ant-design/ant-design/pull/53877) [@QuentinHsu](https://github.com/QuentinHsu)
|
||||
- 💄 修复 Tabs 点击后有残留 focus 样式的问题。[#53901](https://github.com/ant-design/ant-design/pull/53901)
|
||||
|
||||
## 5.25.2
|
||||
|
||||
`2025-05-19`
|
||||
|
||||
- 🐞 修复 AutoComplete 中使用 Input 时 `onPaste` 回调不生效的问题。[#53839](https://github.com/ant-design/ant-design/issues/53839) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 🐞 修复 ColorPicker 的十六进制输入框无法输入的问题。[#53814](https://github.com/ant-design/ant-design/pull/53814) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 🐞 修复 Statistic.Timer ssr 注水渲染不一致的问题。[#53817](https://github.com/ant-design/ant-design/pull/53817) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修复 Table 开启 `sticky` 时的一个列头渲染闪烁问题。[#53803](https://github.com/ant-design/ant-design/pull/53803) [@afc163](https://github.com/afc163)
|
||||
- 💄 修复 Input.Search `variant="filled"` 的样式。[#53787](https://github.com/ant-design/ant-design/pull/53787) [@afc163](https://github.com/afc163)
|
||||
- TypeScript
|
||||
- 🤖 修复 Upload.Dragger 不支持泛型参数的问题。[#53842](https://github.com/ant-design/ant-design/pull/53842) [@fnoopv](https://github.com/fnoopv)
|
||||
- 🤖 移除 Modal 无效的属性类型定义。[#53808](https://github.com/ant-design/ant-design/pull/53808) [@wanpan11](https://github.com/wanpan11)
|
||||
|
||||
## 5.25.1
|
||||
|
||||
`2025-05-09`
|
||||
|
||||
- 🐞 Splitter 修复拖拽结束时,屏幕仍然无法操作的问题。[#53767](https://github.com/ant-design/ant-design/pull/53767) [@wanpan11](https://github.com/wanpan11)
|
||||
- 🌐 Image 组件国际化添加希伯来语支持。[#53771](https://github.com/ant-design/ant-design/pull/53771) [@Sagie501](https://github.com/Sagie501)
|
||||
|
||||
## 5.25.0
|
||||
|
||||
`2025-05-07`
|
||||
|
||||
- 🔥 新组件 Statistic.Timer,支持正计时和倒计时。[#53401](https://github.com/ant-design/ant-design/pull/53401) [@lcgash](https://github.com/lcgash)
|
||||
- 🆕 Tour 新增 `actionsRender` 属性,用于自定义操作按钮。[#53067](https://github.com/ant-design/ant-design/pull/53067) [@dengfuping](https://github.com/dengfuping)
|
||||
- 🆕 Divider 组件新增 `size` 属性。[#53570](https://github.com/ant-design/ant-design/pull/53570) [@coding-ice](https://github.com/coding-ice)
|
||||
- Collapse
|
||||
- 🆕 Collapse 增加 `borderlessContentPadding` 组件 token。[#52858](https://github.com/ant-design/ant-design/pull/52858) [@coding-ice](https://github.com/coding-ice)
|
||||
- 🆕 Collapse 增加 `borderlessContentBg` 组件 token。[#50902](https://github.com/ant-design/ant-design/pull/50902) [@coding-ice](https://github.com/coding-ice)
|
||||
- 🆕 Upload 支持通过 `pastable` 属性粘贴上传文件。[#53463](https://github.com/ant-design/ant-design/pull/53463) [@madocto](https://github.com/madocto)
|
||||
- 🆕 AutoComplete 组件新增 `popup` 语义节点,支持通过 `classNames.popup`、`styles.popup`、`popupRender` 和 `onOpenChange` 自定义弹出菜单,并废弃旧的 `popupClassName`、`dropdownClassName`、`dropdownStyle`、`dropdownRender` 和 `onDropdownVisibleChange` API。[#53257](https://github.com/ant-design/ant-design/pull/53257) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🆕 Cascader 组件新增 `popup` 语义节点,并且废弃部分 props。[#53311](https://github.com/ant-design/ant-design/pull/53311) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🆕 ConfigProvider 支持设置 Button 的 `variant` 和 `color` 属性。[#53165](https://github.com/ant-design/ant-design/pull/53165) [@yellowryan](https://github.com/yellowryan)
|
||||
- 🆕 TreeSelect 组件新增 `popup` 语义节点,并且废弃部分 props。[#53285](https://github.com/ant-design/ant-design/pull/53285) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🆕 DatePicker 和 TimePicker 组件新增 `popup` 语义节点,并且废弃部分 props。[#53718](https://github.com/ant-design/ant-design/pull/53718) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🆕 Select 组件新增 `popup` 语义节点,支持通过 `classNames.popup`、`styles.popup`、`popupRender` 和 `onOpenChange` 自定义弹出菜单,并废弃旧的 `popupClassName`、`dropdownClassName`、`dropdownStyle`、`dropdownRender` 和 `onDropdownVisibleChange` API。[#53243](https://github.com/ant-design/ant-design/pull/53243) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🆕 FloatButton 支持 `tooltip` 属性。[#53138](https://github.com/ant-design/ant-design/pull/53138) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 🆕 Table `rowSelection` 支持 `align` 属性用于定位。[#53127](https://github.com/ant-design/ant-design/pull/53127) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🆕 Radio.Group 和 Checkbox.Group 组件的 `options` 属性支持传入 `className` 属性。[#52917](https://github.com/ant-design/ant-design/pull/52917) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- ⚡️ 优化 ColorPicker 组件,使用派生状态模式替代在 useEffect 中调用 setState。[#53701](https://github.com/ant-design/ant-design/pull/53701) [@DDDDD12138](https://github.com/DDDDD12138)
|
||||
- 🐞 修复 Checkbox 组件 `children` 为 `null` 的时候渲染多余 dom 的问题。[#53723](https://github.com/ant-design/ant-design/pull/53723) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🐞 修复 Anchor 组件点击锚点会刷新页面的问题。[#53687](https://github.com/ant-design/ant-design/pull/53687) [@765477020](https://github.com/765477020)
|
||||
- Splitter
|
||||
- 🐞 修复 Splitter 组件在 lazy 模式下 `onResizeEnd` 被多次调用问题。[#53708](https://github.com/ant-design/ant-design/pull/53708) [@wanpan11](https://github.com/wanpan11)
|
||||
- 🐞 修复 Splitter 组件,当历史值超出范围时使用面板设置的最小值最为兜底值。[#53703](https://github.com/ant-design/ant-design/pull/53703) [@jjlstruggle](https://github.com/jjlstruggle)
|
||||
- 🐞 修复 Tabs 组件卡片类型页签在使用 `cardHeight` token 时高度设置不正确的问题。[#52837](https://github.com/ant-design/ant-design/pull/52837) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 💄 修复 Form 组件中非必选的 label 换行不对齐的问题。[#53552](https://github.com/ant-design/ant-design/pull/53552) [@pre1ude](https://github.com/pre1ude)
|
||||
- 🛠 杂项:重构兼容逻辑,优先使用标准 Web API,不兼容的时候降级为废弃 API。[#53107](https://github.com/ant-design/ant-design/pull/53107) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- ⌨️ 优化 Tour 的 `aria-*` 属性。[#53345](https://github.com/ant-design/ant-design/pull/53345) [@kiner-tang](https://github.com/kiner-tang)
|
||||
- ⌨️ 杂项:优化可关闭组件的无障碍属性。[#53410](https://github.com/ant-design/ant-design/pull/53410) [@kiner-tang](https://github.com/kiner-tang)
|
||||
- 🗑 杂项:废弃多个可开关组件的 `destroy*` 属性,统一为 `destroyOnHidden`。 [#53739](https://github.com/ant-design/ant-design/pull/53739) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🗑 废弃 Dropdown 中 `dropdownRender` 属性,用 `popupRender` 替代。[#53263](https://github.com/ant-design/ant-design/pull/53263) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🗑 废弃 Cascader 组件的 `dropdown*` 等属性。[#53133](https://github.com/ant-design/ant-design/pull/53133) [@aojunhao123](https://github.com/aojunhao123)
|
||||
- 🇨🇿 添加 QRCode 和 ColorPicker 的捷克语(cs_CZ)支持。[#53741](https://github.com/ant-design/ant-design/pull/53741) [@malda26](https://github.com/malda26)
|
||||
|
||||
## 5.24.9
|
||||
|
||||
`2025-04-29`
|
||||
|
||||
- 🐞 修复 Splitter 在 `lazy` 模式下遮罩未能正确关闭的问题。[#53653](https://github.com/ant-design/ant-design/pull/53653) [@wanpan11](https://github.com/wanpan11)
|
||||
- 🐞 修复 Affix 在动态调整 `offsetTop` 和 `offsetBottom` 属性值后固定效果失效的问题。[#53607](https://github.com/ant-design/ant-design/pull/53607) [@yellowryan](https://github.com/yellowryan)
|
||||
- 💄 移动端场景下 Select 当有选中值时始终显示清除按钮。[#53576](https://github.com/ant-design/ant-design/pull/53576) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
|
||||
- 🐞 修复 Slider 设置 `formatter` 后,拖拽滑块导致 Tooltip 位置异常的问题。[#53460](https://github.com/ant-design/ant-design/pull/53460) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
|
||||
- 🐞 修复 Tabs 键盘操作失效的问题。[#53692](https://github.com/ant-design/ant-design/pull/53692) [@afc163](https://github.com/afc163)
|
||||
- RTL
|
||||
- 💄 修复 Image 计数元素在 RTL 模式下的显示方向问题。[#53593](https://github.com/ant-design/ant-design/pull/53593) [@aojunhao123](https://github.com/aojunhao123)
|
||||
|
||||
## 5.24.8
|
||||
|
||||
`2025-04-21`
|
||||
@ -477,7 +554,7 @@ tag: vVERSION
|
||||
- 💄 修复 Transfer 在自定义为 TableTransfer 时,宽度不正确的问题。[#50974](https://github.com/ant-design/ant-design/pull/50974) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🇹🇷 补充 Table 组件 `filterCheckall` 的土耳其语文案。[#51000](https://github.com/ant-design/ant-design/pull/51000) [@ytahirkose](https://github.com/ytahirkose)
|
||||
|
||||
## 5.21.0 🔥
|
||||
## 5.21.0
|
||||
|
||||
`2024-09-22`
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
|
||||
|
||||
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url]
|
||||
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url] [![DeepWiki][deepwiki-image]][deepwiki-url]
|
||||
|
||||
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
|
||||
|
||||
@ -43,6 +43,8 @@
|
||||
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||
[dumi-url]: https://github.com/umijs/dumi
|
||||
[github-issues-url]: https://new-issue.ant.design
|
||||
[deepwiki-url]: https://deepwiki.com/ant-design/ant-design
|
||||
[deepwiki-image]: https://img.shields.io/badge/Chat%20with-DeepWiki%20🤖-20B2AA?style=flat-square
|
||||
|
||||
</div>
|
||||
|
||||
@ -191,8 +193,7 @@ $ npm start
|
||||
|
||||
<a href="https://openomy.app/github/ant-design/ant-design" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=ant-design/ant-design&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
|
||||
</a>
|
||||
|
||||
请参考[贡献指南](https://ant.design/docs/react/contributing-cn).
|
||||
|
||||
@ -214,8 +215,6 @@ $ npm start
|
||||
|
||||
## Issue 赞助
|
||||
|
||||
我们使用 [Polar.sh](https://polar.sh/ant-design) 和 [Issuehunt](https://issuehunt.io/repos/3452688) 来推动您希望看到的针对 antd 的修复和改进,请查看我们的赞助列表:
|
||||
|
||||
<a href="https://polar.sh/ant-design"><img src="https://polar.sh/embed/fund-our-backlog.svg?org=ant-design" /></a>
|
||||
我们使用 [Issuehunt](https://issuehunt.io/repos/3452688) 来推动您希望看到的针对 antd 的修复和改进,请查看我们的赞助列表:
|
||||
|
||||
[](https://issuehunt.io/repos/34526884)
|
||||
|
15
README.md
15
README.md
@ -8,7 +8,7 @@ An enterprise-class UI design language and React UI library.
|
||||
|
||||
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
|
||||
|
||||
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url]
|
||||
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url] [![DeepWiki][deepwiki-image]][deepwiki-url]
|
||||
|
||||
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
|
||||
|
||||
@ -43,6 +43,8 @@ An enterprise-class UI design language and React UI library.
|
||||
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||
[dumi-url]: https://github.com/umijs/dumi
|
||||
[github-issues-url]: https://new-issue.ant.design
|
||||
[deepwiki-url]: https://deepwiki.com/ant-design/ant-design
|
||||
[deepwiki-image]: https://img.shields.io/badge/Chat%20with-DeepWiki%20🤖-20B2AA?style=flat-square
|
||||
|
||||
</div>
|
||||
|
||||
@ -136,7 +138,7 @@ $ npm install
|
||||
$ npm start
|
||||
```
|
||||
|
||||
Open your browser and visit http://127.0.0.1:8001 , see more at [Development](https://github.com/ant-design/ant-design/wiki/Development).
|
||||
Open your browser and visit http://127.0.0.1:8001, see more at [Development](https://github.com/ant-design/ant-design/wiki/Development).
|
||||
|
||||
## 🤝 Contributing [](https://makeapullrequest.com)
|
||||
|
||||
@ -173,19 +175,16 @@ Open your browser and visit http://127.0.0.1:8001 , see more at [Development](ht
|
||||
|
||||
<a href="https://openomy.app/github/ant-design/ant-design" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=ant-design/ant-design&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
|
||||
</a>
|
||||
|
||||
Let's build a better antd together.
|
||||
|
||||
We warmly invite contributions from everyone. Before you get started, please take a moment to review our [Contributing Guide](https://ant.design/docs/react/contributing). Feel free to share your ideas through [Pull Requests](https://github.com/ant-design/ant-design/pulls) or [GitHub Issues](https://github.com/ant-design/ant-design/issues). If you're interested in enhancing our codebase, explore the [Development Instructions](https://github.com/ant-design/ant-design/wiki/Development) and enjoy your coding journey! :)
|
||||
We warmly invite contributions from everyone. Before you get started, please take a moment to review our [Contribution Guide](https://ant.design/docs/react/contributing). Feel free to share your ideas through [Pull Requests](https://github.com/ant-design/ant-design/pulls) or [GitHub Issues](https://github.com/ant-design/ant-design/issues). If you're interested in enhancing our codebase, explore the [Development Instructions](https://github.com/ant-design/ant-design/wiki/Development) and enjoy your coding journey! :)
|
||||
|
||||
For collaborators, adhere to our [Pull Request Principle](https://github.com/ant-design/ant-design/wiki/PR-principle) and utilize our [Pull Request Template](https://github.com/ant-design/ant-design/wiki/PR-principle#pull-request-template) when creating a Pull Request.
|
||||
|
||||
## Issue funding
|
||||
|
||||
We use [Polar.sh](https://polar.sh/ant-design) and [Issuehunt](https://issuehunt.io/repos/3452688) to up-vote and promote specific features that you would like to see and implement. Check our backlog and help us:
|
||||
|
||||
<a href="https://polar.sh/ant-design"><img src="https://polar.sh/embed/fund-our-backlog.svg?org=ant-design" /></a>
|
||||
We use [Issuehunt](https://issuehunt.io/repos/3452688) to up-vote and promote specific features that you would like to see and implement. Check our backlog and help us:
|
||||
|
||||
[](https://issuehunt.io/repos/34526884)
|
||||
|
@ -41,4 +41,9 @@ describe('unstable', () => {
|
||||
expect(document.querySelector('.ant-modal')).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it('unstableSetRender without param', async () => {
|
||||
const currentRender = unstableSetRender();
|
||||
expect(currentRender).toBeInstanceOf(Function);
|
||||
});
|
||||
});
|
||||
|
@ -5,23 +5,20 @@ import useResponsiveObserver from '../responsiveObserver';
|
||||
|
||||
describe('Test ResponsiveObserve', () => {
|
||||
it('test ResponsiveObserve subscribe and unsubscribe', () => {
|
||||
let responsiveObserveRef: any;
|
||||
const Demo = () => {
|
||||
let responsiveRef: any = null;
|
||||
const Demo: React.FC = () => {
|
||||
const responsiveObserver = useResponsiveObserver();
|
||||
responsiveObserveRef = responsiveObserver;
|
||||
responsiveRef = responsiveObserver;
|
||||
return null;
|
||||
};
|
||||
render(<Demo />);
|
||||
const subscribeFunc = jest.fn();
|
||||
const token = responsiveObserveRef.subscribe(subscribeFunc);
|
||||
expect(
|
||||
responsiveObserveRef.matchHandlers[responsiveObserveRef.responsiveMap.xs].mql.matches,
|
||||
).toBeTruthy();
|
||||
const token = responsiveRef.subscribe(subscribeFunc);
|
||||
expect(responsiveRef.matchHandlers[responsiveRef.responsiveMap.xs].mql.matches).toBeTruthy();
|
||||
expect(subscribeFunc).toHaveBeenCalledTimes(1);
|
||||
|
||||
responsiveObserveRef.unsubscribe(token);
|
||||
responsiveRef.unsubscribe(token);
|
||||
expect(
|
||||
responsiveObserveRef.matchHandlers[responsiveObserveRef.responsiveMap.xs].mql.removeListener,
|
||||
responsiveRef.matchHandlers[responsiveRef.responsiveMap.xs].mql?.removeEventListener,
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
20
components/_util/convertToTooltipProps.ts
Normal file
20
components/_util/convertToTooltipProps.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { isValidElement } from 'react';
|
||||
import type { TooltipProps } from '../tooltip';
|
||||
|
||||
function convertToTooltipProps<P extends TooltipProps>(tooltip: P | ReactNode): P | null {
|
||||
// isNil
|
||||
if (tooltip === undefined || tooltip === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof tooltip === 'object' && !isValidElement(tooltip)) {
|
||||
return tooltip as P;
|
||||
}
|
||||
|
||||
return {
|
||||
title: tooltip,
|
||||
} as P;
|
||||
}
|
||||
|
||||
export default convertToTooltipProps;
|
@ -1,21 +1,19 @@
|
||||
import type { AnyObject } from './type';
|
||||
|
||||
const extendsObject = <T extends AnyObject = AnyObject>(...list: T[]) => {
|
||||
const result: AnyObject = { ...list[0] };
|
||||
|
||||
for (let i = 1; i < list.length; i++) {
|
||||
const obj = list[i];
|
||||
if (obj) {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
const val = obj[key];
|
||||
if (val !== undefined) {
|
||||
result[key] = val;
|
||||
// copied https://github.com/ant-design/ant-design-mobile/blob/d3b3bae/src/utils/with-default-props.tsx
|
||||
function mergeProps<A, B>(a: A, b: B): B & A;
|
||||
function mergeProps<A, B, C>(a: A, b: B, c: C): C & B & A;
|
||||
function mergeProps<A, B, C, D>(a: A, b: B, c: C, d: D): D & C & B & A;
|
||||
function mergeProps(...items: any[]) {
|
||||
const ret: any = {};
|
||||
items.forEach((item) => {
|
||||
if (item) {
|
||||
Object.keys(item).forEach((key) => {
|
||||
if (item[key] !== undefined) {
|
||||
ret[key] = item[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export default extendsObject;
|
||||
export default mergeProps;
|
||||
|
@ -4,6 +4,11 @@ import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||
import type { DialogProps } from 'rc-dialog';
|
||||
import pickAttrs from 'rc-util/lib/pickAttrs';
|
||||
|
||||
import { useLocale } from '../../locale';
|
||||
import defaultLocale from '../../locale/en_US';
|
||||
import type { HTMLAriaDataAttributes } from '../aria-data-attrs';
|
||||
import extendsObject from '../extendsObject';
|
||||
|
||||
export type ClosableType = DialogProps['closable'];
|
||||
|
||||
export type BaseContextClosable = { closable?: ClosableType; closeIcon?: ReactNode };
|
||||
@ -58,56 +63,42 @@ function useClosableConfig(closableCollection?: ClosableCollection | null) {
|
||||
...closable,
|
||||
};
|
||||
}
|
||||
|
||||
return closableConfig;
|
||||
}, [closable, closeIcon]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign object without `undefined` field. Will skip if is `false`.
|
||||
* This helps to handle both closableConfig or false
|
||||
*/
|
||||
function assignWithoutUndefined<T extends object>(
|
||||
...objList: (Partial<T> | false | null | undefined)[]
|
||||
): Partial<T> {
|
||||
const target: Partial<T> = {};
|
||||
|
||||
objList.forEach((obj) => {
|
||||
if (obj) {
|
||||
(Object.keys(obj) as (keyof T)[]).forEach((key) => {
|
||||
if (obj[key] !== undefined) {
|
||||
target[key] = obj[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/** Collection contains the all the props related with closable. e.g. `closable`, `closeIcon` */
|
||||
interface ClosableCollection {
|
||||
closable?: ClosableType;
|
||||
closeIcon?: ReactNode;
|
||||
}
|
||||
|
||||
interface FallbackCloseCollection extends ClosableCollection {
|
||||
/**
|
||||
* Some components need to wrap CloseIcon twice,
|
||||
* this method will be executed once after the final CloseIcon is calculated
|
||||
*/
|
||||
closeIconRender?: (closeIcon: ReactNode) => ReactNode;
|
||||
}
|
||||
|
||||
/** Use same object to support `useMemo` optimization */
|
||||
const EmptyFallbackCloseCollection: ClosableCollection = {};
|
||||
const EmptyFallbackCloseCollection: FallbackCloseCollection = {};
|
||||
|
||||
export default function useClosable(
|
||||
propCloseCollection?: ClosableCollection,
|
||||
contextCloseCollection?: ClosableCollection | null,
|
||||
fallbackCloseCollection: ClosableCollection & {
|
||||
/**
|
||||
* Some components need to wrap CloseIcon twice,
|
||||
* this method will be executed once after the final CloseIcon is calculated
|
||||
*/
|
||||
closeIconRender?: (closeIcon: ReactNode) => ReactNode;
|
||||
} = EmptyFallbackCloseCollection,
|
||||
): [closable: boolean, closeIcon: React.ReactNode, closeBtnIsDisabled: boolean] {
|
||||
fallbackCloseCollection: FallbackCloseCollection = EmptyFallbackCloseCollection,
|
||||
): [
|
||||
closable: boolean,
|
||||
closeIcon: React.ReactNode,
|
||||
closeBtnIsDisabled: boolean,
|
||||
ariaOrDataProps?: HTMLAriaDataAttributes,
|
||||
] {
|
||||
// Align the `props`, `context` `fallback` to config object first
|
||||
const propCloseConfig = useClosableConfig(propCloseCollection);
|
||||
const contextCloseConfig = useClosableConfig(contextCloseCollection);
|
||||
|
||||
const [contextLocale] = useLocale('global', defaultLocale.global);
|
||||
const closeBtnIsDisabled =
|
||||
typeof propCloseConfig !== 'boolean' ? !!propCloseConfig?.disabled : false;
|
||||
const mergedFallbackCloseCollection = React.useMemo(
|
||||
@ -127,11 +118,7 @@ export default function useClosable(
|
||||
}
|
||||
|
||||
if (propCloseConfig) {
|
||||
return assignWithoutUndefined(
|
||||
mergedFallbackCloseCollection,
|
||||
contextCloseConfig,
|
||||
propCloseConfig,
|
||||
);
|
||||
return extendsObject(mergedFallbackCloseCollection, contextCloseConfig, propCloseConfig);
|
||||
}
|
||||
|
||||
// =============== Context Second ==============
|
||||
@ -141,7 +128,7 @@ export default function useClosable(
|
||||
}
|
||||
|
||||
if (contextCloseConfig) {
|
||||
return assignWithoutUndefined(mergedFallbackCloseCollection, contextCloseConfig);
|
||||
return extendsObject(mergedFallbackCloseCollection, contextCloseConfig);
|
||||
}
|
||||
|
||||
// ============= Fallback Default ==============
|
||||
@ -151,30 +138,34 @@ export default function useClosable(
|
||||
// Calculate the final closeIcon
|
||||
return React.useMemo(() => {
|
||||
if (mergedClosableConfig === false) {
|
||||
return [false, null, closeBtnIsDisabled];
|
||||
return [false, null, closeBtnIsDisabled, {}];
|
||||
}
|
||||
|
||||
const { closeIconRender } = mergedFallbackCloseCollection;
|
||||
const { closeIcon } = mergedClosableConfig;
|
||||
|
||||
let mergedCloseIcon: ReactNode = closeIcon;
|
||||
|
||||
// Wrap the closeIcon with aria props
|
||||
const ariaOrDataProps = pickAttrs(mergedClosableConfig, true);
|
||||
|
||||
if (mergedCloseIcon !== null && mergedCloseIcon !== undefined) {
|
||||
// Wrap the closeIcon if needed
|
||||
if (closeIconRender) {
|
||||
mergedCloseIcon = closeIconRender(closeIcon);
|
||||
}
|
||||
|
||||
// Wrap the closeIcon with aria props
|
||||
const ariaProps = pickAttrs(mergedClosableConfig, true);
|
||||
if (Object.keys(ariaProps).length) {
|
||||
mergedCloseIcon = React.isValidElement(mergedCloseIcon) ? (
|
||||
React.cloneElement(mergedCloseIcon, ariaProps)
|
||||
) : (
|
||||
<span {...ariaProps}>{mergedCloseIcon}</span>
|
||||
);
|
||||
}
|
||||
mergedCloseIcon = React.isValidElement(mergedCloseIcon) ? (
|
||||
React.cloneElement(mergedCloseIcon, {
|
||||
'aria-label': contextLocale.close,
|
||||
...ariaOrDataProps,
|
||||
} as HTMLAriaDataAttributes)
|
||||
) : (
|
||||
<span aria-label={contextLocale.close} {...ariaOrDataProps}>
|
||||
{mergedCloseIcon}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return [true, mergedCloseIcon, closeBtnIsDisabled];
|
||||
return [true, mergedCloseIcon, closeBtnIsDisabled, ariaOrDataProps];
|
||||
}, [mergedClosableConfig, mergedFallbackCloseCollection]);
|
||||
}
|
||||
|
107
components/_util/hooks/useMergeSemantic/index.ts
Normal file
107
components/_util/hooks/useMergeSemantic/index.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import * as React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { ValidChar } from './interface';
|
||||
|
||||
type TemplateSemanticClassNames<T extends string> = Partial<Record<T, string>>;
|
||||
|
||||
export type SemanticSchema = {
|
||||
_default?: string;
|
||||
} & {
|
||||
[key: `${ValidChar}${string}`]: SemanticSchema;
|
||||
};
|
||||
|
||||
// ========================= ClassNames =========================
|
||||
export function mergeClassNames<
|
||||
T extends string,
|
||||
SemanticClassNames extends Partial<Record<T, any>> = TemplateSemanticClassNames<T>,
|
||||
>(schema: SemanticSchema | undefined, ...classNames: (SemanticClassNames | undefined)[]) {
|
||||
const mergedSchema = schema || {};
|
||||
|
||||
return classNames.reduce((acc: any, cur) => {
|
||||
// Loop keys of the current classNames
|
||||
Object.keys(cur || {}).forEach((key) => {
|
||||
const keySchema = mergedSchema[key as keyof SemanticSchema] as SemanticSchema;
|
||||
const curVal = (cur as SemanticClassNames)[key as keyof SemanticClassNames];
|
||||
|
||||
if (keySchema && typeof keySchema === 'object') {
|
||||
if (curVal && typeof curVal === 'object') {
|
||||
// Loop fill
|
||||
acc[key] = mergeClassNames(keySchema, acc[key], curVal);
|
||||
} else {
|
||||
// Covert string to object structure
|
||||
const { _default: defaultField } = keySchema;
|
||||
acc[key] = acc[key] || {};
|
||||
acc[key][defaultField!] = classnames(acc[key][defaultField!], curVal);
|
||||
}
|
||||
} else {
|
||||
// Flatten fill
|
||||
acc[key] = classnames(acc[key], curVal);
|
||||
}
|
||||
});
|
||||
return acc;
|
||||
}, {} as SemanticClassNames) as SemanticClassNames;
|
||||
}
|
||||
|
||||
function useSemanticClassNames<ClassNamesType extends object>(
|
||||
schema: SemanticSchema | undefined,
|
||||
...classNames: (Partial<ClassNamesType> | undefined)[]
|
||||
): Partial<ClassNamesType> {
|
||||
return React.useMemo(
|
||||
() => mergeClassNames(schema, ...classNames),
|
||||
[classNames],
|
||||
) as ClassNamesType;
|
||||
}
|
||||
|
||||
// =========================== Styles ===========================
|
||||
function useSemanticStyles<StylesType extends object>(
|
||||
...styles: (Partial<StylesType> | undefined)[]
|
||||
) {
|
||||
return React.useMemo(() => {
|
||||
return styles.reduce(
|
||||
(acc, cur = {}) => {
|
||||
Object.keys(cur).forEach((key) => {
|
||||
acc[key] = { ...acc[key], ...(cur as Record<string, React.CSSProperties>)[key] };
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, React.CSSProperties>,
|
||||
);
|
||||
}, [styles]) as StylesType;
|
||||
}
|
||||
|
||||
// =========================== Export ===========================
|
||||
function fillObjectBySchema<T extends object>(obj: T, schema: SemanticSchema): T {
|
||||
const newObj: any = { ...obj };
|
||||
|
||||
Object.keys(schema).forEach((key) => {
|
||||
if (key !== '_default') {
|
||||
const nestSchema = (schema as any)[key] as SemanticSchema;
|
||||
const nextValue = newObj[key] || {};
|
||||
|
||||
newObj[key] = nestSchema ? fillObjectBySchema(nextValue, nestSchema) : nextValue;
|
||||
}
|
||||
});
|
||||
|
||||
return newObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge classNames and styles from multiple sources.
|
||||
* When `schema` is provided, it will **must** provide the nest object structure.
|
||||
*/
|
||||
export default function useMergeSemantic<ClassNamesType extends object, StylesType extends object>(
|
||||
classNamesList: (ClassNamesType | undefined)[],
|
||||
stylesList: (StylesType | undefined)[],
|
||||
schema?: SemanticSchema,
|
||||
) {
|
||||
const mergedClassNames = useSemanticClassNames(schema, ...classNamesList) as ClassNamesType;
|
||||
const mergedStyles = useSemanticStyles(...stylesList) as StylesType;
|
||||
|
||||
return React.useMemo(() => {
|
||||
return [
|
||||
fillObjectBySchema(mergedClassNames, schema!) as ClassNamesType,
|
||||
fillObjectBySchema(mergedStyles, schema!) as StylesType,
|
||||
] as const;
|
||||
}, [mergedClassNames, mergedStyles]);
|
||||
}
|
27
components/_util/hooks/useMergeSemantic/interface.ts
Normal file
27
components/_util/hooks/useMergeSemantic/interface.ts
Normal file
@ -0,0 +1,27 @@
|
||||
export type ValidChar =
|
||||
| 'a'
|
||||
| 'b'
|
||||
| 'c'
|
||||
| 'd'
|
||||
| 'e'
|
||||
| 'f'
|
||||
| 'g'
|
||||
| 'h'
|
||||
| 'i'
|
||||
| 'j'
|
||||
| 'k'
|
||||
| 'l'
|
||||
| 'm'
|
||||
| 'n'
|
||||
| 'o'
|
||||
| 'p'
|
||||
| 'q'
|
||||
| 'r'
|
||||
| 's'
|
||||
| 't'
|
||||
| 'u'
|
||||
| 'v'
|
||||
| 'w'
|
||||
| 'x'
|
||||
| 'y'
|
||||
| 'z';
|
19
components/_util/mediaQueryUtil.ts
Normal file
19
components/_util/mediaQueryUtil.ts
Normal file
@ -0,0 +1,19 @@
|
||||
type MQListenerHandler = (mql: MediaQueryList, handler: (e: MediaQueryListEvent) => void) => void;
|
||||
|
||||
export const addMediaQueryListener: MQListenerHandler = (mql, handler) => {
|
||||
// Don't delete here, please keep the code compatible
|
||||
if (typeof mql?.addEventListener !== 'undefined') {
|
||||
mql.addEventListener('change', handler);
|
||||
} else if (typeof mql?.addListener !== 'undefined') {
|
||||
mql.addListener(handler);
|
||||
}
|
||||
};
|
||||
|
||||
export const removeMediaQueryListener: MQListenerHandler = (mql, handler) => {
|
||||
// Don't delete here, please keep the code compatible
|
||||
if (typeof mql?.removeEventListener !== 'undefined') {
|
||||
mql.removeEventListener('change', handler);
|
||||
} else if (typeof mql?.removeListener !== 'undefined') {
|
||||
mql.removeListener(handler);
|
||||
}
|
||||
};
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
|
||||
import type { GlobalToken } from '../theme/internal';
|
||||
import { useToken } from '../theme/internal';
|
||||
import { addMediaQueryListener, removeMediaQueryListener } from './mediaQueryUtil';
|
||||
|
||||
export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
||||
export type BreakpointMap = Record<Breakpoint, string>;
|
||||
@ -121,22 +122,20 @@ const useResponsiveObserver = () => {
|
||||
}
|
||||
},
|
||||
register() {
|
||||
Object.keys(responsiveMap).forEach((screen) => {
|
||||
const matchMediaQuery = responsiveMap[screen as Breakpoint];
|
||||
Object.entries(responsiveMap).forEach(([screen, mediaQuery]) => {
|
||||
const listener = ({ matches }: { matches: boolean }) => {
|
||||
this.dispatch({ ...screens, [screen]: matches });
|
||||
};
|
||||
const mql = window.matchMedia(matchMediaQuery);
|
||||
mql.addListener(listener);
|
||||
this.matchHandlers[matchMediaQuery] = { mql, listener };
|
||||
const mql = window.matchMedia(mediaQuery);
|
||||
addMediaQueryListener(mql, listener);
|
||||
this.matchHandlers[mediaQuery] = { mql, listener };
|
||||
listener(mql);
|
||||
});
|
||||
},
|
||||
unregister() {
|
||||
Object.keys(responsiveMap).forEach((screen) => {
|
||||
const matchMediaQuery = responsiveMap[screen as Breakpoint];
|
||||
const handler = this.matchHandlers[matchMediaQuery];
|
||||
handler?.mql.removeListener(handler?.listener);
|
||||
Object.values(responsiveMap).forEach((mediaQuery) => {
|
||||
const handler = this.matchHandlers[mediaQuery];
|
||||
removeMediaQueryListener(handler?.mql, handler?.listener);
|
||||
});
|
||||
subscribers.clear();
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ import CSSMotion from 'rc-motion';
|
||||
import raf from 'rc-util/lib/raf';
|
||||
import { composeRef } from 'rc-util/lib/ref';
|
||||
|
||||
import { getReactRender } from '../../config-provider/UnstableContext';
|
||||
import { unstableSetRender } from '../../config-provider/UnstableContext';
|
||||
import type { UnmountType } from '../../config-provider/UnstableContext';
|
||||
import { TARGET_CLS } from './interface';
|
||||
import type { ShowWaveEffect } from './interface';
|
||||
@ -160,7 +160,7 @@ const showWaveEffect: ShowWaveEffect = (target, info) => {
|
||||
holder.style.top = '0px';
|
||||
target?.insertBefore(holder, target?.firstChild);
|
||||
|
||||
const reactRender = getReactRender();
|
||||
const reactRender = unstableSetRender();
|
||||
|
||||
let unmountCallback: UnmountType | null = null;
|
||||
|
||||
|
@ -238,7 +238,8 @@ const Affix = React.forwardRef<AffixRef, InternalAffixProps>((props, ref) => {
|
||||
|
||||
React.useEffect(() => {
|
||||
addListeners();
|
||||
}, [target, affixStyle, lastAffix]);
|
||||
return () => removeListeners();
|
||||
}, [target, affixStyle, lastAffix, offsetTop, offsetBottom]);
|
||||
|
||||
React.useEffect(() => {
|
||||
updatePosition();
|
||||
|
@ -44,10 +44,26 @@ const AnchorLink: React.FC<AnchorLinkProps> = (props) => {
|
||||
const handleClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
onClick?.(e, { title, href });
|
||||
scrollTo?.(href);
|
||||
if (replace) {
|
||||
e.preventDefault();
|
||||
window.location.replace(href);
|
||||
|
||||
// Support clicking on an anchor does not record history.
|
||||
if (e.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isExternalLink = href.startsWith('http://') || href.startsWith('https://');
|
||||
// Support external link
|
||||
if (isExternalLink) {
|
||||
if (replace) {
|
||||
e.preventDefault();
|
||||
window.location.replace(href);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handling internal anchor link
|
||||
e.preventDefault();
|
||||
const historyMethod = replace ? 'replaceState' : 'pushState';
|
||||
window.history[historyMethod](null, '', href);
|
||||
};
|
||||
|
||||
// =================== Warning =====================
|
||||
|
@ -268,6 +268,27 @@ describe('Anchor Render', () => {
|
||||
expect(container.querySelector(`a[href="#${hash}_1"]`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not proceed when event is default prevented', () => {
|
||||
const hash = getHashUrl();
|
||||
const handleClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||
const pushStateSpy = jest.spyOn(window.history, 'pushState');
|
||||
const replaceStateSpy = jest.spyOn(window.history, 'replaceState');
|
||||
const { container } = render(
|
||||
<Anchor items={[{ key: hash, href: `#${hash}`, title: hash }]} onClick={handleClick} />,
|
||||
);
|
||||
|
||||
const link = container.querySelector(`a[href="#${hash}"]`)!;
|
||||
|
||||
fireEvent.click(link);
|
||||
|
||||
expect(scrollToSpy).toHaveBeenCalled();
|
||||
expect(pushStateSpy).not.toHaveBeenCalled();
|
||||
expect(replaceStateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('targetOffset prop', async () => {
|
||||
const hash = getHashUrl();
|
||||
|
||||
@ -350,13 +371,27 @@ describe('Anchor Render', () => {
|
||||
expect(link).toEqual({ href, title });
|
||||
});
|
||||
|
||||
it('replaces item href in browser history', () => {
|
||||
it('replaces item href in browser history (hash href)', () => {
|
||||
const hash = getHashUrl();
|
||||
|
||||
const href = `#${hash}`;
|
||||
const title = hash;
|
||||
const { container } = render(<Anchor replace items={[{ key: hash, href, title }]} />);
|
||||
|
||||
jest.spyOn(window.history, 'replaceState').mockImplementation(() => {});
|
||||
|
||||
fireEvent.click(container.querySelector(`a[href="${href}"]`)!);
|
||||
|
||||
expect(window.history.replaceState).toHaveBeenCalledWith(null, '', href);
|
||||
});
|
||||
|
||||
it('replaces item href in browser history (external href)', () => {
|
||||
const hash = getHashUrl();
|
||||
|
||||
const href = `http://www.example.com/#${hash}`;
|
||||
const title = hash;
|
||||
const { container } = render(<Anchor replace items={[{ key: hash, href, title }]} />);
|
||||
|
||||
fireEvent.click(container.querySelector(`a[href="${href}"]`)!);
|
||||
expect(window.location.replace).toHaveBeenCalledWith(href);
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import cls from 'classnames';
|
||||
import type { BaseSelectRef } from 'rc-select';
|
||||
import toArray from 'rc-util/lib/Children/toArray';
|
||||
import omit from 'rc-util/lib/omit';
|
||||
@ -36,12 +36,21 @@ export interface AutoCompleteProps<
|
||||
/** @deprecated Please use `options` instead */
|
||||
dataSource?: DataSourceItemType[];
|
||||
status?: InputStatus;
|
||||
/** @deprecated Please use `classNames.popup.root` instead */
|
||||
popupClassName?: string;
|
||||
/** @deprecated Please use `popupClassName` instead */
|
||||
/** @deprecated Please use `classNames.popup.root` instead */
|
||||
dropdownClassName?: string;
|
||||
/** @deprecated Please use `popupMatchSelectWidth` instead */
|
||||
dropdownMatchSelectWidth?: boolean | number;
|
||||
popupMatchSelectWidth?: boolean | number;
|
||||
/** @deprecated Please use `popupRender` instead */
|
||||
dropdownRender?: (menu: React.ReactElement) => React.ReactElement;
|
||||
popupRender?: (menu: React.ReactElement) => React.ReactElement;
|
||||
/** @deprecated Please use `styles.popup.root` instead */
|
||||
dropdownStyle?: React.CSSProperties;
|
||||
/** @deprecated Please use `onOpenChange` instead */
|
||||
onDropdownVisibleChange?: (visible: boolean) => void;
|
||||
onOpenChange?: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
function isSelectOptionOrSelectOptGroup(child: any): boolean {
|
||||
@ -59,9 +68,21 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
|
||||
dropdownClassName,
|
||||
children,
|
||||
dataSource,
|
||||
dropdownStyle,
|
||||
dropdownRender,
|
||||
popupRender,
|
||||
onDropdownVisibleChange,
|
||||
onOpenChange,
|
||||
styles,
|
||||
classNames,
|
||||
} = props;
|
||||
const childNodes: React.ReactElement[] = toArray(children);
|
||||
|
||||
const mergedPopupStyle = styles?.popup?.root || dropdownStyle;
|
||||
const mergedPopupClassName = classNames?.popup?.root || popupClassName || dropdownClassName;
|
||||
const mergedPopupRender = popupRender || dropdownRender;
|
||||
const mergedOnOpenChange = onOpenChange || onDropdownVisibleChange;
|
||||
|
||||
// ============================= Input =============================
|
||||
let customizeInput: React.ReactElement | undefined;
|
||||
|
||||
@ -112,15 +133,25 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('AutoComplete');
|
||||
|
||||
warning.deprecated(!('dataSource' in props), 'dataSource', 'options');
|
||||
|
||||
warning(
|
||||
!customizeInput || !('size' in props),
|
||||
'usage',
|
||||
'You need to control style self instead of setting `size` when using customize input.',
|
||||
);
|
||||
|
||||
warning.deprecated(!dropdownClassName, 'dropdownClassName', 'popupClassName');
|
||||
const deprecatedProps = {
|
||||
dropdownMatchSelectWidth: 'popupMatchSelectWidth',
|
||||
dropdownStyle: 'styles.popup.root',
|
||||
dropdownClassName: 'classNames.popup.root',
|
||||
popupClassName: 'classNames.popup.root',
|
||||
dropdownRender: 'popupRender',
|
||||
onDropdownVisibleChange: 'onOpenChange',
|
||||
dataSource: 'options',
|
||||
};
|
||||
|
||||
Object.entries(deprecatedProps).forEach(([oldProp, newProp]) => {
|
||||
warning.deprecated(!(oldProp in props), oldProp, newProp);
|
||||
});
|
||||
}
|
||||
|
||||
const { getPrefixCls } = React.useContext<ConfigConsumerProps>(ConfigContext);
|
||||
@ -128,21 +159,33 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
|
||||
const prefixCls = getPrefixCls('select', customizePrefixCls);
|
||||
|
||||
// ============================ zIndex ============================
|
||||
const [zIndex] = useZIndex('SelectLike', props.dropdownStyle?.zIndex as number);
|
||||
const [zIndex] = useZIndex('SelectLike', mergedPopupStyle?.zIndex as number);
|
||||
|
||||
return (
|
||||
<Select
|
||||
ref={ref}
|
||||
suffixIcon={null}
|
||||
{...omit(props, ['dataSource', 'dropdownClassName'])}
|
||||
{...omit(props, ['dataSource', 'dropdownClassName', 'popupClassName'])}
|
||||
prefixCls={prefixCls}
|
||||
popupClassName={popupClassName || dropdownClassName}
|
||||
dropdownStyle={{
|
||||
...props.dropdownStyle,
|
||||
zIndex,
|
||||
classNames={{
|
||||
popup: {
|
||||
root: mergedPopupClassName,
|
||||
},
|
||||
root: classNames?.root,
|
||||
}}
|
||||
className={classNames(`${prefixCls}-auto-complete`, className)}
|
||||
styles={{
|
||||
popup: {
|
||||
root: {
|
||||
...mergedPopupStyle,
|
||||
zIndex,
|
||||
},
|
||||
},
|
||||
root: styles?.root,
|
||||
}}
|
||||
className={cls(`${prefixCls}-auto-complete`, className)}
|
||||
mode={Select.SECRET_COMBOBOX_MODE_DO_NOT_USE as SelectProps['mode']}
|
||||
popupRender={mergedPopupRender}
|
||||
onOpenChange={mergedOnOpenChange}
|
||||
{...{
|
||||
// Internal api
|
||||
getInputElement,
|
||||
|
@ -728,7 +728,7 @@ exports[`renders components/auto-complete/demo/basic.tsx extend context correctl
|
||||
|
||||
exports[`renders components/auto-complete/demo/certain-category.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-select ant-select-lg ant-select-outlined ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
|
||||
class="ant-select ant-select-outlined ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
|
||||
style="width: 250px;"
|
||||
>
|
||||
<div
|
||||
@ -1096,11 +1096,7 @@ exports[`renders components/auto-complete/demo/certain-category.tsx extend conte
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/auto-complete/demo/certain-category.tsx extend context correctly 2`] = `
|
||||
[
|
||||
"Warning: [antd: AutoComplete] You need to control style self instead of setting \`size\` when using customize input.",
|
||||
]
|
||||
`;
|
||||
exports[`renders components/auto-complete/demo/certain-category.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/auto-complete/demo/custom.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
@ -2899,7 +2895,7 @@ exports[`renders components/auto-complete/demo/status.tsx extend context correct
|
||||
|
||||
exports[`renders components/auto-complete/demo/uncertain-category.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-select ant-select-lg ant-select-outlined ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
|
||||
class="ant-select ant-select-outlined ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
|
||||
style="width: 300px;"
|
||||
>
|
||||
<div
|
||||
@ -2935,7 +2931,7 @@ exports[`renders components/auto-complete/demo/uncertain-category.tsx extend con
|
||||
class="ant-input-group-addon"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-lg ant-input-search-button"
|
||||
class="ant-btn ant-btn-default ant-btn-color-primary ant-btn-variant-solid ant-btn-lg ant-input-search-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
@ -2986,11 +2982,7 @@ exports[`renders components/auto-complete/demo/uncertain-category.tsx extend con
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/auto-complete/demo/uncertain-category.tsx extend context correctly 2`] = `
|
||||
[
|
||||
"Warning: [antd: AutoComplete] You need to control style self instead of setting \`size\` when using customize input.",
|
||||
]
|
||||
`;
|
||||
exports[`renders components/auto-complete/demo/uncertain-category.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/auto-complete/demo/variant.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
|
@ -445,7 +445,7 @@ Array [
|
||||
|
||||
exports[`renders components/auto-complete/demo/certain-category.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-select ant-select-lg ant-select-outlined ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
|
||||
class="ant-select ant-select-outlined ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
|
||||
style="width:250px"
|
||||
>
|
||||
<div
|
||||
@ -1669,7 +1669,7 @@ exports[`renders components/auto-complete/demo/status.tsx correctly 1`] = `
|
||||
|
||||
exports[`renders components/auto-complete/demo/uncertain-category.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-select ant-select-lg ant-select-outlined ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
|
||||
class="ant-select ant-select-outlined ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
|
||||
style="width:300px"
|
||||
>
|
||||
<div
|
||||
@ -1704,7 +1704,7 @@ exports[`renders components/auto-complete/demo/uncertain-category.tsx correctly
|
||||
class="ant-input-group-addon"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-lg ant-input-search-button"
|
||||
class="ant-btn ant-btn-default ant-btn-color-primary ant-btn-variant-solid ant-btn-lg ant-input-search-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
|
@ -111,10 +111,101 @@ describe('AutoComplete', () => {
|
||||
/>,
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: AutoComplete] `dropdownClassName` is deprecated. Please use `popupClassName` instead.',
|
||||
'Warning: [antd: AutoComplete] `dropdownClassName` is deprecated. Please use `classNames.popup.root` instead.',
|
||||
);
|
||||
expect(container.querySelector('.legacy')).toBeTruthy();
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('deprecated popupClassName', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const { container } = render(
|
||||
<AutoComplete
|
||||
popupClassName="legacy"
|
||||
open
|
||||
options={[{ label: 'little', value: 'little' }]}
|
||||
searchValue="l"
|
||||
/>,
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: AutoComplete] `popupClassName` is deprecated. Please use `classNames.popup.root` instead.',
|
||||
);
|
||||
expect(container.querySelector('.legacy')).toBeTruthy();
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('deprecated dropdownMatchSelectWidth', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(
|
||||
<AutoComplete
|
||||
dropdownMatchSelectWidth
|
||||
open
|
||||
options={[{ label: 'little', value: 'little' }]}
|
||||
/>,
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: AutoComplete] `dropdownMatchSelectWidth` is deprecated. Please use `popupMatchSelectWidth` instead.',
|
||||
);
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('deprecated dropdownStyle', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(
|
||||
<AutoComplete
|
||||
dropdownStyle={{ color: 'red' }}
|
||||
open
|
||||
options={[{ label: 'little', value: 'little' }]}
|
||||
/>,
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: AutoComplete] `dropdownStyle` is deprecated. Please use `styles.popup.root` instead.',
|
||||
);
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('deprecated dropdownRender', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(
|
||||
<AutoComplete
|
||||
dropdownRender={(menu) => <div>{menu}</div>}
|
||||
open
|
||||
options={[{ label: 'little', value: 'little' }]}
|
||||
/>,
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: AutoComplete] `dropdownRender` is deprecated. Please use `popupRender` instead.',
|
||||
);
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('deprecated onDropdownVisibleChange', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(
|
||||
<AutoComplete
|
||||
onDropdownVisibleChange={() => {}}
|
||||
options={[{ label: 'little', value: 'little' }]}
|
||||
/>,
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: AutoComplete] `onDropdownVisibleChange` is deprecated. Please use `onOpenChange` instead.',
|
||||
);
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
32
components/auto-complete/demo/_semantic.tsx
Normal file
32
components/auto-complete/demo/_semantic.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { AutoComplete } from 'antd';
|
||||
|
||||
import SelectSemanticTemplate from '../../../.dumi/theme/common/SelectSemanticTemplate';
|
||||
|
||||
const mockVal = (str: string, repeat = 1) => ({
|
||||
value: str.repeat(repeat),
|
||||
label: str.repeat(repeat),
|
||||
});
|
||||
|
||||
const getPanelValue = (searchText: string) =>
|
||||
!searchText ? [] : [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)];
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [options, setOptions] = React.useState([
|
||||
{ value: 'aojunhao123', label: 'aojunhao123' },
|
||||
{ value: 'thinkasany', label: 'thinkasany' },
|
||||
]);
|
||||
|
||||
return (
|
||||
<SelectSemanticTemplate
|
||||
component={AutoComplete}
|
||||
componentName="AutoComplete"
|
||||
style={{ width: 200 }}
|
||||
options={options}
|
||||
onSearch={(text: string) => setOptions(getPanelValue(text))}
|
||||
placeholder="input here"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -40,11 +40,10 @@ const options = [
|
||||
|
||||
const App: React.FC = () => (
|
||||
<AutoComplete
|
||||
popupClassName="certain-category-search-dropdown"
|
||||
classNames={{ popup: { root: 'certain-category-search-dropdown' } }}
|
||||
popupMatchSelectWidth={500}
|
||||
style={{ width: 250 }}
|
||||
options={options}
|
||||
size="large"
|
||||
>
|
||||
<Input.Search size="large" placeholder="input here" />
|
||||
</AutoComplete>
|
||||
|
@ -53,7 +53,6 @@ const App: React.FC = () => {
|
||||
options={options}
|
||||
onSelect={onSelect}
|
||||
onSearch={handleSearch}
|
||||
size="large"
|
||||
>
|
||||
<Input.Search size="large" placeholder="input here" enterButton />
|
||||
</AutoComplete>
|
||||
|
@ -48,12 +48,15 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| backfill | If backfill selected item the input when using keyboard | boolean | false | |
|
||||
| children (for customize input element) | Customize input element | HTMLInputElement \| HTMLTextAreaElement \| React.ReactElement<InputProps> | <Input /> | |
|
||||
| children (for dataSource) | Data source to auto complete | React.ReactElement<OptionProps> \| Array<React.ReactElement<OptionProps>> | - | |
|
||||
| classNames | Semantic DOM class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.25.0 |
|
||||
| defaultActiveFirstOption | Whether active first option by default | boolean | true | |
|
||||
| defaultOpen | Initial open state of dropdown | boolean | - | |
|
||||
| defaultValue | Initial selected option | string | - | |
|
||||
| disabled | Whether disabled select | boolean | false | |
|
||||
| dropdownRender | Customize dropdown content | (menus: ReactNode) => ReactNode | - | 4.24.0 |
|
||||
| popupClassName | The className of dropdown menu | string | - | 4.23.0 |
|
||||
| ~~dropdownRender~~ | Customize dropdown content, use `popupRender` instead | (originNode: ReactNode) => ReactNode | - | 4.24.0 |
|
||||
| popupRender | Customize dropdown content | (originNode: ReactNode) => ReactNode | - | |
|
||||
| ~~dropdownStyle~~ | The style of dropdown menu, use `styles.popup.root` instead | CSSProperties | - | |
|
||||
| ~~popupClassName~~ | The className of dropdown menu, use `classNames.popup.root` instead | string | - | 4.23.0 |
|
||||
| popupMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | true | |
|
||||
| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns true, the option will be included in the filtered set; Otherwise, it will be excluded | boolean \| function(inputValue, option) | true | |
|
||||
| getPopupContainer | Parent node of the dropdown. Default to body, if you encountered positioning problems during scroll, try changing to the scrollable area and position relative to it. [Example](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
|
||||
@ -64,11 +67,13 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
|
||||
| size | The size of the input box | `large` \| `middle` \| `small` | - | |
|
||||
| value | Selected option | string | - | |
|
||||
| styles | Semantic DOM style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.25.0 |
|
||||
| variant | Variants of input | `outlined` \| `borderless` \| `filled` | `outlined` | 5.13.0 |
|
||||
| virtual | Disable virtual scroll when set to false | boolean | true | 4.1.0 |
|
||||
| onBlur | Called when leaving the component | function() | - | |
|
||||
| onChange | Called when selecting an option or changing an input value | function(value) | - | |
|
||||
| onDropdownVisibleChange | Call when dropdown open | function(open) | - | |
|
||||
| ~~onDropdownVisibleChange~~ | Called when dropdown open, use `onOpenChange` instead | (open: boolean) => void | - | |
|
||||
| onOpenChange | Called when dropdown open | (open: boolean) => void | - | |
|
||||
| onFocus | Called when entering the component | function() | - | |
|
||||
| onSearch | Called when searching items | function(value) | - | |
|
||||
| onSelect | Called when a option is selected. param is option's value and option instance | function(value, option) | - | |
|
||||
@ -83,6 +88,10 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| blur() | Remove focus | |
|
||||
| focus() | Get focus | |
|
||||
|
||||
## Semantic DOM
|
||||
|
||||
<code src="./demo/_semantic.tsx" simplify="true"></code>
|
||||
|
||||
## Design Token
|
||||
|
||||
<ComponentTokenTable component="Select"></ComponentTokenTable>
|
||||
|
@ -49,12 +49,15 @@ demo:
|
||||
| backfill | 使用键盘选择选项的时候把选中项回填到输入框中 | boolean | false | |
|
||||
| children (自动完成的数据源) | 自动完成的数据源,不能和自定义输入框同时配置 | React.ReactElement<OptionProps> \| Array<React.ReactElement<OptionProps>> | - | |
|
||||
| children (自定义输入框) | 自定义输入框,不能和自动完成的数据源同时配置 | HTMLInputElement \| HTMLTextAreaElement \| React.ReactElement<InputProps> | <Input /> | |
|
||||
| classNames | 语义化结构 class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.25.0 |
|
||||
| defaultActiveFirstOption | 是否默认高亮第一个选项 | boolean | true | |
|
||||
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |
|
||||
| defaultValue | 指定默认选中的条目 | string | - | |
|
||||
| disabled | 是否禁用 | boolean | false | |
|
||||
| dropdownRender | 自定义下拉框内容 | (menus: ReactNode) => ReactNode | - | 4.24.0 |
|
||||
| popupClassName | 下拉菜单的 className 属性 | string | - | 4.23.0 |
|
||||
| ~~dropdownRender~~ | 自定义下拉框内容,使用 `popupRender` 替换 | (originNode: ReactNode) => ReactNode | - | 4.24.0 |
|
||||
| popupRender | 自定义下拉框内容 | (originNode: ReactNode) => ReactNode | - | |
|
||||
| ~~popupClassName~~ | 下拉菜单的 className 属性,使用 `classNames.popup.root` 替换 | string | - | 4.23.0 |
|
||||
| ~~dropdownStyle~~ | 下拉菜单的 style 属性,使用 `styles.popup.root` 替换 | CSSProperties | - | |
|
||||
| popupMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | |
|
||||
| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 true,反之则返回 false | boolean \| function(inputValue, option) | true | |
|
||||
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
|
||||
@ -64,12 +67,14 @@ demo:
|
||||
| placeholder | 输入框提示 | string | - | |
|
||||
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
|
||||
| size | 控件大小 | `large` \| `middle` \| `small` | - | |
|
||||
| styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.25.0 |
|
||||
| value | 指定当前选中的条目 | string | - | |
|
||||
| variant | 形态变体 | `outlined` \| `borderless` \| `filled` | `outlined` | 5.13.0 |
|
||||
| virtual | 设置 false 时关闭虚拟滚动 | boolean | true | 4.1.0 |
|
||||
| onBlur | 失去焦点时的回调 | function() | - | |
|
||||
| onChange | 选中 option,或 input 的 value 变化时,调用此函数 | function(value) | - | |
|
||||
| onDropdownVisibleChange | 展开下拉菜单的回调 | function(open) | - | |
|
||||
| ~~onDropdownVisibleChange~~ | 展开下拉菜单的回调,使用 `onOpenChange` 替换 | (open: boolean) => void | - | |
|
||||
| onOpenChange | 展开下拉菜单的回调 | (open: boolean) => void | - | |
|
||||
| onFocus | 获得焦点时的回调 | function() | - | |
|
||||
| onSearch | 搜索补全项的时候调用 | function(value) | - | |
|
||||
| onSelect | 被选中时调用,参数为选中项的 value 值 | function(value, option) | - | |
|
||||
@ -84,6 +89,10 @@ demo:
|
||||
| blur() | 移除焦点 | |
|
||||
| focus() | 获取焦点 | |
|
||||
|
||||
## Semantic DOM
|
||||
|
||||
<code src="./demo/_semantic.tsx" simplify="true"></code>
|
||||
|
||||
## 主题变量(Design Token)
|
||||
|
||||
<ComponentTokenTable component="Select"></ComponentTokenTable>
|
||||
|
@ -127,7 +127,7 @@ const AvatarGroup: React.FC<AvatarGroupProps> = (props) => {
|
||||
};
|
||||
|
||||
childrenShow.push(
|
||||
<Popover key="avatar-popover-key" destroyTooltipOnHide {...mergeProps}>
|
||||
<Popover key="avatar-popover-key" destroyOnHidden {...mergeProps}>
|
||||
<Avatar style={mergeStyle}>{`+${numOfChildren - mergeCount}`}</Avatar>
|
||||
</Popover>,
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Avatar, Badge } from 'antd';
|
||||
|
||||
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
|
||||
import SemanticPreview from '../../../.dumi/theme/common/SemanticPreview';
|
||||
import useLocale from '../../../.dumi/hooks/useLocale';
|
||||
|
||||
const locales = {
|
||||
|
@ -245,7 +245,6 @@ describe('Breadcrumb', () => {
|
||||
items={[
|
||||
{
|
||||
title: 'xxx',
|
||||
// @ts-ignore
|
||||
'data-custom': 'custom-item',
|
||||
},
|
||||
{
|
||||
|
@ -508,9 +508,46 @@ describe('Button', () => {
|
||||
const { getByRole } = render(
|
||||
<Button href="https://example.com" onClick={handleClick}>
|
||||
Link
|
||||
</Button>
|
||||
</Button>,
|
||||
);
|
||||
fireEvent.click(getByRole('link'));
|
||||
expect(handleClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ConfigProvider support button variant', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider button={{ variant: 'dashed', color: 'blue' }}>
|
||||
<Button>Button</Button>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
|
||||
expect(container.firstChild).toHaveClass('ant-btn-variant-dashed');
|
||||
expect(container.firstChild).toHaveClass('ant-btn-color-blue');
|
||||
});
|
||||
|
||||
it('should show the component internal properties', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider button={{ variant: 'dashed', color: 'blue' }}>
|
||||
<Button variant="filled" color="green">
|
||||
Button
|
||||
</Button>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
|
||||
expect(container.firstChild).toHaveClass('ant-btn-variant-filled');
|
||||
expect(container.firstChild).toHaveClass('ant-btn-color-green');
|
||||
});
|
||||
|
||||
it('button type win the context', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider button={{ variant: 'dashed', color: 'green' }}>
|
||||
<Button type="primary" danger>
|
||||
Button
|
||||
</Button>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
|
||||
expect(container.querySelector('.ant-btn-variant-solid')).toBeTruthy();
|
||||
expect(container.querySelector('.ant-btn-color-dangerous')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
@ -5,7 +5,7 @@ import { useComposeRef } from 'rc-util/lib/ref';
|
||||
|
||||
import { devUseWarning } from '../_util/warning';
|
||||
import Wave from '../_util/wave';
|
||||
import { useComponentConfig } from '../config-provider/context';
|
||||
import { ConfigContext, useComponentConfig } from '../config-provider/context';
|
||||
import DisabledContext from '../config-provider/DisabledContext';
|
||||
import useSize from '../config-provider/hooks/useSize';
|
||||
import type { SizeType } from '../config-provider/SizeContext';
|
||||
@ -127,20 +127,31 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
// https://github.com/ant-design/ant-design/issues/47605
|
||||
// Compatible with original `type` behavior
|
||||
const mergedType = type || 'default';
|
||||
const { button } = React.useContext(ConfigContext);
|
||||
|
||||
const [mergedColor, mergedVariant] = useMemo<ColorVariantPairType>(() => {
|
||||
// >>>>> Local
|
||||
// Color & Variant
|
||||
if (color && variant) {
|
||||
return [color, variant];
|
||||
}
|
||||
|
||||
const colorVariantPair = ButtonTypeMap[mergedType] || [];
|
||||
|
||||
if (danger) {
|
||||
return ['danger', colorVariantPair[1]];
|
||||
// Sugar syntax
|
||||
if (type || danger) {
|
||||
const colorVariantPair = ButtonTypeMap[mergedType] || [];
|
||||
if (danger) {
|
||||
return ['danger', colorVariantPair[1]];
|
||||
}
|
||||
return colorVariantPair;
|
||||
}
|
||||
|
||||
return colorVariantPair;
|
||||
}, [type, color, variant, danger]);
|
||||
// >>> Context fallback
|
||||
if (button?.color && button?.variant) {
|
||||
return [button.color, button.variant];
|
||||
}
|
||||
|
||||
return ['default', 'outlined'];
|
||||
}, [type, color, variant, danger, button?.variant, button?.color]);
|
||||
|
||||
const isDanger = mergedColor === 'danger';
|
||||
const mergedColorText = isDanger ? 'dangerous' : mergedColor;
|
||||
@ -291,9 +302,10 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
cssVarCls,
|
||||
{
|
||||
[`${prefixCls}-${shape}`]: shape !== 'default' && shape,
|
||||
// line(253 - 254): Compatible with versions earlier than 5.21.0
|
||||
// Compatible with versions earlier than 5.21.0
|
||||
[`${prefixCls}-${mergedType}`]: mergedType,
|
||||
[`${prefixCls}-dangerous`]: danger,
|
||||
|
||||
[`${prefixCls}-color-${mergedColorText}`]: mergedColorText,
|
||||
[`${prefixCls}-variant-${mergedVariant}`]: mergedVariant,
|
||||
[`${prefixCls}-${sizeCls}`]: sizeCls,
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { AntDesignOutlined } from '@ant-design/icons';
|
||||
import { Button } from 'antd';
|
||||
|
||||
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
|
||||
import SemanticPreview from '../../../.dumi/theme/common/SemanticPreview';
|
||||
import useLocale from '../../../.dumi/hooks/useLocale';
|
||||
|
||||
const locales = {
|
||||
|
@ -91,7 +91,7 @@ group:
|
||||
|
||||
> type PresetColors = 'blue' | 'purple' | 'cyan' | 'green' | 'magenta' | 'pink' | 'red' | 'orange' | 'yellow' | 'volcano' | 'geekblue' | 'lime' | 'gold';
|
||||
|
||||
## Semantic DOM {#semantic-dom}
|
||||
## Semantic DOM
|
||||
|
||||
<code src="./demo/_semantic.tsx" simplify="true"></code>
|
||||
|
||||
|
@ -98,8 +98,8 @@ const useStyle = createStyles(({ token, css, cx }) => {
|
||||
const App: React.FC = () => {
|
||||
const { styles } = useStyle({ test: true });
|
||||
|
||||
const [selectDate, setSelectDate] = React.useState<Dayjs>(dayjs());
|
||||
const [panelDateDate, setPanelDate] = React.useState<Dayjs>(dayjs());
|
||||
const [selectDate, setSelectDate] = React.useState<Dayjs>(() => dayjs());
|
||||
const [panelDateDate, setPanelDate] = React.useState<Dayjs>(() => dayjs());
|
||||
|
||||
const onPanelChange = (value: Dayjs, mode: CalendarProps<Dayjs>['mode']) => {
|
||||
console.log(value.format('YYYY-MM-DD'), mode);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
|
||||
import SemanticPreview from '../../../.dumi/theme/common/SemanticPreview';
|
||||
import useLocale from '../../../.dumi/hooks/useLocale';
|
||||
|
||||
import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
|
@ -93,13 +93,11 @@ describe('Cascader', () => {
|
||||
});
|
||||
|
||||
it('popup correctly when panel is open', () => {
|
||||
const onPopupVisibleChange = jest.fn();
|
||||
const { container } = render(
|
||||
<Cascader options={options} onPopupVisibleChange={onPopupVisibleChange} />,
|
||||
);
|
||||
const onOpenChange = jest.fn();
|
||||
const { container } = render(<Cascader options={options} onOpenChange={onOpenChange} />);
|
||||
toggleOpen(container);
|
||||
expect(isOpen(container)).toBeTruthy();
|
||||
expect(onPopupVisibleChange).toHaveBeenCalledWith(true);
|
||||
expect(onOpenChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('support controlled mode', () => {
|
||||
@ -548,13 +546,87 @@ describe('Cascader', () => {
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const { container } = render(<Cascader dropdownClassName="legacy" open />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Cascader] `dropdownClassName` is deprecated. Please use `popupClassName` instead.',
|
||||
'Warning: [antd: Cascader] `dropdownClassName` is deprecated. Please use `classNames.popup.root` instead.',
|
||||
);
|
||||
expect(container.querySelector('.legacy')).toBeTruthy();
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('legacy dropdownStyle', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const customStyle = { background: 'red' };
|
||||
const { container } = render(<Cascader dropdownStyle={customStyle} open />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Cascader] `dropdownStyle` is deprecated. Please use `styles.popup.root` instead.',
|
||||
);
|
||||
expect(container.querySelector('.ant-select-dropdown')?.getAttribute('style')).toContain(
|
||||
'background: red',
|
||||
);
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('legacy dropdownRender', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const customContent = <div className="custom-dropdown-content">Custom Content</div>;
|
||||
const dropdownRender = (menu: React.ReactElement) => (
|
||||
<>
|
||||
{menu}
|
||||
{customContent}
|
||||
</>
|
||||
);
|
||||
|
||||
const { container } = render(<Cascader dropdownRender={dropdownRender} open />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Cascader] `dropdownRender` is deprecated. Please use `popupRender` instead.',
|
||||
);
|
||||
expect(container.querySelector('.custom-dropdown-content')).toBeTruthy();
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('legacy dropdownMenuColumnStyle', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const columnStyle = { background: 'red' };
|
||||
const { getByRole } = render(
|
||||
<Cascader
|
||||
options={[{ label: 'test', value: 1 }]}
|
||||
dropdownMenuColumnStyle={columnStyle}
|
||||
open
|
||||
/>,
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Cascader] `dropdownMenuColumnStyle` is deprecated. Please use `popupMenuColumnStyle` instead.',
|
||||
);
|
||||
const menuColumn = getByRole('menuitemcheckbox');
|
||||
expect(menuColumn.style.background).toBe('red');
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('legacy onDropdownVisibleChange', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const onDropdownVisibleChange = jest.fn();
|
||||
const { container } = render(<Cascader onDropdownVisibleChange={onDropdownVisibleChange} />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Cascader] `onDropdownVisibleChange` is deprecated. Please use `onOpenChange` instead.',
|
||||
);
|
||||
|
||||
toggleOpen(container);
|
||||
expect(onDropdownVisibleChange).toHaveBeenCalledWith(true);
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should support showCheckedStrategy child', () => {
|
||||
const multipleOptions = [
|
||||
{
|
||||
|
78
components/cascader/demo/_semantic.tsx
Normal file
78
components/cascader/demo/_semantic.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { Cascader } from 'antd';
|
||||
|
||||
import SemanticPreview from '../../../.dumi/theme/common/SemanticPreview';
|
||||
import useLocale from '../../../.dumi/hooks/useLocale';
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
root: '根元素',
|
||||
'popup.root': '弹出菜单元素',
|
||||
},
|
||||
en: {
|
||||
root: 'Root element',
|
||||
'popup.root': 'Popup element',
|
||||
},
|
||||
};
|
||||
const options = [
|
||||
{
|
||||
value: 'contributors',
|
||||
label: 'contributors',
|
||||
children: [
|
||||
{
|
||||
value: 'aojunhao123',
|
||||
label: 'aojunhao123',
|
||||
},
|
||||
{
|
||||
value: 'thinkasany',
|
||||
label: 'thinkasany',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const Block = (props: any) => {
|
||||
const divRef = React.useRef<HTMLDivElement>(null);
|
||||
const [value, setValue] = React.useState<string[]>(['contributors', 'aojunhao123']);
|
||||
const onChange = (newValue: string[]) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
return (
|
||||
<div ref={divRef} style={{ marginBottom: 60 }}>
|
||||
<Cascader
|
||||
{...props}
|
||||
open
|
||||
styles={{
|
||||
popup: {
|
||||
root: {
|
||||
zIndex: 1,
|
||||
height: 70,
|
||||
},
|
||||
},
|
||||
}}
|
||||
getPopupContainer={() => divRef.current}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
placement="bottomLeft"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const App: React.FC = () => {
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
return (
|
||||
<SemanticPreview
|
||||
componentName="Cascader"
|
||||
semantics={[
|
||||
{ name: 'root', desc: locale.root, version: '5.25.0' },
|
||||
{ name: 'popup.root', desc: locale['popup.root'], version: '5.25.0' },
|
||||
]}
|
||||
>
|
||||
<Block />
|
||||
</SemanticPreview>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -42,7 +42,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const dropdownRender = (menus: React.ReactNode) => (
|
||||
const popupRender = (menus: React.ReactNode) => (
|
||||
<div>
|
||||
{menus}
|
||||
<Divider style={{ margin: 0 }} />
|
||||
@ -51,7 +51,7 @@ const dropdownRender = (menus: React.ReactNode) => (
|
||||
);
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Cascader options={options} dropdownRender={dropdownRender} placeholder="Please select" />
|
||||
<Cascader options={options} popupRender={popupRender} placeholder="Please select" />
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
@ -55,13 +55,16 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| autoFocus | If get focus when component mounted | boolean | false | |
|
||||
| changeOnSelect | Change value on each selection if set to true, see above demo for details | boolean | false | |
|
||||
| className | The additional css class | string | - | |
|
||||
| classNames | Semantic DOM class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.25.0 |
|
||||
| defaultOpen | Initial visible of cascader popup | boolean | - | |
|
||||
| defaultValue | Initial selected value | string\[] \| number\[] | \[] | |
|
||||
| disabled | Whether disabled select | boolean | false | |
|
||||
| displayRender | The render function of displaying selected options | (label, selectedOptions) => ReactNode | label => label.join(`/`) | `multiple`: 4.18.0 |
|
||||
| tagRender | Custom render function for tags in `multiple` mode | (label: string, onClose: function, value: string) => ReactNode | - | |
|
||||
| popupClassName | The additional className of popup overlay | string | - | 4.23.0 |
|
||||
| dropdownRender | Customize dropdown content | (menus: ReactNode) => ReactNode | - | 4.4.0 |
|
||||
| ~~popupClassName~~ | The additional className of popup overlay, use `classNames.popup.root` instead | string | - | 4.23.0 |
|
||||
| ~~dropdownRender~~ | Customize dropdown content, use `popupRender` instead | (menus: ReactNode) => ReactNode | - | 4.4.0 |
|
||||
| popupRender | Customize dropdown content | (menus: ReactNode) => ReactNode | - | |
|
||||
| ~~dropdownStyle~~ | The style of dropdown menu, use `styles.popup.root` instead | CSSProperties | - | |
|
||||
| expandIcon | Customize the current item expand icon | ReactNode | - | 4.4.0 |
|
||||
| expandTrigger | expand current item when click or hover, one of `click` `hover` | string | `click` | |
|
||||
| fieldNames | Custom field name for label and value and children | object | { label: `label`, value: `value`, children: `children` } | |
|
||||
@ -79,18 +82,20 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| showSearch | Whether show search input in single mode | boolean \| [Object](#showsearch) | false | |
|
||||
| size | The input size | `large` \| `middle` \| `small` | - | |
|
||||
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
|
||||
| style | The additional style | CSSProperties | - | |
|
||||
| styles | Semantic DOM style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.25.0 |
|
||||
| suffixIcon | The custom suffix icon | ReactNode | - | |
|
||||
| value | The selected value | string\[] \| number\[] | - | |
|
||||
| variant | Variants of selector | `outlined` \| `borderless` \| `filled` \| `underlined` | `outlined` | 5.13.0 \| `underlined`: 5.24.0 |
|
||||
| onChange | Callback when finishing cascader select | (value, selectedOptions) => void | - | |
|
||||
| onDropdownVisibleChange | Callback when popup shown or hidden | (value) => void | - | 4.17.0 |
|
||||
| ~~onDropdownVisibleChange~~ | Callback when popup shown or hidden, use `onOpenChange` instead | (value) => void | - | 4.17.0 |
|
||||
| onOpenChange | Callback when popup shown or hidden | (value) => void | - | |
|
||||
| multiple | Support multiple or not | boolean | - | 4.17.0 |
|
||||
| removeIcon | The custom remove icon | ReactNode | - | |
|
||||
| showCheckedStrategy | The way show selected item in box. ** `SHOW_CHILD`: ** just show child treeNode. **`Cascader.SHOW_PARENT`:** just show parent treeNode (when all child treeNode under the parent treeNode are checked) | `Cascader.SHOW_PARENT` \| `Cascader.SHOW_CHILD` | `Cascader.SHOW_PARENT` | 4.20.0 |
|
||||
| searchValue | Set search value, Need work with `showSearch` | string | - | 4.17.0 |
|
||||
| onSearch | The callback function triggered when input changed | (search: string) => void | - | 4.17.0 |
|
||||
| dropdownMenuColumnStyle | The style of the drop-down menu column | CSSProperties | - | |
|
||||
| ~~dropdownMenuColumnStyle~~ | The style of the drop-down menu column, use `popupMenuColumnStyle` instead | CSSProperties | - | |
|
||||
| popupMenuColumnStyle | The style of the drop-down menu column | CSSProperties | - | |
|
||||
| loadingIcon | The appearance of lazy loading (now is useless) | ReactNode | - | |
|
||||
| optionRender | Customize the rendering dropdown options | (option: Option) => React.ReactNode | - | 5.16.0 |
|
||||
|
||||
@ -126,6 +131,10 @@ interface Option {
|
||||
| blur() | Remove focus | |
|
||||
| focus() | Get focus | |
|
||||
|
||||
## Semantic DOM
|
||||
|
||||
<code src="./demo/_semantic.tsx" simplify="true"></code>
|
||||
|
||||
## Design Token
|
||||
|
||||
<ComponentTokenTable component="Cascader"></ComponentTokenTable>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import cls from 'classnames';
|
||||
import type {
|
||||
BaseOptionType,
|
||||
DefaultOptionType,
|
||||
@ -50,6 +50,9 @@ export type FieldNamesType = FieldNames;
|
||||
|
||||
export type FilledFieldNamesType = Required<FieldNamesType>;
|
||||
|
||||
type SemanticName = 'root';
|
||||
type PopupSemantic = 'root';
|
||||
|
||||
const { SHOW_CHILD, SHOW_PARENT } = RcCascader;
|
||||
|
||||
function highlightKeyword(str: string, lowerKeyword: string, prefixCls?: string) {
|
||||
@ -127,14 +130,32 @@ export interface CascaderProps<
|
||||
autoClearSearchValue?: boolean;
|
||||
|
||||
rootClassName?: string;
|
||||
/** @deprecated Please use `classNames.popup.root` instead */
|
||||
popupClassName?: string;
|
||||
/** @deprecated Please use `popupClassName` instead */
|
||||
/** @deprecated Please use `classNames.popup.root` instead */
|
||||
dropdownClassName?: string;
|
||||
/** @deprecated Please use `styles.popup.root` instead */
|
||||
dropdownStyle?: React.CSSProperties;
|
||||
/** @deprecated Please use `popupRender` instead */
|
||||
dropdownRender?: (menu: React.ReactElement) => React.ReactElement;
|
||||
popupRender?: (menu: React.ReactElement) => React.ReactElement;
|
||||
/** @deprecated Please use `popupMenuColumnStyle` instead */
|
||||
dropdownMenuColumnStyle?: React.CSSProperties;
|
||||
popupMenuColumnStyle?: React.CSSProperties;
|
||||
/** @deprecated Please use `onOpenChange` instead */
|
||||
onDropdownVisibleChange?: (visible: boolean) => void;
|
||||
onOpenChange?: (visible: boolean) => void;
|
||||
/**
|
||||
* @since 5.13.0
|
||||
* @default "outlined"
|
||||
*/
|
||||
variant?: Variant;
|
||||
classNames?: Partial<Record<SemanticName, string>> & {
|
||||
popup?: Partial<Record<PopupSemantic, string>>;
|
||||
};
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>> & {
|
||||
popup?: Partial<Record<PopupSemantic, React.CSSProperties>>;
|
||||
};
|
||||
}
|
||||
export type CascaderAutoProps<
|
||||
OptionType extends DefaultOptionType = DefaultOptionType,
|
||||
@ -173,6 +194,15 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
builtinPlacements,
|
||||
style,
|
||||
variant: customVariant,
|
||||
dropdownRender,
|
||||
onDropdownVisibleChange,
|
||||
dropdownMenuColumnStyle,
|
||||
popupRender,
|
||||
dropdownStyle,
|
||||
popupMenuColumnStyle,
|
||||
onOpenChange,
|
||||
styles,
|
||||
classNames,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@ -183,6 +213,8 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
getPopupContainer: getContextPopupContainer,
|
||||
className: contextClassName,
|
||||
style: contextStyle,
|
||||
classNames: contextClassNames,
|
||||
styles: contextStyles,
|
||||
} = useComponentConfig('cascader');
|
||||
|
||||
const { popupOverflow } = React.useContext(ConfigContext);
|
||||
@ -200,15 +232,25 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('Cascader');
|
||||
|
||||
warning.deprecated(!dropdownClassName, 'dropdownClassName', 'popupClassName');
|
||||
// v5 deprecated dropdown api
|
||||
const deprecatedProps = {
|
||||
dropdownClassName: 'classNames.popup.root',
|
||||
dropdownStyle: 'styles.popup.root',
|
||||
dropdownRender: 'popupRender',
|
||||
dropdownMenuColumnStyle: 'popupMenuColumnStyle',
|
||||
onDropdownVisibleChange: 'onOpenChange',
|
||||
bordered: 'variant',
|
||||
};
|
||||
|
||||
Object.entries(deprecatedProps).forEach(([oldProp, newProp]) => {
|
||||
warning.deprecated(!(oldProp in props), oldProp, newProp);
|
||||
});
|
||||
|
||||
warning(
|
||||
!('showArrow' in props),
|
||||
'deprecated',
|
||||
'`showArrow` is deprecated which will be removed in next major version. It will be a default behavior, you can hide it by setting `suffixIcon` to null.',
|
||||
);
|
||||
|
||||
warning.deprecated(!('bordered' in props), 'bordered', 'variant');
|
||||
}
|
||||
|
||||
// ==================== Prefix =====================
|
||||
@ -235,19 +277,26 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
);
|
||||
|
||||
// =================== Dropdown ====================
|
||||
const mergedDropdownClassName = classNames(
|
||||
popupClassName || dropdownClassName,
|
||||
const mergedPopupClassName = cls(
|
||||
classNames?.popup?.root || contextClassNames.popup?.root || popupClassName || dropdownClassName,
|
||||
`${cascaderPrefixCls}-dropdown`,
|
||||
{
|
||||
[`${cascaderPrefixCls}-dropdown-rtl`]: mergedDirection === 'rtl',
|
||||
},
|
||||
rootClassName,
|
||||
rootCls,
|
||||
contextClassNames.root,
|
||||
classNames?.root,
|
||||
cascaderRootCls,
|
||||
hashId,
|
||||
cssVarCls,
|
||||
);
|
||||
|
||||
const mergedPopupRender = popupRender || dropdownRender;
|
||||
const mergedPopupMenuColumnStyle = popupMenuColumnStyle || dropdownMenuColumnStyle;
|
||||
const mergedOnOpenChange = onOpenChange || onDropdownVisibleChange;
|
||||
const mergedPopupStyle = styles?.popup?.root || contextStyles.popup?.root || dropdownStyle;
|
||||
|
||||
// ==================== Search =====================
|
||||
const mergedShowSearch = React.useMemo(() => {
|
||||
if (!showSearch) {
|
||||
@ -304,13 +353,13 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
const mergedAllowClear = allowClear === true ? { clearIcon } : allowClear;
|
||||
|
||||
// ============================ zIndex ============================
|
||||
const [zIndex] = useZIndex('SelectLike', restProps.dropdownStyle?.zIndex as number);
|
||||
const [zIndex] = useZIndex('SelectLike', mergedPopupStyle?.zIndex as number);
|
||||
|
||||
// ==================== Render =====================
|
||||
const renderNode = (
|
||||
<RcCascader
|
||||
prefixCls={prefixCls}
|
||||
className={classNames(
|
||||
className={cls(
|
||||
!customizePrefixCls && cascaderPrefixCls,
|
||||
{
|
||||
[`${prefixCls}-lg`]: mergedSize === 'large',
|
||||
@ -324,13 +373,15 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
contextClassName,
|
||||
className,
|
||||
rootClassName,
|
||||
classNames?.root,
|
||||
contextClassNames.root,
|
||||
rootCls,
|
||||
cascaderRootCls,
|
||||
hashId,
|
||||
cssVarCls,
|
||||
)}
|
||||
disabled={mergedDisabled}
|
||||
style={{ ...contextStyle, ...style }}
|
||||
style={{ ...contextStyles.root, ...styles?.root, ...contextStyle, ...style }}
|
||||
{...(restProps as any)}
|
||||
builtinPlacements={mergedBuiltinPlacements(builtinPlacements, popupOverflow)}
|
||||
direction={mergedDirection}
|
||||
@ -343,9 +394,12 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
removeIcon={removeIcon}
|
||||
loadingIcon={loadingIcon}
|
||||
checkable={checkable}
|
||||
dropdownClassName={mergedDropdownClassName}
|
||||
dropdownClassName={mergedPopupClassName}
|
||||
dropdownPrefixCls={customizePrefixCls || cascaderPrefixCls}
|
||||
dropdownStyle={{ ...restProps.dropdownStyle, zIndex }}
|
||||
dropdownStyle={{ ...mergedPopupStyle, zIndex }}
|
||||
dropdownRender={mergedPopupRender}
|
||||
dropdownMenuColumnStyle={mergedPopupMenuColumnStyle}
|
||||
onOpenChange={mergedOnOpenChange}
|
||||
choiceTransitionName={getTransitionName(rootPrefixCls, '', choiceTransitionName)}
|
||||
transitionName={getTransitionName(rootPrefixCls, 'slide-up', transitionName)}
|
||||
getPopupContainer={getPopupContainer || getContextPopupContainer}
|
||||
|
@ -56,13 +56,16 @@ demo:
|
||||
| autoFocus | 自动获取焦点 | boolean | false | |
|
||||
| changeOnSelect | 单选时生效(multiple 下始终都可以选择),点选每级菜单选项值都会发生变化。 | boolean | false | |
|
||||
| className | 自定义类名 | string | - | |
|
||||
| classNames | 语义化结构 class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.25.0 |
|
||||
| defaultOpen | 是否默认展示浮层 | boolean | - | |
|
||||
| defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | |
|
||||
| disabled | 禁用 | boolean | false | |
|
||||
| displayRender | 选择后展示的渲染函数 | (label, selectedOptions) => ReactNode | label => label.join(`/`) | `multiple`: 4.18.0 |
|
||||
| tagRender | 自定义 tag 内容 render,仅在多选时生效 | ({ label: string, onClose: function, value: string }) => ReactNode | - | |
|
||||
| popupClassName | 自定义浮层类名 | string | - | 4.23.0 |
|
||||
| dropdownRender | 自定义下拉框内容 | (menus: ReactNode) => ReactNode | - | 4.4.0 |
|
||||
| ~~popupClassName~~ | 自定义浮层类名,使用 `classNames.popup.root` 替换 | string | - | 4.23.0 |
|
||||
| ~~dropdownRender~~ | 自定义下拉框内容,请使用 `popupRender` 替换 | (menus: ReactNode) => ReactNode | - | 4.4.0 |
|
||||
| popupRender | 自定义下拉框内容 | (menus: ReactNode) => ReactNode | - | |
|
||||
| ~~dropdownStyle~~ | 下拉菜单的 style 属性,使用 `styles.popup.root` 替换 | CSSProperties | - | |
|
||||
| expandIcon | 自定义次级菜单展开图标 | ReactNode | - | 4.4.0 |
|
||||
| expandTrigger | 次级菜单的展开方式,可选 'click' 和 'hover' | string | `click` | |
|
||||
| fieldNames | 自定义 options 中 label value children 的字段 | object | { label: `label`, value: `value`, children: `children` } | |
|
||||
@ -80,18 +83,20 @@ demo:
|
||||
| showSearch | 在选择框中显示搜索框 | boolean \| [Object](#showsearch) | false | |
|
||||
| size | 输入框大小 | `large` \| `middle` \| `small` | - | |
|
||||
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
|
||||
| style | 自定义样式 | CSSProperties | - | |
|
||||
| styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.25.0 |
|
||||
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
|
||||
| value | 指定选中项 | string\[] \| number\[] | - | |
|
||||
| variant | 形态变体 | `outlined` \| `borderless` \| `filled` \| `underlined` | `outlined` | 5.13.0 \| `underlined`: 5.24.0 |
|
||||
| onChange | 选择完成后的回调 | (value, selectedOptions) => void | - | |
|
||||
| onDropdownVisibleChange | 显示/隐藏浮层的回调 | (value) => void | - | 4.17.0 |
|
||||
| ~~onDropdownVisibleChange~~ | 显示/隐藏浮层的回调,请使用 `onOpenChange` 替换 | (value) => void | - | 4.17.0 |
|
||||
| onOpenChange | 显示/隐藏浮层的回调 | (value) => void | - | |
|
||||
| multiple | 支持多选节点 | boolean | - | 4.17.0 |
|
||||
| showCheckedStrategy | 定义选中项回填的方式。`Cascader.SHOW_CHILD`: 只显示选中的子节点。`Cascader.SHOW_PARENT`: 只显示父节点(当父节点下所有子节点都选中时)。 | `Cascader.SHOW_PARENT` \| `Cascader.SHOW_CHILD` | `Cascader.SHOW_PARENT` | 4.20.0 |
|
||||
| removeIcon | 自定义的多选框清除图标 | ReactNode | - | |
|
||||
| searchValue | 设置搜索的值,需要与 `showSearch` 配合使用 | string | - | 4.17.0 |
|
||||
| onSearch | 监听搜索,返回输入的值 | (search: string) => void | - | 4.17.0 |
|
||||
| dropdownMenuColumnStyle | 下拉菜单列的样式 | CSSProperties | - | |
|
||||
| ~~dropdownMenuColumnStyle~~ | 下拉菜单列的样式,请使用 `popupMenuColumnStyle` 替换 | CSSProperties | - | |
|
||||
| popupMenuColumnStyle | 下拉菜单列的样式 | CSSProperties | - | |
|
||||
| optionRender | 自定义渲染下拉选项 | (option: Option) => React.ReactNode | - | 5.16.0 |
|
||||
|
||||
### showSearch
|
||||
@ -129,6 +134,10 @@ interface Option {
|
||||
|
||||
> 注意,如果需要获得中国省市区数据,可以参考 [china-division](https://gist.github.com/afc163/7582f35654fd03d5be7009444345ea17)。
|
||||
|
||||
## Semantic DOM
|
||||
|
||||
<code src="./demo/_semantic.tsx" simplify="true"></code>
|
||||
|
||||
## 主题变量(Design Token)
|
||||
|
||||
<ComponentTokenTable component="Cascader"></ComponentTokenTable>
|
||||
|
@ -177,7 +177,9 @@ const InternalCheckbox: React.ForwardRefRenderFunction<CheckboxRef, CheckboxProp
|
||||
disabled={mergedDisabled}
|
||||
ref={mergedRef}
|
||||
/>
|
||||
{children !== undefined && <span className={`${prefixCls}-label`}>{children}</span>}
|
||||
{children !== undefined && children !== null && (
|
||||
<span className={`${prefixCls}-label`}>{children}</span>
|
||||
)}
|
||||
</label>
|
||||
</Wave>,
|
||||
);
|
||||
|
@ -14,6 +14,7 @@ export interface CheckboxOptionType<T = any> {
|
||||
label: React.ReactNode;
|
||||
value: T;
|
||||
style?: React.CSSProperties;
|
||||
className?: string; // 👈 5.25.0+
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
id?: string;
|
||||
@ -125,7 +126,7 @@ const CheckboxGroup = React.forwardRef(
|
||||
value={option.value}
|
||||
checked={value.includes(option.value)}
|
||||
onChange={option.onChange}
|
||||
className={`${groupPrefixCls}-item`}
|
||||
className={classNames(`${groupPrefixCls}-item`, option.className)}
|
||||
style={option.style}
|
||||
title={option.title}
|
||||
id={option.id}
|
||||
@ -136,15 +137,19 @@ const CheckboxGroup = React.forwardRef(
|
||||
))
|
||||
: children;
|
||||
|
||||
const context: CheckboxGroupContext<any> = {
|
||||
toggleOption,
|
||||
value,
|
||||
disabled: restProps.disabled,
|
||||
name: restProps.name,
|
||||
// https://github.com/ant-design/ant-design/issues/16376
|
||||
registerValue,
|
||||
cancelValue,
|
||||
};
|
||||
const memoizedContext = React.useMemo<CheckboxGroupContext<any>>(
|
||||
() => ({
|
||||
toggleOption,
|
||||
value,
|
||||
disabled: restProps.disabled,
|
||||
name: restProps.name,
|
||||
// https://github.com/ant-design/ant-design/issues/16376
|
||||
registerValue,
|
||||
cancelValue,
|
||||
}),
|
||||
[toggleOption, value, restProps.disabled, restProps.name, registerValue, cancelValue],
|
||||
);
|
||||
|
||||
const classString = classNames(
|
||||
groupPrefixCls,
|
||||
{
|
||||
@ -156,9 +161,10 @@ const CheckboxGroup = React.forwardRef(
|
||||
rootCls,
|
||||
hashId,
|
||||
);
|
||||
|
||||
return wrapCSSVar(
|
||||
<div className={classString} style={style} {...domProps} ref={ref}>
|
||||
<GroupContext.Provider value={context}>{childrenNode}</GroupContext.Provider>
|
||||
<GroupContext.Provider value={memoizedContext}>{childrenNode}</GroupContext.Provider>
|
||||
</div>,
|
||||
);
|
||||
},
|
||||
|
@ -691,7 +691,7 @@ Array [
|
||||
class="ant-checkbox-group"
|
||||
>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-group-item label-1"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target"
|
||||
@ -712,7 +712,7 @@ Array [
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-checkbox-group-item label-2"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target ant-checkbox-checked"
|
||||
@ -734,7 +734,7 @@ Array [
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-group-item label-3"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target"
|
||||
@ -761,7 +761,7 @@ Array [
|
||||
class="ant-checkbox-group"
|
||||
>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-checkbox-wrapper-disabled ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-checkbox-wrapper-disabled ant-checkbox-group-item label-1"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target ant-checkbox-checked ant-checkbox-disabled"
|
||||
@ -784,7 +784,7 @@ Array [
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled ant-checkbox-group-item label-2"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target ant-checkbox-disabled"
|
||||
@ -806,7 +806,7 @@ Array [
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled ant-checkbox-group-item label-3"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target ant-checkbox-disabled"
|
||||
|
@ -649,7 +649,7 @@ Array [
|
||||
class="ant-checkbox-group"
|
||||
>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-group-item label-1"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target"
|
||||
@ -670,7 +670,7 @@ Array [
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-checkbox-group-item label-2"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target ant-checkbox-checked"
|
||||
@ -692,7 +692,7 @@ Array [
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-group-item label-3"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target"
|
||||
@ -719,7 +719,7 @@ Array [
|
||||
class="ant-checkbox-group"
|
||||
>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-checkbox-wrapper-disabled ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-checkbox-wrapper-disabled ant-checkbox-group-item label-1"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target ant-checkbox-checked ant-checkbox-disabled"
|
||||
@ -742,7 +742,7 @@ Array [
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled ant-checkbox-group-item label-2"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target ant-checkbox-disabled"
|
||||
@ -764,7 +764,7 @@ Array [
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled ant-checkbox-group-item"
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled ant-checkbox-group-item label-3"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target ant-checkbox-disabled"
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Checkbox } from 'antd';
|
||||
import type { GetProp } from 'antd';
|
||||
import type { CheckboxOptionType, GetProp } from 'antd';
|
||||
|
||||
const onChange: GetProp<typeof Checkbox.Group, 'onChange'> = (checkedValues) => {
|
||||
console.log('checked = ', checkedValues);
|
||||
@ -8,16 +8,16 @@ const onChange: GetProp<typeof Checkbox.Group, 'onChange'> = (checkedValues) =>
|
||||
|
||||
const plainOptions = ['Apple', 'Pear', 'Orange'];
|
||||
|
||||
const options = [
|
||||
{ label: 'Apple', value: 'Apple' },
|
||||
{ label: 'Pear', value: 'Pear' },
|
||||
{ label: 'Orange', value: 'Orange' },
|
||||
const options: CheckboxOptionType<string>[] = [
|
||||
{ label: 'Apple', value: 'Apple', className: 'label-1' },
|
||||
{ label: 'Pear', value: 'Pear', className: 'label-2' },
|
||||
{ label: 'Orange', value: 'Orange', className: 'label-3' },
|
||||
];
|
||||
|
||||
const optionsWithDisabled = [
|
||||
{ label: 'Apple', value: 'Apple' },
|
||||
{ label: 'Pear', value: 'Pear' },
|
||||
{ label: 'Orange', value: 'Orange', disabled: false },
|
||||
const optionsWithDisabled: CheckboxOptionType<string>[] = [
|
||||
{ label: 'Apple', value: 'Apple', className: 'label-1' },
|
||||
{ label: 'Pear', value: 'Pear', className: 'label-2' },
|
||||
{ label: 'Orange', value: 'Orange', className: 'label-3', disabled: false },
|
||||
];
|
||||
|
||||
const App: React.FC = () => (
|
||||
|
@ -44,7 +44,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| onBlur | Called when leaving the component | function() | - | |
|
||||
| onFocus | Called when entering the component | function() | - | |
|
||||
|
||||
#### Checkbox Group
|
||||
#### Checkbox.Group
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
@ -53,6 +53,9 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| name | The `name` property of all `input[type="checkbox"]` children | string | - | |
|
||||
| options | Specifies options | string\[] \| number\[] \| Option\[] | \[] | |
|
||||
| value | Used for setting the currently selected value | (string \| number \| boolean)\[] | \[] | |
|
||||
| title | title of the option | `string` | - | |
|
||||
| className | className of the option | `string` | - | 5.25.0 |
|
||||
| style | styles of the option | `React.CSSProperties` | - | |
|
||||
| onChange | The callback function that is triggered when the state changes | (checkedValue: T[]) => void | - | |
|
||||
|
||||
##### Option
|
||||
|
@ -45,7 +45,7 @@ demo:
|
||||
| onBlur | 失去焦点时的回调 | function() | - | |
|
||||
| onFocus | 获得焦点时的回调 | function() | - | |
|
||||
|
||||
#### Checkbox Group
|
||||
#### Checkbox.Group
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
@ -54,6 +54,9 @@ demo:
|
||||
| name | CheckboxGroup 下所有 `input[type="checkbox"]` 的 `name` 属性 | string | - | |
|
||||
| options | 指定可选项 | string\[] \| number\[] \| Option\[] | \[] | |
|
||||
| value | 指定选中的选项 | (string \| number \| boolean)\[] | \[] | |
|
||||
| title | 选项的 title | `string` | - | |
|
||||
| className | 选项的类名 | `string` | - | 5.25.0 |
|
||||
| style | 选项的样式 | `React.CSSProperties` | - | |
|
||||
| onChange | 变化时的回调函数 | (checkedValue: T[]) => void | - | |
|
||||
|
||||
##### Option
|
||||
|
@ -26,7 +26,12 @@ export interface CollapseProps extends Pick<RcCollapseProps, 'items'> {
|
||||
defaultActiveKey?: Array<string | number> | string | number;
|
||||
/** 手风琴效果 */
|
||||
accordion?: boolean;
|
||||
/** @deprecated Please use `destroyOnHidden` instead */
|
||||
destroyInactivePanel?: boolean;
|
||||
/**
|
||||
* @since 5.25.0
|
||||
*/
|
||||
destroyOnHidden?: boolean;
|
||||
onChange?: (key: string[]) => void;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
@ -76,6 +81,8 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
|
||||
size: customizeSize,
|
||||
expandIconPosition = 'start',
|
||||
children,
|
||||
destroyInactivePanel,
|
||||
destroyOnHidden,
|
||||
expandIcon,
|
||||
} = props;
|
||||
|
||||
@ -93,6 +100,11 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
|
||||
'deprecated',
|
||||
'`expandIconPosition` with `left` or `right` is deprecated. Please use `start` or `end` instead.',
|
||||
);
|
||||
warning.deprecated(
|
||||
!('destroyInactivePanel' in props),
|
||||
'destroyInactivePanel',
|
||||
'destroyOnHidden',
|
||||
);
|
||||
}
|
||||
|
||||
// Align with logic position
|
||||
@ -185,6 +197,8 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
|
||||
prefixCls={prefixCls}
|
||||
className={collapseClassName}
|
||||
style={{ ...contextStyle, ...style }}
|
||||
// TODO: In the future, destroyInactivePanel in rc-collapse needs to be upgrade to destroyOnHidden
|
||||
destroyInactivePanel={destroyOnHidden ?? destroyInactivePanel}
|
||||
>
|
||||
{items}
|
||||
</RcCollapse>,
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
|
||||
describe('Collapse', () => {
|
||||
const Collapse = require('..').default;
|
||||
@ -275,6 +276,52 @@ describe('Collapse', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should support borderlessContentBg component token', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Collapse: {
|
||||
borderlessContentBg: 'red',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Collapse bordered={false} defaultActiveKey={['1']}>
|
||||
<Collapse.Panel header="This is panel header 1" key="1">
|
||||
content
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
expect(container.querySelector('.ant-collapse-content')).toHaveStyle({
|
||||
backgroundColor: 'red',
|
||||
});
|
||||
});
|
||||
|
||||
it('should support borderlessContentPadding component token', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Collapse: {
|
||||
borderlessContentPadding: '10px',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Collapse bordered={false} defaultActiveKey={['1']}>
|
||||
<Collapse.Panel header="This is panel header 1" key="1">
|
||||
content
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
expect(container.querySelector('.ant-collapse-content-box')).toHaveStyle({
|
||||
padding: '10px',
|
||||
});
|
||||
});
|
||||
|
||||
it('should support styles and classNames', () => {
|
||||
const { container } = render(
|
||||
<Collapse
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import type { CollapseProps } from 'antd';
|
||||
import { Collapse } from 'antd';
|
||||
|
||||
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
|
||||
import SemanticPreview from '../../../.dumi/theme/common/SemanticPreview';
|
||||
import useLocale from '../../../.dumi/hooks/useLocale';
|
||||
|
||||
const locales = {
|
||||
|
@ -83,7 +83,8 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| bordered | Toggles rendering of the border around the collapse block | boolean | true | |
|
||||
| collapsible | Specify how to trigger Collapse. Either by clicking icon or by clicking any area in header or disable collapse functionality itself | `header` \| `icon` \| `disabled` | - | 4.9.0 |
|
||||
| defaultActiveKey | Key of the initial active panel | string\[] \| string <br/> number\[] \| number | - | |
|
||||
| destroyInactivePanel | Destroy Inactive Panel | boolean | false | |
|
||||
| ~~destroyInactivePanel~~ | Destroy Inactive Panel | boolean | false | |
|
||||
| destroyOnHidden | Destroy Inactive Panel | boolean | false | 5.25.0 |
|
||||
| expandIcon | Allow to customize collapse icon | (panelProps) => ReactNode | - | |
|
||||
| expandIconPosition | Set expand icon position | `start` \| `end` | - | 4.21.0 |
|
||||
| ghost | Make the collapse borderless and its background transparent | boolean | false | 4.4.0 |
|
||||
|
@ -84,7 +84,8 @@ const items: CollapseProps['items'] = [
|
||||
| bordered | 带边框风格的折叠面板 | boolean | true | |
|
||||
| collapsible | 所有子面板是否可折叠或指定可折叠触发区域 | `header` \| `icon` \| `disabled` | - | 4.9.0 |
|
||||
| defaultActiveKey | 初始化选中面板的 key | string\[] \| string<br/> number\[] \| number | - | |
|
||||
| destroyInactivePanel | 销毁折叠隐藏的面板 | boolean | false | |
|
||||
| ~~destroyInactivePanel~~ | 销毁折叠隐藏的面板 | boolean | false | |
|
||||
| destroyOnHidden | 销毁折叠隐藏的面板 | boolean | false | 5.25.0 |
|
||||
| expandIcon | 自定义切换图标 | (panelProps) => ReactNode | - | |
|
||||
| expandIconPosition | 设置图标位置 | `start` \| `end` | - | 4.21.0 |
|
||||
| ghost | 使折叠面板透明且无边框 | boolean | false | 4.4.0 |
|
||||
|
@ -29,6 +29,16 @@ export interface ComponentToken {
|
||||
* @descEN Background of content
|
||||
*/
|
||||
contentBg: string;
|
||||
/**
|
||||
* @desc 简约风格折叠面板的内容内边距
|
||||
* @descEN Padding of content in borderless style
|
||||
*/
|
||||
borderlessContentPadding: CSSProperties['padding'];
|
||||
/**
|
||||
* @desc 简约风格折叠面板的内容背景
|
||||
* @descEN Background of content in borderless style
|
||||
*/
|
||||
borderlessContentBg: string;
|
||||
}
|
||||
|
||||
type CollapseToken = FullToken<'Collapse'> & {
|
||||
@ -271,13 +281,8 @@ const genArrowStyle: GenerateStyle<CollapseToken> = (token) => {
|
||||
};
|
||||
|
||||
const genBorderlessStyle: GenerateStyle<CollapseToken> = (token) => {
|
||||
const {
|
||||
componentCls,
|
||||
headerBg,
|
||||
paddingXXS,
|
||||
|
||||
colorBorder,
|
||||
} = token;
|
||||
const { componentCls, headerBg, borderlessContentPadding, borderlessContentBg, colorBorder } =
|
||||
token;
|
||||
|
||||
return {
|
||||
[`${componentCls}-borderless`]: {
|
||||
@ -300,12 +305,12 @@ const genBorderlessStyle: GenerateStyle<CollapseToken> = (token) => {
|
||||
},
|
||||
|
||||
[`> ${componentCls}-item > ${componentCls}-content`]: {
|
||||
backgroundColor: 'transparent',
|
||||
backgroundColor: borderlessContentBg,
|
||||
borderTop: 0,
|
||||
},
|
||||
|
||||
[`> ${componentCls}-item > ${componentCls}-content > ${componentCls}-content-box`]: {
|
||||
paddingTop: paddingXXS,
|
||||
padding: borderlessContentPadding,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -337,6 +342,8 @@ export const prepareComponentToken: GetDefaultToken<'Collapse'> = (token) => ({
|
||||
headerBg: token.colorFillAlter,
|
||||
contentPadding: `${token.padding}px 16px`, // Fixed Value
|
||||
contentBg: token.colorBgContainer,
|
||||
borderlessContentPadding: `${token.paddingXXS}px 16px ${token.padding}px`,
|
||||
borderlessContentBg: 'transparent',
|
||||
});
|
||||
|
||||
export default genStyleHooks(
|
||||
|
@ -60,6 +60,7 @@ const ColorPicker: CompoundedComponent = (props) => {
|
||||
getPopupContainer,
|
||||
autoAdjustOverflow = true,
|
||||
destroyTooltipOnHide,
|
||||
destroyOnHidden,
|
||||
disabledFormat,
|
||||
...rest
|
||||
} = props;
|
||||
@ -211,7 +212,7 @@ const ColorPicker: CompoundedComponent = (props) => {
|
||||
rootClassName,
|
||||
getPopupContainer,
|
||||
autoAdjustOverflow,
|
||||
destroyTooltipOnHide,
|
||||
destroyOnHidden: destroyOnHidden ?? !!destroyTooltipOnHide,
|
||||
};
|
||||
|
||||
const mergedStyle: React.CSSProperties = { ...colorPicker?.style, ...style };
|
||||
|
@ -6,6 +6,7 @@ import ColorHexInput from '../components/ColorHexInput';
|
||||
import ColorHsbInput from '../components/ColorHsbInput';
|
||||
import ColorRgbInput from '../components/ColorRgbInput';
|
||||
import ColorSteppers from '../components/ColorSteppers';
|
||||
import { generateColor } from '../util';
|
||||
|
||||
describe('ColorPicker Components test', () => {
|
||||
beforeEach(() => {
|
||||
@ -94,4 +95,63 @@ describe('ColorPicker Components test', () => {
|
||||
expect(rgbInputEls[2]?.getAttribute('value')).toEqual('21');
|
||||
expect(handleAlphaChange).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('Should update input value when external value changes', () => {
|
||||
// Create a container to hold the component instance for re-rendering
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
// Create initial color value
|
||||
const initialColor = generateColor('#ff0000');
|
||||
|
||||
// Render component with initial color
|
||||
const { rerender, getByRole } = render(
|
||||
<ColorHexInput prefixCls="test" value={initialColor} onChange={jest.fn()} />,
|
||||
{ container },
|
||||
);
|
||||
|
||||
// Get input element
|
||||
const input = getByRole('textbox');
|
||||
|
||||
// Verify initial value is displayed correctly
|
||||
expect(input.getAttribute('value')).toEqual('ff0000');
|
||||
|
||||
// Create new color value
|
||||
const newColor = generateColor('#00ff00');
|
||||
|
||||
// Re-render component with new color value
|
||||
rerender(<ColorHexInput prefixCls="test" value={newColor} onChange={jest.fn()} />);
|
||||
|
||||
// Verify input value has been updated to the new color value
|
||||
expect(input.getAttribute('value')).toEqual('00ff00');
|
||||
|
||||
// Cleanup
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('Should handle user input correctly and maintain state when value prop does not change', () => {
|
||||
const onChange = jest.fn();
|
||||
const { container } = render(<ColorHexInput prefixCls="test" onChange={onChange} />);
|
||||
|
||||
// Get input element
|
||||
const input = container.querySelector('.test-hex-input input')!;
|
||||
|
||||
// Simulate user input
|
||||
fireEvent.change(input, { target: { value: 'ff5500' } });
|
||||
|
||||
// Verify input value has been updated
|
||||
expect(input.getAttribute('value')).toEqual('ff5500');
|
||||
|
||||
// Verify onChange callback has been called
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Simulate invalid input
|
||||
fireEvent.change(input, { target: { value: 'xyz' } });
|
||||
|
||||
// Verify input value has been updated but formatted as valid hex format
|
||||
expect(input.getAttribute('value')).toEqual('xyz');
|
||||
|
||||
// onChange should not be called because the input is invalid
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
@ -685,7 +685,7 @@ describe('ColorPicker', () => {
|
||||
value,
|
||||
)}]`, async () => {
|
||||
const Demo = () => {
|
||||
const [color, setColor] = useState<ColorValueType>(generateColor('red'));
|
||||
const [color, setColor] = useState<ColorValueType>(() => generateColor('red'));
|
||||
useEffect(() => {
|
||||
setColor(value);
|
||||
}, []);
|
||||
@ -699,7 +699,7 @@ describe('ColorPicker', () => {
|
||||
|
||||
it('Controlled string value should work with allowClear correctly', async () => {
|
||||
const Demo = (props: any) => {
|
||||
const [color, setColor] = useState<ColorValueType>(generateColor('#FF0000'));
|
||||
const [color, setColor] = useState<ColorValueType>(() => generateColor('#FF0000'));
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof props.value !== 'undefined') {
|
||||
@ -738,7 +738,7 @@ describe('ColorPicker', () => {
|
||||
|
||||
it('Controlled value should work with allowClear correctly', async () => {
|
||||
const Demo = (props: any) => {
|
||||
const [color, setColor] = useState<ColorValueType>(generateColor('red'));
|
||||
const [color, setColor] = useState<ColorValueType>(() => generateColor('red'));
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof props.value !== 'undefined') {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import type { AggregationColor } from '../color';
|
||||
import { generateColor, getColorAlpha } from '../util';
|
||||
@ -13,22 +13,19 @@ interface ColorAlphaInputProps {
|
||||
|
||||
const ColorAlphaInput: FC<ColorAlphaInputProps> = ({ prefixCls, value, onChange }) => {
|
||||
const colorAlphaInputPrefixCls = `${prefixCls}-alpha-input`;
|
||||
const [alphaValue, setAlphaValue] = useState<AggregationColor>(generateColor(value || '#000'));
|
||||
const [internalValue, setInternalValue] = useState<AggregationColor>(() =>
|
||||
generateColor(value || '#000'),
|
||||
);
|
||||
|
||||
// Update step value
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
setAlphaValue(value);
|
||||
}
|
||||
}, [value]);
|
||||
const alphaValue = value || internalValue;
|
||||
|
||||
const handleAlphaChange = (step: number | null) => {
|
||||
const hsba = alphaValue.toHsb();
|
||||
hsba.a = (step || 0) / 100;
|
||||
const genColor = generateColor(hsba);
|
||||
if (!value) {
|
||||
setAlphaValue(genColor);
|
||||
}
|
||||
|
||||
setInternalValue(genColor);
|
||||
|
||||
onChange?.(genColor);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import type { HSB } from '@rc-component/color-picker';
|
||||
|
||||
import type { AggregationColor } from '../color';
|
||||
@ -14,22 +14,19 @@ interface ColorHsbInputProps {
|
||||
|
||||
const ColorHsbInput: FC<ColorHsbInputProps> = ({ prefixCls, value, onChange }) => {
|
||||
const colorHsbInputPrefixCls = `${prefixCls}-hsb-input`;
|
||||
const [hsbValue, setHsbValue] = useState<AggregationColor>(generateColor(value || '#000'));
|
||||
const [internalValue, setInternalValue] = useState<AggregationColor>(() =>
|
||||
generateColor(value || '#000'),
|
||||
);
|
||||
|
||||
// Update step value
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
setHsbValue(value);
|
||||
}
|
||||
}, [value]);
|
||||
const hsbValue = value || internalValue;
|
||||
|
||||
const handleHsbChange = (step: number, type: keyof HSB) => {
|
||||
const hsb = hsbValue.toHsb();
|
||||
hsb[type] = type === 'h' ? step : (step || 0) / 100;
|
||||
const genColor = generateColor(hsb);
|
||||
if (!value) {
|
||||
setHsbValue(genColor);
|
||||
}
|
||||
|
||||
setInternalValue(genColor);
|
||||
|
||||
onChange?.(genColor);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import type { RGB } from '@rc-component/color-picker';
|
||||
|
||||
import type { AggregationColor } from '../color';
|
||||
@ -14,22 +14,19 @@ interface ColorRgbInputProps {
|
||||
|
||||
const ColorRgbInput: FC<ColorRgbInputProps> = ({ prefixCls, value, onChange }) => {
|
||||
const colorRgbInputPrefixCls = `${prefixCls}-rgb-input`;
|
||||
const [rgbValue, setRgbValue] = useState<AggregationColor>(generateColor(value || '#000'));
|
||||
const [internalValue, setInternalValue] = useState<AggregationColor>(() =>
|
||||
generateColor(value || '#000'),
|
||||
);
|
||||
|
||||
// Update step value
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
setRgbValue(value);
|
||||
}
|
||||
}, [value]);
|
||||
const rgbValue = value || internalValue;
|
||||
|
||||
const handleRgbChange = (step: number | null, type: keyof RGB) => {
|
||||
const rgb = rgbValue.toRgb();
|
||||
rgb[type] = step || 0;
|
||||
const genColor = generateColor(rgb);
|
||||
if (!value) {
|
||||
setRgbValue(genColor);
|
||||
}
|
||||
|
||||
setInternalValue(genColor);
|
||||
|
||||
onChange?.(genColor);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import type { InputNumberProps } from '../../input-number';
|
||||
@ -26,14 +26,9 @@ const ColorSteppers: FC<ColorSteppersProps> = ({
|
||||
formatter,
|
||||
}) => {
|
||||
const colorSteppersPrefixCls = `${prefixCls}-steppers`;
|
||||
const [stepValue, setStepValue] = useState(value);
|
||||
const [internalValue, setInternalValue] = useState<number | undefined>(0);
|
||||
|
||||
// Update step value
|
||||
useEffect(() => {
|
||||
if (!Number.isNaN(value)) {
|
||||
setStepValue(value);
|
||||
}
|
||||
}, [value]);
|
||||
const stepValue = !Number.isNaN(value) ? value : internalValue;
|
||||
|
||||
return (
|
||||
<InputNumber
|
||||
@ -44,9 +39,7 @@ const ColorSteppers: FC<ColorSteppersProps> = ({
|
||||
formatter={formatter}
|
||||
size="small"
|
||||
onChange={(step) => {
|
||||
if (!value) {
|
||||
setStepValue(step || 0);
|
||||
}
|
||||
setInternalValue(step || 0);
|
||||
onChange?.(step);
|
||||
}}
|
||||
/>
|
||||
|
@ -50,7 +50,8 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| disabled | Disable ColorPicker | boolean | - | |
|
||||
| disabledAlpha | Disable Alpha | boolean | - | 5.8.0 |
|
||||
| disabledFormat | Disable format of color | boolean | - | |
|
||||
| destroyTooltipOnHide | Whether destroy popover when hidden | `boolean` | false | 5.7.0 |
|
||||
| ~~destroyTooltipOnHide~~ | Whether destroy dom when close | `boolean` | false | 5.7.0 |
|
||||
| destroyOnHidden | Whether destroy dom when close | `boolean` | false | 5.25.0 |
|
||||
| format | Format of color | `rgb` \| `hex` \| `hsb` | - | |
|
||||
| mode | Configure single or gradient color | `'single' \| 'gradient' \| ('single' \| 'gradient')[]` | `single` | 5.20.0 |
|
||||
| open | Whether to show popup | boolean | - | |
|
||||
|
@ -51,7 +51,8 @@ group:
|
||||
| disabled | 禁用颜色选择器 | boolean | - | |
|
||||
| disabledAlpha | 禁用透明度 | boolean | - | 5.8.0 |
|
||||
| disabledFormat | 禁用选择颜色格式 | boolean | - | |
|
||||
| destroyTooltipOnHide | 关闭后是否销毁弹窗 | `boolean` | false | 5.7.0 |
|
||||
| ~~destroyTooltipOnHide~~ | 关闭后是否销毁弹窗 | `boolean` | false | 5.7.0 |
|
||||
| destroyOnHidden | 关闭后是否销毁弹窗 | `boolean` | false | 5.25.0 |
|
||||
| format | 颜色格式 | `rgb` \| `hex` \| `hsb` | - | |
|
||||
| mode | 选择器模式,用于配置单色与渐变 | `'single' \| 'gradient' \| ('single' \| 'gradient')[]` | `single` | 5.20.0 |
|
||||
| open | 是否显示弹出窗口 | boolean | - | |
|
||||
|
@ -94,4 +94,7 @@ export type ColorPickerProps = Omit<
|
||||
onClear?: () => void;
|
||||
onChangeComplete?: (value: AggregationColor) => void;
|
||||
disabledFormat?: boolean;
|
||||
} & Pick<PopoverProps, 'getPopupContainer' | 'autoAdjustOverflow' | 'destroyTooltipOnHide'>;
|
||||
} & Pick<
|
||||
PopoverProps,
|
||||
'getPopupContainer' | 'autoAdjustOverflow' | 'destroyTooltipOnHide' | 'destroyOnHidden'
|
||||
>;
|
||||
|
@ -37,10 +37,9 @@ let unstableRender: RenderType = defaultReactRender;
|
||||
* This is internal usage only compatible with React 19.
|
||||
* And will be removed in next major version.
|
||||
*/
|
||||
export function unstableSetRender(render: RenderType) {
|
||||
unstableRender = render;
|
||||
}
|
||||
|
||||
export function getReactRender() {
|
||||
export function unstableSetRender(render?: RenderType) {
|
||||
if (render) {
|
||||
unstableRender = render;
|
||||
}
|
||||
return unstableRender;
|
||||
}
|
||||
|
@ -14405,14 +14405,14 @@ exports[`ConfigProvider components Divider configProvider componentSize large 1`
|
||||
|
||||
exports[`ConfigProvider components Divider configProvider componentSize middle 1`] = `
|
||||
<div
|
||||
class="config-divider config-divider-horizontal"
|
||||
class="config-divider config-divider-horizontal config-divider-md"
|
||||
role="separator"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`ConfigProvider components Divider configProvider componentSize small 1`] = `
|
||||
<div
|
||||
class="config-divider config-divider-horizontal"
|
||||
class="config-divider config-divider-horizontal config-divider-sm"
|
||||
role="separator"
|
||||
/>
|
||||
`;
|
||||
@ -18568,6 +18568,7 @@ exports[`ConfigProvider components Modal configProvider 1`] = `
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="Close"
|
||||
class="config-modal-close-x"
|
||||
>
|
||||
<span
|
||||
@ -18660,6 +18661,7 @@ exports[`ConfigProvider components Modal configProvider componentDisabled 1`] =
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="Close"
|
||||
class="config-modal-close-x"
|
||||
>
|
||||
<span
|
||||
@ -18752,6 +18754,7 @@ exports[`ConfigProvider components Modal configProvider componentSize large 1`]
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="Close"
|
||||
class="config-modal-close-x"
|
||||
>
|
||||
<span
|
||||
@ -18844,6 +18847,7 @@ exports[`ConfigProvider components Modal configProvider componentSize middle 1`]
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="Close"
|
||||
class="config-modal-close-x"
|
||||
>
|
||||
<span
|
||||
@ -18936,6 +18940,7 @@ exports[`ConfigProvider components Modal configProvider componentSize small 1`]
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="Close"
|
||||
class="config-modal-close-x"
|
||||
>
|
||||
<span
|
||||
@ -19028,6 +19033,7 @@ exports[`ConfigProvider components Modal normal 1`] = `
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="Close"
|
||||
class="ant-modal-close-x"
|
||||
>
|
||||
<span
|
||||
@ -19120,6 +19126,7 @@ exports[`ConfigProvider components Modal prefixCls 1`] = `
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="Close"
|
||||
class="prefix-Modal-close-x"
|
||||
>
|
||||
<span
|
||||
@ -39268,7 +39275,7 @@ Array [
|
||||
`;
|
||||
|
||||
exports[`ConfigProvider components Timeline configProvider 1`] = `
|
||||
<ul
|
||||
<ol
|
||||
class="config-timeline"
|
||||
>
|
||||
<li
|
||||
@ -39286,11 +39293,11 @@ exports[`ConfigProvider components Timeline configProvider 1`] = `
|
||||
Bamboo
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
`;
|
||||
|
||||
exports[`ConfigProvider components Timeline configProvider componentDisabled 1`] = `
|
||||
<ul
|
||||
<ol
|
||||
class="config-timeline"
|
||||
>
|
||||
<li
|
||||
@ -39308,11 +39315,11 @@ exports[`ConfigProvider components Timeline configProvider componentDisabled 1`]
|
||||
Bamboo
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
`;
|
||||
|
||||
exports[`ConfigProvider components Timeline configProvider componentSize large 1`] = `
|
||||
<ul
|
||||
<ol
|
||||
class="config-timeline"
|
||||
>
|
||||
<li
|
||||
@ -39330,11 +39337,11 @@ exports[`ConfigProvider components Timeline configProvider componentSize large 1
|
||||
Bamboo
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
`;
|
||||
|
||||
exports[`ConfigProvider components Timeline configProvider componentSize middle 1`] = `
|
||||
<ul
|
||||
<ol
|
||||
class="config-timeline"
|
||||
>
|
||||
<li
|
||||
@ -39352,11 +39359,11 @@ exports[`ConfigProvider components Timeline configProvider componentSize middle
|
||||
Bamboo
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
`;
|
||||
|
||||
exports[`ConfigProvider components Timeline configProvider componentSize small 1`] = `
|
||||
<ul
|
||||
<ol
|
||||
class="config-timeline"
|
||||
>
|
||||
<li
|
||||
@ -39374,11 +39381,11 @@ exports[`ConfigProvider components Timeline configProvider componentSize small 1
|
||||
Bamboo
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
`;
|
||||
|
||||
exports[`ConfigProvider components Timeline normal 1`] = `
|
||||
<ul
|
||||
<ol
|
||||
class="ant-timeline"
|
||||
>
|
||||
<li
|
||||
@ -39396,11 +39403,11 @@ exports[`ConfigProvider components Timeline normal 1`] = `
|
||||
Bamboo
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
`;
|
||||
|
||||
exports[`ConfigProvider components Timeline prefixCls 1`] = `
|
||||
<ul
|
||||
<ol
|
||||
class="prefix-Timeline"
|
||||
>
|
||||
<li
|
||||
@ -39418,7 +39425,7 @@ exports[`ConfigProvider components Timeline prefixCls 1`] = `
|
||||
Bamboo
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
`;
|
||||
|
||||
exports[`ConfigProvider components Tooltip configProvider 1`] = `
|
||||
|
@ -161,7 +161,7 @@ export type TextAreaConfig = ComponentStyleConfig &
|
||||
Pick<TextAreaProps, 'autoComplete' | 'classNames' | 'styles' | 'allowClear' | 'variant'>;
|
||||
|
||||
export type ButtonConfig = ComponentStyleConfig &
|
||||
Pick<ButtonProps, 'classNames' | 'styles' | 'autoInsertSpace'>;
|
||||
Pick<ButtonProps, 'classNames' | 'styles' | 'autoInsertSpace' | 'variant' | 'color'>;
|
||||
|
||||
export type NotificationConfig = ComponentStyleConfig & Pick<ArgsProps, 'closeIcon'>;
|
||||
|
||||
@ -184,7 +184,8 @@ export type FloatButtonGroupConfig = Pick<FloatButtonGroupProps, 'closeIcon'>;
|
||||
|
||||
export type PaginationConfig = ComponentStyleConfig & Pick<PaginationProps, 'showSizeChanger'>;
|
||||
|
||||
export type SelectConfig = ComponentStyleConfig & Pick<SelectProps, 'showSearch' | 'variant'>;
|
||||
export type SelectConfig = ComponentStyleConfig &
|
||||
Pick<SelectProps, 'showSearch' | 'variant' | 'classNames' | 'styles'>;
|
||||
|
||||
export type SpaceConfig = ComponentStyleConfig & Pick<SpaceProps, 'size' | 'classNames' | 'styles'>;
|
||||
|
||||
@ -203,15 +204,19 @@ export type SpinConfig = ComponentStyleConfig & Pick<SpinProps, 'indicator'>;
|
||||
|
||||
export type InputNumberConfig = ComponentStyleConfig & Pick<InputNumberProps, 'variant'>;
|
||||
|
||||
export type CascaderConfig = ComponentStyleConfig & Pick<CascaderProps, 'variant'>;
|
||||
export type CascaderConfig = ComponentStyleConfig &
|
||||
Pick<CascaderProps, 'variant' | 'styles' | 'classNames'>;
|
||||
|
||||
export type TreeSelectConfig = ComponentStyleConfig & Pick<TreeSelectProps, 'variant'>;
|
||||
export type TreeSelectConfig = ComponentStyleConfig &
|
||||
Pick<TreeSelectProps, 'variant' | 'styles' | 'classNames'>;
|
||||
|
||||
export type DatePickerConfig = ComponentStyleConfig & Pick<DatePickerProps, 'variant'>;
|
||||
export type DatePickerConfig = ComponentStyleConfig &
|
||||
Pick<DatePickerProps, 'variant' | 'styles' | 'classNames'>;
|
||||
|
||||
export type RangePickerConfig = ComponentStyleConfig & Pick<RangePickerProps, 'variant'>;
|
||||
|
||||
export type TimePickerConfig = ComponentStyleConfig & Pick<TimePickerProps, 'variant'>;
|
||||
export type TimePickerConfig = ComponentStyleConfig &
|
||||
Pick<TimePickerProps, 'variant' | 'styles' | 'classNames'>;
|
||||
|
||||
export type MentionsConfig = ComponentStyleConfig & Pick<MentionsProps, 'variant'>;
|
||||
|
||||
|
@ -4,4 +4,4 @@
|
||||
|
||||
## en-US
|
||||
|
||||
Use `holderRender` to set the `Provider` for the static methods `message` 、`modal` 、`notification`.
|
||||
Use `holderRender` to set the `Provider` for the static methods `message`,`modal`,`notification`.
|
||||
|
@ -25,7 +25,7 @@ const Demo: React.FC = () => (
|
||||
export default Demo;
|
||||
```
|
||||
|
||||
### Content Security Policy
|
||||
### Content Security Policy {#csp}
|
||||
|
||||
Some components use dynamic style to support wave effect. You can config `csp` prop if Content Security Policy (CSP) is enabled:
|
||||
|
||||
@ -69,9 +69,9 @@ Some components use dynamic style to support wave effect. You can config `csp` p
|
||||
| virtual | Disable virtual scroll when set to `false` | boolean | - | 4.3.0 |
|
||||
| warning | Config warning level, when `strict` is `false`, it will aggregate deprecated information into a single message | { strict: boolean } | - | 5.10.0 |
|
||||
|
||||
### ConfigProvider.config()
|
||||
### ConfigProvider.config() {#config}
|
||||
|
||||
Setting `Modal`、`Message`、`Notification` static config. Not work on hooks.
|
||||
Setting `Modal`, `Message`, `Notification` static config. Not work on hooks.
|
||||
|
||||
```tsx
|
||||
ConfigProvider.config({
|
||||
@ -88,9 +88,9 @@ ConfigProvider.config({
|
||||
});
|
||||
```
|
||||
|
||||
### ConfigProvider.useConfig() `5.3.0+`
|
||||
### ConfigProvider.useConfig() <Badge>5.3.0+</Badge> {#useconfig}
|
||||
|
||||
Available since `5.2.0`. Get the value of the parent `Provider`. Such as `DisabledContextProvider`, `SizeContextProvider`.
|
||||
Get the value of the parent `Provider`, Such as `DisabledContextProvider`, `SizeContextProvider`.
|
||||
|
||||
```jsx
|
||||
const {
|
||||
@ -113,15 +113,15 @@ const {
|
||||
| avatar | Set Avatar common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| badge | Set Badge common props | { className?: string, style?: React.CSSProperties, classNames?: [BadgeProps\["classNames"\]](/components/badge#api), styles?: [BadgeProps\["styles"\]](/components/badge#api) } | - | 5.7.0 |
|
||||
| breadcrumb | Set Breadcrumb common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| button | Set Button common props | { className?: string, style?: React.CSSProperties, classNames?: [ButtonProps\["classNames"\]](/components/button#api), styles?: [ButtonProps\["styles"\]](/components/button#api), autoInsertSpace?: boolean } | - | 5.6.0, `autoInsertSpace`: 5.17.0 |
|
||||
| button | Set Button common props | { className?: string, style?: React.CSSProperties, classNames?: [ButtonProps\["classNames"\]](/components/button#api), styles?: [ButtonProps\["styles"\]](/components/button#api), autoInsertSpace?: boolean, variant?: ButtonVariantType, color?: ButtonColorType } | - | 5.6.0, `autoInsertSpace`: 5.17.0, `variant` and `color`: 5.25.0 |
|
||||
| card | Set Card common props | { className?: string, style?: React.CSSProperties, classNames?: [CardProps\["classNames"\]](/components/card#api), styles?: [CardProps\["styles"\]](/components/card#api) } | - | 5.7.0, `classNames` and `styles`: 5.14.0 |
|
||||
| calendar | Set Calendar common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| carousel | Set Carousel common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| cascader | Set Cascader common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| cascader | Set Cascader common props | { className?: string, style?: React.CSSProperties, classNames?: [CascaderProps\["classNames"\]](/components/cascader#semantic-dom), styles?: [CascaderProps\["styles"\]](/components/cascader#semantic-dom) } | - | 5.7.0, `classNames` and `styles`: 5.25.0 |
|
||||
| checkbox | Set Checkbox common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| collapse | Set Collapse common props | { className?: string, style?: React.CSSProperties, expandIcon?: (props) => ReactNode } | - | 5.7.0, `expandIcon`: 5.15.0 |
|
||||
| colorPicker | Set ColorPicker common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| datePicker | Set datePicker common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| datePicker | Set datePicker common props | { className?: string, style?: React.CSSProperties, classNames?: [DatePickerConfig\["classNames"\]](/components/date-picker#semantic-dom), styles?: [DatePickerConfig\["styles"\]](/components/date-picker#semantic-dom) } | - | 5.7.0, `classNames` 和 `styles`: 5.25.0 |
|
||||
| rangePicker | Set rangePicker common props | { className?: string, style?: React.CSSProperties } | - | 5.11.0 |
|
||||
| descriptions | Set Descriptions common props | { className?: string, style?: React.CSSProperties, classNames?: [DescriptionsProps\["classNames"\]](/components/descriptions#api), styles?: [DescriptionsProps\["styles"\]](/components/descriptions#api) } | - | 5.7.0, `classNames` and `styles`: 5.23.0 |
|
||||
| divider | Set Divider common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
@ -148,7 +148,7 @@ const {
|
||||
| result | Set Result common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| skeleton | Set Skeleton common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| segmented | Set Segmented common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| select | Set Select common props | { className?: string, showSearch?: boolean, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| select | Set Select common props | { className?: string, showSearch?: boolean, style?: React.CSSProperties, classNames?: [SelectProps\["classNames"\]](/components/select#api), styles?: [SelectProps\["styles"\]](/components/select#api) } | - | 5.7.0, `classNames` and `styles`: 5.25.0 |
|
||||
| slider | Set Slider common props | { className?: string, style?: React.CSSProperties, classNames?: [SliderProps\["classNames"\]](/components/slider#api), styles?: [SliderProps\["styles"\]](/components/slider#api) } | - | 5.7.0, `classNames` and `styles`: 5.23.0 |
|
||||
| switch | Set Switch common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| space | Set Space common props, ref [Space](/components/space) | { size: `small` \| `middle` \| `large` \| `number`, className?: string, style?: React.CSSProperties, classNames?: [SpaceProps\["classNames"\]](/components/space#api), styles?: [SpaceProps\["styles"\]](/components/space#api) } | - | 5.6.0 |
|
||||
@ -160,28 +160,29 @@ const {
|
||||
| tabs | Set Tabs common props | { className?: string, style?: React.CSSProperties, indicator?: { size?: GetIndicatorSize, align?: `start` \| `center` \| `end` }, moreIcon?: ReactNode, addIcon?: ReactNode, removeIcon?: ReactNode } | - | 5.7.0, `moreIcon` and `addIcon`: 5.14.0, `removeIcon`: 5.15.0 |
|
||||
| tag | Set Tag common props | { className?: string, style?: React.CSSProperties, closeIcon?: React.ReactNode } | - | 5.7.0, `closeIcon`: 5.14.0 |
|
||||
| timeline | Set Timeline common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| timePicker | Set TimePicker common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| timePicker | Set TimePicker common props | { className?: string, style?: React.CSSProperties, classNames?: [TimePickerConfig\["classNames"\]](/components/time-picker#semantic-dom), styles?: [TimePickerConfig\["styles"\]](/components/time-picker#semantic-dom) } | - | 5.7.0, `classNames` 和 `styles`: 5.25.0 |
|
||||
| tour | Set Tour common props | { closeIcon?: React.ReactNode } | - | 5.14.0 |
|
||||
| tooltip | Set Tooltip common props | { className?: string, style?: React.CSSProperties, classNames?:[Tooltip\["classNames"\]](/components/tooltip#api), styles?: [Tooltip\["styles"\]](/components/tooltip#api) } | - | 5.23.0 |
|
||||
| popover | Set Popover common props | { className?: string, style?: React.CSSProperties, classNames?:[Popover\["classNames"\]](/components/popover#api), styles?: [Popover\["styles"\]](/components/popover#api) } | - | 5.23.0 |
|
||||
| popconfirm | Set Popconfirm common props | { className?: string, style?: React.CSSProperties, classNames?:[Popconfirm\["classNames"\]](/components/popconfirm#api), styles?: [Popconfirm\["styles"\]](/components/popconfirm#api) } | - | 5.23.0 |
|
||||
| transfer | Set Transfer common props | { className?: string, style?: React.CSSProperties, selectionsIcon?: React.ReactNode } | - | 5.7.0, `selectionsIcon`: 5.14.0 |
|
||||
| tree | Set Tree common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| treeSelect | Set TreeSelect common props | { classNames?:[TreeSelect\["classNames"\]](/components/tree-select#api), styles?: [TreeSelect\["styles"\]](/components/tree-select#api) } | - | 5.25.0 |
|
||||
| typography | Set Typography common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| upload | Set Upload common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| wave | Config wave effect | { disabled?: boolean, showEffect?: (node: HTMLElement, info: { className, token, component }) => void } | - | 5.8.0 |
|
||||
|
||||
## FAQ
|
||||
|
||||
#### How to contribute a new language?
|
||||
### How to contribute a new language? {#faq-add-locale}
|
||||
|
||||
See [<Adding new language>](/docs/react/i18n#adding-newplanguage).
|
||||
|
||||
#### Date-related components locale is not working?
|
||||
### Date-related components locale is not working? {#faq-locale-not-work}
|
||||
|
||||
See FAQ [Date-related-components-locale-is-not-working?](/docs/react/faq#date-related-components-locale-is-not-working)
|
||||
|
||||
#### Modal throw error when setting `getPopupContainer`?
|
||||
### Modal throw error when setting `getPopupContainer`? {#faq-get-popup-container}
|
||||
|
||||
Related issue: <https://github.com/ant-design/ant-design/issues/19974>
|
||||
|
||||
@ -201,17 +202,17 @@ When you config `getPopupContainer` to parentNode globally, Modal will throw err
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
#### Why can't ConfigProvider props (like `prefixCls` and `theme`) affect ReactNode inside `message.info`, `notification.open`, `Modal.confirm`?
|
||||
### Why can't ConfigProvider props (like `prefixCls` and `theme`) affect ReactNode inside `message.info`, `notification.open`, `Modal.confirm`? {#faq-message-inherit}
|
||||
|
||||
antd will dynamic create React instance by `ReactDOM.render` when call message methods. Whose context is different with origin code located context. We recommend `useMessage`, `useNotification` and `useModal` which , the methods came from `message/notification/Modal` has been deprecated in 5.x.
|
||||
|
||||
#### Locale is not working with Vite in production mode?
|
||||
### Locale is not working with Vite in production mode? {#faq-vite-locale-not-work}
|
||||
|
||||
Related issue: [#39045](https://github.com/ant-design/ant-design/issues/39045)
|
||||
|
||||
In production mode of Vite, default exports from cjs file should be used like this: `enUS.default`. So you can directly import locale from `es/` directory like `import enUS from 'antd/es/locale/en_US'` to make dev and production have the same behavior.
|
||||
|
||||
#### `prefixCls` priority(The former is covered by the latter)
|
||||
### `prefixCls` priority(The former is covered by the latter) {#faq-prefixcls-priority}
|
||||
|
||||
1. `ConfigProvider.config({ prefixCls: 'prefix-1' })`
|
||||
2. `ConfigProvider.config({ holderRender: (children) => <ConfigProvider prefixCls="prefix-2">{children}</ConfigProvider> })`
|
||||
|
@ -8,7 +8,7 @@ cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*NVKORa7BCVwAAAAAAA
|
||||
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YC4ERpGAddoAAAAAAAAAAAAADrJ8AQ/original
|
||||
---
|
||||
|
||||
## 使用
|
||||
## 使用 {#usage}
|
||||
|
||||
ConfigProvider 使用 React 的 [context](https://facebook.github.io/react/docs/context.html) 特性,只需在应用外围包裹一次即可全局生效。
|
||||
|
||||
@ -26,7 +26,7 @@ const Demo: React.FC = () => (
|
||||
export default Demo;
|
||||
```
|
||||
|
||||
### Content Security Policy
|
||||
### 内容安全策略(CSP){#csp}
|
||||
|
||||
部分组件为了支持波纹效果,使用了动态样式。如果开启了 Content Security Policy (CSP),你可以通过 `csp` 属性来进行配置:
|
||||
|
||||
@ -36,7 +36,7 @@ export default Demo;
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
## 代码演示 {#examples}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<code src="./demo/locale.tsx">国际化</code>
|
||||
@ -70,7 +70,7 @@ export default Demo;
|
||||
| virtual | 设置 `false` 时关闭虚拟滚动 | boolean | - | 4.3.0 |
|
||||
| warning | 设置警告等级,`strict` 为 `false` 时会将废弃相关信息聚合为单条信息 | { strict: boolean } | - | 5.10.0 |
|
||||
|
||||
### ConfigProvider.config()
|
||||
### ConfigProvider.config() {#config}
|
||||
|
||||
设置 `Modal`、`Message`、`Notification` 静态方法配置,只会对非 hooks 的静态方法调用生效。
|
||||
|
||||
@ -89,9 +89,9 @@ ConfigProvider.config({
|
||||
});
|
||||
```
|
||||
|
||||
### ConfigProvider.useConfig() `5.3.0+`
|
||||
### ConfigProvider.useConfig() <Badge>5.3.0+</Badge> {#useconfig}
|
||||
|
||||
`5.2.0` 版本后可用。获取父级 `Provider` 的值。如 `DisabledContextProvider`、`SizeContextProvider`。
|
||||
获取父级 `Provider` 的值,如 `DisabledContextProvider`、`SizeContextProvider`。
|
||||
|
||||
```jsx
|
||||
const {
|
||||
@ -106,7 +106,7 @@ const {
|
||||
| componentDisabled | antd 组件禁用状态 | boolean | - | 5.3.0 |
|
||||
| componentSize | antd 组件大小状态 | `small` \| `middle` \| `large` | - | 5.3.0 |
|
||||
|
||||
### 组件配置
|
||||
### 组件配置 {#component-config}
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
@ -115,15 +115,15 @@ const {
|
||||
| avatar | 设置 Avatar 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| badge | 设置 Badge 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [BadgeProps\["classNames"\]](/components/badge-cn#api), styles?: [BadgeProps\["styles"\]](/components/badge-cn#api) } | - | 5.7.0 |
|
||||
| breadcrumb | 设置 Breadcrumb 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| button | 设置 Button 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [ButtonProps\["classNames"\]](/components/button-cn#api), styles?: [ButtonProps\["styles"\]](/components/button-cn#api), autoInsertSpace?: boolean } | - | 5.6.0, `autoInsertSpace`: 5.17.0 |
|
||||
| button | 设置 Button 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [ButtonProps\["classNames"\]](/components/button-cn#api), styles?: [ButtonProps\["styles"\]](/components/button-cn#api), autoInsertSpace?: boolean, variant?: ButtonVariantType, color?: ButtonColorType } | - | 5.6.0, `autoInsertSpace`: 5.17.0, `variant` 和 `color`: 5.25.0 |
|
||||
| calendar | 设置 Calendar 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| card | 设置 Card 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [CardProps\["classNames"\]](/components/card-cn#api), styles?: [CardProps\["styles"\]](/components/card-cn#api) } | - | 5.7.0, `classNames` 和 `styles`: 5.14.0 |
|
||||
| carousel | 设置 Carousel 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| cascader | 设置 Cascader 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| cascader | 设置 Cascader 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [CascaderProps\["classNames"\]](/components/cascader-cn#semantic-dom), styles?: [CascaderProps\["styles"\]](/components/cascader-cn#semantic-dom) } | - | 5.7.0, `classNames` 和 `styles`: 5.25.0 |
|
||||
| checkbox | 设置 Checkbox 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| collapse | 设置 Collapse 组件的通用属性 | { className?: string, style?: React.CSSProperties, expandIcon?: (props) => ReactNode } | - | 5.7.0, `expandIcon`: 5.15.0 |
|
||||
| colorPicker | 设置 ColorPicker 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| datePicker | 设置 DatePicker 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| datePicker | 设置 DatePicker 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [DatePickerConfig\["classNames"\]](/components/date-picker-cn#semantic-dom), styles?: [DatePickerConfig\["styles"\]](/components/date-picker-cn#semantic-dom) } | - | 5.7.0, `classNames` 和 `styles`: 5.25.0 |
|
||||
| rangePicker | 设置 RangePicker 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.11.0 |
|
||||
| descriptions | 设置 Descriptions 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [DescriptionsProps\["classNames"\]](/components/descriptions-cn#api), styles?: [DescriptionsProps\["styles"\]](/components/descriptions-cn#api) } | - | 5.7.0, `classNames` 和 `styles`: 5.23.0 |
|
||||
| divider | 设置 Divider 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
@ -150,7 +150,7 @@ const {
|
||||
| result | 设置 Result 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| skeleton | 设置 Skeleton 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| segmented | 设置 Segmented 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| select | 设置 Select 组件的通用属性 | { className?: string, showSearch?: boolean, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| select | 设置 Select 组件的通用属性 | { className?: string, showSearch?: boolean, style?: React.CSSProperties, classNames?: [SelectProps\["classNames"\]](/components/select-cn#api), styles?: [SelectProps\["styles"\]](/components/select-cn#api) } | - | 5.7.0, `classNames` 和 `styles`: 5.25.0 |
|
||||
| slider | 设置 Slider 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [SliderProps\["classNames"\]](/components/slider-cn#api), styles?: [SliderProps\["styles"\]](/components/slider-cn#api) } | - | 5.7.0, `classNames` 和 `styles`: 5.23.0 |
|
||||
| switch | 设置 Switch 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| space | 设置 Space 的通用属性,参考 [Space](/components/space-cn) | { size: `small` \| `middle` \| `large` \| `number`, className?: string, style?: React.CSSProperties, classNames?: [SpaceProps\["classNames"\]](/components/space-cn#api), styles?: [SpaceProps\["styles"\]](/components/space-cn#api) } | - | 5.6.0 |
|
||||
@ -162,28 +162,29 @@ const {
|
||||
| tabs | 设置 Tabs 组件的通用属性 | { className?: string, style?: React.CSSProperties, indicator?: { size?: GetIndicatorSize, align?: `start` \| `center` \| `end` }, moreIcon?: ReactNode, addIcon?: ReactNode, removeIcon?: ReactNode } | - | 5.7.0, `moreIcon` and `addIcon`: 5.14.0, `removeIcon`: 5.15.0 |
|
||||
| tag | 设置 Tag 组件的通用属性 | { className?: string, style?: React.CSSProperties, closeIcon?: React.ReactNode } | - | 5.7.0, `closeIcon`: 5.14.0 |
|
||||
| timeline | 设置 Timeline 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| timePicker | 设置 TimePicker 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| timePicker | 设置 TimePicker 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [TimePickerConfig\["classNames"\]](/components/time-picker-cn#semantic-dom), styles?: [TimePickerConfig\["styles"\]](/components/time-picker-cn#semantic-dom) } | - | 5.7.0, `classNames` 和 `styles`: 5.25.0 |
|
||||
| tour | 设置 Tour 组件的通用属性 | { closeIcon?: React.ReactNode } | - | 5.14.0 |
|
||||
| tooltip | 设置 Tooltip 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?:[Tooltip\["classNames"\]](/components/tooltip-cn#api), styles?: [Tooltip\["styles"\]](/components/tooltip-cn#api) } | - | 5.23.0 |
|
||||
| popover | 设置 Popover 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?:[Popover\["classNames"\]](/components/popover-cn#api), styles?: [Popover\["styles"\]](/components/popover-cn#api) } | - | 5.23.0 |
|
||||
| popconfirm | 设置 Popconfirm 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?:[Popconfirm\["classNames"\]](/components/popconfirm-cn#api), styles?: [Popconfirm\["styles"\]](/components/popconfirm-cn#api) } | - | 5.23.0 |
|
||||
| transfer | 设置 Transfer 组件的通用属性 | { className?: string, style?: React.CSSProperties, selectionsIcon?: React.ReactNode } | - | 5.7.0, `selectionsIcon`: 5.14.0 |
|
||||
| tree | 设置 Tree 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| treeSelect | 设置 TreeSelect 组件的通用属性 | { classNames?:[TreeSelect\["classNames"\]](/components/tree-select-cn#api), styles?: [TreeSelect\["styles"\]](/components/tree-select-cn#api) } | - | 5.25.0 |
|
||||
| typography | 设置 Typography 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| upload | 设置 Upload 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| wave | 设置水波纹特效 | { disabled?: boolean, showEffect?: (node: HTMLElement, info: { className, token, component }) => void } | - | 5.8.0 |
|
||||
|
||||
## FAQ
|
||||
|
||||
#### 如何增加一个新的语言包?
|
||||
### 如何增加一个新的语言包? {#faq-add-locale}
|
||||
|
||||
参考[《增加语言包》](/docs/react/i18n#%E5%A2%9E%E5%8A%A0%E8%AF%AD%E8%A8%80%E5%8C%85)。
|
||||
|
||||
#### 为什么时间类组件的国际化 locale 设置不生效?
|
||||
### 为什么时间类组件的国际化 locale 设置不生效? {#faq-locale-not-work}
|
||||
|
||||
参考 FAQ [为什么时间类组件的国际化 locale 设置不生效?](/docs/react/faq#为什么时间类组件的国际化-locale-设置不生效)。
|
||||
|
||||
#### 配置 `getPopupContainer` 导致 Modal 报错?
|
||||
### 配置 `getPopupContainer` 导致 Modal 报错? {#faq-get-popup-container}
|
||||
|
||||
相关 issue:<https://github.com/ant-design/ant-design/issues/19974>
|
||||
|
||||
@ -203,17 +204,17 @@ const {
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
#### 为什么 message.info、notification.open 或 Modal.confirm 等方法内的 ReactNode 无法继承 ConfigProvider 的属性?比如 `prefixCls` 和 `theme`。
|
||||
### 为什么 message.info、notification.open 或 Modal.confirm 等方法内的 ReactNode 无法继承 ConfigProvider 的属性?比如 `prefixCls` 和 `theme`。 {#faq-message-inherit}
|
||||
|
||||
静态方法是使用 ReactDOM.render 重新渲染一个 React 根节点上,和主应用的 React 节点是脱离的。我们建议使用 useMessage、useNotification 和 useModal 来使用相关方法。原先的静态方法在 5.0 中已被废弃。
|
||||
|
||||
#### Vite 生产模式打包后国际化 locale 设置不生效?
|
||||
### Vite 生产模式打包后国际化 locale 设置不生效? {#faq-vite-locale-not-work}
|
||||
|
||||
相关 issue:[#39045](https://github.com/ant-design/ant-design/issues/39045)
|
||||
|
||||
由于 Vite 生产模式下打包与开发模式不同,cjs 格式的文件会多一层,需要 `zhCN.default` 来获取。推荐 Vite 用户直接从 `antd/es/locale` 目录下引入 esm 格式的 locale 文件。
|
||||
|
||||
#### prefixCls 优先级(前者被后者覆盖)
|
||||
### prefixCls 优先级(前者被后者覆盖) {#faq-prefixcls-priority}
|
||||
|
||||
1. `ConfigProvider.config({ prefixCls: 'prefix-1' })`
|
||||
2. `ConfigProvider.config({ holderRender: (children) => <ConfigProvider prefixCls="prefix-2">{children}</ConfigProvider> })`
|
||||
|
@ -400,16 +400,35 @@ describe('DatePicker', () => {
|
||||
expect(triggerProps?.popupPlacement).toEqual('bottomRight');
|
||||
});
|
||||
|
||||
it('legacy dropdownClassName', () => {
|
||||
it('legacy dropdownClassName & popupClassName', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const { container } = render(<DatePicker dropdownClassName="legacy" open />);
|
||||
const { container, rerender } = render(<DatePicker dropdownClassName="legacy" open />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: DatePicker] `dropdownClassName` is deprecated. Please use `popupClassName` instead.',
|
||||
'Warning: [antd: DatePicker] `dropdownClassName` is deprecated. Please use `classNames.popup.root` instead.',
|
||||
);
|
||||
expect(container.querySelector('.legacy')).toBeTruthy();
|
||||
|
||||
rerender(<DatePicker popupClassName="legacy" open />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: DatePicker] `popupClassName` is deprecated. Please use `classNames.popup.root` instead.',
|
||||
);
|
||||
expect(container.querySelector('.legacy')).toBeTruthy();
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('legacy popupStyle', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const { container } = render(<DatePicker popupStyle={{ backgroundColor: 'red' }} open />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: DatePicker] `popupStyle` is deprecated. Please use `styles.popup.root` instead.',
|
||||
);
|
||||
expect(container.querySelector('.ant-picker-dropdown')).toHaveStyle('background-color: red');
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
|
@ -148,16 +148,39 @@ describe('RangePicker', () => {
|
||||
expect(container.querySelectorAll('input')[1]?.placeholder).toEqual('End quarter');
|
||||
});
|
||||
|
||||
it('legacy dropdownClassName', () => {
|
||||
it('legacy dropdownClassName & popupClassName', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const { container } = render(<DatePicker.RangePicker dropdownClassName="legacy" open />);
|
||||
const { container, rerender } = render(
|
||||
<DatePicker.RangePicker dropdownClassName="legacy" open />,
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: DatePicker.RangePicker] `dropdownClassName` is deprecated. Please use `popupClassName` instead.',
|
||||
'Warning: [antd: DatePicker.RangePicker] `dropdownClassName` is deprecated. Please use `classNames.popup.root` instead.',
|
||||
);
|
||||
expect(container.querySelector('.legacy')).toBeTruthy();
|
||||
|
||||
rerender(<DatePicker.RangePicker popupClassName="legacy" open />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: DatePicker.RangePicker] `popupClassName` is deprecated. Please use `classNames.popup.root` instead.',
|
||||
);
|
||||
expect(container.querySelector('.legacy')).toBeTruthy();
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('legacy popupStyle', () => {
|
||||
resetWarned();
|
||||
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const { container } = render(
|
||||
<DatePicker.RangePicker popupStyle={{ backgroundColor: 'red' }} open />,
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: DatePicker.RangePicker] `popupStyle` is deprecated. Please use `styles.popup.root` instead.',
|
||||
);
|
||||
expect(container.querySelector('.ant-picker-dropdown')).toHaveStyle('background-color: red');
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
|
@ -20466,6 +20466,782 @@ exports[`renders components/date-picker/demo/disabled-date.tsx extend context co
|
||||
|
||||
exports[`renders components/date-picker/demo/disabled-date.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/date-picker/demo/external-panel.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small ant-dropdown-trigger"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span>
|
||||
2016-11-22
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-show-arrow ant-dropdown-placement-bottomLeft"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-dropdown-arrow"
|
||||
/>
|
||||
<ul
|
||||
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
|
||||
data-menu-list="true"
|
||||
role="menu"
|
||||
tabindex="0"
|
||||
>
|
||||
<li
|
||||
aria-describedby="test-id"
|
||||
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
|
||||
data-menu-id="rc-menu-uuid-test-today"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="ant-dropdown-menu-title-content"
|
||||
>
|
||||
Today
|
||||
</span>
|
||||
</li>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; top: 0px; left: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
id="test-id"
|
||||
role="tooltip"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<li
|
||||
aria-describedby="test-id"
|
||||
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
|
||||
data-menu-id="rc-menu-uuid-test-tomorrow"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="ant-dropdown-menu-title-content"
|
||||
>
|
||||
Tomorrow
|
||||
</span>
|
||||
</li>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; top: 0px; left: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
id="test-id"
|
||||
role="tooltip"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<li
|
||||
aria-describedby="test-id"
|
||||
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
|
||||
data-menu-id="rc-menu-uuid-test-custom-date"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="ant-dropdown-menu-title-content"
|
||||
>
|
||||
<div
|
||||
style="position: relative;"
|
||||
>
|
||||
<div>
|
||||
Customize
|
||||
</div>
|
||||
<div
|
||||
style="height: 0px; width: 0px; overflow: hidden; position: absolute; top: 0px; inset-inline-start: 0;"
|
||||
>
|
||||
<div
|
||||
class="ant-picker ant-picker-outlined"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-input"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="off"
|
||||
placeholder="Select date"
|
||||
size="12"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
class="ant-picker-suffix"
|
||||
>
|
||||
<span
|
||||
aria-label="calendar"
|
||||
class="anticon anticon-calendar"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="calendar"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-picker-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-picker-dropdown-placement-bottomLeft"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box; z-index: 1150;"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-panel-container ant-picker-date-panel-container"
|
||||
style="margin-left: 0px; margin-right: auto;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-panel-layout"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="ant-picker-panel"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-date-panel"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-header"
|
||||
>
|
||||
<button
|
||||
aria-label="Last year (Control + left)"
|
||||
class="ant-picker-header-super-prev-btn"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-picker-super-prev-icon"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Previous month (PageUp)"
|
||||
class="ant-picker-header-prev-btn"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-picker-prev-icon"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="ant-picker-header-view"
|
||||
>
|
||||
<button
|
||||
aria-label="Choose a month"
|
||||
class="ant-picker-month-btn"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
Nov
|
||||
</button>
|
||||
<button
|
||||
aria-label="Choose a year"
|
||||
class="ant-picker-year-btn"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
2016
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Next month (PageDown)"
|
||||
class="ant-picker-header-next-btn"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-picker-next-icon"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Next year (Control + right)"
|
||||
class="ant-picker-header-super-next-btn"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-picker-super-next-icon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-picker-body"
|
||||
>
|
||||
<table
|
||||
class="ant-picker-content"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Su
|
||||
</th>
|
||||
<th>
|
||||
Mo
|
||||
</th>
|
||||
<th>
|
||||
Tu
|
||||
</th>
|
||||
<th>
|
||||
We
|
||||
</th>
|
||||
<th>
|
||||
Th
|
||||
</th>
|
||||
<th>
|
||||
Fr
|
||||
</th>
|
||||
<th>
|
||||
Sa
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-10-30"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
30
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-10-31"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
31
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-01"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-02"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
2
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-03"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
3
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-04"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
4
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-05"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
5
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-06"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
6
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-07"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
7
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-08"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
8
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-09"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
9
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-10"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
10
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-11"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
11
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-12"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
12
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-13"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
13
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-14"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
14
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-15"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
15
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-16"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
16
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-17"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
17
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-18"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
18
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-19"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
19
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-20"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
20
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-21"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
21
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view ant-picker-cell-today"
|
||||
title="2016-11-22"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
22
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-23"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
23
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-24"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
24
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-25"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
25
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-26"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
26
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-27"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
27
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-28"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
28
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-29"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
29
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell ant-picker-cell-in-view"
|
||||
title="2016-11-30"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
30
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-12-01"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-12-02"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
2
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-12-03"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
3
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-12-04"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
4
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-12-05"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
5
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-12-06"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
6
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-12-07"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
7
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-12-08"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
8
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-12-09"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
9
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="ant-picker-cell"
|
||||
title="2016-12-10"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-cell-inner"
|
||||
>
|
||||
10
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-picker-footer"
|
||||
>
|
||||
<ul
|
||||
class="ant-picker-ranges"
|
||||
>
|
||||
<li
|
||||
class="ant-picker-now"
|
||||
>
|
||||
<a
|
||||
aria-disabled="false"
|
||||
class="ant-picker-now-btn"
|
||||
>
|
||||
Today
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</li>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; top: 0px; left: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
id="test-id"
|
||||
role="tooltip"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style="display: none;"
|
||||
/>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/date-picker/demo/external-panel.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/date-picker/demo/extra-footer.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user