Merge branch 'feature' into master-merge-feature

This commit is contained in:
MadCcc 2022-09-04 14:46:28 +08:00
commit e5e2159882
169 changed files with 3214 additions and 2110 deletions

View File

@ -2,6 +2,10 @@ import * as React from 'react';
export const { isValidElement } = React;
export function isFragment(child: React.ReactElement): boolean {
return child && child.type === React.Fragment;
}
type AnyObject = Record<any, any>;
type RenderProps = undefined | AnyObject | ((originProps: AnyObject) => AnyObject | undefined);

View File

@ -98,4 +98,18 @@ describe('AutoComplete', () => {
);
expect(screen.getByRole('combobox')).toHaveClass('custom');
});
it('should show warning when use dropdownClassName', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(
<AutoComplete dropdownClassName="myCustomClassName">
<AutoComplete.Option value="111">111</AutoComplete.Option>
<AutoComplete.Option value="222">222</AutoComplete.Option>
</AutoComplete>,
);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: AutoComplete] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
);
errorSpy.mockRestore();
});
});

View File

@ -66,7 +66,7 @@ const options = [
const App: React.FC = () => (
<AutoComplete
dropdownClassName="certain-category-search-dropdown"
popupClassName="certain-category-search-dropdown"
dropdownMatchSelectWidth={500}
style={{ width: 250 }}
options={options}

View File

@ -31,7 +31,7 @@ The differences with Select are:
| defaultOpen | Initial open state of dropdown | boolean | - | |
| defaultValue | Initial selected option | string | - | |
| disabled | Whether disabled select | boolean | false | |
| dropdownClassName | The className of dropdown menu | string | - | |
| popupClassName | The className of dropdown menu | string | - | 4.23.0 |
| dropdownMatchSelectWidth | 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 | |
| notFoundContent | Specify content to show when no result matches | string | `Not Found` | |

View File

@ -41,6 +41,12 @@ export interface AutoCompleteProps<
> {
dataSource?: DataSourceItemType[];
status?: InputStatus;
/**
* @deprecated `dropdownClassName` is deprecated which will be removed in next major version.
* Please use `popupClassName` instead.
*/
dropdownClassName?: string;
popupClassName?: string;
}
function isSelectOptionOrSelectOptGroup(child: any): Boolean {
@ -51,7 +57,14 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
props,
ref,
) => {
const { prefixCls: customizePrefixCls, className, children, dataSource } = props;
const {
prefixCls: customizePrefixCls,
className,
popupClassName,
dropdownClassName,
children,
dataSource,
} = props;
const childNodes: React.ReactElement[] = toArray(children);
// ============================= Input =============================
@ -112,6 +125,12 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
'`dataSource` is deprecated, please use `options` instead.',
);
warning(
!dropdownClassName,
'AutoComplete',
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
);
warning(
!customizeInput || !('size' in props),
'AutoComplete',
@ -128,6 +147,7 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
ref={ref}
{...omit(props, ['dataSource'])}
prefixCls={prefixCls}
dropdownClassName={popupClassName || dropdownClassName}
className={classNames(`${prefixCls}-auto-complete`, className)}
mode={Select.SECRET_COMBOBOX_MODE_DO_NOT_USE as any}
{...{

View File

@ -32,7 +32,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/qtJm4yt45/AutoComplete.svg
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |
| defaultValue | 指定默认选中的条目 | string | - | |
| disabled | 是否禁用 | boolean | false | |
| dropdownClassName | 下拉菜单的 className 属性 | string | - | |
| popupClassName | 下拉菜单的 className 属性 | string | - | 4.23.0 |
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `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 | |

View File

@ -851,13 +851,6 @@ Array [
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>
@ -1007,26 +1000,12 @@ Array [
<div
class="ant-tabs-content ant-tabs-content-top"
>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>

View File

@ -832,13 +832,6 @@ Array [
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>
@ -969,26 +962,12 @@ Array [
<div
class="ant-tabs-content ant-tabs-content-top"
>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>

View File

@ -541,7 +541,7 @@ describe('Cascader', () => {
it('popupClassName', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { container } = render(
<Cascader open popupPlacement="bottomLeft" popupClassName="mock-cls" />,
<Cascader open popupPlacement="bottomLeft" dropdownClassName="mock-cls" />,
);
expect(container.querySelector('.mock-cls')).toBeTruthy();
@ -550,7 +550,7 @@ describe('Cascader', () => {
expect((global as any).triggerProps.popupPlacement).toEqual('bottomLeft');
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Cascader] `popupClassName` is deprecated. Please use `dropdownClassName` instead.',
'Warning: [antd: Cascader] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
);
errorSpy.mockRestore();

View File

@ -30,7 +30,7 @@ Cascade selection box.
| 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 |
| dropdownClassName | The additional className of popup overlay | string | - | 4.17.0 |
| popupClassName | The additional className of popup overlay | string | - | 4.23.0 |
| dropdownRender | Customize dropdown content | (menus: ReactNode) => ReactNode | - | 4.4.0 |
| 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` | |

View File

@ -108,6 +108,11 @@ export type CascaderProps<DataNodeType> = UnionCascaderProps & {
suffixIcon?: React.ReactNode;
options?: DataNodeType[];
status?: InputStatus;
/**
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
* version.Please use `popupClassName` instead.
*/
dropdownClassName?: string;
};
export interface CascaderRef {
@ -164,9 +169,9 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
// =================== Warning =====================
warning(
popupClassName === undefined,
!dropdownClassName,
'Cascader',
'`popupClassName` is deprecated. Please use `dropdownClassName` instead.',
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
);
warning(
@ -185,7 +190,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
// =================== Dropdown ====================
const mergedDropdownClassName = classNames(
dropdownClassName || popupClassName,
popupClassName || dropdownClassName,
`${cascaderPrefixCls}-dropdown`,
{
[`${cascaderPrefixCls}-dropdown-rtl`]: mergedDirection === 'rtl',

View File

@ -31,7 +31,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
| defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | |
| disabled | 禁用 | boolean | false | |
| displayRender | 选择后展示的渲染函数 | (label, selectedOptions) => ReactNode | label => label.join(`/`) | `multiple`: 4.18.0 |
| dropdownClassName | 自定义浮层类名 | string | - | 4.17.0 |
| popupClassName | 自定义浮层类名 | string | - | 4.23.0 |
| dropdownRender | 自定义下拉框内容 | (menus: ReactNode) => ReactNode | - | 4.4.0 |
| expandIcon | 自定义次级菜单展开图标 | ReactNode | - | 4.4.0 |
| expandTrigger | 次级菜单的展开方式,可选 'click' 和 'hover' | string | `click` | |

View File

@ -15806,10 +15806,10 @@ exports[`ConfigProvider components Form configProvider 1`] = `
>
<div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected"
role="alert"
>
<div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -15854,10 +15854,10 @@ exports[`ConfigProvider components Form configProvider componentDisabled 1`] = `
>
<div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected"
role="alert"
>
<div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -15901,10 +15901,10 @@ exports[`ConfigProvider components Form configProvider componentSize large 1`] =
>
<div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected"
role="alert"
>
<div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -15948,10 +15948,10 @@ exports[`ConfigProvider components Form configProvider componentSize middle 1`]
>
<div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected"
role="alert"
>
<div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -15995,10 +15995,10 @@ exports[`ConfigProvider components Form configProvider virtual and dropdownMatch
>
<div
class="ant-form-item-explain ant-show-help-appear ant-show-help-appear-start ant-show-help ant-form-item-explain-connected"
role="alert"
>
<div
class="ant-show-help-item-appear ant-show-help-item-appear-start ant-show-help-item ant-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -16042,10 +16042,10 @@ exports[`ConfigProvider components Form normal 1`] = `
>
<div
class="ant-form-item-explain ant-show-help-appear ant-show-help-appear-start ant-show-help ant-form-item-explain-connected"
role="alert"
>
<div
class="ant-show-help-item-appear ant-show-help-item-appear-start ant-show-help-item ant-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -16089,10 +16089,10 @@ exports[`ConfigProvider components Form prefixCls 1`] = `
>
<div
class="prefix-Form-item-explain ant-show-help-appear ant-show-help-appear-start ant-show-help prefix-Form-item-explain-connected"
role="alert"
>
<div
class="ant-show-help-item-appear ant-show-help-item-appear-start ant-show-help-item prefix-Form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light

View File

@ -276,7 +276,7 @@ describe('ConfigProvider', () => {
testPair('Divider', props => <Divider {...props} />);
// Drawer
testPair('Drawer', props => <Drawer {...props} visible getContainer={false} />);
testPair('Drawer', props => <Drawer {...props} open getContainer={false} />);
// Dropdown
testPair('Dropdown', props => {
@ -393,7 +393,7 @@ describe('ConfigProvider', () => {
// Modal
testPair('Modal', props => (
<div>
<Modal {...props} visible getContainer={false}>
<Modal {...props} open getContainer={false}>
Bamboo is Little Light
</Modal>
</div>
@ -417,7 +417,7 @@ describe('ConfigProvider', () => {
// Popconfirm
testPair('Popconfirm', props => (
<div>
<Popconfirm {...props} visible>
<Popconfirm {...props} open>
<span>Bamboo</span>
</Popconfirm>
</div>
@ -426,7 +426,7 @@ describe('ConfigProvider', () => {
// Popover
testPair('Popover', props => (
<div>
<Popover {...props} visible>
<Popover {...props} open>
<span>Light</span>
</Popover>
</div>
@ -466,9 +466,14 @@ describe('ConfigProvider', () => {
testPair('Slider', props => {
const myProps = { ...props };
if (myProps.prefixCls) {
myProps.tooltipPrefixCls = `${myProps.prefixCls}-tooltip`;
return (
<Slider
tooltip={{ open: true, prefixCls: `${myProps.prefixCls}-tooltip` }}
{...myProps}
/>
);
}
return <Slider tooltipVisible {...myProps} />;
return <Slider tooltip={{ open: true }} {...myProps} />;
});
// Spin
@ -507,7 +512,7 @@ describe('ConfigProvider', () => {
children: [{ text: 'Green', value: 'Green' }],
},
],
filterDropdownVisible: true,
filterDropdownOpen: true,
onFilter: (value, record) => record.name.indexOf(value) === 0,
sorter: (a, b) => a.name.length - b.name.length,
},
@ -550,7 +555,7 @@ describe('ConfigProvider', () => {
// Tooltip
testPair('Tooltip', props => (
<Tooltip {...props} title="Bamboo" visible>
<Tooltip {...props} title="Bamboo" open>
<span>Light</span>
</Tooltip>
));

View File

@ -30,12 +30,12 @@ describe('ConfigProvider.GetPopupContainer', () => {
it('Drawer', () => {
const getPopupContainer = jest.fn(node => node.parentNode);
const Demo: React.FC<{ visible?: boolean }> = ({ visible }) => (
const Demo: React.FC<{ open?: boolean }> = ({ open }) => (
<ConfigProvider getPopupContainer={getPopupContainer}>
<Drawer visible={visible} />
<Drawer open={open} />
</ConfigProvider>
);
render(<Demo visible />);
render(<Demo open />);
expect(getPopupContainer).toHaveBeenCalled();
});

View File

@ -108,7 +108,7 @@ type Placement = 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight';
const Page: React.FC<{ popupPlacement: Placement }> = ({ popupPlacement }) => {
const [currentStep, setCurrentStep] = useState(0);
const [modalVisible, setModalVisible] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [badgeCount, setBadgeCount] = useState(5);
const [showBadge, setShowBadge] = useState(true);
@ -139,17 +139,17 @@ const Page: React.FC<{ popupPlacement: Placement }> = ({ popupPlacement }) => {
// ==== Modal ====
const showModal = () => {
setModalVisible(true);
setModalOpen(true);
};
const handleOk = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
setModalVisible(false);
setModalOpen(false);
};
const handleCancel = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
setModalVisible(false);
setModalOpen(false);
};
// ==== End Modal ====
@ -378,12 +378,7 @@ const Page: React.FC<{ popupPlacement: Placement }> = ({ popupPlacement }) => {
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="پنچره ساده"
visible={modalVisible}
onOk={handleOk}
onCancel={handleCancel}
>
<Modal title="پنچره ساده" open={modalOpen} onOk={handleOk} onCancel={handleCancel}>
<p>نگاشته‌های خود را اینجا قراردهید</p>
<p>نگاشته‌های خود را اینجا قراردهید</p>
<p>نگاشته‌های خود را اینجا قراردهید</p>

View File

@ -58,14 +58,14 @@ const columns = [
];
const Page = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showModal = () => {
setVisible(true);
setOpen(true);
};
const hideModal = () => {
setVisible(false);
setOpen(false);
};
const info = () => {
@ -115,7 +115,7 @@ const Page = () => {
<div className="example">
<Table dataSource={[]} columns={columns} />
</div>
<Modal title="Locale Modal" visible={visible} onCancel={hideModal}>
<Modal title="Locale Modal" open={open} onCancel={hideModal}>
<p>Locale Modal</p>
</Modal>
</div>

View File

@ -221,6 +221,20 @@ describe('DatePicker', () => {
).toBe(60);
});
it('DatePicker should show warning when use dropdownClassName', () => {
render(<DatePicker dropdownClassName="myCustomClassName" />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: DatePicker] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
);
});
it('RangePicker should show warning when use dropdownClassName', () => {
render(<DatePicker.RangePicker dropdownClassName="myCustomClassName" />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: RangePicker] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
);
});
it('DatePicker.RangePicker with defaultPickerValue and showTime', () => {
const startDate = moment('1982-02-12');
const endDate = moment('1982-02-22');

View File

@ -18,6 +18,7 @@ import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
import enUS from '../locale/en_US';
import { getRangePlaceholder, transPlacement2DropdownAlign } from '../util';
import type { CommonPickerMethods, PickerComponentClass } from './interface';
import warning from '../../_util/warning';
export default function generateRangePicker<DateType>(
generateConfig: GenerateConfig<DateType>,
@ -26,7 +27,14 @@ export default function generateRangePicker<DateType>(
const RangePicker = forwardRef<
InternalRangePickerProps | CommonPickerMethods,
RangePickerProps<DateType>
RangePickerProps<DateType> & {
/**
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
* version.Please use `popupClassName` instead.
*/
dropdownClassName: string;
popupClassName?: string;
}
>((props, ref) => {
const {
prefixCls: customizePrefixCls,
@ -37,6 +45,8 @@ export default function generateRangePicker<DateType>(
disabled: customDisabled,
bordered = true,
placeholder,
popupClassName,
dropdownClassName,
status: customStatus,
...restProps
} = props;
@ -54,6 +64,12 @@ export default function generateRangePicker<DateType>(
...(picker === 'time' ? getTimeProps({ format, ...props, picker }) : {}),
};
warning(
!dropdownClassName,
'RangePicker',
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
);
// ===================== Size =====================
const size = React.useContext(SizeContext);
const mergedSize = customizeSize || size;
@ -92,6 +108,7 @@ export default function generateRangePicker<DateType>(
}
disabled={mergedDisabled}
ref={innerRef}
dropdownClassName={popupClassName || dropdownClassName}
dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
placeholder={getRangePlaceholder(picker, locale, placeholder)}
suffixIcon={suffixNode}

View File

@ -24,8 +24,13 @@ import type { CommonPickerMethods, DatePickRef, PickerComponentClass } from './i
export default function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
type DatePickerProps = PickerProps<DateType> & {
status?: InputStatus;
/**
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
* version.Please use `popupClassName` instead.
*/
dropdownClassName?: string;
popupClassName?: string;
};
function getPicker<InnerPickerProps extends DatePickerProps>(
picker?: PickerMode,
displayName?: string,
@ -40,17 +45,13 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
bordered = true,
placement,
placeholder,
popupClassName,
dropdownClassName,
disabled: customDisabled,
status: customStatus,
...restProps
} = props;
warning(
picker !== 'quarter',
displayName!,
`DatePicker.${displayName} is legacy usage. Please use DatePicker[picker='${picker}'] directly.`,
);
const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext);
const prefixCls = getPrefixCls('picker', customizePrefixCls);
const innerRef = React.useRef<RCPicker<DateType>>(null);
@ -80,6 +81,18 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
};
const rootPrefixCls = getPrefixCls();
// =================== Warning =====================
warning(
picker !== 'quarter',
displayName!,
`DatePicker.${displayName} is legacy usage. Please use DatePicker[picker='${picker}'] directly.`,
);
warning(
!dropdownClassName,
'DatePicker',
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
);
// ===================== Size =====================
const size = React.useContext(SizeContext);
const mergedSize = customizeSize || size;
@ -110,6 +123,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
placeholder={getPlaceholder(mergedPicker, locale, placeholder)}
suffixIcon={suffixNode}
dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
dropdownClassName={popupClassName || dropdownClassName}
clearIcon={<CloseCircleFilled />}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}

View File

@ -59,7 +59,7 @@ The following APIs are shared by DatePicker, RangePicker.
| dateRender | Custom rendering function for date cells | function(currentDate: moment, today: moment) => React.ReactNode | - | |
| disabled | Determine whether the DatePicker is disabled | boolean | false | |
| disabledDate | Specify the date that cannot be selected | (currentDate: moment) => boolean | - | |
| dropdownClassName | To customize the className of the popup calendar | string | - | |
| popupClassName | To customize the className of the popup calendar | string | - | 4.23.0 |
| getPopupContainer | To set the container of the floating layer, while the default is to create a `div` element in `body` | function(trigger) | - | |
| inputReadOnly | Set the `readonly` attribute of the input tag (avoids virtual keyboard on touch devices) | boolean | false | |
| locale | Localization configuration | object | [default](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | |

View File

@ -60,7 +60,7 @@ import locale from 'antd/es/locale/zh_CN';
| dateRender | 自定义日期单元格的内容 | function(currentDate: moment, today: moment) => React.ReactNode | - | |
| disabled | 禁用 | boolean | false | |
| disabledDate | 不可选择的日期 | (currentDate: moment) => boolean | - | |
| dropdownClassName | 额外的弹出日历 className | string | - | |
| popupClassName | 额外的弹出日历 className | string | - | 4.23.0 |
| getPopupContainer | 定义浮层的容器,默认为 body 上新建 div | function(trigger) | - | |
| inputReadOnly | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | boolean | false | |
| locale | 国际化配置 | object | [默认配置](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | |

View File

@ -8,7 +8,7 @@ import ConfigProvider from '../../config-provider';
const DrawerTest = ({ getContainer }) => (
<div>
<Drawer visible width={400} getContainer={getContainer}>
<Drawer open width={400} getContainer={getContainer}>
Here is content of Drawer
</Drawer>
</div>
@ -48,7 +48,7 @@ describe('Drawer', () => {
it('render correctly', () => {
const { container: wrapper } = render(
<Drawer visible width={400} getContainer={false}>
<Drawer open width={400} getContainer={false}>
Here is content of Drawer
</Drawer>,
);
@ -70,7 +70,7 @@ describe('Drawer', () => {
it('render top drawer', () => {
const { container: wrapper } = render(
<Drawer visible height={400} placement="top" getContainer={false}>
<Drawer open height={400} placement="top" getContainer={false}>
Here is content of Drawer
</Drawer>,
);
@ -81,7 +81,7 @@ describe('Drawer', () => {
it('have a title', () => {
const { container: wrapper } = render(
<Drawer visible title="Test Title" getContainer={false}>
<Drawer open title="Test Title" getContainer={false}>
Here is content of Drawer
</Drawer>,
);
@ -92,7 +92,7 @@ describe('Drawer', () => {
it('closable is false', () => {
const { container: wrapper } = render(
<Drawer visible closable={false} getContainer={false}>
<Drawer open closable={false} getContainer={false}>
Here is content of Drawer
</Drawer>,
);
@ -103,7 +103,7 @@ describe('Drawer', () => {
it('destroyOnClose is true', () => {
const { container: wrapper } = render(
<Drawer destroyOnClose visible={false} getContainer={false}>
<Drawer destroyOnClose open={false} getContainer={false}>
Here is content of Drawer
</Drawer>,
);
@ -114,7 +114,7 @@ describe('Drawer', () => {
it('className is test_drawer', () => {
const { container: wrapper } = render(
<Drawer destroyOnClose visible className="test_drawer" getContainer={false}>
<Drawer destroyOnClose open className="test_drawer" getContainer={false}>
Here is content of Drawer
</Drawer>,
);
@ -129,7 +129,7 @@ describe('Drawer', () => {
};
const { container: wrapper } = render(
<Drawer
visible
open
style={style}
drawerStyle={style}
headerStyle={style}
@ -146,7 +146,7 @@ describe('Drawer', () => {
it('have a footer', () => {
const { container: wrapper } = render(
<Drawer visible footer="Test Footer" getContainer={false}>
<Drawer open footer="Test Footer" getContainer={false}>
Here is content of Drawer
</Drawer>,
);
@ -176,7 +176,7 @@ describe('Drawer', () => {
it('support closeIcon', () => {
const { container: wrapper } = render(
<Drawer visible closable closeIcon={<span>close</span>} width={400} getContainer={false}>
<Drawer open closable closeIcon={<span>close</span>} width={400} getContainer={false}>
Here is content of Drawer
</Drawer>,
);
@ -190,7 +190,7 @@ describe('Drawer', () => {
render(
<ConfigProvider virtual>
<Drawer visible>Bamboo is Light</Drawer>
<Drawer open>Bamboo is Light</Drawer>
</ConfigProvider>,
);
@ -200,9 +200,22 @@ describe('Drawer', () => {
});
it('zIndex should work', () => {
const { container } = render(<Drawer getContainer={false} visible zIndex={903} />);
const { container } = render(<Drawer getContainer={false} open zIndex={903} />);
expect(container.querySelector('.ant-drawer')).toHaveStyle({
zIndex: 903,
});
});
it('deprecated warning', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { rerender } = render(<Drawer visible />);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Drawer] `visible` is deprecated which will be removed in next major version, please use `open` instead.',
);
rerender(<Drawer afterVisibleChange={() => {}} />);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Drawer] `afterVisibleChange` is deprecated which will be removed in next major version, please use `afterOpenChange` instead.',
);
errSpy.mockRestore();
});
});

View File

@ -4,7 +4,7 @@ import { act, fireEvent, render } from '../../../tests/utils';
describe('Drawer', () => {
const getDrawer = props => (
<Drawer visible getContainer={false} {...props}>
<Drawer open getContainer={false} {...props}>
Here is content of Drawer
</Drawer>
);
@ -21,7 +21,7 @@ describe('Drawer', () => {
const { container, asFragment, rerender } = render(getDrawer());
expect(container.querySelector('.ant-drawer-body')).toBeTruthy();
rerender(getDrawer({ visible: false }));
rerender(getDrawer({ open: false }));
expect(container.querySelector('.ant-drawer-body').textContent).toEqual(
'Here is content of Drawer',
@ -58,7 +58,7 @@ describe('Drawer', () => {
const { container, rerender } = render(getDrawer({ destroyOnClose: true }));
expect(container.querySelector('.ant-drawer')).toBeTruthy();
rerender(getDrawer({ destroyOnClose: true, visible: false }));
rerender(getDrawer({ destroyOnClose: true, open: false }));
act(() => {
jest.runAllTimers();
});
@ -70,7 +70,7 @@ describe('Drawer', () => {
const { container, rerender } = render(getDrawer());
expect(container.querySelector('.ant-drawer')).toBeTruthy();
rerender(getDrawer({ visible: false }));
rerender(getDrawer({ open: false }));
act(() => {
jest.runAllTimers();
});
@ -80,11 +80,11 @@ describe('Drawer', () => {
});
it('dom should be existed after close twice when getContainer is false', () => {
const { container, rerender } = render(getDrawer({ visible: true, getContainer: false }));
const { container, rerender } = render(getDrawer({ open: true, getContainer: false }));
expect(container.querySelector('.ant-drawer-content')).toBeTruthy();
// Hide
rerender(getDrawer({ visible: false, getContainer: false }));
rerender(getDrawer({ open: false, getContainer: false }));
act(() => {
jest.runAllTimers();
});
@ -92,12 +92,12 @@ describe('Drawer', () => {
expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeTruthy();
// Show
rerender(getDrawer({ visible: true, getContainer: false }));
rerender(getDrawer({ open: true, getContainer: false }));
expect(container.querySelector('.ant-drawer-content-wrapper')).toBeTruthy();
expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeFalsy();
// Hide
rerender(getDrawer({ visible: false, getContainer: false }));
rerender(getDrawer({ open: false, getContainer: false }));
act(() => {
jest.runAllTimers();
});
@ -107,8 +107,8 @@ describe('Drawer', () => {
it('test afterVisibleChange', async () => {
const afterVisibleChange = jest.fn();
const { container, rerender } = render(getDrawer({ afterVisibleChange, visible: true }));
rerender(getDrawer({ afterVisibleChange, visible: false }));
const { container, rerender } = render(getDrawer({ afterVisibleChange, open: true }));
rerender(getDrawer({ afterVisibleChange, open: false }));
act(() => {
jest.runAllTimers();
@ -128,18 +128,18 @@ describe('Drawer', () => {
const RefDemo = () => {
const ref = React.useRef();
const [visible, setVisible] = React.useState(false);
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
if (visible) {
if (open) {
refCallback(ref.current);
}
}, [visible]);
}, [open]);
return (
<>
<a onClick={() => setVisible(true)}>open</a>
<Drawer visible={visible}>
<a onClick={() => setOpen(true)}>open</a>
<Drawer open={open}>
<div ref={ref} />
</Drawer>
</>

View File

@ -4,18 +4,18 @@ import { fireEvent, render } from '../../../tests/utils';
import Button from '../../button';
class MultiDrawer extends React.Component {
state = { visible: false, childrenDrawer: false, hasChildren: true };
state = { open: false, childrenDrawer: false, hasChildren: true };
showDrawer = () => {
this.setState({
visible: true,
open: true,
hasChildren: true,
});
};
onClose = () => {
this.setState({
visible: false,
open: false,
});
};
@ -39,7 +39,7 @@ class MultiDrawer extends React.Component {
};
render() {
const { childrenDrawer, visible, hasChildren } = this.state;
const { childrenDrawer, open, hasChildren } = this.state;
const { placement, push } = this.props;
return (
<div>
@ -56,7 +56,7 @@ class MultiDrawer extends React.Component {
onClose={this.onClose}
getContainer={false}
placement={placement}
visible={visible}
open={open}
push={push}
>
<Button type="primary" id="open_two_drawer" onClick={this.showChildrenDrawer}>
@ -70,7 +70,7 @@ class MultiDrawer extends React.Component {
getContainer={false}
placement={placement}
onClose={this.onChildrenDrawerClose}
visible={childrenDrawer}
open={childrenDrawer}
>
<div id="two_drawer_text">This is two-level drawer</div>
</Drawer>

View File

@ -600,6 +600,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="name"
placeholder="Please enter user name"
@ -655,6 +656,7 @@ HTMLCollection [
http://
</span>
<input
aria-required="true"
class="ant-input"
id="url"
placeholder="Please enter url"
@ -710,6 +712,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -724,6 +727,7 @@ HTMLCollection [
aria-controls="owner_list"
aria-haspopup="listbox"
aria-owns="owner_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="owner"
@ -887,6 +891,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -901,6 +906,7 @@ HTMLCollection [
aria-controls="type_list"
aria-haspopup="listbox"
aria-owns="type_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="type"
@ -1069,6 +1075,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -1083,6 +1090,7 @@ HTMLCollection [
aria-controls="approver_list"
aria-haspopup="listbox"
aria-owns="approver_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="approver"
@ -1246,6 +1254,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-picker ant-picker-range"
style="width: 100%;"
>
@ -2470,6 +2479,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<textarea
aria-required="true"
class="ant-input"
id="description"
placeholder="please enter url description"

View File

@ -10,7 +10,7 @@ describe('Drawer.typescript', () => {
placement="right"
closable={false}
onClose={onClose}
visible={false}
open={false}
contentWrapperStyle={{
background: '#f00',
}}

View File

@ -18,14 +18,14 @@ import { Button, Drawer } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showDrawer = () => {
setVisible(true);
setOpen(true);
};
const onClose = () => {
setVisible(false);
setOpen(false);
};
return (
@ -33,7 +33,7 @@ const App: React.FC = () => {
<Button type="primary" onClick={showDrawer}>
Open
</Button>
<Drawer title="Basic Drawer" placement="right" onClose={onClose} visible={visible}>
<Drawer title="Basic Drawer" placement="right" onClose={onClose} open={open}>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>

View File

@ -20,14 +20,14 @@ import React, { useRef, useState } from 'react';
const App: React.FC = () => {
const domRef = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showDrawer = () => {
setVisible(true);
setOpen(true);
};
const onClose = () => {
setVisible(false);
setOpen(false);
};
return (
@ -41,7 +41,7 @@ const App: React.FC = () => {
title="ConfigProvider"
placement="right"
onClose={onClose}
visible={visible}
open={open}
>
<p>Some contents...</p>
<p>Some contents...</p>

View File

@ -11,7 +11,7 @@ title:
## en-US
Extra actions should be placed at corner of drawer in Ant Design, you can using `extra` prop for that.
Extra actions should be placed at corner of drawer in Ant Design, you can use `extra` prop for that.
```tsx
import { Button, Drawer, Radio, Space } from 'antd';
@ -20,11 +20,11 @@ import type { RadioChangeEvent } from 'antd/es/radio';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [placement, setPlacement] = useState<DrawerProps['placement']>('right');
const showDrawer = () => {
setVisible(true);
setOpen(true);
};
const onChange = (e: RadioChangeEvent) => {
@ -32,7 +32,7 @@ const App: React.FC = () => {
};
const onClose = () => {
setVisible(false);
setOpen(false);
};
return (
@ -53,7 +53,7 @@ const App: React.FC = () => {
placement={placement}
width={500}
onClose={onClose}
visible={visible}
open={open}
extra={
<Space>
<Button onClick={onClose}>Cancel</Button>

View File

@ -21,14 +21,14 @@ import React, { useState } from 'react';
const { Option } = Select;
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showDrawer = () => {
setVisible(true);
setOpen(true);
};
const onClose = () => {
setVisible(false);
setOpen(false);
};
return (
@ -40,7 +40,7 @@ const App: React.FC = () => {
title="Create a new account"
width={720}
onClose={onClose}
visible={visible}
open={open}
bodyStyle={{ paddingBottom: 80 }}
extra={
<Space>

View File

@ -18,15 +18,15 @@ import { Button, Drawer } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [childrenDrawer, setChildrenDrawer] = useState(false);
const showDrawer = () => {
setVisible(true);
setOpen(true);
};
const onClose = () => {
setVisible(false);
setOpen(false);
};
const showChildrenDrawer = () => {
@ -42,13 +42,7 @@ const App: React.FC = () => {
<Button type="primary" onClick={showDrawer}>
Open drawer
</Button>
<Drawer
title="Multi-level drawer"
width={520}
closable={false}
onClose={onClose}
visible={visible}
>
<Drawer title="Multi-level drawer" width={520} closable={false} onClose={onClose} open={open}>
<Button type="primary" onClick={showChildrenDrawer}>
Two-level drawer
</Button>
@ -57,7 +51,7 @@ const App: React.FC = () => {
width={320}
closable={false}
onClose={onChildrenDrawerClose}
visible={childrenDrawer}
open={childrenDrawer}
>
This is two-level drawer
</Drawer>

View File

@ -19,14 +19,14 @@ import { Button, Drawer } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showDrawer = () => {
setVisible(true);
setOpen(true);
};
const onClose = () => {
setVisible(false);
setOpen(false);
};
return (
@ -39,7 +39,7 @@ const App: React.FC = () => {
placement="right"
mask={false}
onClose={onClose}
visible={visible}
open={open}
contentWrapperStyle={{
width: 333,
background: 'red',

View File

@ -19,15 +19,15 @@ import { Button, Drawer, Radio, Space } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [placement, setPlacement] = useState<DrawerProps['placement']>('left');
const showDrawer = () => {
setVisible(true);
setOpen(true);
};
const onClose = () => {
setVisible(false);
setOpen(false);
};
const onChange = (e: RadioChangeEvent) => {
@ -52,7 +52,7 @@ const App: React.FC = () => {
placement={placement}
closable={false}
onClose={onClose}
visible={visible}
open={open}
key={placement}
>
<p>Some contents...</p>

View File

@ -18,14 +18,14 @@ import { Button, Drawer } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showDrawer = () => {
setVisible(true);
setOpen(true);
};
const onClose = () => {
setVisible(false);
setOpen(false);
};
return (
@ -41,7 +41,7 @@ const App: React.FC = () => {
placement="right"
closable={false}
onClose={onClose}
visible={visible}
open={open}
getContainer={false}
style={{ position: 'absolute' }}
>

View File

@ -19,21 +19,21 @@ import type { DrawerProps } from 'antd/es/drawer';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [size, setSize] = useState<DrawerProps['size']>();
const showDefaultDrawer = () => {
setSize('default');
setVisible(true);
setOpen(true);
};
const showLargeDrawer = () => {
setSize('large');
setVisible(true);
setOpen(true);
};
const onClose = () => {
setVisible(false);
setOpen(false);
};
return (
@ -51,7 +51,7 @@ const App: React.FC = () => {
placement="right"
size={size}
onClose={onClose}
visible={visible}
open={open}
extra={
<Space>
<Button onClick={onClose}>Cancel</Button>

View File

@ -30,14 +30,14 @@ const DescriptionItem = ({ title, content }: DescriptionItemProps) => (
);
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showDrawer = () => {
setVisible(true);
setOpen(true);
};
const onClose = () => {
setVisible(false);
setOpen(false);
};
return (
@ -73,7 +73,7 @@ const App: React.FC = () => {
</List.Item>
)}
/>
<Drawer width={640} placement="right" closable={false} onClose={onClose} visible={visible}>
<Drawer width={640} placement="right" closable={false} onClose={onClose} open={open}>
<p className="site-description-item-profile-p" style={{ marginBottom: 24 }}>
User Profile
</p>

View File

@ -21,7 +21,7 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
| Props | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| autoFocus | Whether Drawer should get focused after open | boolean | true | 4.17.0 |
| afterVisibleChange | Callback after the animation ends when switching drawers | function(visible) | - | |
| afterOpenChange | Callback after the animation ends when switching drawers | function(open) | - | 4.23.0 |
| bodyStyle | Style of the drawer content part | CSSProperties | - | |
| className | The class name of the container of the Drawer dialog | string | - | |
| closable | Whether a close (x) button is visible on top left of the Drawer dialog or not | boolean | true | |
@ -45,7 +45,7 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
| style | Style of wrapper element which **contains mask** compare to `drawerStyle` | CSSProperties | - | |
| size | presetted size of drawer, default `378px` and large `736px` | 'default' \| 'large' | 'default' | 4.17.0 |
| title | The title for Drawer | ReactNode | - | |
| visible | Whether the Drawer dialog is visible or not | boolean | false | |
| open | Whether the Drawer dialog is visible or not | boolean | false | 4.23.0 |
| width | Width of the Drawer dialog | string \| number | 378 | |
| zIndex | The `z-index` of the Drawer | number | 1000 | |
| onClose | Specify a callback that will be called when a user clicks mask, close button or Cancel button | function(e) | - | |

View File

@ -8,6 +8,7 @@ import { ConfigContext } from '../config-provider';
import { NoFormStyle } from '../form/context';
import { getTransitionName } from '../_util/motion';
import { tuple } from '../_util/type';
import warning from '../_util/warning';
const SizeTypes = tuple('default', 'large');
type sizeType = typeof SizeTypes[number];
@ -29,41 +30,55 @@ export interface DrawerProps extends RcDrawerProps {
footerStyle?: React.CSSProperties;
title?: React.ReactNode;
/**
* @deprecated `visible` is deprecated which will be removed in next major version. Please use
* `open` instead.
*/
visible?: boolean;
open?: boolean;
footer?: React.ReactNode;
extra?: React.ReactNode;
/**
* @deprecated `afterVisibleChange` is deprecated which will be removed in next major version.
* Please use `afterOpenChange` instead.
*/
afterVisibleChange?: (visible: boolean) => void;
afterOpenChange?: (open: boolean) => void;
}
const defaultPushState: PushState = { distance: 180 };
function Drawer({
width,
height,
size = 'default',
closable = true,
mask = true,
push = defaultPushState,
closeIcon = <CloseOutlined />,
bodyStyle,
drawerStyle,
className,
visible,
children,
style,
title,
headerStyle,
onClose,
footer,
footerStyle,
prefixCls: customizePrefixCls,
getContainer: customizeGetContainer,
extra,
afterVisibleChange,
...rest
}: DrawerProps) {
function Drawer(props: DrawerProps) {
const {
width,
height,
size = 'default',
closable = true,
mask = true,
push = defaultPushState,
closeIcon = <CloseOutlined />,
bodyStyle,
drawerStyle,
className,
visible,
open,
children,
style,
title,
headerStyle,
onClose,
footer,
footerStyle,
prefixCls: customizePrefixCls,
getContainer: customizeGetContainer,
extra,
afterVisibleChange,
afterOpenChange,
...rest
} = props;
const { getPopupContainer, getPrefixCls, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('drawer', customizePrefixCls);
@ -79,6 +94,17 @@ function Drawer({
</button>
);
[
['visible', 'open'],
['afterVisibleChange', 'afterOpenChange'],
].forEach(([deprecatedName, newName]) => {
warning(
!(deprecatedName in props),
'Drawer',
`\`${deprecatedName}\` is deprecated which will be removed in next major version, please use \`${newName}\` instead.`,
);
});
function renderHeader() {
if (!title && !closable) {
return null;
@ -152,15 +178,16 @@ function Drawer({
prefixCls={prefixCls}
onClose={onClose}
{...rest}
open={visible}
open={open || visible}
mask={mask}
push={push}
width={mergedWidth}
height={mergedHeight}
rootClassName={drawerClassName}
getContainer={getContainer}
afterOpenChange={open => {
afterVisibleChange?.(open);
afterOpenChange={isOpen => {
afterOpenChange?.(isOpen);
afterVisibleChange?.(isOpen);
}}
maskMotion={maskMotion}
motion={panelMotion}

View File

@ -20,7 +20,7 @@ cover: https://img.alicdn.com/imgextra/i4/O1CN019djdZP1OHwXSRGCOW_!!600000000168
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| autoFocus | 抽屉展开后是否将焦点切换至其 Dom 节点 | boolean | true | 4.17.0 |
| afterVisibleChange | 切换抽屉时动画结束后的回调 | function(visible) | - | |
| afterOpenChange | 切换抽屉时动画结束后的回调 | function(open) | - | 4.23.0 |
| bodyStyle | 可用于设置 Drawer 内容部分的样式 | CSSProperties | - | |
| className | 对话框外层容器的类名 | string | - | |
| closable | 是否显示左上角的关闭按钮 | boolean | true | |
@ -44,7 +44,7 @@ cover: https://img.alicdn.com/imgextra/i4/O1CN019djdZP1OHwXSRGCOW_!!600000000168
| size | 预设抽屉宽度或高度default `378px` 和 large `736px` | 'default' \| 'large' | 'default' | 4.17.0 |
| style | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | CSSProperties | - | |
| title | 标题 | ReactNode | - | |
| visible | Drawer 是否可见 | boolean | - | |
| open | Drawer 是否可见 | boolean | - | 4.23.0 |
| width | 宽度 | string \| number | 378 | |
| zIndex | 设置 Drawer 的 `z-index` | number | 1000 | |
| onClose | 点击遮罩层或左上角叉或取消按钮的回调 | function(e) | - | |

View File

@ -4246,7 +4246,7 @@ exports[`renders ./components/dropdown/demo/dropdown-button.md extend context co
</div>
<div
class="ant-space-item"
style="padding-bottom:8px"
style="margin-right:8px;padding-bottom:8px"
>
<button
class="ant-btn ant-btn-default ant-dropdown-trigger"
@ -4528,6 +4528,288 @@ exports[`renders ./components/dropdown/demo/dropdown-button.md extend context co
</div>
</div>
</div>
<div
class="ant-space-item"
style="padding-bottom:8px"
>
<div
class="ant-btn-group ant-dropdown-button"
>
<button
class="ant-btn ant-btn-default ant-btn-dangerous"
type="button"
>
<span>
Danger
</span>
</button>
<button
class="ant-btn ant-btn-default ant-btn-icon-only ant-btn-dangerous ant-dropdown-trigger"
type="button"
>
<span
aria-label="ellipsis"
class="anticon anticon-ellipsis"
role="img"
>
<svg
aria-hidden="true"
data-icon="ellipsis"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
/>
</svg>
</span>
</button>
<div>
<div
class="ant-dropdown"
style="opacity:0"
>
<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
class="ant-dropdown-menu-item"
role="menuitem"
tabindex="-1"
>
<span
aria-label="user"
class="anticon anticon-user ant-dropdown-menu-item-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="user"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 00-80.6-119.5 375.63 375.63 0 00-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 00-80.6 119.5A371.7 371.7 0 00136 901.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 008-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</span>
<span
class="ant-dropdown-menu-title-content"
>
1st menu item
</span>
</li>
<div>
<div
class="ant-tooltip ant-dropdown-menu-inline-collapsed-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>
<li
class="ant-dropdown-menu-item"
role="menuitem"
tabindex="-1"
>
<span
aria-label="user"
class="anticon anticon-user ant-dropdown-menu-item-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="user"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 00-80.6-119.5 375.63 375.63 0 00-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 00-80.6 119.5A371.7 371.7 0 00136 901.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 008-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</span>
<span
class="ant-dropdown-menu-title-content"
>
2nd menu item
</span>
</li>
<div>
<div
class="ant-tooltip ant-dropdown-menu-inline-collapsed-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>
<li
class="ant-dropdown-menu-item"
role="menuitem"
tabindex="-1"
>
<span
aria-label="user"
class="anticon anticon-user ant-dropdown-menu-item-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="user"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 00-80.6-119.5 375.63 375.63 0 00-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 00-80.6 119.5A371.7 371.7 0 00136 901.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 008-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</span>
<span
class="ant-dropdown-menu-title-content"
>
3rd menu item
</span>
</li>
<div>
<div
class="ant-tooltip ant-dropdown-menu-inline-collapsed-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>
</ul>
<div
aria-hidden="true"
style="display:none"
>
<div>
<div
class="ant-tooltip ant-dropdown-menu-inline-collapsed-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>
<div>
<div
class="ant-tooltip ant-dropdown-menu-inline-collapsed-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>
<div>
<div
class="ant-tooltip ant-dropdown-menu-inline-collapsed-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
@ -6652,7 +6934,7 @@ Array [
]
`;
exports[`renders ./components/dropdown/demo/overlay-visible.md extend context correctly 1`] = `
exports[`renders ./components/dropdown/demo/overlay-open.md extend context correctly 1`] = `
Array [
<a
class="ant-dropdown-trigger"

View File

@ -334,7 +334,7 @@ exports[`renders ./components/dropdown/demo/dropdown-button.md correctly 1`] = `
</div>
<div
class="ant-space-item"
style="padding-bottom:8px"
style="margin-right:8px;padding-bottom:8px"
>
<button
class="ant-btn ant-btn-default ant-dropdown-trigger"
@ -375,6 +375,47 @@ exports[`renders ./components/dropdown/demo/dropdown-button.md correctly 1`] = `
</div>
</button>
</div>
<div
class="ant-space-item"
style="padding-bottom:8px"
>
<div
class="ant-btn-group ant-dropdown-button"
>
<button
class="ant-btn ant-btn-default ant-btn-dangerous"
type="button"
>
<span>
Danger
</span>
</button>
<button
class="ant-btn ant-btn-default ant-btn-icon-only ant-btn-dangerous ant-dropdown-trigger"
type="button"
>
<span
aria-label="ellipsis"
class="anticon anticon-ellipsis"
role="img"
>
<svg
aria-hidden="true"
data-icon="ellipsis"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
`;
@ -714,7 +755,7 @@ exports[`renders ./components/dropdown/demo/menu-full.md correctly 1`] = `
</a>
`;
exports[`renders ./components/dropdown/demo/overlay-visible.md correctly 1`] = `
exports[`renders ./components/dropdown/demo/overlay-open.md correctly 1`] = `
<a
class="ant-dropdown-trigger"
>

View File

@ -43,25 +43,28 @@ describe('DropdownButton', () => {
),
disabled: false,
trigger: ['hover'],
visible: true,
onVisibleChange: () => {},
open: true,
onOpenChange: () => {},
};
render(<DropdownButton {...props} />);
const { rerender } = render(<DropdownButton {...props} />);
Object.keys(props).forEach((key: keyof DropdownProps) => {
expect(dropdownProps[key]).toBe(props[key]);
});
rerender(<DropdownButton overlay={<div>123</div>} visible />);
expect(dropdownProps.open).toBe(true);
});
it("don't pass visible to Dropdown if it's not exits", () => {
it("don't pass open to Dropdown if it's not exits", () => {
const menu = (
<Menu>
<Menu.Item key="1">foo</Menu.Item>
</Menu>
);
render(<DropdownButton overlay={menu} />);
expect('visible' in dropdownProps).toBe(false);
expect('open' in dropdownProps).toBe(false);
});
it('should support href like Button', () => {
@ -100,7 +103,7 @@ describe('DropdownButton', () => {
overlayClassName="className"
overlayStyle={{ color: 'red' }}
overlay={menu}
visible
open
/>,
);
expect(container.querySelector('.ant-dropdown')?.classList).toContain('className');

View File

@ -38,7 +38,7 @@ describe('Dropdown', () => {
it('overlay is function and has custom transitionName', () => {
const { asFragment } = render(
<Dropdown overlay={() => <div>menu</div>} transitionName="move-up" visible>
<Dropdown overlay={() => <div>menu</div>} transitionName="move-up" open>
<button type="button">button</button>
</Dropdown>,
);
@ -47,7 +47,7 @@ describe('Dropdown', () => {
it('overlay is string', () => {
const { asFragment } = render(
<Dropdown overlay={'string' as any} visible>
<Dropdown overlay={'string' as any} open>
<button type="button">button</button>
</Dropdown>,
);
@ -64,7 +64,7 @@ describe('Dropdown', () => {
</Menu.SubMenu>
</Menu>
),
visible: true,
open: true,
getPopupContainer: node => node,
};
@ -95,12 +95,13 @@ describe('Dropdown', () => {
expect(error).toHaveBeenCalledWith(
expect.stringContaining("[antd: Dropdown] You are using 'topCenter'"),
);
error.mockRestore();
});
// zombieJ: when replaced with react test lib, it may be mock fully content
it('dropdown should support auto adjust placement', () => {
render(
<Dropdown overlay={<div>menu</div>} visible>
<Dropdown overlay={<div>menu</div>} open>
<button type="button">button</button>
</Dropdown>,
);
@ -166,4 +167,27 @@ describe('Dropdown', () => {
jest.useRealTimers();
});
it('deprecated warning', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { rerender } = render(
<Dropdown visible overlay={<div>menu</div>}>
<a />
</Dropdown>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Dropdown] `visible` is deprecated which will be removed in next major version, please use `open` instead.',
);
rerender(
<Dropdown onVisibleChange={() => {}} overlay={<div>menu</div>}>
<a />
</Dropdown>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Dropdown] `onVisibleChange` is deprecated which will be removed in next major version, please use `onOpenChange` instead.',
);
errSpy.mockRestore();
});
});

View File

@ -82,6 +82,9 @@ const App: React.FC = () => (
</Space>
</Button>
</Dropdown>
<Dropdown.Button danger onClick={handleButtonClick} overlay={menu}>
Danger
</Dropdown.Button>
</Space>
);

View File

@ -20,16 +20,16 @@ import { Dropdown, Menu, Space } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const handleMenuClick: MenuProps['onClick'] = e => {
if (e.key === '3') {
setVisible(false);
setOpen(false);
}
};
const handleVisibleChange = (flag: boolean) => {
setVisible(flag);
const handleOpenChange = (flag: boolean) => {
setOpen(flag);
};
const menu = (
@ -53,7 +53,7 @@ const App: React.FC = () => {
);
return (
<Dropdown overlay={menu} onVisibleChange={handleVisibleChange} visible={visible}>
<Dropdown overlay={menu} onOpenChange={handleOpenChange} open={open}>
<a onClick={e => e.preventDefault()}>
<Space>
Hover me

View File

@ -16,6 +16,7 @@ export type DropdownButtonType = 'default' | 'primary' | 'ghost' | 'dashed' | 'l
export interface DropdownButtonProps extends ButtonGroupProps, DropdownProps {
type?: DropdownButtonType;
htmlType?: ButtonHTMLType;
danger?: boolean;
disabled?: boolean;
loading?: ButtonProps['loading'];
onClick?: React.MouseEventHandler<HTMLButtonElement>;
@ -40,6 +41,7 @@ const DropdownButton: DropdownButtonInterface = props => {
const {
prefixCls: customizePrefixCls,
type = 'default',
danger,
disabled,
loading,
onClick,
@ -50,7 +52,9 @@ const DropdownButton: DropdownButtonInterface = props => {
trigger,
align,
visible,
open,
onVisibleChange,
onOpenChange,
placement,
getPopupContainer,
href,
@ -71,7 +75,7 @@ const DropdownButton: DropdownButtonInterface = props => {
overlay,
disabled,
trigger: disabled ? [] : trigger,
onVisibleChange,
onOpenChange: onOpenChange || onVisibleChange,
getPopupContainer: getPopupContainer || getContextPopupContainer,
mouseEnterDelay,
mouseLeaveDelay,
@ -80,8 +84,10 @@ const DropdownButton: DropdownButtonInterface = props => {
destroyPopupOnHide,
} as DropdownProps;
if ('visible' in props) {
dropdownProps.visible = visible;
if ('open' in props) {
dropdownProps.open = open;
} else if ('visible' in props) {
dropdownProps.open = visible;
}
if ('placement' in props) {
@ -93,6 +99,7 @@ const DropdownButton: DropdownButtonInterface = props => {
const leftButton = (
<Button
type={type}
danger={danger}
disabled={disabled}
loading={loading}
onClick={onClick}
@ -104,7 +111,7 @@ const DropdownButton: DropdownButtonInterface = props => {
</Button>
);
const rightButton = <Button type={type} icon={icon} />;
const rightButton = <Button type={type} danger={danger} icon={icon} />;
const [leftButtonToRender, rightButtonToRender] = buttonsRender!([leftButton, rightButton]);

View File

@ -49,8 +49,18 @@ export interface DropdownProps {
arrow?: boolean | DropdownArrowOptions;
trigger?: ('click' | 'hover' | 'contextMenu')[];
overlay: React.ReactElement | OverlayFunc;
/**
* @deprecated `onVisibleChange` is deprecated which will be removed in next major version. Please
* use `onOpenChange` instead.
*/
onVisibleChange?: (visible: boolean) => void;
onOpenChange?: (open: boolean) => void;
/**
* @deprecated `visible` is deprecated which will be removed in next major version. Please use
* `open` instead.
*/
visible?: boolean;
open?: boolean;
disabled?: boolean;
destroyPopupOnHide?: boolean;
align?: Align;
@ -79,6 +89,20 @@ const Dropdown: DropdownInterface = props => {
direction,
} = React.useContext(ConfigContext);
// Warning for deprecated usage
if (process.env.NODE_ENV !== 'production') {
[
['visible', 'open'],
['onVisibleChange', 'onOpenChange'],
].forEach(([deprecatedName, newName]) => {
warning(
!(deprecatedName in props),
'Dropdown',
`\`${deprecatedName}\` is deprecated which will be removed in next major version, please use \`${newName}\` instead.`,
);
});
}
const getTransitionName = () => {
const rootPrefixCls = getPrefixCls();
const { placement = '', transitionName } = props;
@ -119,7 +143,9 @@ const Dropdown: DropdownInterface = props => {
getPopupContainer,
overlayClassName,
visible,
open,
onVisibleChange,
onOpenChange,
} = props;
const prefixCls = getPrefixCls('dropdown', customizePrefixCls);
@ -143,13 +169,14 @@ const Dropdown: DropdownInterface = props => {
}
// =========================== Visible ============================
const [mergedVisible, setVisible] = useMergedState(false, {
value: visible,
const [mergedOpen, setOpen] = useMergedState(false, {
value: open !== undefined ? open : visible,
});
const onInnerVisibleChange = useEvent((nextVisible: boolean) => {
onVisibleChange?.(nextVisible);
setVisible(nextVisible);
const onInnerOpenChange = useEvent((nextOpen: boolean) => {
onVisibleChange?.(nextOpen);
onOpenChange?.(nextOpen);
setOpen(nextOpen);
});
// =========================== Overlay ============================
@ -163,7 +190,7 @@ const Dropdown: DropdownInterface = props => {
});
const onMenuClick = React.useCallback(() => {
setVisible(false);
setOpen(false);
}, []);
const renderOverlay = () => {
@ -211,7 +238,7 @@ const Dropdown: DropdownInterface = props => {
<RcDropdown
alignPoint={alignPoint}
{...props}
visible={mergedVisible}
visible={mergedOpen}
builtinPlacements={builtinPlacements}
arrow={!!arrow}
overlayClassName={overlayClassNameCustomized}
@ -221,7 +248,7 @@ const Dropdown: DropdownInterface = props => {
trigger={triggerActions}
overlay={renderOverlay}
placement={getPlacement()}
onVisibleChange={onInnerVisibleChange}
onVisibleChange={onInnerOpenChange}
>
{dropdownTrigger}
</RcDropdown>

View File

@ -27,8 +27,8 @@ When there are more than a few options to choose from, you can wrap them in a `D
| overlayStyle | The style of the dropdown root element | CSSProperties | - | |
| placement | Placement of popup menu: `bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | |
| trigger | The trigger mode which executes the dropdown action. Note that hover can't be used on touchscreens | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| visible | Whether the dropdown menu is currently visible | boolean | - | |
| onVisibleChange | Called when the visible state is changed. Not trigger when hidden by click item | (visible: boolean) => void | - | |
| open | Whether the dropdown menu is currently open | boolean | - | 4.23.0 |
| onOpenChange | Called when the open state is changed. Not trigger when hidden by click item | (open: boolean) => void | - | 4.23.0 |
You should use [Menu](/components/menu/) as `overlay`. The menu items and dividers are also available by using `Menu.Item` and `Menu.Divider`.
@ -42,6 +42,7 @@ You should use [Menu](/components/menu/) as `overlay`. The menu items and divide
| --- | --- | --- | --- | --- |
| buttonsRender | Custom buttons inside Dropdown.Button | (buttons: ReactNode\[]) => ReactNode\[] | - | |
| loading | Set the loading status of button | boolean \| { delay: number } | false | |
| danger | Set the danger status of button | boolean | - | 4.23.0 |
| disabled | Whether the dropdown menu is disabled | boolean | - | |
| icon | Icon (appears on the right) | ReactNode | - | |
| overlay | The dropdown menu | [Menu](/components/menu) | - | |
@ -49,6 +50,6 @@ You should use [Menu](/components/menu/) as `overlay`. The menu items and divide
| size | Size of the button, the same as [Button](/components/button/#API) | string | `default` | |
| trigger | The trigger mode which executes the dropdown action | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| type | Type of the button, the same as [Button](/components/button/#API) | string | `default` | |
| visible | Whether the dropdown menu is currently visible | boolean | - | |
| open | Whether the dropdown menu is currently open | boolean | - | 4.23.0 |
| onClick | The same as [Button](/components/button/#API): called when you click the button on the left | (event) => void | - | |
| onVisibleChange | Called when the visible state is changed | (visible: boolean) => void | - | |
| onOpenChange | Called when the open state is changed | (open: boolean) => void | - | 4.23.0 |

View File

@ -31,8 +31,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
| overlayStyle | 下拉根元素的样式 | CSSProperties | - | |
| placement | 菜单弹出位置:`bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | |
| trigger | 触发下拉的行为, 移动端不支持 hover | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| visible | 菜单是否显示 | boolean | - | |
| onVisibleChange | 菜单显示状态改变时调用,参数为 `visible`。点击菜单按钮导致的消失不会触发 | (visible: boolean) => void | - | |
| open | 菜单是否显示 | boolean | - | 4.23.0 |
| onOpenChange | 菜单显示状态改变时调用,参数为 `visible`。点击菜单按钮导致的消失不会触发 | (open: boolean) => void | - | 4.23.0 |
`overlay` 菜单使用 [Menu](/components/menu/),还包括菜单项 `Menu.Item`,分割线 `Menu.Divider`
@ -46,6 +46,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
| --- | --- | --- | --- | --- |
| buttonsRender | 自定义左右两个按钮 | (buttons: ReactNode\[]) => ReactNode\[] | - | |
| loading | 设置按钮载入状态 | boolean \| { delay: number } | false | |
| danger | 设置危险按钮 | boolean | - | 4.23.0 |
| disabled | 菜单是否禁用 | boolean | - | |
| icon | 右侧的 icon | ReactNode | - | |
| overlay | 菜单 | [Menu](/components/menu/) | - | |
@ -53,6 +54,6 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
| size | 按钮大小,和 [Button](/components/button/#API) 一致 | string | `default` | |
| trigger | 触发下拉的行为 | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| type | 按钮类型,和 [Button](/components/button/#API) 一致 | string | `default` | |
| visible | 菜单是否显示 | boolean | - | |
| open | 菜单是否显示 | boolean | - | 4.23.0 |
| onClick | 点击左侧按钮的回调,和 [Button](/components/button/#API) 一致 | (event) => void | - | |
| onVisibleChange | 菜单显示状态改变时调用,参数为 `visible` | (visible: boolean) => void | - | |
| onOpenChange | 菜单显示状态改变时调用,参数为 `visible` | (open: boolean) => void | - | 4.23.0 |

View File

@ -29,6 +29,7 @@ function toErrorEntity(
}
export interface ErrorListProps {
fieldId?: string;
help?: React.ReactNode;
helpStatus?: ValidateStatus;
errors?: React.ReactNode[];
@ -43,6 +44,7 @@ export default function ErrorList({
errors = EMPTY_LIST,
warnings = EMPTY_LIST,
className: rootClassName,
fieldId,
onVisibleChanged,
}: ErrorListProps) {
const { prefixCls } = React.useContext(FormItemPrefixContext);
@ -69,6 +71,12 @@ export default function ErrorList({
];
}, [help, helpStatus, debounceErrors, debounceWarnings]);
const helpProps: { id?: string } = {};
if (fieldId) {
helpProps.id = `${fieldId}_help`;
}
return (
<CSSMotion
motionDeadline={collapseMotion.motionDeadline}
@ -81,8 +89,10 @@ export default function ErrorList({
return (
<div
{...helpProps}
className={classNames(baseClassName, holderClassName, rootClassName)}
style={holderStyle}
role="alert"
>
<CSSMotionList
keys={fullKeyList}
@ -102,7 +112,6 @@ export default function ErrorList({
return (
<div
key={key}
role="alert"
className={classNames(itemClassName, {
[`${baseClassName}-${errorStatus}`]: errorStatus,
})}

View File

@ -37,11 +37,16 @@ interface MemoInputProps {
value: any;
update: any;
children: React.ReactNode;
childProps: any[];
}
const MemoInput = React.memo(
({ children }: MemoInputProps) => children as JSX.Element,
(prev, next) => prev.value === next.value && prev.update === next.update,
(prev, next) =>
prev.value === next.value &&
prev.update === next.update &&
prev.childProps.length === next.childProps.length &&
prev.childProps.every((value, index) => value === next.childProps[index]),
);
export interface FormItemProps<Values = any>
@ -305,6 +310,25 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
childProps.id = fieldId;
}
if (props.help || mergedErrors.length > 0 || mergedWarnings.length > 0 || props.extra) {
const describedbyArr = [];
if (props.help || mergedErrors.length > 0) {
describedbyArr.push(`${fieldId}_help`);
}
if (props.extra) {
describedbyArr.push(`${fieldId}_extra`);
}
childProps['aria-describedby'] = describedbyArr.join(' ');
}
if (mergedErrors.length > 0) {
childProps['aria-invalid'] = 'true';
}
if (isRequired) {
childProps['aria-required'] = 'true';
}
if (supportRef(children)) {
childProps.ref = getItemRef(mergedName, children);
}
@ -322,8 +346,19 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
};
});
// List of props that need to be watched for changes -> if changes are detected in MemoInput -> rerender
const watchingChildProps = [
childProps['aria-required'],
childProps['aria-invalid'],
childProps['aria-describedby'],
];
childNode = (
<MemoInput value={mergedControl[props.valuePropName || 'value']} update={children}>
<MemoInput
value={mergedControl[props.valuePropName || 'value']}
update={children}
childProps={watchingChildProps}
>
{cloneElement(children, childProps)}
</MemoInput>
);

View File

@ -32,6 +32,7 @@ export interface FormItemInputProps {
extra?: React.ReactNode;
status?: ValidateStatus;
help?: React.ReactNode;
fieldId?: string;
}
const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = props => {
@ -45,6 +46,7 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
_internalItemRender: formItemRender,
extra,
help,
fieldId,
marginBottom,
onErrorVisibleChanged,
} = props;
@ -72,6 +74,7 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
<div style={{ display: 'flex', flexWrap: 'nowrap' }}>
<FormItemPrefixContext.Provider value={formItemContext}>
<ErrorList
fieldId={fieldId}
errors={errors}
warnings={warnings}
help={help}
@ -84,9 +87,19 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
</div>
) : null;
const extraProps: { id?: string } = {};
if (fieldId) {
extraProps.id = `${fieldId}_extra`;
}
// If extra = 0, && will goes wrong
// 0&&error -> 0
const extraDom = extra ? <div className={`${baseClassName}-extra`}>{extra}</div> : null;
const extraDom = extra ? (
<div {...extraProps} className={`${baseClassName}-extra`}>
{extra}
</div>
) : null;
const dom =
formItemRender && formItemRender.mark === 'pro_table_render' && formItemRender.render ? (

View File

@ -41,6 +41,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-0"
placeholder="placeholder"
@ -84,6 +85,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -98,6 +100,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
aria-controls="advanced_search_field-1_list"
aria-haspopup="listbox"
aria-owns="advanced_search_field-1_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="advanced_search_field-1"
@ -262,6 +265,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-2"
placeholder="placeholder"
@ -305,6 +309,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-3"
placeholder="placeholder"
@ -348,6 +353,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -362,6 +368,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
aria-controls="advanced_search_field-4_list"
aria-haspopup="listbox"
aria-owns="advanced_search_field-4_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="advanced_search_field-4"
@ -526,6 +533,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-5"
placeholder="placeholder"
@ -631,6 +639,7 @@ exports[`renders ./components/form/demo/basic.md extend context correctly 1`] =
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basic_username"
type="text"
@ -672,6 +681,7 @@ exports[`renders ./components/form/demo/basic.md extend context correctly 1`] =
>
<input
action="click"
aria-required="true"
class="ant-input"
id="basic_password"
type="password"
@ -815,6 +825,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basic_username"
type="text"
@ -856,6 +867,7 @@ Array [
>
<input
action="click"
aria-required="true"
class="ant-input"
id="basic_password"
type="password"
@ -1184,6 +1196,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="responsive_username"
type="text"
@ -1225,6 +1238,7 @@ Array [
>
<input
action="click"
aria-required="true"
class="ant-input"
id="responsive_password"
type="password"
@ -1601,6 +1615,7 @@ exports[`renders ./components/form/demo/control-hooks.md extend context correctl
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="control-hooks_note"
type="text"
@ -1638,6 +1653,7 @@ exports[`renders ./components/form/demo/control-hooks.md extend context correctl
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -1652,6 +1668,7 @@ exports[`renders ./components/form/demo/control-hooks.md extend context correctl
aria-controls="control-hooks_gender_list"
aria-haspopup="listbox"
aria-owns="control-hooks_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="control-hooks_gender"
@ -1879,6 +1896,7 @@ exports[`renders ./components/form/demo/control-ref.md extend context correctly
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="control-ref_note"
type="text"
@ -1916,6 +1934,7 @@ exports[`renders ./components/form/demo/control-ref.md extend context correctly
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -1930,6 +1949,7 @@ exports[`renders ./components/form/demo/control-ref.md extend context correctly
aria-controls="control-ref_gender_list"
aria-haspopup="listbox"
aria-owns="control-ref_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="control-ref_gender"
@ -5972,6 +5992,7 @@ exports[`renders ./components/form/demo/dynamic-form-items-complex.md extend con
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -5986,6 +6007,7 @@ exports[`renders ./components/form/demo/dynamic-form-items-complex.md extend con
aria-controls="dynamic_form_nest_item_area_list"
aria-haspopup="listbox"
aria-owns="dynamic_form_nest_item_area_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="dynamic_form_nest_item_area"
@ -6347,6 +6369,7 @@ exports[`renders ./components/form/demo/dynamic-rule.md extend context correctly
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="dynamic_rule_username"
placeholder="Please input your name"
@ -6497,6 +6520,7 @@ exports[`renders ./components/form/demo/form-context.md extend context correctly
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basicForm_group"
type="text"
@ -6648,6 +6672,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="global_state_username"
type="text"
@ -6721,6 +6746,7 @@ exports[`renders ./components/form/demo/inline-login.md extend context correctly
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="horizontal_login_username"
placeholder="Username"
@ -6775,6 +6801,7 @@ exports[`renders ./components/form/demo/inline-login.md extend context correctly
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="horizontal_login_password"
placeholder="Password"
@ -7209,6 +7236,7 @@ exports[`renders ./components/form/demo/layout-can-wrap.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="wrap_username"
type="text"
@ -7248,6 +7276,7 @@ exports[`renders ./components/form/demo/layout-can-wrap.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="wrap_password"
type="text"
@ -7333,6 +7362,7 @@ exports[`renders ./components/form/demo/nest-messages.md extend context correctl
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="nest-messages_user_name"
type="text"
@ -7635,6 +7665,7 @@ exports[`renders ./components/form/demo/normal-login.md extend context correctly
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="normal_login_username"
placeholder="Username"
@ -7689,6 +7720,7 @@ exports[`renders ./components/form/demo/normal-login.md extend context correctly
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="normal_login_password"
placeholder="Password"
@ -7902,6 +7934,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="register_email"
type="text"
@ -7943,6 +7976,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
>
<input
action="click"
aria-required="true"
class="ant-input"
id="register_password"
type="password"
@ -8012,6 +8046,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
>
<input
action="click"
aria-required="true"
class="ant-input"
id="register_confirm"
type="password"
@ -8124,6 +8159,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="register_nickname"
type="text"
@ -8161,6 +8197,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-cascader ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -8174,6 +8211,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
aria-controls="register_residence_list"
aria-haspopup="listbox"
aria-owns="register_residence_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="register_residence"
@ -8521,6 +8559,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
</div>
</span>
<input
aria-required="true"
class="ant-input"
id="register_phone"
type="text"
@ -8631,6 +8670,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-input-number-input-wrap"
>
<input
aria-required="true"
autocomplete="off"
class="ant-input-number-input"
id="register_donation"
@ -8818,6 +8858,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
>
<div
@ -8832,6 +8873,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
aria-controls="register_website_list"
aria-haspopup="listbox"
aria-owns="register_website_list"
aria-required="true"
autocomplete="off"
class="ant-input ant-select-selection-search-input"
id="register_website"
@ -8897,6 +8939,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
data-count="0 / 100"
>
<textarea
aria-required="true"
class="ant-input"
id="register_intro"
/>
@ -8933,6 +8976,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -8947,6 +8991,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
aria-controls="register_gender_list"
aria-haspopup="listbox"
aria-owns="register_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="register_gender"
@ -9129,6 +9174,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
style="padding-left:4px;padding-right:4px"
>
<input
aria-required="true"
class="ant-input"
id="register_captcha"
type="text"
@ -10986,6 +11032,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_date-picker"
placeholder="Select date"
@ -11611,6 +11658,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_date-time-picker"
placeholder="Select date"
@ -13589,6 +13637,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_month-picker"
placeholder="Select month"
@ -13844,6 +13893,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-picker ant-picker-range"
>
<div
@ -15054,6 +15104,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-picker ant-picker-range"
>
<div
@ -17085,6 +17136,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_time-picker"
placeholder="Select time"
@ -18748,6 +18800,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-has-feedback ant-select-single ant-select-show-arrow"
>
<div
@ -18762,6 +18815,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
aria-controls="validate_other_select_list"
aria-haspopup="listbox"
aria-owns="validate_other_select_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="validate_other_select"
@ -18920,6 +18974,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-multiple ant-select-show-search"
>
<div
@ -18942,6 +18997,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
aria-controls="validate_other_select-multiple_list"
aria-haspopup="listbox"
aria-owns="validate_other_select-multiple_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="validate_other_select-multiple"
@ -19475,6 +19531,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-radio-group ant-radio-group-outline"
id="validate_other_radio-button"
>
@ -20094,6 +20151,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
>
<input
accept=""
aria-describedby="validate_other_upload_extra"
id="validate_other_upload"
style="display:none"
type="file"
@ -20135,6 +20193,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
</div>
<div
class="ant-form-item-extra"
id="validate_other_upload_extra"
>
longgggggggggggggggggggggggggggggggggg
</div>
@ -26406,6 +26465,7 @@ exports[`renders ./components/form/demo/warning-only.md extend context correctly
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="url"
placeholder="input placeholder"

View File

@ -41,6 +41,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-0"
placeholder="placeholder"
@ -84,6 +85,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -98,6 +100,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
aria-controls="advanced_search_field-1_list"
aria-haspopup="listbox"
aria-owns="advanced_search_field-1_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="advanced_search_field-1"
@ -180,6 +183,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-2"
placeholder="placeholder"
@ -223,6 +227,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-3"
placeholder="placeholder"
@ -266,6 +271,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -280,6 +286,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
aria-controls="advanced_search_field-4_list"
aria-haspopup="listbox"
aria-owns="advanced_search_field-4_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="advanced_search_field-4"
@ -362,6 +369,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-5"
placeholder="placeholder"
@ -467,6 +475,7 @@ exports[`renders ./components/form/demo/basic.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basic_username"
type="text"
@ -508,6 +517,7 @@ exports[`renders ./components/form/demo/basic.md correctly 1`] = `
>
<input
action="click"
aria-required="true"
class="ant-input"
id="basic_password"
type="password"
@ -651,6 +661,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basic_username"
type="text"
@ -692,6 +703,7 @@ Array [
>
<input
action="click"
aria-required="true"
class="ant-input"
id="basic_password"
type="password"
@ -913,6 +925,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="responsive_username"
type="text"
@ -954,6 +967,7 @@ Array [
>
<input
action="click"
aria-required="true"
class="ant-input"
id="responsive_password"
type="password"
@ -1223,6 +1237,7 @@ exports[`renders ./components/form/demo/control-hooks.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="control-hooks_note"
type="text"
@ -1260,6 +1275,7 @@ exports[`renders ./components/form/demo/control-hooks.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -1274,6 +1290,7 @@ exports[`renders ./components/form/demo/control-hooks.md correctly 1`] = `
aria-controls="control-hooks_gender_list"
aria-haspopup="listbox"
aria-owns="control-hooks_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="control-hooks_gender"
@ -1402,6 +1419,7 @@ exports[`renders ./components/form/demo/control-ref.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="control-ref_note"
type="text"
@ -1439,6 +1457,7 @@ exports[`renders ./components/form/demo/control-ref.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -1453,6 +1472,7 @@ exports[`renders ./components/form/demo/control-ref.md correctly 1`] = `
aria-controls="control-ref_gender_list"
aria-haspopup="listbox"
aria-owns="control-ref_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="control-ref_gender"
@ -3455,6 +3475,7 @@ exports[`renders ./components/form/demo/dynamic-form-items-complex.md correctly
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -3469,6 +3490,7 @@ exports[`renders ./components/form/demo/dynamic-form-items-complex.md correctly
aria-controls="dynamic_form_nest_item_area_list"
aria-haspopup="listbox"
aria-owns="dynamic_form_nest_item_area_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="dynamic_form_nest_item_area"
@ -3748,6 +3770,7 @@ exports[`renders ./components/form/demo/dynamic-rule.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="dynamic_rule_username"
placeholder="Please input your name"
@ -3898,6 +3921,7 @@ exports[`renders ./components/form/demo/form-context.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basicForm_group"
type="text"
@ -4049,6 +4073,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="global_state_username"
type="text"
@ -4122,6 +4147,7 @@ exports[`renders ./components/form/demo/inline-login.md correctly 1`] = `
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="horizontal_login_username"
placeholder="Username"
@ -4176,6 +4202,7 @@ exports[`renders ./components/form/demo/inline-login.md correctly 1`] = `
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="horizontal_login_password"
placeholder="Password"
@ -4610,6 +4637,7 @@ exports[`renders ./components/form/demo/layout-can-wrap.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="wrap_username"
type="text"
@ -4649,6 +4677,7 @@ exports[`renders ./components/form/demo/layout-can-wrap.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="wrap_password"
type="text"
@ -4734,6 +4763,7 @@ exports[`renders ./components/form/demo/nest-messages.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="nest-messages_user_name"
type="text"
@ -5036,6 +5066,7 @@ exports[`renders ./components/form/demo/normal-login.md correctly 1`] = `
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="normal_login_username"
placeholder="Username"
@ -5090,6 +5121,7 @@ exports[`renders ./components/form/demo/normal-login.md correctly 1`] = `
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="normal_login_password"
placeholder="Password"
@ -5303,6 +5335,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="register_email"
type="text"
@ -5344,6 +5377,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
>
<input
action="click"
aria-required="true"
class="ant-input"
id="register_password"
type="password"
@ -5413,6 +5447,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
>
<input
action="click"
aria-required="true"
class="ant-input"
id="register_confirm"
type="password"
@ -5501,6 +5536,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="register_nickname"
type="text"
@ -5538,6 +5574,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-cascader ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -5551,6 +5588,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
aria-controls="register_residence_list"
aria-haspopup="listbox"
aria-owns="register_residence_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="register_residence"
@ -5726,6 +5764,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
</div>
</span>
<input
aria-required="true"
class="ant-input"
id="register_phone"
type="text"
@ -5836,6 +5875,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-input-number-input-wrap"
>
<input
aria-required="true"
autocomplete="off"
class="ant-input-number-input"
id="register_donation"
@ -5941,6 +5981,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
>
<div
@ -5955,6 +5996,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
aria-controls="register_website_list"
aria-haspopup="listbox"
aria-owns="register_website_list"
aria-required="true"
autocomplete="off"
class="ant-input ant-select-selection-search-input"
id="register_website"
@ -6006,6 +6048,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
data-count="0 / 100"
>
<textarea
aria-required="true"
class="ant-input"
id="register_intro"
/>
@ -6042,6 +6085,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -6056,6 +6100,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
aria-controls="register_gender_list"
aria-haspopup="listbox"
aria-owns="register_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="register_gender"
@ -6139,6 +6184,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
style="padding-left:4px;padding-right:4px"
>
<input
aria-required="true"
class="ant-input"
id="register_captcha"
type="text"
@ -7186,6 +7232,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_date-picker"
placeholder="Select date"
@ -7257,6 +7304,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_date-time-picker"
placeholder="Select date"
@ -7328,6 +7376,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_month-picker"
placeholder="Select month"
@ -7393,6 +7442,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-picker ant-picker-range"
>
<div
@ -7506,6 +7556,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-picker ant-picker-range"
>
<div
@ -7625,6 +7676,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_time-picker"
placeholder="Select time"
@ -7923,6 +7975,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-has-feedback ant-select-single ant-select-show-arrow"
>
<div
@ -7937,6 +7990,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
aria-controls="validate_other_select_list"
aria-haspopup="listbox"
aria-owns="validate_other_select_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="validate_other_select"
@ -8013,6 +8067,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-multiple ant-select-show-search"
>
<div
@ -8035,6 +8090,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
aria-controls="validate_other_select-multiple_list"
aria-haspopup="listbox"
aria-owns="validate_other_select-multiple_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="validate_other_select-multiple"
@ -8463,6 +8519,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-radio-group ant-radio-group-outline"
id="validate_other_radio-button"
>
@ -9082,6 +9139,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
>
<input
accept=""
aria-describedby="validate_other_upload_extra"
id="validate_other_upload"
style="display:none"
type="file"
@ -9123,6 +9181,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
</div>
<div
class="ant-form-item-extra"
id="validate_other_upload_extra"
>
longgggggggggggggggggggggggggggggggggg
</div>
@ -11063,6 +11122,7 @@ exports[`renders ./components/form/demo/warning-only.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="url"
placeholder="input placeholder"

View File

@ -203,6 +203,158 @@ describe('Form', () => {
);
});
it('input element should have the prop aria-describedby pointing to the help id when there is a help message', () => {
const wrapper = mount(
<Form>
<Form.Item name="test" help="This is a help">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBe('test_help');
const help = wrapper.find('.ant-form-item-explain');
expect(help.prop('id')).toBe('test_help');
});
it('input element should not have the prop aria-describedby pointing to the help id when there is a help message and name is not defined', () => {
const wrapper = mount(
<Form>
<Form.Item help="This is a help">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBeUndefined();
const help = wrapper.find('.ant-form-item-explain');
expect(help.prop('id')).toBeUndefined();
});
it('input element should have the prop aria-describedby concatenated with the form name pointing to the help id when there is a help message', () => {
const wrapper = mount(
<Form name="form">
<Form.Item name="test" help="This is a help">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBe('form_test_help');
const help = wrapper.find('.ant-form-item-explain');
expect(help.prop('id')).toBe('form_test_help');
});
it('input element should have the prop aria-describedby pointing to the help id when there are errors', async () => {
const wrapper = mount(
<Form>
<Form.Item name="test" rules={[{ len: 3 }, { type: 'number' }]}>
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
input.simulate('change', { target: { value: 'Invalid number' } });
await sleep(800);
wrapper.update();
const inputChanged = wrapper.find('input');
expect(inputChanged.prop('aria-describedby')).toBe('test_help');
const help = wrapper.find('.ant-form-item-explain');
expect(help.prop('id')).toBe('test_help');
});
it('input element should have the prop aria-invalid when there are errors', async () => {
const wrapper = mount(
<Form>
<Form.Item name="test" rules={[{ len: 3 }, { type: 'number' }]}>
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
input.simulate('change', { target: { value: 'Invalid number' } });
await sleep(800);
wrapper.update();
const inputChanged = wrapper.find('input');
expect(inputChanged.prop('aria-invalid')).toBe('true');
});
it('input element should have the prop aria-required when the prop `required` is true', async () => {
const wrapper = mount(
<Form>
<Form.Item name="test" required>
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-required')).toBe('true');
});
it('input element should have the prop aria-required when there is a rule with required', async () => {
const wrapper = mount(
<Form>
<Form.Item name="test" rules={[{ required: true }]}>
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-required')).toBe('true');
});
it('input element should have the prop aria-describedby pointing to the extra id when there is a extra message', () => {
const wrapper = mount(
<Form>
<Form.Item name="test" extra="This is a extra message">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBe('test_extra');
const extra = wrapper.find('.ant-form-item-extra');
expect(extra.prop('id')).toBe('test_extra');
});
it('input element should not have the prop aria-describedby pointing to the extra id when there is a extra message and name is not defined', () => {
const wrapper = mount(
<Form>
<Form.Item extra="This is a extra message">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBeUndefined();
const extra = wrapper.find('.ant-form-item-extra');
expect(extra.prop('id')).toBeUndefined();
});
it('input element should have the prop aria-describedby pointing to the help and extra id when there is a help and extra message', () => {
const wrapper = mount(
<Form>
<Form.Item name="test" help="This is a help" extra="This is a extra message">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBe('test_help test_extra');
});
describe('scrollToField', () => {
function test(name, genForm) {
it(name, () => {
@ -710,9 +862,7 @@ describe('Form', () => {
await sleep(100);
wrapper.update();
await sleep(100);
expect(wrapper.find('.ant-form-item-explain div').getDOMNode().getAttribute('role')).toBe(
'alert',
);
expect(wrapper.find('.ant-form-item-explain').getDOMNode().getAttribute('role')).toBe('alert');
});
it('return same form instance', () => {
@ -1232,12 +1382,12 @@ describe('Form', () => {
const Demo = () => (
<Form>
<Form.Item labelCol={4} validateStatus="error">
<Modal visible>
<Modal open>
<Select className="modal-select" />
</Modal>
</Form.Item>
<Form.Item validateStatus="error">
<Drawer visible>
<Drawer open>
<Select className="drawer-select" />
</Drawer>
</Form.Item>

View File

@ -33,31 +33,31 @@ interface UserType {
}
interface ModalFormProps {
visible: boolean;
open: boolean;
onCancel: () => void;
}
// reset form fields when modal is form, closed
const useResetFormOnCloseModal = ({ form, visible }: { form: FormInstance; visible: boolean }) => {
const prevVisibleRef = useRef<boolean>();
const useResetFormOnCloseModal = ({ form, open }: { form: FormInstance; open: boolean }) => {
const prevOpenRef = useRef<boolean>();
useEffect(() => {
prevVisibleRef.current = visible;
}, [visible]);
const prevVisible = prevVisibleRef.current;
prevOpenRef.current = open;
}, [open]);
const prevOpen = prevOpenRef.current;
useEffect(() => {
if (!visible && prevVisible) {
if (!open && prevOpen) {
form.resetFields();
}
}, [form, prevVisible, visible]);
}, [form, prevOpen, open]);
};
const ModalForm: React.FC<ModalFormProps> = ({ visible, onCancel }) => {
const ModalForm: React.FC<ModalFormProps> = ({ open, onCancel }) => {
const [form] = Form.useForm();
useResetFormOnCloseModal({
form,
visible,
open,
});
const onOk = () => {
@ -65,7 +65,7 @@ const ModalForm: React.FC<ModalFormProps> = ({ visible, onCancel }) => {
};
return (
<Modal title="Basic Drawer" visible={visible} onOk={onOk} onCancel={onCancel}>
<Modal title="Basic Drawer" open={open} onOk={onOk} onCancel={onCancel}>
<Form form={form} layout="vertical" name="userForm">
<Form.Item name="name" label="User Name" rules={[{ required: true }]}>
<Input />
@ -79,14 +79,14 @@ const ModalForm: React.FC<ModalFormProps> = ({ visible, onCancel }) => {
};
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showUserModal = () => {
setVisible(true);
setOpen(true);
};
const hideUserModal = () => {
setVisible(false);
setOpen(false);
};
const onFinish = (values: any) => {
@ -100,7 +100,7 @@ const App: React.FC = () => {
const { basicForm } = forms;
const users = basicForm.getFieldValue('users') || [];
basicForm.setFieldsValue({ users: [...users, values] });
setVisible(false);
setOpen(false);
}
}}
>
@ -140,7 +140,7 @@ const App: React.FC = () => {
</Form.Item>
</Form>
<ModalForm visible={visible} onCancel={hideUserModal} />
<ModalForm open={open} onCancel={hideUserModal} />
</Form.Provider>
);
};

View File

@ -26,20 +26,20 @@ interface Values {
}
interface CollectionCreateFormProps {
visible: boolean;
open: boolean;
onCreate: (values: Values) => void;
onCancel: () => void;
}
const CollectionCreateForm: React.FC<CollectionCreateFormProps> = ({
visible,
open,
onCreate,
onCancel,
}) => {
const [form] = Form.useForm();
return (
<Modal
visible={visible}
open={open}
title="Create a new collection"
okText="Create"
cancelText="Cancel"
@ -84,11 +84,11 @@ const CollectionCreateForm: React.FC<CollectionCreateFormProps> = ({
};
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const onCreate = (values: any) => {
console.log('Received values of form: ', values);
setVisible(false);
setOpen(false);
};
return (
@ -96,16 +96,16 @@ const App: React.FC = () => {
<Button
type="primary"
onClick={() => {
setVisible(true);
setOpen(true);
}}
>
New Collection
</Button>
<CollectionCreateForm
visible={visible}
open={open}
onCreate={onCreate}
onCancel={() => {
setVisible(false);
setOpen(false);
}}
/>
</div>

View File

@ -59,7 +59,7 @@ const App: React.FC = () => {
onChange={setGutterKey}
marks={gutters}
step={null}
tipFormatter={value => value && gutters[value]}
tooltip={{ formatter: value => value && gutters[value] }}
/>
</div>
<span>Vertical Gutter (px): </span>
@ -71,7 +71,7 @@ const App: React.FC = () => {
onChange={setVgutterKey}
marks={vgutters}
step={null}
tipFormatter={value => value && vgutters[value]}
tooltip={{ formatter: value => value && vgutters[value] }}
/>
</div>
<span>Column Count:</span>
@ -83,7 +83,7 @@ const App: React.FC = () => {
onChange={setColCountKey}
marks={colCounts}
step={null}
tipFormatter={value => value && colCounts[value]}
tooltip={{ formatter: value => value && colCounts[value] }}
/>
</div>
<Row gutter={[gutters[gutterKey], vgutters[vgutterKey]]}>

View File

@ -17,7 +17,7 @@ import type { InputFocusOptions } from './Input';
import { fixControlledValue, resolveOnChange, triggerFocus } from './Input';
interface ShowCountProps {
formatter: (args: { count: number; maxLength?: number }) => string;
formatter: (args: { value: string; count: number; maxLength?: number }) => string;
}
function fixEmojiLength(value: string, maxLength: number) {
@ -233,7 +233,7 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
let dataCount = '';
if (typeof showCount === 'object') {
dataCount = showCount.formatter({ count: valueLength, maxLength });
dataCount = showCount.formatter({ value: val, count: valueLength, maxLength });
} else {
dataCount = `${valueLength}${hasMaxLength ? ` / ${maxLength}` : ''}`;
}

View File

@ -272,12 +272,14 @@ describe('should support showCount', () => {
const { container } = render(
<Input
maxLength={5}
showCount={{ formatter: ({ count, maxLength }) => `${count}, ${maxLength}` }}
showCount={{
formatter: ({ value, count, maxLength }) => `${value}, ${count}, ${maxLength}`,
}}
value="12345"
/>,
);
expect(container.querySelector('input')?.getAttribute('value')).toBe('12345');
expect(container.querySelector('.ant-input-show-count-suffix')?.innerHTML).toBe('5, 5');
expect(container.querySelector('.ant-input-show-count-suffix')?.innerHTML).toBe('12345, 5, 5');
});
});

View File

@ -304,13 +304,15 @@ describe('TextArea', () => {
const { container } = render(
<TextArea
maxLength={5}
showCount={{ formatter: ({ count, maxLength }) => `${count}, ${maxLength}` }}
showCount={{
formatter: ({ value, count, maxLength }) => `${value}, ${count}, ${maxLength}`,
}}
value="12345"
/>,
);
expect(container.querySelector('textarea').value).toBe('12345');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe(
'5, 5',
'12345, 5, 5',
);
});
});

View File

@ -26,7 +26,7 @@ A basic widget for getting the user input is a text field. Keyboard and mouse ca
| disabled | Whether the input is disabled | boolean | false | |
| id | The ID for input | string | - | |
| maxLength | The max length | number | - | |
| showCount | Whether show text count | boolean \| { formatter: ({ count: number, maxLength?: number }) => ReactNode } | false | 4.18.0 |
| showCount | Whether show text count | boolean \| { formatter: (info: { value: string, count: number, maxLength?: number }) => ReactNode } | false | 4.18.0 info.value: 4.23.0 |
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| prefix | The prefix icon for the Input | ReactNode | - | |
| size | The size of the input box. Note: in the context of a form, the `middle` size is used | `large` \| `middle` \| `small` | - | |
@ -49,7 +49,7 @@ The rest of the props of Input are exactly the same as the original [input](http
| bordered | Whether has border style | boolean | true | 4.5.0 |
| defaultValue | The initial input content | string | - | |
| maxLength | The max length | number | - | 4.7.0 |
| showCount | Whether show text count | boolean \| { formatter: ({ count: number, maxLength?: number }) => string } | false | 4.7.0 (formatter: 4.10.0) |
| showCount | Whether show text count | boolean \| { formatter: (info: { value: string, count: number, maxLength?: number }) => string } | false | 4.7.0 formatter: 4.10.0 info.value: 4.23.0 |
| value | The input content value | string | - | |
| onPressEnter | The callback function that is triggered when Enter key is pressed | function(e) | - | |
| onResize | The callback function that is triggered when resize | function({ width, height }) | - | |

View File

@ -27,7 +27,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/xS9YEJhfe/Input.svg
| disabled | 是否禁用状态,默认为 false | boolean | false | |
| id | 输入框的 id | string | - | |
| maxLength | 最大长度 | number | - | |
| showCount | 是否展示字数 | boolean \| { formatter: ({ count: number, maxLength?: number }) => ReactNode } | false | 4.18.0 |
| showCount | 是否展示字数 | boolean \| { formatter: (info: { value: string, count: number, maxLength?: number }) => ReactNode } | false | 4.18.0 info.value: 4.23.0 |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| prefix | 带有前缀图标的 input | ReactNode | - | |
| size | 控件大小。注:标准表单内的输入框大小限制为 `middle` | `large` \| `middle` \| `small` | - | |
@ -50,7 +50,7 @@ Input 的其他属性和 React 自带的 [input](https://reactjs.org/docs/dom-el
| bordered | 是否有边框 | boolean | true | 4.5.0 |
| defaultValue | 输入框默认内容 | string | - | |
| maxLength | 内容最大长度 | number | - | 4.7.0 |
| showCount | 是否展示字数 | boolean \| { formatter: ({ count: number, maxLength?: number }) => string } | false | 4.7.0 (formatter: 4.10.0) |
| showCount | 是否展示字数 | boolean \| { formatter: (info: { value: string, count: number, maxLength?: number }) => string } | false | 4.7.0 formatter: 4.10.0 info.value: 4.23.0 |
| value | 输入框内容 | string | - | |
| onPressEnter | 按下回车的回调 | function(e) | - | |
| onResize | resize 回调 | function({ width, height }) | - | |

View File

@ -177,13 +177,13 @@ const App: React.FC = () => (
<DatePicker open />
<TimePicker open defaultOpenValue={moment()} />
<RangePicker open style={{ width: 200 }} />
<Popconfirm title="Question?" visible>
<Popconfirm title="Question?" open>
<a>Click to confirm</a>
</Popconfirm>
<Transfer dataSource={[]} showSearch targetKeys={[]} render={(item: any) => item.title} />
<Calendar fullscreen={false} value={moment()} />
<Table dataSource={[]} columns={columns} />
<Modal title="Locale Modal" visible getContainer={false}>
<Modal title="Locale Modal" open getContainer={false}>
<p>Locale Modal</p>
</Modal>
</div>

View File

@ -112,6 +112,7 @@ exports[`renders ./components/mentions/demo/form.md extend context correctly 1`]
class="ant-mentions"
>
<textarea
aria-required="true"
class="rc-textarea"
id="bio"
placeholder="You can use @ to ref user here"

View File

@ -112,6 +112,7 @@ exports[`renders ./components/mentions/demo/form.md correctly 1`] = `
class="ant-mentions"
>
<textarea
aria-required="true"
class="rc-textarea"
id="bio"
placeholder="You can use @ to ref user here"

View File

@ -55,9 +55,9 @@ export default class MenuItem extends React.Component<MenuItemProps> {
if (!siderCollapsed && !inlineCollapsed) {
tooltipProps.title = null;
// Reset `visible` to fix control mode tooltip display not correct
// Reset `open` to fix control mode tooltip display not correct
// ref: https://github.com/ant-design/ant-design/issues/16742
tooltipProps.visible = false;
tooltipProps.open = false;
}
const childrenLength = toArray(children).length;

View File

@ -120,7 +120,7 @@ return <Menu items={items} />;
#### SubMenuType
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- |
| children | 子菜单的菜单项 | [ItemType\[\]](#ItemType) | - | |
| disabled | 是否禁用 | boolean | false | |
| icon | 菜单图标 | ReactNode | - | |

View File

@ -24,6 +24,7 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
zIndex,
afterClose,
visible,
open,
keyboard,
centered,
getContainer,
@ -90,8 +91,8 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
{ [`${contentPrefixCls}-centered`]: !!props.centered },
wrapClassName,
)}
onCancel={() => close({ triggerCancel: true })}
visible={visible}
onCancel={() => close?.({ triggerCancel: true })}
open={open || visible}
title=""
footer=""
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}

View File

@ -12,6 +12,7 @@ import { NoFormStyle } from '../form/context';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import { getTransitionName } from '../_util/motion';
import { canUseDocElement } from '../_util/styleChecker';
import warning from '../_util/warning';
import { getConfirmLocale } from './locale';
let mousePosition: { x: number; y: number } | null;
@ -37,7 +38,12 @@ if (canUseDocElement()) {
export interface ModalProps {
/** 对话框是否可见 */
/**
* @deprecated `visible` is deprecated which will be removed in next major version. Please use
* `open` instead.
*/
visible?: boolean;
open?: boolean;
/** 确定按钮 loading */
confirmLoading?: boolean;
/** 标题 */
@ -92,7 +98,12 @@ type getContainerFunc = () => HTMLElement;
export interface ModalFuncProps {
prefixCls?: string;
className?: string;
/**
* @deprecated `visible` is deprecated which will be removed in next major version. Please use
* `open` instead.
*/
visible?: boolean;
open?: boolean;
title?: React.ReactNode;
closable?: boolean;
content?: React.ReactNode;
@ -151,6 +162,12 @@ const Modal: React.FC<ModalProps> = props => {
onOk?.(e);
};
warning(
!('visible' in props),
'Modal',
`\`visible\` will be removed in next major version, please use \`open\` instead.`,
);
const renderFooter = (locale: ModalLocale) => {
const { okText, okType, cancelText, confirmLoading } = props;
return (
@ -174,6 +191,7 @@ const Modal: React.FC<ModalProps> = props => {
prefixCls: customizePrefixCls,
footer,
visible,
open,
wrapClassName,
centered,
getContainer,
@ -211,7 +229,7 @@ const Modal: React.FC<ModalProps> = props => {
prefixCls={prefixCls}
wrapClassName={wrapClassNameExtended}
footer={footer === undefined ? defaultFooter : footer}
visible={visible}
visible={open || visible}
mousePosition={mousePosition}
onClose={handleCancel}
closeIcon={closeIconToRender}
@ -226,7 +244,7 @@ const Modal: React.FC<ModalProps> = props => {
Modal.defaultProps = {
width: 520,
confirmLoading: false,
visible: false,
open: false,
okType: 'primary' as LegacyButtonType,
};

View File

@ -4,14 +4,15 @@ import Modal from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
import { resetWarned } from '../../_util/warning';
jest.mock('rc-util/lib/Portal');
class ModalTester extends React.Component<ModalProps, { visible: boolean }> {
state = { visible: false };
class ModalTester extends React.Component<ModalProps, { open: boolean }> {
state = { open: false };
componentDidMount() {
this.setState({ visible: true }); // eslint-disable-line react/no-did-mount-set-state
this.setState({ open: true }); // eslint-disable-line react/no-did-mount-set-state
}
container = React.createRef<HTMLDivElement>();
@ -19,11 +20,11 @@ class ModalTester extends React.Component<ModalProps, { visible: boolean }> {
getContainer = () => this.container?.current!;
render() {
const { visible } = this.state;
const { open } = this.state;
return (
<div>
<div ref={this.container} />
<Modal {...this.props} visible={visible} getContainer={this.getContainer}>
<Modal {...this.props} open={open} getContainer={this.getContainer}>
Here is content of Modal
</Modal>
</div>
@ -36,7 +37,7 @@ describe('Modal', () => {
rtlTest(Modal);
it('support closeIcon', () => {
render(<Modal closeIcon={<a>closeIcon</a>} visible />);
render(<Modal closeIcon={<a>closeIcon</a>} open />);
expect(document.body.querySelectorAll('.ant-modal-root')[0]).toMatchSnapshot();
});
@ -52,35 +53,35 @@ describe('Modal', () => {
it('onCancel should be called', () => {
const onCancel = jest.fn();
render(<Modal visible onCancel={onCancel} />);
render(<Modal open onCancel={onCancel} />);
fireEvent.click(document.body.querySelectorAll('.ant-btn')[0]);
expect(onCancel).toHaveBeenCalled();
});
it('onOk should be called', () => {
const onOk = jest.fn();
render(<Modal visible onOk={onOk} />);
render(<Modal open onOk={onOk} />);
const btns = document.body.querySelectorAll('.ant-btn');
fireEvent.click(btns[btns.length - 1]);
expect(onOk).toHaveBeenCalled();
});
it('danger type', () => {
render(<Modal okType="danger" okText="123" visible />);
render(<Modal okType="danger" okText="123" open />);
const btns = document.body.querySelectorAll('.ant-btn');
expect(btns[btns.length - 1].classList.contains('ant-btn-dangerous')).toBeTruthy();
});
it('mouse position', () => {
const Demo = () => {
const [visible, setVisible] = React.useState(false);
const [open, setOpen] = React.useState(false);
const containerRef = React.useRef<HTMLDivElement>(null);
return (
<div ref={containerRef}>
<div id="trigger" onClick={() => setVisible(true)}>
<div id="trigger" onClick={() => setOpen(true)}>
click me
</div>
<Modal visible={visible} getContainer={() => containerRef.current!} />
<Modal open={open} getContainer={() => containerRef.current!} />
</div>
);
};
@ -90,4 +91,18 @@ describe('Modal', () => {
(container.querySelectorAll('.ant-modal')[0] as HTMLDivElement).style.transformOrigin,
).toBeTruthy();
});
it('deprecated warning', () => {
resetWarned();
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<Modal visible />);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Modal] `visible` will be removed in next major version, please use `open` instead.',
);
expect(document.querySelector('.ant-modal')).toBeTruthy();
errSpy.mockRestore();
});
});

View File

@ -29,7 +29,7 @@ export type ModalStaticFunctions = Record<NonNullable<ModalFuncProps['type']>, M
export default function confirm(config: ModalFuncProps) {
const container = document.createDocumentFragment();
// eslint-disable-next-line @typescript-eslint/no-use-before-define
let currentConfig = { ...config, close, visible: true } as any;
let currentConfig = { ...config, close, open: true } as any;
function destroy(...args: any[]) {
const triggerCancel = args.some(param => param && param.triggerCancel);
@ -79,7 +79,7 @@ export default function confirm(config: ModalFuncProps) {
function close(...args: any[]) {
currentConfig = {
...currentConfig,
visible: false,
open: false,
afterClose: () => {
if (typeof config.afterClose === 'function') {
config.afterClose();

View File

@ -18,26 +18,26 @@ import { Button, Modal } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const [modalText, setModalText] = useState('Content of the modal');
const showModal = () => {
setVisible(true);
setOpen(true);
};
const handleOk = () => {
setModalText('The modal will be closed after two seconds');
setConfirmLoading(true);
setTimeout(() => {
setVisible(false);
setOpen(false);
setConfirmLoading(false);
}, 2000);
};
const handleCancel = () => {
console.log('Clicked cancel button');
setVisible(false);
setOpen(false);
};
return (
@ -47,7 +47,7 @@ const App: React.FC = () => {
</Button>
<Modal
title="Title"
visible={visible}
open={open}
onOk={handleOk}
confirmLoading={confirmLoading}
onCancel={handleCancel}

View File

@ -18,18 +18,18 @@ import { Button, Modal } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = () => {
setIsModalVisible(true);
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalVisible(false);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalVisible(false);
setIsModalOpen(false);
};
return (
@ -37,7 +37,7 @@ const App: React.FC = () => {
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal title="Basic Modal" visible={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
<Modal title="Basic Modal" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>

View File

@ -18,20 +18,20 @@ import { Button, Modal } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showModal = () => {
setVisible(true);
setOpen(true);
};
const handleOk = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
setVisible(false);
setOpen(false);
};
const handleCancel = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
setVisible(false);
setOpen(false);
};
return (
@ -41,7 +41,7 @@ const App: React.FC = () => {
</Button>
<Modal
title="Basic Modal"
visible={visible}
open={open}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{ disabled: true }}

View File

@ -298,7 +298,7 @@ const TableTransfer = ({ leftColumns, rightColumns, ...restProps }) => (
);
export default () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [targetKeys, setTargetKeys] = useState(oriTargetKeys);
const [selectedKeys, setSelectedKeys] = useState([]);
const [disabled, setDisabled] = useState(false);
@ -329,17 +329,17 @@ export default () => {
};
const showModal = () => {
setVisible(true);
setOpen(true);
};
const handleOk = e => {
console.log(e);
setVisible(false);
setOpen(false);
};
const handleCancel = e => {
console.log(e);
setVisible(false);
setOpen(false);
};
const columns = [
@ -385,7 +385,7 @@ export default () => {
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal title="Basic Modal" visible={visible} onOk={handleOk} onCancel={handleCancel}>
<Modal title="Basic Modal" open={open} onOk={handleOk} onCancel={handleCancel}>
<Switch
unCheckedChildren="disabled"
checkedChildren="disabled"

View File

@ -23,22 +23,22 @@ import React, { useState } from 'react';
const App: React.FC = () => {
const [loading, setLoading] = useState(false);
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showModal = () => {
setVisible(true);
setOpen(true);
};
const handleOk = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setVisible(false);
setOpen(false);
}, 3000);
};
const handleCancel = () => {
setVisible(false);
setOpen(false);
};
return (
@ -47,7 +47,7 @@ const App: React.FC = () => {
Open Modal with customized footer
</Button>
<Modal
visible={visible}
open={open}
title="Title"
onOk={handleOk}
onCancel={handleCancel}

View File

@ -19,14 +19,14 @@ import { Button, Modal, Space } from 'antd';
import React, { useState } from 'react';
const LocalizedModal = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const showModal = () => {
setVisible(true);
setOpen(true);
};
const hideModal = () => {
setVisible(false);
setOpen(false);
};
return (
@ -36,7 +36,7 @@ const LocalizedModal = () => {
</Button>
<Modal
title="Modal"
visible={visible}
open={open}
onOk={hideModal}
onCancel={hideModal}
okText="确认"

View File

@ -20,23 +20,23 @@ import type { DraggableData, DraggableEvent } from 'react-draggable';
import Draggable from 'react-draggable';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [disabled, setDisabled] = useState(false);
const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 });
const draggleRef = useRef<HTMLDivElement>(null);
const showModal = () => {
setVisible(true);
setOpen(true);
};
const handleOk = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
setVisible(false);
setOpen(false);
};
const handleCancel = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
setVisible(false);
setOpen(false);
};
const onStart = (_event: DraggableEvent, uiData: DraggableData) => {
@ -80,7 +80,7 @@ const App: React.FC = () => {
Draggable Modal
</div>
}
visible={visible}
open={open}
onOk={handleOk}
onCancel={handleCancel}
modalRender={modal => (

View File

@ -18,20 +18,20 @@ import { Button, Modal } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [modal1Visible, setModal1Visible] = useState(false);
const [modal2Visible, setModal2Visible] = useState(false);
const [modal1Open, setModal1Open] = useState(false);
const [modal2Open, setModal2Open] = useState(false);
return (
<>
<Button type="primary" onClick={() => setModal1Visible(true)}>
<Button type="primary" onClick={() => setModal1Open(true)}>
Display a modal dialog at 20px to Top
</Button>
<Modal
title="20px to Top"
style={{ top: 20 }}
visible={modal1Visible}
onOk={() => setModal1Visible(false)}
onCancel={() => setModal1Visible(false)}
visible={modal1Open}
onOk={() => setModal1Open(false)}
onCancel={() => setModal1Open(false)}
>
<p>some contents...</p>
<p>some contents...</p>
@ -39,15 +39,15 @@ const App: React.FC = () => {
</Modal>
<br />
<br />
<Button type="primary" onClick={() => setModal2Visible(true)}>
<Button type="primary" onClick={() => setModal2Open(true)}>
Vertically centered modal dialog
</Button>
<Modal
title="Vertically centered modal dialog"
centered
visible={modal2Visible}
onOk={() => setModal2Visible(false)}
onCancel={() => setModal2Visible(false)}
visible={modal2Open}
onOk={() => setModal2Open(false)}
onCancel={() => setModal2Open(false)}
>
<p>some contents...</p>
<p>some contents...</p>

View File

@ -18,19 +18,19 @@ import { Button, Modal } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
return (
<>
<Button type="primary" onClick={() => setVisible(true)}>
<Button type="primary" onClick={() => setOpen(true)}>
Open Modal of 1000px width
</Button>
<Modal
title="Modal 1000px width"
centered
visible={visible}
onOk={() => setVisible(false)}
onCancel={() => setVisible(false)}
open={open}
onOk={() => setOpen(false)}
onCancel={() => setOpen(false)}
width={1000}
>
<p>some contents...</p>

View File

@ -38,7 +38,7 @@ When requiring users to interact with the application, but without jumping to a
| okType | Button `type` of the OK button | string | `primary` | |
| style | Style of floating layer, typically used at least for adjusting the position | CSSProperties | - | |
| title | The modal dialog's title | ReactNode | - | |
| visible | Whether the modal dialog is visible or not | boolean | false | |
| open | Whether the modal dialog is visible or not | boolean | false | 4.23.0 |
| width | Width of the modal dialog | string \| number | 520 | |
| wrapClassName | The class name of the container of the modal dialog | string | - | |
| zIndex | The `z-index` of the Modal | number | 1000 | |

View File

@ -41,7 +41,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3StSdUlSH/Modal.svg
| okType | 确认按钮类型 | string | `primary` | |
| style | 可用于设置浮层的样式,调整浮层位置等 | CSSProperties | - | |
| title | 标题 | ReactNode | - | |
| visible | 对话框是否可见 | boolean | - | |
| open | 对话框是否可见 | boolean | - | 4.23.0 |
| width | 宽度 | string \| number | 520 | |
| wrapClassName | 对话框外层容器的类名 | string | - | |
| zIndex | 设置 Modal 的 `z-index` | number | 1000 | |

View File

@ -25,7 +25,7 @@ const HookModal: React.ForwardRefRenderFunction<HookModalRef, HookModalProps> =
{ afterClose, config },
ref,
) => {
const [visible, setVisible] = React.useState(true);
const [open, setOpen] = React.useState(true);
const [innerConfig, setInnerConfig] = React.useState(config);
const { direction, getPrefixCls } = React.useContext(ConfigContext);
@ -33,7 +33,7 @@ const HookModal: React.ForwardRefRenderFunction<HookModalRef, HookModalProps> =
const rootPrefixCls = getPrefixCls();
const close = (...args: any[]) => {
setVisible(false);
setOpen(false);
const triggerCancel = args.some(param => param && param.triggerCancel);
if (innerConfig.onCancel && triggerCancel) {
innerConfig.onCancel(() => {}, ...args.slice(1));
@ -58,7 +58,7 @@ const HookModal: React.ForwardRefRenderFunction<HookModalRef, HookModalProps> =
rootPrefixCls={rootPrefixCls}
{...innerConfig}
close={close}
visible={visible}
open={open}
afterClose={afterClose}
okText={
innerConfig.okText ||

View File

@ -1637,13 +1637,6 @@ exports[`renders ./components/page-header/demo/responsive.md extend context corr
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>

View File

@ -1416,13 +1416,6 @@ exports[`renders ./components/page-header/demo/responsive.md correctly 1`] = `
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane"
role="tabpanel"
style="display:none"
tabindex="-1"
/>
</div>
</div>
</div>

View File

@ -23,7 +23,7 @@ describe('Popconfirm', () => {
});
it('should popup Popconfirm dialog', () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const wrapper = mount(
<Popconfirm
@ -32,7 +32,7 @@ describe('Popconfirm', () => {
cancelText="No"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
onOpenChange={onOpenChange}
>
<span>Delete</span>
</Popconfirm>,
@ -40,11 +40,11 @@ describe('Popconfirm', () => {
const triggerNode = wrapper.find('span').at(0);
triggerNode.simulate('click');
expect(onVisibleChange).toHaveBeenLastCalledWith(true, undefined);
expect(onOpenChange).toHaveBeenLastCalledWith(true, undefined);
expect(wrapper.find('.popconfirm-test').length).toBe(1);
triggerNode.simulate('click');
expect(onVisibleChange).toHaveBeenLastCalledWith(false, undefined);
expect(onOpenChange).toHaveBeenLastCalledWith(false, undefined);
});
it('should show overlay when trigger is clicked', async () => {
@ -88,7 +88,7 @@ describe('Popconfirm', () => {
expect(popup.innerHTML).toMatchSnapshot();
});
it('should be controlled by visible', () => {
it('should be controlled by open', () => {
const ref = React.createRef();
jest.useFakeTimers();
const popconfirm = mount(
@ -97,10 +97,10 @@ describe('Popconfirm', () => {
</Popconfirm>,
);
expect(ref.current.getPopupDomNode()).toBeFalsy();
popconfirm.setProps({ visible: true });
popconfirm.setProps({ open: true });
expect(ref.current.getPopupDomNode()).toBeTruthy();
expect(ref.current.getPopupDomNode().className).not.toContain('ant-popover-hidden');
popconfirm.setProps({ visible: false });
popconfirm.setProps({ open: false });
popconfirm.update(); // https://github.com/enzymejs/enzyme/issues/2305
act(() => {
jest.runAllTimers();
@ -112,14 +112,9 @@ describe('Popconfirm', () => {
it('should trigger onConfirm and onCancel', () => {
const confirm = jest.fn();
const cancel = jest.fn();
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const popconfirm = mount(
<Popconfirm
title="code"
onConfirm={confirm}
onCancel={cancel}
onVisibleChange={onVisibleChange}
>
<Popconfirm title="code" onConfirm={confirm} onCancel={cancel} onOpenChange={onOpenChange}>
<span>show me your code</span>
</Popconfirm>,
);
@ -127,11 +122,11 @@ describe('Popconfirm', () => {
triggerNode.simulate('click');
popconfirm.find('.ant-btn-primary').simulate('click');
expect(confirm).toHaveBeenCalled();
expect(onVisibleChange).toHaveBeenLastCalledWith(false, eventObject);
expect(onOpenChange).toHaveBeenLastCalledWith(false, eventObject);
triggerNode.simulate('click');
popconfirm.find('.ant-btn').at(0).simulate('click');
expect(cancel).toHaveBeenCalled();
expect(onVisibleChange).toHaveBeenLastCalledWith(false, eventObject);
expect(onOpenChange).toHaveBeenLastCalledWith(false, eventObject);
});
it('should support onConfirm to return Promise', async () => {
@ -139,20 +134,20 @@ describe('Popconfirm', () => {
new Promise(res => {
setTimeout(res, 300);
});
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const popconfirm = mount(
<Popconfirm title="code" onConfirm={confirm} onVisibleChange={onVisibleChange}>
<Popconfirm title="code" onConfirm={confirm} onOpenChange={onOpenChange}>
<span>show me your code</span>
</Popconfirm>,
);
const triggerNode = popconfirm.find('span').at(0);
triggerNode.simulate('click');
expect(onVisibleChange).toHaveBeenCalledTimes(1);
expect(onOpenChange).toHaveBeenCalledTimes(1);
popconfirm.find('.ant-btn').at(0).simulate('click');
await sleep(400);
expect(onVisibleChange).toHaveBeenCalledWith(false, eventObject);
expect(onOpenChange).toHaveBeenCalledWith(false, eventObject);
});
it('should support customize icon', () => {
@ -171,7 +166,7 @@ describe('Popconfirm', () => {
const btnPrefixCls = 'custom-btn';
const wrapper = mount(
<Popconfirm
visible
open
title="x"
prefixCls="custom-popconfirm"
okButtonProps={{ prefixCls: btnPrefixCls }}
@ -185,10 +180,10 @@ describe('Popconfirm', () => {
expect(wrapper.find('.custom-btn').length).toBeGreaterThan(0);
});
it('should support defaultVisible', () => {
it('should support defaultOpen', () => {
const ref = React.createRef();
mount(
<Popconfirm ref={ref} title="code" defaultVisible>
<Popconfirm ref={ref} title="code" defaultOpen>
<span>show me your code</span>
</Popconfirm>,
);
@ -209,22 +204,17 @@ describe('Popconfirm', () => {
});
it('should be closed by pressing ESC', () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const wrapper = mount(
<Popconfirm
title="title"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
>
<Popconfirm title="title" mouseEnterDelay={0} mouseLeaveDelay={0} onOpenChange={onOpenChange}>
<span>Delete</span>
</Popconfirm>,
);
const triggerNode = wrapper.find('span').at(0);
triggerNode.simulate('click');
expect(onVisibleChange).toHaveBeenLastCalledWith(true, undefined);
expect(onOpenChange).toHaveBeenLastCalledWith(true, undefined);
triggerNode.simulate('keydown', { key: 'Escape', keyCode: 27 });
expect(onVisibleChange).toHaveBeenLastCalledWith(false, eventObject);
expect(onOpenChange).toHaveBeenLastCalledWith(false, eventObject);
});
it('should not warn memory leaking if setState in async callback', async () => {

View File

@ -18,31 +18,31 @@ import { Button, Popconfirm } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const showPopconfirm = () => {
setVisible(true);
setOpen(true);
};
const handleOk = () => {
setConfirmLoading(true);
setTimeout(() => {
setVisible(false);
setOpen(false);
setConfirmLoading(false);
}, 2000);
};
const handleCancel = () => {
console.log('Clicked cancel button');
setVisible(false);
setOpen(false);
};
return (
<Popconfirm
title="Title"
visible={visible}
open={open}
onConfirm={handleOk}
okButtonProps={{ loading: confirmLoading }}
onCancel={handleCancel}

View File

@ -18,7 +18,7 @@ import { message, Popconfirm, Switch } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [condition, setCondition] = useState(true);
const changeCondition = (checked: boolean) => {
@ -26,18 +26,18 @@ const App: React.FC = () => {
};
const confirm = () => {
setVisible(false);
setOpen(false);
message.success('Next step.');
};
const cancel = () => {
setVisible(false);
setOpen(false);
message.error('Click on cancel.');
};
const handleVisibleChange = (newVisible: boolean) => {
if (!newVisible) {
setVisible(newVisible);
const handleOpenChange = (newOpen: boolean) => {
if (!newOpen) {
setOpen(newOpen);
return;
}
// Determining condition before show the popconfirm.
@ -45,7 +45,7 @@ const App: React.FC = () => {
if (condition) {
confirm(); // next step
} else {
setVisible(newVisible);
setOpen(newOpen);
}
};
@ -53,8 +53,8 @@ const App: React.FC = () => {
<div>
<Popconfirm
title="Are you sure delete this task?"
visible={visible}
onVisibleChange={handleVisibleChange}
open={open}
onOpenChange={handleOpenChange}
onConfirm={confirm}
onCancel={cancel}
okText="Yes"

View File

@ -25,11 +25,7 @@ const App: React.FC = () => {
});
return (
<Popconfirm
title="Title"
onConfirm={confirm}
onVisibleChange={() => console.log('visible change')}
>
<Popconfirm title="Title" onConfirm={confirm} onOpenChange={() => console.log('open change')}>
<Button type="primary">Open Popconfirm with Promise</Button>
</Popconfirm>
);

View File

@ -23,56 +23,65 @@ export interface PopconfirmProps extends AbstractTooltipProps {
cancelButtonProps?: ButtonProps;
showCancel?: boolean;
icon?: React.ReactNode;
/**
* @deprecated `onVisibleChange` is deprecated which will be removed in next major version. Please
* use `onOpenChange` instead.
*/
onVisibleChange?: (
visible: boolean,
e?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLDivElement>,
) => void;
onOpenChange?: (
open: boolean,
e?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLDivElement>,
) => void;
}
export interface PopconfirmState {
visible?: boolean;
open?: boolean;
}
const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
const { getPrefixCls } = React.useContext(ConfigContext);
const [visible, setVisible] = useMergedState(false, {
value: props.visible,
defaultValue: props.defaultVisible,
const [open, setOpen] = useMergedState(false, {
value: props.open !== undefined ? props.open : props.visible,
defaultValue: props.defaultOpen !== undefined ? props.defaultOpen : props.defaultVisible,
});
// const isDestroyed = useDestroyed();
const settingVisible = (
const settingOpen = (
value: boolean,
e?: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLDivElement>,
) => {
setVisible(value, true);
setOpen(value, true);
props.onVisibleChange?.(value, e);
props.onOpenChange?.(value, e);
};
const close = (e: React.MouseEvent<HTMLButtonElement>) => {
settingVisible(false, e);
settingOpen(false, e);
};
const onConfirm = (e: React.MouseEvent<HTMLButtonElement>) => props.onConfirm?.call(this, e);
const onCancel = (e: React.MouseEvent<HTMLButtonElement>) => {
settingVisible(false, e);
settingOpen(false, e);
props.onCancel?.call(this, e);
};
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.keyCode === KeyCode.ESC && visible) {
settingVisible(false, e);
if (e.keyCode === KeyCode.ESC && open) {
settingOpen(false, e);
}
};
const onVisibleChange = (value: boolean) => {
const onOpenChange = (value: boolean) => {
const { disabled } = props;
if (disabled) {
return;
}
settingVisible(value);
settingOpen(value);
};
const {
@ -91,8 +100,8 @@ const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
{...restProps}
prefixCls={prefixCls}
placement={placement}
onVisibleChange={onVisibleChange}
visible={visible}
onOpenChange={onOpenChange}
open={open}
_overlay={
<Overlay
{...props}

View File

@ -90,7 +90,7 @@ describe('Popover', () => {
it(`should be rendered correctly in RTL direction`, () => {
const wrapper = render(
<ConfigProvider direction="rtl">
<Popover title="RTL" visible>
<Popover title="RTL" open>
<span>show me your Rtl demo</span>
</Popover>
</ConfigProvider>,

View File

@ -7,25 +7,25 @@ title:
## zh-CN
使用 `visible` 属性控制浮层显示。
使用 `open` 属性控制浮层显示。
## en-US
Use `visible` prop to control the display of the card.
Use `open` prop to control the display of the card.
```tsx
import { Button, Popover } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const hide = () => {
setVisible(false);
setOpen(false);
};
const handleVisibleChange = (newVisible: boolean) => {
setVisible(newVisible);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
return (
@ -33,8 +33,8 @@ const App: React.FC = () => {
content={<a onClick={hide}>Close</a>}
title="Title"
trigger="click"
visible={visible}
onVisibleChange={handleVisibleChange}
open={open}
onOpenChange={handleOpenChange}
>
<Button type="primary">Click me</Button>
</Popover>

View File

@ -26,14 +26,14 @@ const App: React.FC = () => {
setHovered(false);
};
const handleHoverChange = (visible: boolean) => {
setHovered(visible);
const handleHoverChange = (open: boolean) => {
setHovered(open);
setClicked(false);
};
const handleClickChange = (visible: boolean) => {
const handleClickChange = (open: boolean) => {
setHovered(false);
setClicked(visible);
setClicked(open);
};
const hoverContent = <div>This is hover content.</div>;
@ -44,8 +44,8 @@ const App: React.FC = () => {
content={hoverContent}
title="Hover title"
trigger="hover"
visible={hovered}
onVisibleChange={handleHoverChange}
open={hovered}
onOpenChange={handleHoverChange}
>
<Popover
content={
@ -56,8 +56,8 @@ const App: React.FC = () => {
}
title="Click title"
trigger="click"
visible={clicked}
onVisibleChange={handleClickChange}
open={clicked}
onOpenChange={handleClickChange}
>
<Button>Hover and click / 悬停并单击</Button>
</Popover>

View File

@ -90,6 +90,16 @@ describe('Select', () => {
expect(container.querySelectorAll('.anticon-down').length).toBe(0);
expect(container.querySelectorAll('.anticon-search').length).toBe(1);
});
it('should show warning when use dropdownClassName', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<Select dropdownClassName="myCustomClassName" />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Select] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
);
errorSpy.mockRestore();
});
//
describe('Select Custom Icons', () => {
it('should support customized icons', () => {

View File

@ -33,7 +33,7 @@ Select component to select value from options.
| defaultOpen | Initial open state of dropdown | boolean | - | |
| defaultValue | Initial selected option | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue\[] | - | |
| disabled | Whether disabled select | boolean | false | |
| dropdownClassName | The className of dropdown menu | string | - | |
| popupClassName | The className of dropdown menu | string | - | 4.23.0 |
| dropdownMatchSelectWidth | 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 | |
| dropdownRender | Customize dropdown content | (originNode: ReactNode) => ReactNode | - | |
| dropdownStyle | The style of dropdown menu | CSSProperties | - | |

View File

@ -19,6 +19,7 @@ import { getTransitionDirection, getTransitionName } from '../_util/motion';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import getIcons from './utils/iconUtil';
import warning from '../_util/warning';
type RawValue = string | number;
@ -53,6 +54,12 @@ export interface SelectProps<
placement?: SelectCommonPlacement;
mode?: 'multiple' | 'tags';
status?: InputStatus;
/**
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
* version.Please use `popupClassName` instead.
*/
dropdownClassName?: string;
popupClassName?: string;
}
const SECRET_COMBOBOX_MODE_DO_NOT_USE = 'SECRET_COMBOBOX_MODE_DO_NOT_USE';
@ -64,6 +71,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
className,
getPopupContainer,
dropdownClassName,
popupClassName,
listHeight = 256,
placement,
listItemHeight = 24,
@ -107,6 +115,13 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
const mergedShowArrow =
showArrow !== undefined ? showArrow : props.loading || !(isMultiple || mode === 'combobox');
// =================== Warning =====================
warning(
!dropdownClassName,
'Select',
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
);
// ===================== Form Status =====================
const {
status: contextStatus,
@ -138,7 +153,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
const selectProps = omit(props as typeof props & { itemIcon: any }, ['suffixIcon', 'itemIcon']);
const rcSelectRtlDropdownClassName = classNames(dropdownClassName, {
const rcSelectRtlDropdownClassName = classNames(popupClassName || dropdownClassName, {
[`${prefixCls}-dropdown-${direction}`]: direction === 'rtl',
});

View File

@ -34,7 +34,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |
| defaultValue | 指定默认选中的条目 | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue\[] | - | |
| disabled | 是否禁用 | boolean | false | |
| dropdownClassName | 下拉菜单的 className 属性 | string | - | |
| popupClassName | 下拉菜单的 className 属性 | string | - | 4.23.0 |
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | |
| dropdownRender | 自定义下拉框内容 | (originNode: ReactNode) => ReactNode | - | |
| dropdownStyle | 下拉菜单的 style 属性 | CSSProperties | - | |

View File

@ -6,7 +6,7 @@ import type { TooltipProps } from '../tooltip';
import Tooltip from '../tooltip';
const SliderTooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
const { visible } = props;
const { open } = props;
const innerRef = useRef<any>(null);
const rafRef = useRef<number | null>(null);
@ -24,14 +24,14 @@ const SliderTooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
}
React.useEffect(() => {
if (visible) {
if (open) {
keepAlign();
} else {
cancelKeepAlign();
}
return cancelKeepAlign;
}, [visible, props.title]);
}, [open, props.title]);
return <Tooltip ref={composeRef(innerRef, ref)} {...props} />;
});

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