chore: merge master to next

This commit is contained in:
afc163 2022-06-24 11:32:42 +08:00
commit a5fe576d48
26 changed files with 5417 additions and 741 deletions

View File

@ -9,8 +9,8 @@ import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled';
import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined';
import classNames from 'classnames';
import CSSMotion from 'rc-motion';
import type { ReactElement } from 'react';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import getDataOrAriaProps from '../_util/getDataOrAriaProps';
import { replaceElement } from '../_util/reactNode';
@ -65,6 +65,43 @@ const iconMapOutlined = {
warning: ExclamationCircleOutlined,
};
interface IconNodeProps {
type: AlertProps['type'];
icon: AlertProps['icon'];
prefixCls: AlertProps['prefixCls'];
description: AlertProps['description'];
}
const IconNode: React.FC<IconNodeProps> = props => {
const { description, icon, prefixCls, type } = props;
const iconType = (description ? iconMapOutlined : iconMapFilled)[type!] || null;
if (icon) {
return replaceElement(icon, <span className={`${prefixCls}-icon`}>{icon}</span>, () => ({
className: classNames(`${prefixCls}-icon`, {
[(icon as ReactElement).props.className]: (icon as ReactElement).props.className,
}),
})) as ReactElement;
}
return React.createElement(iconType, { className: `${prefixCls}-icon` });
};
interface CloseIconProps {
isClosable: boolean;
prefixCls: AlertProps['prefixCls'];
closeText: AlertProps['closeText'];
closeIcon: AlertProps['closeIcon'];
handleClose: AlertProps['onClose'];
}
const CloseIcon: React.FC<CloseIconProps> = props => {
const { isClosable, closeText, prefixCls, closeIcon, handleClose } = props;
return isClosable ? (
<button type="button" onClick={handleClose} className={`${prefixCls}-close-icon`} tabIndex={0}>
{closeText ? <span className={`${prefixCls}-close-text`}>{closeText}</span> : closeIcon}
</button>
) : null;
};
interface AlertInterface extends React.FC<AlertProps> {
ErrorBoundary: typeof ErrorBoundary;
}
@ -112,32 +149,6 @@ const Alert: AlertInterface = ({
const isClosable = closeText ? true : closable;
const type = getType();
const renderIconNode = () => {
const { icon } = props;
// use outline icon in alert with description
const iconType = (description ? iconMapOutlined : iconMapFilled)[type] || null;
if (icon) {
return replaceElement(icon, <span className={`${prefixCls}-icon`}>{icon}</span>, () => ({
className: classNames(`${prefixCls}-icon`, {
[(icon as any).props.className]: (icon as any).props.className,
}),
}));
}
return React.createElement(iconType, { className: `${prefixCls}-icon` });
};
const renderCloseIcon = () =>
isClosable ? (
<button
type="button"
onClick={handleClose}
className={`${prefixCls}-close-icon`}
tabIndex={0}
>
{closeText ? <span className={`${prefixCls}-close-text`}>{closeText}</span> : closeIcon}
</button>
) : null;
// banner 模式默认有 Icon
const isShowIcon = banner && showIcon === undefined ? true : showIcon;
@ -179,13 +190,26 @@ const Alert: AlertInterface = ({
role="alert"
{...dataOrAriaProps}
>
{isShowIcon ? renderIconNode() : null}
{isShowIcon ? (
<IconNode
description={description}
icon={props.icon}
prefixCls={prefixCls}
type={type}
/>
) : null}
<div className={`${prefixCls}-content`}>
{message ? <div className={`${prefixCls}-message`}>{message}</div> : null}
{description ? <div className={`${prefixCls}-description`}>{description}</div> : null}
</div>
{action ? <div className={`${prefixCls}-action`}>{action}</div> : null}
{renderCloseIcon()}
<CloseIcon
isClosable={!!isClosable}
closeText={closeText}
prefixCls={prefixCls}
closeIcon={closeIcon}
handleClose={handleClose}
/>
</div>
)}
</CSSMotion>,

View File

@ -816,6 +816,7 @@ describe('Menu', () => {
);
expect(onOpen).not.toHaveBeenCalled();
expect(onClose).not.toHaveBeenCalled();
errorSpy.mockRestore();
});
// https://github.com/ant-design/ant-design/issues/18825
@ -1002,4 +1003,13 @@ describe('Menu', () => {
expect(wrapper.render()).toMatchSnapshot();
});
it('should not warning deprecated message when items={undefined}', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
mount(<Menu items={undefined} />);
expect(errorSpy).not.toHaveBeenCalledWith(
expect.stringContaining('`children` will be removed in next major version'),
);
errorSpy.mockRestore();
});
});

