mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-02 03:59:01 +08:00
chore: merge master to next
This commit is contained in:
commit
a5fe576d48
@ -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>,
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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.',
|
||||
);
|
||||
|
@ -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 */}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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
File diff suppressed because it is too large
Load Diff
@ -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}
|
||||
|
@ -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 }}
|
||||
dataSource={data}
|
||||
/>
|
||||
<>
|
||||
<Table
|
||||
columns={columns}
|
||||
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"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -271,75 +271,83 @@ 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) => {
|
||||
if (selection === SELECTION_ALL) {
|
||||
return {
|
||||
key: 'all',
|
||||
text: tableLocale.selectionAll,
|
||||
onSelect() {
|
||||
setSelectedKeys(
|
||||
data
|
||||
.map((record, index) => getRowKey(record, index))
|
||||
.filter(key => {
|
||||
const checkProps = checkboxPropsMap.get(key);
|
||||
return !checkProps?.disabled || derivedSelectedKeySet.has(key);
|
||||
}),
|
||||
'all',
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
if (selection === SELECTION_INVERT) {
|
||||
return {
|
||||
key: 'invert',
|
||||
text: tableLocale.selectInvert,
|
||||
onSelect() {
|
||||
const keySet = new Set(derivedSelectedKeySet);
|
||||
pageData.forEach((record, index) => {
|
||||
const key = getRowKey(record, index);
|
||||
const checkProps = checkboxPropsMap.get(key);
|
||||
|
||||
if (!checkProps?.disabled) {
|
||||
if (keySet.has(key)) {
|
||||
keySet.delete(key);
|
||||
} else {
|
||||
keySet.add(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const keys = Array.from(keySet);
|
||||
if (onSelectInvert) {
|
||||
warning(
|
||||
false,
|
||||
'Table',
|
||||
'`onSelectInvert` will be removed in future. Please use `onChange` instead.',
|
||||
return selectionList
|
||||
.map((selection: INTERNAL_SELECTION_ITEM) => {
|
||||
if (selection === SELECTION_ALL) {
|
||||
return {
|
||||
key: 'all',
|
||||
text: tableLocale.selectionAll,
|
||||
onSelect() {
|
||||
setSelectedKeys(
|
||||
data
|
||||
.map((record, index) => getRowKey(record, index))
|
||||
.filter(key => {
|
||||
const checkProps = checkboxPropsMap.get(key);
|
||||
return !checkProps?.disabled || derivedSelectedKeySet.has(key);
|
||||
}),
|
||||
'all',
|
||||
);
|
||||
onSelectInvert(keys);
|
||||
}
|
||||
|
||||
setSelectedKeys(keys, 'invert');
|
||||
},
|
||||
};
|
||||
}
|
||||
if (selection === SELECTION_NONE) {
|
||||
return {
|
||||
key: 'none',
|
||||
text: tableLocale.selectNone,
|
||||
onSelect() {
|
||||
onSelectNone?.();
|
||||
setSelectedKeys(
|
||||
Array.from(derivedSelectedKeySet).filter(key => {
|
||||
},
|
||||
};
|
||||
}
|
||||
if (selection === SELECTION_INVERT) {
|
||||
return {
|
||||
key: 'invert',
|
||||
text: tableLocale.selectInvert,
|
||||
onSelect() {
|
||||
const keySet = new Set(derivedSelectedKeySet);
|
||||
pageData.forEach((record, index) => {
|
||||
const key = getRowKey(record, index);
|
||||
const checkProps = checkboxPropsMap.get(key);
|
||||
return checkProps?.disabled;
|
||||
}),
|
||||
'none',
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
return selection as SelectionItem;
|
||||
});
|
||||
|
||||
if (!checkProps?.disabled) {
|
||||
if (keySet.has(key)) {
|
||||
keySet.delete(key);
|
||||
} else {
|
||||
keySet.add(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const keys = Array.from(keySet);
|
||||
if (onSelectInvert) {
|
||||
warning(
|
||||
false,
|
||||
'Table',
|
||||
'`onSelectInvert` will be removed in future. Please use `onChange` instead.',
|
||||
);
|
||||
onSelectInvert(keys);
|
||||
}
|
||||
|
||||
setSelectedKeys(keys, 'invert');
|
||||
},
|
||||
};
|
||||
}
|
||||
if (selection === SELECTION_NONE) {
|
||||
return {
|
||||
key: 'none',
|
||||
text: tableLocale.selectNone,
|
||||
onSelect() {
|
||||
onSelectNone?.();
|
||||
setSelectedKeys(
|
||||
Array.from(derivedSelectedKeySet).filter(key => {
|
||||
const checkProps = checkboxPropsMap.get(key);
|
||||
return checkProps?.disabled;
|
||||
}),
|
||||
'none',
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
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>(
|
||||
}
|
||||
}
|
||||
|
||||
setLastSelectedKey(key);
|
||||
if (checked) {
|
||||
setLastSelectedKey(null);
|
||||
} else {
|
||||
setLastSelectedKey(key);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
|
@ -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`]: {
|
||||
|
@ -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 {
|
||||
|
@ -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 [
|
||||
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
@ -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`,
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -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>(
|
||||
|
@ -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;
|
||||
|
@ -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>,
|
||||
);
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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)
|
||||
|
@ -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` 文件。
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user