Merge branch 'master' into fix-form-vertical-offset

This commit is contained in:
afc163 2025-05-30 16:22:50 +08:00 committed by GitHub
commit 7979593ca2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
439 changed files with 11251 additions and 4106 deletions

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

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

View File

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

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

View File

@ -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';
}
})();

View 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);
};
})();

View File

@ -160,7 +160,7 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
{title}
<Popover
title={null}
destroyTooltipOnHide
destroyOnHidden
styles={{ root: { width: 400 } }}
content={
<Typography>

View File

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

View File

@ -1,91 +0,0 @@
import React, { Suspense, useEffect, useRef, useState } from 'react';
import { Tooltip } from 'antd';
import { FormattedMessage } from 'dumi';
import type { IPreviewerProps } from 'dumi';
import RiddleIcon from '../../icons/RiddleIcon';
import { ping } from '../../utils';
let pingDeferrer: PromiseLike<boolean>;
function useShowRiddleButton() {
const [showRiddleButton, setShowRiddleButton] = useState(false);
useEffect(() => {
pingDeferrer ??= new Promise<boolean>((resolve) => {
ping((status) => {
if (status !== 'timeout' && status !== 'error') {
return resolve(true);
}
return resolve(false);
});
});
pingDeferrer.then(setShowRiddleButton);
}, []);
return showRiddleButton;
}
interface RiddleButtonProps {
title?: string;
dependencies: Record<PropertyKey, string>;
jsx: string;
track: ({
type,
demo,
}: {
type: string;
demo: string;
}) => void;
asset: IPreviewerProps['asset'];
}
const RiddleButton: React.FC<RiddleButtonProps> = ({
title,
dependencies = {},
jsx,
track,
asset,
}) => {
const riddleIconRef = useRef<HTMLFormElement>(null);
const showRiddleButton = useShowRiddleButton();
const riddlePrefillConfig = {
title: `${title} - antd@${dependencies.antd}`,
js: `${
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
/export default/,
'const ComponentDemo =',
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
css: '',
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
};
return showRiddleButton ? (
<form
className="code-box-code-action"
action="//riddle.alibaba-inc.com/riddles/define"
method="POST"
target="_blank"
ref={riddleIconRef}
onClick={() => {
track({ type: 'riddle', demo: asset.id });
riddleIconRef.current?.submit();
}}
>
<input type="hidden" name="data" value={JSON.stringify(riddlePrefillConfig)} />
<Tooltip title={<FormattedMessage id="app.demo.riddle" />}>
<RiddleIcon className="code-box-riddle" />
</Tooltip>
</form>
) : null;
};
export default (props: RiddleButtonProps) => (
<Suspense>
<RiddleButton {...props} />
</Suspense>
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 ? '通过' : '失败');

View File

@ -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'");
});
});

View File

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

View File

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

View File

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

View File

@ -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(),
},
{

View File

@ -1,8 +1,8 @@
<!--
First of all, thank you for your contribution! 😄
For requesting to pull a new feature or bugfix, please send it from a feature/bugfix branch based on the `master` branch.
Before submitting your pull request, please make sure the checklist below is confirmed.
Your pull requests will be merged after one of the collaborators approve.
Before submitting your pull request, please make sure the checklist below is filled out.
Your pull requests will be merged after one of the collaborators approves.
Thank you!
-->
@ -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

View File

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

View File

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

View File

@ -75,5 +75,7 @@
"https://github.com/ant-design/ant-design/issues/51420",
"https://github.com/ant-design/ant-design/issues/51430"
],
"5.22.6": ["https://github.com/ant-design/ant-design/issues/52124"]
"5.22.6": ["https://github.com/ant-design/ant-design/issues/52124"],
"5.24.8": ["https://github.com/ant-design/ant-design/issues/53652"],
"5.25.0": ["https://github.com/ant-design/ant-design/issues/53764"]
}

View File

@ -9,12 +9,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)
- 🛠 MISCRefactor compatible code, use standard web API first, and downgrade to deprecated API when not compatibale [#53107](https://github.com/ant-design/ant-design/pull/53107) [@li-jia-nan](https://github.com/li-jia-nan)
- ⌨️ Opt Tour's `aria-*` props. [#53345](https://github.com/ant-design/ant-design/pull/53345) [@kiner-tang](https://github.com/kiner-tang)
- ⌨️ MISC: Optimized closable component's aria props. [#53410](https://github.com/ant-design/ant-design/pull/53410) [@kiner-tang](https://github.com/kiner-tang)
- 🗑 MISC: Deprecate `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`

View File

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

View File

@ -8,7 +8,7 @@
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url]
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url] [![DeepWiki][deepwiki-image]][deepwiki-url]
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
@ -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 的修复和改进,请查看我们的赞助列表:
[![Let's fund issues in this repository](https://raw.githubusercontent.com/BoostIO/issuehunt-materials/master/v1/issuehunt-button-v1.svg)](https://issuehunt.io/repos/34526884)

View File

@ -8,7 +8,7 @@ An enterprise-class UI design language and React UI library.
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url]
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url] [![DeepWiki][deepwiki-image]][deepwiki-url]
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
@ -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 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](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:
[![Let's fund issues in this repository](https://raw.githubusercontent.com/BoostIO/issuehunt-materials/master/v1/issuehunt-button-v1.svg)](https://issuehunt.io/repos/34526884)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import React from 'react';
import type { GlobalToken } from '../theme/internal';
import { useToken } from '../theme/internal';
import { addMediaQueryListener, removeMediaQueryListener } from './mediaQueryUtil';
export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
export type BreakpointMap = Record<Breakpoint, string>;
@ -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();
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
});
});

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

View File

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

View File

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

View File

@ -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&lt;InputProps> | &lt;Input /> | |
| children (for dataSource) | Data source to auto complete | React.ReactElement&lt;OptionProps> \| Array&lt;React.ReactElement&lt;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>

View File

@ -49,12 +49,15 @@ demo:
| backfill | 使用键盘选择选项的时候把选中项回填到输入框中 | boolean | false | |
| children (自动完成的数据源) | 自动完成的数据源,不能和自定义输入框同时配置 | React.ReactElement&lt;OptionProps> \| Array&lt;React.ReactElement&lt;OptionProps>> | - | |
| children (自定义输入框) | 自定义输入框,不能和自动完成的数据源同时配置 | HTMLInputElement \| HTMLTextAreaElement \| React.ReactElement&lt;InputProps> | &lt;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>

View File

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

View File

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

View File

@ -245,7 +245,6 @@ describe('Breadcrumb', () => {
items={[
{
title: 'xxx',
// @ts-ignore
'data-custom': 'custom-item',
},
{

View File

@ -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();
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = [
{

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = () => (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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') {

View File

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

View File

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

View File

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

View File

@ -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);
}}
/>

View File

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

View File

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

View File

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

View File

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

View File

@ -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`] = `

View File

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

View File

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

View File

@ -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 [&lt;Adding new language&gt;](/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> })`

View File

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

View File

@ -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();
});

View File

@ -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();
});

View File

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