View File

@ -91,7 +91,7 @@ const InternalMenu = forwardRef<MenuRef, InternalMenuProps>((props, ref) => {
);
warning(
!!items && !children,
'items' in props && !children,
'Menu',
'`children` will be removed in next major version. Please use `items` instead.',
);

View File

@ -66,7 +66,7 @@ const App: React.FC = () => {
Error
</Button>
</Space>
{/* `contextHolder` should always under the context you want to access */}
{/* `contextHolder` should always be placed under the context you want to access */}
{contextHolder}
{/* Can not access this context since `contextHolder` is not in it */}

View File

@ -1,8 +1,8 @@
import { mount } from 'enzyme';
import React from 'react';
import Pagination from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import Select from '../../select';
@ -20,13 +20,15 @@ describe('Pagination', () => {
}
return originalElement;
};
const wrapper = mount(<Pagination defaultCurrent={1} total={50} itemRender={itemRender} />);
expect(wrapper.find('button').at(0).props().disabled).toBe(true);
const { container } = render(
<Pagination defaultCurrent={1} total={50} itemRender={itemRender} />,
);
expect(container.querySelector('button').disabled).toBe(true);
});
it('should autometically be small when size is not specified', async () => {
const wrapper = mount(<Pagination responsive />);
expect(wrapper.find('ul').at(0).hasClass('ant-pagination-mini')).toBe(true);
const { container } = render(<Pagination responsive />);
expect(container.querySelector('ul').className.includes('ant-pagination-mini')).toBe(true);
});
// https://github.com/ant-design/ant-design/issues/24913
@ -34,7 +36,7 @@ describe('Pagination', () => {
it('should onChange called when pageSize change', () => {
const onChange = jest.fn();
const onShowSizeChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Pagination
defaultCurrent={1}
total={500}
@ -42,9 +44,11 @@ describe('Pagination', () => {
onShowSizeChange={onShowSizeChange}
/>,
);
wrapper.find('.ant-select-selector').simulate('mousedown');
expect(wrapper.find('.ant-select-item-option').length).toBe(4);
wrapper.find('.ant-select-item-option').at(1).simulate('click');
fireEvent.mouseDown(container.querySelector('.ant-select-selector'));
expect(container.querySelectorAll('.ant-select-item-option').length).toBe(4);
fireEvent.click(container.querySelectorAll('.ant-select-item-option')[1]);
expect(onChange).toHaveBeenCalledWith(1, 20);
});
@ -55,30 +59,30 @@ describe('Pagination', () => {
CustomSelect.Option = Select.Option;
const wrapper = mount(
const { container } = render(
<Pagination defaultCurrent={1} total={500} selectComponentClass={CustomSelect} />,
);
expect(wrapper.find('.custom-select').length).toBeTruthy();
expect(container.querySelectorAll('.custom-select').length).toBeTruthy();
});
describe('ConfigProvider', () => {
it('should be rendered correctly in RTL', () => {
const wrapper = mount(
const { asFragment } = render(
<ConfigProvider direction="rtl">
<Pagination defaultCurrent={1} total={50} />
</ConfigProvider>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should be rendered correctly when componentSize is large', () => {
const wrapper = mount(
const { container, asFragment } = render(
<ConfigProvider componentSize="large">
<Pagination defaultCurrent={1} total={50} showSizeChanger />
</ConfigProvider>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(wrapper.find('.ant-select-lg').length).toBe(0);
expect(asFragment().firstChild).toMatchSnapshot();
expect(container.querySelectorAll('.ant-select-lg').length).toBe(0);
});
});
});

View File

@ -292,6 +292,79 @@ describe('Table.rowSelection', () => {
]);
});
it('reset last select key after performing select and bulk operations', async () => {
jest.useFakeTimers();
const onChange = jest.fn();
const { container, baseElement } = render(
createTable({
checkbox: true,
rowSelection: {
selections: [Table.SELECTION_NONE],
onChange: keys => onChange(keys),
},
}),
);
const last = () => {
const elements = container.querySelectorAll('td input');
return elements[elements.length - 1];
};
const first = () => {
const elements = container.querySelectorAll('td input');
return elements[0];
};
const allElement = () => container.querySelector('th input');
// Multiple select normal
fireEvent.click(last());
expect(onChange).toHaveBeenLastCalledWith([3]);
fireEvent.click(first(), {
shiftKey: true,
});
expect(onChange).toHaveBeenLastCalledWith([3, 0, 1, 2]);
fireEvent.click(allElement());
console.log(baseElement.innerHTML);
expect(onChange).toHaveBeenLastCalledWith([]);
// Reset last select key when select all
fireEvent.click(last());
expect(onChange).toHaveBeenLastCalledWith([3]);
fireEvent.click(allElement());
fireEvent.click(allElement());
expect(onChange).toHaveBeenLastCalledWith([]);
fireEvent.click(first(), {
shiftKey: true,
});
expect(onChange).toHaveBeenLastCalledWith([0]);
// Reset last select key when deselect
fireEvent.click(last());
expect(onChange).toHaveBeenLastCalledWith([0, 3]);
fireEvent.click(first());
expect(onChange).toHaveBeenLastCalledWith([3]);
fireEvent.click(first(), {
shiftKey: true,
});
expect(onChange).toHaveBeenLastCalledWith([3, 0]);
// Reset last select key when bulk operations
fireEvent.mouseEnter(container.querySelector('.ant-dropdown-trigger'));
act(() => {
jest.runAllTimers();
});
fireEvent.click(baseElement.querySelector('li.ant-dropdown-menu-item'));
expect(onChange).toHaveBeenLastCalledWith([]);
fireEvent.click(first(), {
shiftKey: true,
});
expect(onChange).toHaveBeenLastCalledWith([0]);
jest.useRealTimers();
});
it('fires selectAll event', () => {
const order = [];
const handleSelectAll = jest.fn().mockImplementation(() => {

File diff suppressed because it is too large Load Diff

View File

@ -16,8 +16,8 @@ To see if bordered style applied to other tables.
```tsx
import { DownOutlined } from '@ant-design/icons';
import type { TableColumnsType } from 'antd';
import { Badge, Dropdown, Form, Menu, Space, Switch, Table } from 'antd';
import type { ColumnsType } from 'antd/lib/table';
import React, { useState } from 'react';
interface DataType {
@ -93,7 +93,7 @@ const App: React.FC = () => {
return <Table columns={columns} dataSource={data} pagination={false} bordered={bordered} />;
};
const columns: ColumnsType<DataType> = [
const columns: TableColumnsType<DataType> = [
{ title: 'Name', dataIndex: 'name', key: 'name' },
{ title: 'Platform', dataIndex: 'platform', key: 'platform' },
{ title: 'Version', dataIndex: 'version', key: 'version' },
@ -135,7 +135,6 @@ const App: React.FC = () => {
<Table
title={() => 'cool'}
footer={() => 'cool'}
className="components-table-demo-nested"
columns={columns}
expandable={{ expandedRowRender: createExpandedRowRender(childTableBordered) }}
dataSource={data}

View File

@ -15,8 +15,8 @@ Showing more detailed info of every row.
```tsx
import { DownOutlined } from '@ant-design/icons';
import type { TableColumnsType } from 'antd';
import { Badge, Dropdown, Menu, Space, Table } from 'antd';
import type { ColumnsType } from 'antd/lib/table';
import React from 'react';
interface DataType {
@ -47,7 +47,7 @@ const menu = (
const App: React.FC = () => {
const expandedRowRender = () => {
const columns: ColumnsType<ExpandedDataType> = [
const columns: TableColumnsType<ExpandedDataType> = [
{ title: 'Date', dataIndex: 'date', key: 'date' },
{ title: 'Name', dataIndex: 'name', key: 'name' },
{
@ -82,7 +82,7 @@ const App: React.FC = () => {
const data = [];
for (let i = 0; i < 3; ++i) {
data.push({
key: i,
key: i.toString(),
date: '2014-12-24 23:12:00',
name: 'This is production name',
upgradeNum: 'Upgraded: 56',
@ -91,7 +91,7 @@ const App: React.FC = () => {
return <Table columns={columns} dataSource={data} pagination={false} />;
};
const columns: ColumnsType<DataType> = [
const columns: TableColumnsType<DataType> = [
{ title: 'Name', dataIndex: 'name', key: 'name' },
{ title: 'Platform', dataIndex: 'platform', key: 'platform' },
{ title: 'Version', dataIndex: 'version', key: 'version' },
@ -104,7 +104,7 @@ const App: React.FC = () => {
const data: DataType[] = [];
for (let i = 0; i < 3; ++i) {
data.push({
key: i,
key: i.toString(),
name: 'Screem',
platform: 'iOS',
version: '10.3.4.5654',
@ -115,12 +115,25 @@ const App: React.FC = () => {
}
return (
<>
<Table
className="components-table-demo-nested"
columns={columns}
expandable={{ expandedRowRender }}
expandable={{ expandedRowRender, defaultExpandedRowKeys: ['0'] }}
dataSource={data}
/>
<Table
columns={columns}
expandable={{ expandedRowRender, defaultExpandedRowKeys: ['0'] }}
dataSource={data}
size="middle"
/>
<Table
columns={columns}
expandable={{ expandedRowRender, defaultExpandedRowKeys: ['0'] }}
dataSource={data}
size="small"
/>
</>
);
};

View File

@ -271,7 +271,8 @@ export default function useSelection<RecordType>(
const selectionList: INTERNAL_SELECTION_ITEM[] =
selections === true ? [SELECTION_ALL, SELECTION_INVERT, SELECTION_NONE] : selections;
return selectionList.map((selection: INTERNAL_SELECTION_ITEM) => {
return selectionList
.map((selection: INTERNAL_SELECTION_ITEM) => {
if (selection === SELECTION_ALL) {
return {
key: 'all',
@ -339,7 +340,14 @@ export default function useSelection<RecordType>(
};
}
return selection as SelectionItem;
});
})
.map(selection => ({
...selection,
onSelect: (...rest) => {
selection.onSelect?.(...rest);
setLastSelectedKey(null);
},
}));
}, [selections, derivedSelectedKeySet, pageData, getRowKey, onSelectInvert, setSelectedKeys]);
// ======================= Columns ========================
@ -393,6 +401,7 @@ export default function useSelection<RecordType>(
);
setSelectedKeys(keys, 'all');
setLastSelectedKey(null);
};
// ===================== Render =====================
@ -605,7 +614,11 @@ export default function useSelection<RecordType>(
}
}
if (checked) {
setLastSelectedKey(null);
} else {
setLastSelectedKey(key);
}
}}
/>
),

View File

@ -10,12 +10,12 @@ const genExpandStyle: GenerateStyle<TableToken, CSSObject> = token => {
controlInteractiveSize: checkboxSize,
motionDurationSlow,
controlLineWidth,
padding,
paddingXXS,
paddingXS,
controlLineType,
tableBorderColor,
tableExpandIconBg,
tableExpandColumnWidth,
radiusBase,
tablePaddingVertical,
tablePaddingHorizontal,
@ -30,7 +30,7 @@ const genExpandStyle: GenerateStyle<TableToken, CSSObject> = token => {
return {
[`${componentCls}-wrapper`]: {
[`${componentCls}-expand-icon-col`]: {
width: checkboxSize + padding * 2,
width: tableExpandColumnWidth,
},
[`${componentCls}-row-expand-icon-cell`]: {

View File

@ -12,6 +12,7 @@
// @table-sticky-zindex: calc(@zindex-table-fixed + 1);
// @table-sticky-scroll-bar-active-bg: fade(@table-sticky-scroll-bar-bg, 80%);
// @table-filter-dropdown-max-height: 264px;
// @table-expand-column-width: 48px;
// .@{table-prefix-cls}-wrapper {
// clear: both;
@ -130,8 +131,8 @@
// > .@{table-prefix-cls}-wrapper:only-child,
// > .@{table-prefix-cls}-expanded-row-fixed > .@{table-prefix-cls}-wrapper:only-child {
// .@{table-prefix-cls} {
// margin: -@table-padding-vertical -@table-padding-horizontal -@table-padding-vertical (@table-padding-horizontal +
// ceil(@font-size-sm * 1.4));
// margin: -@table-padding-vertical -@table-padding-horizontal -@table-padding-vertical (@table-expand-column-width -
// @table-padding-horizontal);
// &-tbody > tr:last-child > td {
// border-bottom: 0;
@ -476,7 +477,7 @@
// // ========================== Expandable ==========================
// &-expand-icon-col {
// width: 48px;
// width: @table-expand-column-width;
// }
// &-row-expand-icon-cell {

View File

@ -57,6 +57,7 @@ export interface TableToken extends FullToken<'Table'> {
tableFontSizeSmall: number;
tableSelectionColumnWidth: number;
tableExpandIconBg: string;
tableExpandColumnWidth: number;
tableExpandedRowBg: string;
tableFilterDropdownWidth: number;
tableFilterDropdownSearchWidth: number;
@ -181,7 +182,9 @@ const genTableStyle: GenerateStyle<TableToken, CSSObject> = token => {
`]: {
[componentCls]: {
marginBlock: `-${tablePaddingVertical}px`,
marginInline: `${tablePaddingHorizontal * 2}px -${tablePaddingHorizontal}px`,
marginInline: `${
token.tableExpandColumnWidth - tablePaddingHorizontal
}px -${tablePaddingHorizontal}px`,
[`${componentCls}-tbody > tr:last-child > td`]: {
borderBottom: 0,
'&:first-child, &:last-child': {
@ -245,6 +248,7 @@ export default genComponentStyleHook(
radiusBase,
headerHoverBgColor,
headerSortActiveBgColor,
controlInteractiveSize: checkboxSize,
} = token;
const baseColorAction = new TinyColor(colorAction);
@ -305,6 +309,8 @@ export default genComponentStyleHook(
tableScrollThumbBg: colorTextPlaceholder,
tableScrollThumbBgHover: colorTextHeading,
tableScrollBg: colorSplit,
tableExpandColumnWidth: checkboxSize + 2 * token.padding,
});
return [

View File

@ -27,8 +27,8 @@
// // ========================= Nest Table ===========================
// .@{table-prefix-cls}-wrapper:only-child {
// .@{table-prefix-cls} {
// margin: -@padding-vertical -@padding-horizontal -@padding-vertical (@padding-horizontal +
// ceil((@font-size-sm * 1.4)));
// margin: -@padding-vertical -@padding-horizontal -@padding-vertical (@table-expand-column-width -
// @padding-horizontal);
// }
// }
// }

View File

@ -35,7 +35,9 @@ const genSizeStyle: GenerateStyle<TableToken, CSSObject> = token => {
// ========================= Nest Table ===========================
[`${componentCls}-wrapper:only-child ${componentCls}`]: {
marginBlock: `-${paddingVertical}px`,
marginInline: `${paddingHorizontal * 2}px -${paddingHorizontal}px`,
marginInline: `${
token.tableExpandColumnWidth - paddingHorizontal
}px -${paddingHorizontal}px`,
},
},

View File

@ -47,6 +47,7 @@ export interface TreeSelectProps<
| 'getInputElement'
| 'backfill'
| 'treeLine'
| 'switcherIcon'
> {
suffixIcon?: React.ReactNode;
size?: SizeType;
@ -55,7 +56,7 @@ export interface TreeSelectProps<
bordered?: boolean;
treeLine?: TreeProps['showLine'];
status?: InputStatus;
switcherIcon?: SwitcherIcon;
switcherIcon?: SwitcherIcon | RcTreeSelectProps<ValueType, OptionType>['switcherIcon'];
}
const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionType = BaseOptionType>(

View File

@ -104,7 +104,10 @@ interface DraggableConfig {
}
export interface TreeProps<T extends BasicDataNode = DataNode>
extends Omit<RcTreeProps<T>, 'prefixCls' | 'showLine' | 'direction' | 'draggable'> {
extends Omit<
RcTreeProps<T>,
'prefixCls' | 'showLine' | 'direction' | 'draggable' | 'icon' | 'switcherIcon'
> {
showLine?: boolean | { showLeafIcon: boolean };
className?: string;
/** 是否支持多选 */
@ -141,8 +144,11 @@ export interface TreeProps<T extends BasicDataNode = DataNode>
draggable?: DraggableFn | boolean | DraggableConfig;
style?: React.CSSProperties;
showIcon?: boolean;
icon?: ((nodeProps: AntdTreeNodeAttribute) => React.ReactNode) | React.ReactNode;
switcherIcon?: SwitcherIcon;
icon?:
| ((nodeProps: AntdTreeNodeAttribute) => React.ReactNode)
| React.ReactNode
| RcTreeProps<T>['icon'];
switcherIcon?: SwitcherIcon | RcTreeProps<T>['switcherIcon'];
prefixCls?: string;
children?: React.ReactNode;
blockNode?: boolean;

View File

@ -411,6 +411,8 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
</div>
);
const uploadButton = renderUploadButton(children ? undefined : { display: 'none' });
if (listType === 'picture-card') {
return wrapSSR(
<span
@ -422,14 +424,14 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
hashId,
)}
>
{renderUploadList(renderUploadButton(), !!children)}
{renderUploadList(uploadButton, !!children)}
</span>,
);
}
return wrapSSR(
<span className={classNames(`${prefixCls}-wrapper`, rtlCls, className, hashId)}>
{renderUploadButton(children ? undefined : { display: 'none' })}
{uploadButton}
{renderUploadList()}
</span>,
);

View File

@ -937,8 +937,11 @@ describe('Upload', () => {
);
rerender(<Upload listType="picture-card" />);
expect(container.querySelector('.ant-upload-select')).not.toHaveStyle({
display: 'none',
expect(container.querySelector('.ant-upload-select-picture-card')).toHaveClass(
'ant-upload-animate-inline-leave-start',
);
expect(container.querySelector('.ant-upload-select-picture-card')).toHaveStyle({
pointerEvents: 'none',
});
// Motion leave status change: start > active
@ -946,11 +949,10 @@ describe('Upload', () => {
jest.runAllTimers();
});
fireEvent.animationEnd(container.querySelector('.ant-upload-select'));
expect(container.querySelector('.ant-upload-select')).toHaveStyle({
display: 'none',
});
fireEvent.animationEnd(container.querySelector('.ant-upload-select-picture-card'));
expect(container.querySelector('.ant-upload-select-picture-card')).not.toHaveClass(
'ant-upload-animate-inline-leave-start',
);
jest.useRealTimers();
});

View File

@ -1499,4 +1499,43 @@ describe('Upload List', () => {
});
unmount();
});
describe('should not display upload file-select button when listType is picture-card and children is empty', () => {
it('when showUploadList is true', () => {
const list = [
{
uid: '0',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
},
];
const { container: wrapper, unmount } = render(
<Upload fileList={list} listType="picture-card" />,
);
expect(wrapper.querySelectorAll('.ant-upload-select').length).toBe(1);
expect(wrapper.querySelectorAll('.ant-upload-select')[0].style.display).toBe('none');
unmount();
});
// https://github.com/ant-design/ant-design/issues/36183
it('when showUploadList is false', () => {
const list = [
{
uid: '0',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
},
];
const { container: wrapper, unmount } = render(
<Upload fileList={list} showUploadList={false} listType="picture-card" />,
);
expect(wrapper.querySelectorAll('.ant-upload-select').length).toBe(1);
expect(wrapper.querySelectorAll('.ant-upload-select')[0].style.display).toBe('none');
unmount();
});
});
});

View File

@ -11,6 +11,16 @@ Here are the frequently asked questions about Ant Design and antd that you shoul
There is currently no plan to add support for Sass/Stylus(etc.) style files, but using tools on Google you can easily convert the provided Less files to your desired style format.
## Is there a difference between undefined and null in the controlled components of antd?
**Yes. antd will treats `undefined` as uncontrolled but `null` as controlled components which means empty value of it.**
As input element, React treats both `undefined` and `null` as uncontrolled. When the `value` is converted from a valid value to `undefined` or `null`, the component is no longer controlled, which causes some unexpected cases.
But in antd, `undefined` is treated as uncontrolled, and `null` is used as an explicit empty value of controlled components. To deal with some cases (e.g. `allowClear`) like clearing the `value` when the `value` is non-primitive. If you need a component controlled with the `value` valid, just set the `value` as `null`.
Note: For `options` in `Select-like` components, it is **strongly recommended not** to use `undefined` and `null` as `value` in `option`. Please use `string | number` as a valid `value` in `option`.
## `Select Dropdown DatePicker TimePicker Popover Popconfirm` disappears when I click another popup component inside it. How do I resolve this?
This is an old bug that has been fixed since `v3.11.x`. If you're using an older version, you can use `<Select getPopupContainer={trigger => trigger.parentElement}>` to render a component inside Popover. (Or other `getXxxxContainer` props)

View File

@ -11,6 +11,16 @@ title: FAQ
暂无计划。事实上你可以使用工具(请自行 Google将 Less 转换成 Sass/Stylus 等。
## `undefined``null``antd` 的受控组件中有区别吗?
**有。antd 约定:`undefined` 是非受控的标志,`null` 作为显式的受控空值。**
在输入元素中React 认为 `undefined``null` 都属于非受控的标志。当 `value` 由非空值转化为 `undefined``null` 时,组件不再受控,这通常是一些意外情况发生的原因。
但在 antd 中,我们定义 `undefined` 为非受控的标志,而 `null` 则作为显式的受控空值。为的是处理 `value` 为复杂数据类型时的清空(如 `allowClear`)置 `value` 为空值等场景。如果需要让组件受控且希望将 `value` 置为空值,请将 `value` 设置为 `null`
注意:对于类 `Select` 组件的 `options`,我们**强烈不建议**使用 `undefined``null` 作为 `option` 中的 `value`,请使用 `string | number` 作为 `option``value`
## 当我点击 `Select Dropdown DatePicker TimePicker Popover Popconfirm` 内的另一个 popup 组件时它会消失,如何解决?
该问题在 `3.11.0` 后已经解决。如果你仍在使用旧版本,你可以通过 `<Select getPopupContainer={trigger => trigger.parentElement}>` 来在 Popover 中渲染组件,或者使用其他的 `getXxxxContainer` 参数。
@ -85,9 +95,9 @@ antd 内部会对 props 进行浅比较实现性能优化。当状态变更,
- 2.x: https://ant-design-2x.gitee.io/
- 1.x: https://ant-design-1x.gitee.io/
## `antd` 会像 `React` 那样提供单文件引入吗?
## `antd` 可以像 `React` 那样使用单文件引入吗?
是的[你可以用 script 标签引入](https://ant.design/docs/react/introduce-cn#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%BC%95%E5%85%A5)。但是我们推荐使用 `npm` 来引入 `antd`,这样维护起来更简单方便。
可以[你可以用 script 标签引入](https://ant.design/docs/react/introduce-cn#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%BC%95%E5%85%A5)。但是我们推荐使用 `npm` 来引入 `antd`,这样维护起来更简单方便。
## 在我的网络环境下没法获取到 `icon` 文件。

View File

@ -118,7 +118,7 @@
"@ant-design/cssinjs": "^0.0.0-alpha.34",
"@ant-design/icons": "^4.7.0",
"@ant-design/react-slick": "~0.29.1",
"@babel/runtime": "^7.12.5",
"@babel/runtime": "^7.18.3",
"@ctrl/tinycolor": "^3.4.0",
"classnames": "^2.2.6",
"copy-to-clipboard": "^3.2.0",
@ -157,14 +157,14 @@
"rc-tree-select": "~5.4.0",
"rc-trigger": "^5.2.10",
"rc-upload": "~4.3.0",
"rc-util": "^5.22.3",
"rc-util": "^5.22.5",
"scroll-into-view-if-needed": "^2.2.25",
"shallowequal": "^1.1.0"
},
"devDependencies": {
"@ant-design/bisheng-plugin": "^3.3.0-alpha.4",
"@ant-design/hitu": "^0.0.0-alpha.13",
"@ant-design/tools": "^15.0.3",
"@ant-design/tools": "^15.0.4",
"@docsearch/css": "^3.0.0",
"@qixian.cs/github-contributors-list": "^1.0.3",
"@stackblitz/sdk": "^1.3.0",

View File

@ -15,7 +15,7 @@ export function preLoad(list: string[]) {
}
}
export function useSiteData<T>(): [T, boolean] {
export function useSiteData<T extends object>(): [T, boolean] {
const [data, setData] = React.useState<T>({} as any);
const [loading, setLoading] = React.useState<boolean>(false);

View File

@ -1,5 +1,5 @@
import flattenDeep from 'lodash/flattenDeep';
import flatten from 'lodash/flatten';
import flattenDeep from 'lodash/flattenDeep';
import themeConfig from '../../themeConfig';
interface Meta {
@ -212,7 +212,7 @@ export function getMetaDescription(jml?: any[] | null) {
.join('');
return [tag, content];
}),
).find(p => p && typeof p === 'string' && !COMMON_TAGS.includes(p));
).find(p => p && typeof p === 'string' && !COMMON_TAGS.includes(p)) as string;
return paragraph;
}