Merge branch 'master' into feat/addComponentToken

This commit is contained in:
ug 2025-05-07 09:59:58 +08:00 committed by GitHub
commit 9dfeda75b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
360 changed files with 8700 additions and 2953 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)

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

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

View File

@ -277,7 +277,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 +327,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
onClick: () => setShow(true),
})}
<Drawer
destroyOnClose
destroyOnHidden
className={styles.drawerContent}
title={locale.changelog}
extra={

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

1
.gitignore vendored
View File

@ -38,6 +38,7 @@ package-lock.json
pnpm-lock.yaml
bun.lock*
.pnpm-debug.log
.pnpm-store
components/**/*.js
components/**/*.jsx
!components/**/__tests__/**/*.js

View File

@ -75,5 +75,6 @@
"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"]
}

View File

@ -9,16 +9,29 @@ 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.24.9
`2025-04-29`
- 🐞 Fix Splitter mask not hiding correctly in lazy mode. [#53653](https://github.com/ant-design/ant-design/pull/53653) [@wanpan11](https://github.com/wanpan11)
- 🐞 Fix issue when modifying `offsetBottom` and `offsetTop` of the Affix does not take effect. [#53607](https://github.com/ant-design/ant-design/pull/53607) [@yellowryan](https://github.com/yellowryan)
- ⚡️ Fix Select keeps showing `clearIcon` when it has a value on mobile devices. [#53576](https://github.com/ant-design/ant-design/pull/53576) [@EmilyLiu](https://github.com/EmilyLiu)
- 🐞 Fix Slider formatter returns fixed content, Tooltip position abnormal after dragging. [#53460](https://github.com/ant-design/ant-design/pull/53460) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 Fix Tabs keyboard operation not working. [#53692](https://github.com/ant-design/ant-design/pull/53692) [@afc163](https://github.com/afc163)
- RTL
- 💄 Fix counter element direction of Image in RTL mode. [#53593](https://github.com/ant-design/ant-design/pull/53593) [@aojunhao123](https://github.com/aojunhao123)
## 5.24.8
`2025-04-21`
- 📖 Release [llms.txt](/llms.txt) and [llms-full.txt](/llms-full.txt), help LLM or agent to access detailed information during inference.
- 🐞 Fix Tabs throwing `Maximum update depth exceeded` error in some cases. [#53521](https://github.com/ant-design/ant-design/pull/53521) [@afc163](https://github.com/afc163)
- Splitter
- 💄 Make Splitter collapse icon always visible on mobile devices. [#53575](https://github.com/ant-design/ant-design/pull/53575) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
@ -33,7 +46,7 @@ tag: vVERSION
- 🐞 MISC: Fix custom token `colorIcon` not taking effect for some components. [#53511](https://github.com/ant-design/ant-design/pull/53511) [@dengfuping](https://github.com/dengfuping)
- 🐞 Fix message/notification runtime dynamic configuration changes not taking effect. [#53579](https://github.com/ant-design/ant-design/pull/53579) [@Wxh16144](https://github.com/Wxh16144)
- RTL
- 💄 Fix counter element direction of Image and Input.TextArea in RTL mode. [#53530](https://github.com/ant-design/ant-design/pull/53530) [@aojunhao123](https://github.com/aojunhao123)
- 💄 Fix counter element direction of Input.TextArea in RTL mode. [#53530](https://github.com/ant-design/ant-design/pull/53530) [@aojunhao123](https://github.com/aojunhao123)
- 💄 Fix incorrect direction of left and right switch icons in Image.PreviewGroup in RTL mode. [#53525](https://github.com/ant-design/ant-design/pull/53525) [@aojunhao123](https://github.com/aojunhao123)
## 5.24.7

View File

@ -15,10 +15,24 @@ tag: vVERSION
---
## 5.24.9
`2025-04-29`
- 🐞 修复 Splitter 在 `lazy` 模式下遮罩未能正确关闭的问题。[#53653](https://github.com/ant-design/ant-design/pull/53653) [@wanpan11](https://github.com/wanpan11)
- 🐞 修复 Affix 在动态调整 `offsetTop``offsetBottom` 属性值后固定效果失效的问题。[#53607](https://github.com/ant-design/ant-design/pull/53607) [@yellowryan](https://github.com/yellowryan)
- 💄 移动端场景下 Select 当有选中值时始终显示清除按钮。[#53576](https://github.com/ant-design/ant-design/pull/53576) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 修复 Slider 设置 `formatter` 后,拖拽滑块导致 Tooltip 位置异常的问题。[#53460](https://github.com/ant-design/ant-design/pull/53460) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
- 🐞 修复 Tabs 键盘操作失效的问题。[#53692](https://github.com/ant-design/ant-design/pull/53692) [@afc163](https://github.com/afc163)
- RTL
- 💄 修复 Image 计数元素在 RTL 模式下的显示方向问题。[#53593](https://github.com/ant-design/ant-design/pull/53593) [@aojunhao123](https://github.com/aojunhao123)
## 5.24.8
`2025-04-21`
- 📖 发布 [llms.txt](/llms.txt) 和 [llms-full.txt](/llms-full.txt),提供大模型友好的文本信息。
- 🐞 修复 Tabs 有时抛出 `Maximum update depth exceeded` 错误的问题。[#53521](https://github.com/ant-design/ant-design/pull/53521) [@afc163](https://github.com/afc163)
- Splitter
- 💄 Splitter 折叠图标在移动设备上将始终展示。[#53575](https://github.com/ant-design/ant-design/pull/53575) [@EmilyyyLiu](https://github.com/EmilyyyLiu)
@ -33,7 +47,7 @@ tag: vVERSION
- 🐞 MISC: 修复自定义 token `colorIcon` 对部分组件不生效的问题。[#53511](https://github.com/ant-design/ant-design/pull/53511) [@dengfuping](https://github.com/dengfuping)
- 🐞 修复 message/notification 运行时动态修改配置无法生效的问题。[#53579](https://github.com/ant-design/ant-design/pull/53579) [@Wxh16144](https://github.com/Wxh16144)
- RTL
- 💄 修复 Image 和 Input.TextArea 计数元素在 RTL 模式中的方向问题。[#53530](https://github.com/ant-design/ant-design/pull/53530) [@aojunhao123](https://github.com/aojunhao123)
- 💄 修复 Input.TextArea 计数元素在 RTL 模式中的方向问题。[#53530](https://github.com/ant-design/ant-design/pull/53530) [@aojunhao123](https://github.com/aojunhao123)
- 💄 修复 Image.PreviewGroup 在 RTL 模式下左右切换图标方向不正确的问题。[#53525](https://github.com/ant-design/ant-design/pull/53525) [@aojunhao123](https://github.com/aojunhao123)
## 5.24.7

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

@ -3,6 +3,11 @@ import React from 'react';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import type { DialogProps } from 'rc-dialog';
import pickAttrs from 'rc-util/lib/pickAttrs';
import extendsObject from '../extendsObject';
import { useLocale } from '../../locale';
import defaultLocale from '../../locale/en_US';
import type { HTMLAriaDataAttributes } from '../aria-data-attrs';
export type ClosableType = DialogProps['closable'];
@ -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,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
@ -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

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/components/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

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

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

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

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

@ -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 Input from '../../input/Input';
import type { AggregationColor } from '../color';
@ -17,20 +17,14 @@ const isHexString = (hex?: string) => hexReg.test(`#${hex}`);
const ColorHexInput: FC<ColorHexInputProps> = ({ prefixCls, value, onChange }) => {
const colorHexInputPrefixCls = `${prefixCls}-hex-input`;
const [hexValue, setHexValue] = useState(() =>
value ? toHexFormat(value.toHexString()) : undefined,
);
const [internalValue, setInternalValue] = useState('');
// Update step value
useEffect(() => {
if (value) {
setHexValue(toHexFormat(value.toHexString()));
}
}, [value]);
const hexValue = value ? toHexFormat(value.toHexString()) : internalValue;
const handleHexChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const originValue = e.target.value;
setHexValue(toHexFormat(originValue));
setInternalValue(toHexFormat(originValue));
if (isHexString(toHexFormat(originValue, true))) {
onChange?.(generateColor(originValue));
}

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

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,9 +204,11 @@ 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'>;

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

@ -71,7 +71,7 @@ Some components use dynamic style to support wave effect. You can config `csp` p
### ConfigProvider.config()
Setting `Modal`、`Message`、`Notification` static config. Not work on hooks.
Setting `Modal`, `Message`, `Notification` static config. Not work on hooks.
```tsx
ConfigProvider.config({
@ -113,11 +113,11 @@ 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 |
@ -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 |
@ -167,6 +167,7 @@ const {
| 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 |

View File

@ -115,11 +115,11 @@ 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 |
@ -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 |
@ -169,6 +169,7 @@ const {
| 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 |

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"

View File

@ -1485,6 +1485,43 @@ exports[`renders components/date-picker/demo/disabled-date.tsx correctly 1`] = `
</div>
`;
exports[`renders components/date-picker/demo/external-panel.tsx correctly 1`] = `
<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>
`;
exports[`renders components/date-picker/demo/extra-footer.tsx correctly 1`] = `
<div
class="ant-space ant-space-vertical"

View File

@ -0,0 +1,7 @@
## zh-CN
自定义菜单,外置选择面板。
## en-US
Custom menu, external selection panel.

View File

@ -0,0 +1,91 @@
import React from 'react';
import { DownOutlined } from '@ant-design/icons';
import { DatePicker, Dropdown, Space } from 'antd';
import dayjs from 'dayjs';
const App = () => {
const [visible, setVisible] = React.useState(false);
const [panelVisible, setPanelVisible] = React.useState(false);
const [date, setDate] = React.useState(dayjs());
return (
<Dropdown
arrow
open={visible}
trigger={['click']}
destroyOnHidden
onOpenChange={(open) => {
setVisible(open);
if (!open) {
setPanelVisible(false);
}
}}
menu={{
items: [
{
key: 'today',
label: 'Today',
onClick() {
setDate(dayjs());
setVisible(false);
},
},
{
key: 'tomorrow',
label: 'Tomorrow',
onClick() {
setDate(dayjs().add(1, 'day'));
setVisible(false);
},
},
{
key: 'custom-date',
label: (
<div
style={{ position: 'relative' }}
onClick={(e) => {
e.stopPropagation();
setPanelVisible(true);
}}
>
<div>Customize</div>
<div
onClick={(e) => {
e.stopPropagation();
}}
style={{
height: 0,
width: 0,
overflow: 'hidden',
position: 'absolute',
top: 0,
insetInlineStart: 0,
}}
>
<DatePicker
open={panelVisible}
onChange={(date) => {
setDate(date);
setVisible(false);
setPanelVisible(false);
}}
/>
</div>
</div>
),
},
],
}}
>
<Space>
<span>{date.format('YYYY-MM-DD')}</span>
<DownOutlined />
</Space>
</Dropdown>
);
};
export default App;

View File

@ -35,6 +35,7 @@ By clicking the input box, you can select a date from a popup calendar.
<code src="./demo/size.tsx">Three Sizes</code>
<code src="./demo/cell-render.tsx">Customized Cell Rendering</code>
<code src="./demo/components.tsx" version="5.14.0">Customize Panel</code>
<code src="./demo/external-panel.tsx">External use panel</code>
<code src="./demo/buddhist-era.tsx" version="5.14.0">Buddhist Era</code>
<code src="./demo/status.tsx">Status</code>
<code src="./demo/variant.tsx" version="5.13.0">Variants</code>

View File

@ -36,6 +36,7 @@ demo:
<code src="./demo/size.tsx">三种大小</code>
<code src="./demo/cell-render.tsx">定制单元格</code>
<code src="./demo/components.tsx" version="5.14.0">定制面板</code>
<code src="./demo/external-panel.tsx">外部使用面板</code>
<code src="./demo/buddhist-era.tsx" version="5.14.0">佛历格式</code>
<code src="./demo/status.tsx">自定义状态</code>
<code src="./demo/variant.tsx" version="5.13.0">形态变体</code>

View File

@ -203,6 +203,37 @@ Array [
exports[`renders components/divider/demo/plain.tsx extend context correctly 2`] = `[]`;
exports[`renders components/divider/demo/size.tsx extend context correctly 1`] = `
Array [
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
<div
class="ant-divider ant-divider-horizontal ant-divider-sm"
role="separator"
/>,
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
<div
class="ant-divider ant-divider-horizontal ant-divider-md"
role="separator"
/>,
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
<div
class="ant-divider ant-divider-horizontal"
role="separator"
/>,
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
]
`;
exports[`renders components/divider/demo/size.tsx extend context correctly 2`] = `[]`;
exports[`renders components/divider/demo/variant.tsx extend context correctly 1`] = `
Array [
<p>

View File

@ -195,6 +195,35 @@ Array [
]
`;
exports[`renders components/divider/demo/size.tsx correctly 1`] = `
Array [
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
<div
class="ant-divider ant-divider-horizontal ant-divider-sm"
role="separator"
/>,
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
<div
class="ant-divider ant-divider-horizontal ant-divider-md"
role="separator"
/>,
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
<div
class="ant-divider ant-divider-horizontal"
role="separator"
/>,
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
]
`;
exports[`renders components/divider/demo/variant.tsx correctly 1`] = `
Array [
<p>

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import { ConfigProvider } from 'antd';
import Divider from '..';
import mountTest from '../../../tests/shared/mountTest';
@ -40,4 +41,28 @@ describe('Divider', () => {
borderStyle: 'dotted',
});
});
it('should apply the componentSize of ConfigProvider', () => {
const { container, rerender } = render(
<ConfigProvider componentSize="middle">
<Divider />
</ConfigProvider>,
);
expect(container.querySelector<HTMLSpanElement>('.ant-divider-md')).toBeTruthy();
rerender(
<ConfigProvider componentSize="small">
<Divider />
</ConfigProvider>,
);
expect(container.querySelector<HTMLSpanElement>('.ant-divider-sm')).toBeTruthy();
});
it('support vertical size', () => {
const { container, rerender } = render(<Divider type="vertical" size="middle" />);
expect(container.querySelector<HTMLSpanElement>('.ant-divider-md')).toBeTruthy();
rerender(<Divider type="vertical" size="small" />);
expect(container.querySelector<HTMLSpanElement>('.ant-divider-sm')).toBeTruthy();
});
});

View File

@ -0,0 +1,7 @@
## zh-CN
间距的大小。
## en-US
The size of the spacing.

View File

@ -0,0 +1,28 @@
import React from 'react';
import { Divider } from 'antd';
const App: React.FC = () => (
<>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista
probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>
<Divider size="small" />
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista
probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>
<Divider size="middle" />
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista
probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>
<Divider size="large" />
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista
probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>
</>
);
export default App;

View File

@ -21,6 +21,7 @@ group:
<!-- prettier-ignore -->
<code src="./demo/horizontal.tsx">Horizontal</code>
<code src="./demo/with-text.tsx">Divider with title</code>
<code src="./demo/size.tsx" version="5.25.0">Set the spacing size of the divider</code>
<code src="./demo/plain.tsx">Text without heading style</code>
<code src="./demo/vertical.tsx">Vertical</code>
<code src="./demo/customize-style.tsx" debug>Style Customization</code>
@ -41,6 +42,7 @@ Common props ref[Common props](/docs/react/common-props)
| orientationMargin | The margin-left/right between the title and its closest border, while the `orientation` should not be `center`, If a numeric value of type `string` is provided without a unit, it is assumed to be in pixels (px) by default. | string \| number | - | |
| plain | Divider text show as plain style | boolean | true | 4.2.0 |
| style | The style object of container | CSSProperties | - | |
| size | The size of divider. Only valid for horizontal layout | `small` \| `middle` \| `large` | - | 5.25.0 |
| type | The direction type of divider | `horizontal` \| `vertical` | `horizontal` | |
## Design Token

View File

@ -3,6 +3,8 @@ import classNames from 'classnames';
import { devUseWarning } from '../_util/warning';
import { useComponentConfig } from '../config-provider/context';
import useSize from '../config-provider/hooks/useSize';
import { SizeType } from '../config-provider/SizeContext';
import useStyle from './style';
export interface DividerProps {
@ -28,9 +30,12 @@ export interface DividerProps {
*/
variant?: 'dashed' | 'dotted' | 'solid';
style?: React.CSSProperties;
size?: SizeType;
plain?: boolean;
}
const sizeClassNameMap: Record<string, string> = { small: 'sm', middle: 'md' };
const Divider: React.FC<DividerProps> = (props) => {
const {
getPrefixCls,
@ -51,12 +56,16 @@ const Divider: React.FC<DividerProps> = (props) => {
variant = 'solid',
plain,
style,
size: customSize,
...restProps
} = props;
const prefixCls = getPrefixCls('divider', customizePrefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
const sizeFullName = useSize(customSize);
const sizeCls = sizeClassNameMap[sizeFullName];
const hasChildren = !!children;
const mergedOrientation = React.useMemo<'start' | 'end' | 'center'>(() => {
@ -88,6 +97,7 @@ const Divider: React.FC<DividerProps> = (props) => {
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-no-default-orientation-margin-start`]: hasMarginStart,
[`${prefixCls}-no-default-orientation-margin-end`]: hasMarginEnd,
[`${prefixCls}-${sizeCls}`]: !!sizeCls,
},
className,
rootClassName,

View File

@ -22,6 +22,7 @@ group:
<!-- prettier-ignore -->
<code src="./demo/horizontal.tsx">水平分割线</code>
<code src="./demo/with-text.tsx">带文字的分割线</code>
<code src="./demo/size.tsx" version="5.25.0">设置分割线的间距大小</code>
<code src="./demo/plain.tsx">分割文字使用正文样式</code>
<code src="./demo/vertical.tsx">垂直分割线</code>
<code src="./demo/customize-style.tsx" debug>样式自定义</code>
@ -42,6 +43,7 @@ group:
| orientationMargin | 标题和最近 left/right 边框之间的距离,去除了分割线,同时 `orientation` 不能为 `center`。如果传入 `string` 类型的数字且不带单位,默认单位是 px | string \| number | - | |
| plain | 文字是否显示为普通正文样式 | boolean | false | 4.2.0 |
| style | 分割线样式对象 | CSSProperties | - | |
| size | 间距大小,仅对水平布局有效 | `small` \| `middle` \| `large` | - | 5.25.0 |
| type | 水平还是垂直类型 | `horizontal` \| `vertical` | `horizontal` | |
## 主题变量Design Token

View File

@ -40,13 +40,28 @@ interface DividerToken extends FullToken<'Divider'> {
* @descEN Horizontal margin of divider with text
*/
dividerHorizontalWithTextGutterMargin: number | string;
/**
* @desc 线
* @descEN Horizontal margin of divider
*/
dividerHorizontalGutterMargin: number | string;
}
// ============================== Size ================================
const genSizeDividerStyle: GenerateStyle<DividerToken> = (token): CSSObject => {
const { componentCls } = token;
return {
[componentCls]: {
'&-horizontal': {
[`&${componentCls}`]: {
'&-sm': {
marginBlock: token.marginXS,
},
'&-md': {
marginBlock: token.margin,
},
},
},
},
};
};
// ============================== Shared ==============================
const genSharedDividerStyle: GenerateStyle<DividerToken> = (token): CSSObject => {
const {
@ -82,7 +97,7 @@ const genSharedDividerStyle: GenerateStyle<DividerToken> = (token): CSSObject =>
clear: 'both',
width: '100%',
minWidth: '100%', // Fix https://github.com/ant-design/ant-design/issues/10914
margin: `${unit(token.dividerHorizontalGutterMargin)} 0`,
margin: `${unit(token.marginLG)} 0`,
},
[`&-horizontal${componentCls}-with-text`]: {
@ -223,10 +238,9 @@ export default genStyleHooks(
(token) => {
const dividerToken = mergeToken<DividerToken>(token, {
dividerHorizontalWithTextGutterMargin: token.margin,
dividerHorizontalGutterMargin: token.marginLG,
sizePaddingEdgeHorizontal: 0,
});
return [genSharedDividerStyle(dividerToken)];
return [genSharedDividerStyle(dividerToken), genSizeDividerStyle(dividerToken)];
},
prepareComponentToken,
{

View File

@ -4,8 +4,8 @@ import type { DrawerProps as RCDrawerProps } from 'rc-drawer';
import useClosable, { pickClosable } from '../_util/hooks/useClosable';
import type { ClosableType } from '../_util/hooks/useClosable';
import Skeleton from '../skeleton';
import { useComponentConfig } from '../config-provider/context';
import Skeleton from '../skeleton';
export interface DrawerClassNames extends NonNullable<RCDrawerProps['classNames']> {
header?: string;
@ -74,7 +74,7 @@ const DrawerPanel: React.FC<DrawerPanelProps> = (props) => {
const customCloseIconRender = React.useCallback(
(icon: React.ReactNode) => (
<button type="button" onClick={onClose} aria-label="Close" className={`${prefixCls}-close`}>
<button type="button" onClick={onClose} className={`${prefixCls}-close`}>
{icon}
</button>
),

View File

@ -105,9 +105,9 @@ describe('Drawer', () => {
expect(wrapper.firstChild).toMatchSnapshot();
});
it('destroyOnClose is true', () => {
it('destroyOnHidden is true', () => {
const { container: wrapper } = render(
<Drawer destroyOnClose open={false} getContainer={false}>
<Drawer destroyOnHidden open={false} getContainer={false}>
Here is content of Drawer
</Drawer>,
);
@ -118,7 +118,7 @@ describe('Drawer', () => {
it('className is test_drawer', () => {
const { container: wrapper } = render(
<Drawer destroyOnClose open rootClassName="test_drawer" getContainer={false}>
<Drawer destroyOnHidden open rootClassName="test_drawer" getContainer={false}>
Here is content of Drawer
</Drawer>,
);

View File

@ -56,11 +56,11 @@ describe('Drawer', () => {
expect(onClose).not.toHaveBeenCalled();
});
it('dom should be removed after close when destroyOnClose is true', () => {
const { container, rerender } = render(<DrawerTest destroyOnClose />);
it('dom should be removed after close when destroyOnHidden is true', () => {
const { container, rerender } = render(<DrawerTest destroyOnHidden />);
expect(container.querySelector('.ant-drawer')).toBeTruthy();
rerender(<DrawerTest destroyOnClose open={false} />);
rerender(<DrawerTest destroyOnHidden open={false} />);
act(() => {
jest.runAllTimers();
});
@ -68,7 +68,7 @@ describe('Drawer', () => {
expect(container.querySelector('.ant-drawer')).toBeFalsy();
});
it('dom should be existed after close when destroyOnClose is false', () => {
it('dom should be existed after close when destroyOnHidden is false', () => {
const { container, rerender } = render(<DrawerTest />);
expect(container.querySelector('.ant-drawer')).toBeTruthy();

View File

@ -203,7 +203,7 @@ exports[`Drawer closable is false 1`] = `
</div>
`;
exports[`Drawer destroyOnClose is true 1`] = `null`;
exports[`Drawer destroyOnHidden is true 1`] = `null`;
exports[`Drawer getContainer return undefined 1`] = `<div />`;

View File

@ -39,7 +39,7 @@ Array [
class="ant-drawer-header-title"
>
<button
aria-label="Close"
aria-label="Close Button"
class="ant-drawer-close"
type="button"
>

View File

@ -17,7 +17,12 @@ const App: React.FC = () => {
<Button type="primary" onClick={showDrawer}>
Open
</Button>
<Drawer title="Basic Drawer" onClose={onClose} open={open}>
<Drawer
title="Basic Drawer"
closable={{ 'aria-label': 'Close Button' }}
onClose={onClose}
open={open}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>

View File

@ -22,7 +22,7 @@ const App: React.FC = () => {
</Button>
<Drawer
closable
destroyOnClose
destroyOnHidden
title={<p>Loading Drawer</p>}
placement="right"
open={open}

View File

@ -56,7 +56,8 @@ v5 uses `rootClassName` & `rootStyle` to configure the outermost element style,
| className | Config Drawer Panel className. Use `rootClassName` if want to config top DOM style | string | - | |
| classNames | Semantic structure className | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.10.0 |
| closeIcon | Custom close icon. 5.7.0: close button will be hidden when setting to `null` or `false` | ReactNode | &lt;CloseOutlined /> | |
| destroyOnClose | Whether to unmount child components on closing drawer or not | boolean | false | |
| ~~destroyOnClose~~ | Whether to unmount child components on closing drawer or not | boolean | false | |
| destroyOnHidden | Whether to unmount child components on closing drawer or not | boolean | false | 5.25.0 |
| extra | Extra actions area at corner | ReactNode | - | 4.17.0 |
| footer | The footer for Drawer | ReactNode | - | |
| forceRender | Pre-render Drawer component forcibly | boolean | false | |

View File

@ -26,7 +26,7 @@ export interface PushState {
// Drawer diff props: 'open' | 'motion' | 'maskMotion' | 'wrapperClassName'
export interface DrawerProps
extends Omit<RcDrawerProps, 'maskStyle'>,
extends Omit<RcDrawerProps, 'maskStyle' | 'destroyOnClose'>,
Omit<DrawerPanelProps, 'prefixCls'> {
size?: sizeType;
@ -41,6 +41,12 @@ export interface DrawerProps
afterVisibleChange?: (open: boolean) => void;
classNames?: DrawerClassNames;
styles?: DrawerStyles;
/** @deprecated Please use `destroyOnHidden` instead */
destroyOnClose?: boolean;
/**
* @since 5.25.0
*/
destroyOnHidden?: boolean;
}
const defaultPushState: PushState = { distance: 180 };
@ -69,7 +75,8 @@ const Drawer: React.FC<DrawerProps> & {
maskStyle,
drawerStyle,
contentWrapperStyle,
destroyOnClose,
destroyOnHidden,
...rest
} = props;
@ -116,6 +123,7 @@ const Drawer: React.FC<DrawerProps> & {
['contentWrapperStyle', 'styles.wrapper'],
['maskStyle', 'styles.mask'],
['drawerStyle', 'styles.content'],
['destroyInactivePanel', 'destroyOnHidden'],
].forEach(([deprecatedName, newName]) => {
warning.deprecated(!(deprecatedName in props), deprecatedName, newName);
});
@ -210,6 +218,8 @@ const Drawer: React.FC<DrawerProps> & {
afterOpenChange={afterOpenChange ?? afterVisibleChange}
panelRef={panelRef}
zIndex={zIndex}
// TODO: In the future, destroyOnClose in rc-drawer needs to be upgrade to destroyOnHidden
destroyOnClose={destroyOnHidden ?? destroyOnClose}
>
<DrawerPanel prefixCls={prefixCls} {...rest} onClose={onClose} />
</RcDrawer>

View File

@ -56,7 +56,8 @@ v5 使用 `rootClassName` 与 `rootStyle` 来配置最外层元素样式。原 v
| className | Drawer 容器外层 className 设置,如果需要设置最外层,请使用 rootClassName | string | - | |
| classNames | 语义化结构 className | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.10.0 |
| closeIcon | 自定义关闭图标。5.7.0:设置为 `null``false` 时隐藏关闭按钮 | ReactNode | &lt;CloseOutlined /> | |
| destroyOnClose | 关闭时销毁 Drawer 里的子元素 | boolean | false | |
| ~~destroyOnClose~~ | 关闭时销毁 Drawer 里的子元素 | boolean | false | |
| destroyOnHidden | 关闭时销毁 Drawer 里的子元素 | boolean | false | 5.25.0 |
| extra | 抽屉右上角的操作区域 | ReactNode | - | 4.17.0 |
| footer | 抽屉的页脚 | ReactNode | - | |
| forceRender | 预渲染 Drawer 内元素 | boolean | false | |

View File

@ -1,4 +1,5 @@
import React from 'react';
import { resetWarned } from 'rc-util/lib/warning';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
@ -170,4 +171,18 @@ describe('DropdownButton', () => {
await waitFakeTimer();
expect(container.querySelector('.ant-dropdown-menu-item-active')).toBeTruthy();
});
it('legacy destroyPopupOnHide with Dropdown.Button', () => {
resetWarned();
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(
<DropdownButton destroyPopupOnHide menu={{ items: [] }}>
test
</DropdownButton>,
);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Dropdown] `destroyPopupOnHide` is deprecated. Please use `destroyOnHidden` instead.',
);
errorSpy.mockRestore();
});
});

Some files were not shown because too many files have changed in this diff Show More