mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 12:39:49 +08:00
feat: Table filterMode and filterSearch functions (#31809)
* feat: Table supports filterMode="tree-select" * add tree component * fix detail * use tree * add @table-filter-dropdown-max-height * feat: add check all to filter tree * feat: add search * feat: use filterTreeNode * fix code style * fix style * style: tree node selected bg * fix demo * feat: add filterSearch * fix: clear search value after close filter dropdown * update snapshot * code style * fix test case * chore: new FilterDropdown.tsx file * chore: searchValueMatched function * add test case * test: add test cases * feat: reset only works on dropdown state now * chore: add table locales * fix search input width * tweak style * style: update transfer search input style * perf: improve table perf * fix: filterMuiltiple={false} * test: add test for selecting * chore: fix table filter selection * fix lint * remove unused code * fix: style dependencies * test: turn off bail config for duplidated-package-plugin in feature branch * Update components/table/hooks/useFilter/FilterSearch.tsx Co-authored-by: Peach <scdzwyxst@gmail.com> * fix locale link * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: Peach <scdzwyxst@gmail.com>
This commit is contained in:
parent
a43e21e6bf
commit
9c17f94cab
@ -53,6 +53,7 @@ export interface InputProps
|
|||||||
suffix?: React.ReactNode;
|
suffix?: React.ReactNode;
|
||||||
allowClear?: boolean;
|
allowClear?: boolean;
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
|
htmlSize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fixControlledValue<T>(value: T) {
|
export function fixControlledValue<T>(value: T) {
|
||||||
@ -272,7 +273,14 @@ class Input extends React.Component<InputProps, InputState> {
|
|||||||
bordered: boolean,
|
bordered: boolean,
|
||||||
input: ConfigConsumerProps['input'] = {},
|
input: ConfigConsumerProps['input'] = {},
|
||||||
) => {
|
) => {
|
||||||
const { className, addonBefore, addonAfter, size: customizeSize, disabled } = this.props;
|
const {
|
||||||
|
className,
|
||||||
|
addonBefore,
|
||||||
|
addonAfter,
|
||||||
|
size: customizeSize,
|
||||||
|
disabled,
|
||||||
|
htmlSize,
|
||||||
|
} = this.props;
|
||||||
// Fix https://fb.me/react-unknown-prop
|
// Fix https://fb.me/react-unknown-prop
|
||||||
const otherProps = omit(this.props as InputProps & { inputType: any }, [
|
const otherProps = omit(this.props as InputProps & { inputType: any }, [
|
||||||
'prefixCls',
|
'prefixCls',
|
||||||
@ -288,6 +296,7 @@ class Input extends React.Component<InputProps, InputState> {
|
|||||||
'size',
|
'size',
|
||||||
'inputType',
|
'inputType',
|
||||||
'bordered',
|
'bordered',
|
||||||
|
'htmlSize',
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
@ -304,6 +313,7 @@ class Input extends React.Component<InputProps, InputState> {
|
|||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
ref={this.saveInput}
|
ref={this.saveInput}
|
||||||
|
size={htmlSize}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,8 @@ const localeValues: Locale = {
|
|||||||
filterConfirm: 'OK',
|
filterConfirm: 'OK',
|
||||||
filterReset: 'Reset',
|
filterReset: 'Reset',
|
||||||
filterEmptyText: 'No filters',
|
filterEmptyText: 'No filters',
|
||||||
|
filterCheckall: 'Select all items',
|
||||||
|
filterSearchPlaceholder: 'Search in filters',
|
||||||
emptyText: 'No data',
|
emptyText: 'No data',
|
||||||
selectAll: 'Select current page',
|
selectAll: 'Select current page',
|
||||||
selectInvert: 'Invert current page',
|
selectInvert: 'Invert current page',
|
||||||
|
@ -22,6 +22,8 @@ const localeValues: Locale = {
|
|||||||
filterConfirm: '确定',
|
filterConfirm: '确定',
|
||||||
filterReset: '重置',
|
filterReset: '重置',
|
||||||
filterEmptyText: '无筛选项',
|
filterEmptyText: '无筛选项',
|
||||||
|
filterCheckall: '全选',
|
||||||
|
filterSearchPlaceholder: '在筛选项中搜索',
|
||||||
selectAll: '全选当页',
|
selectAll: '全选当页',
|
||||||
selectInvert: '反选当页',
|
selectInvert: '反选当页',
|
||||||
selectNone: '清空所有',
|
selectNone: '清空所有',
|
||||||
|
@ -7,7 +7,10 @@ import Input from '../../input';
|
|||||||
import Tooltip from '../../tooltip';
|
import Tooltip from '../../tooltip';
|
||||||
import Button from '../../button';
|
import Button from '../../button';
|
||||||
import Select from '../../select';
|
import Select from '../../select';
|
||||||
|
import Tree from '../../tree';
|
||||||
import ConfigProvider from '../../config-provider';
|
import ConfigProvider from '../../config-provider';
|
||||||
|
import Checkbox from '../../checkbox';
|
||||||
|
import Menu from '../../menu';
|
||||||
|
|
||||||
// https://github.com/Semantic-Org/Semantic-UI-React/blob/72c45080e4f20b531fda2e3e430e384083d6766b/test/specs/modules/Dropdown/Dropdown-test.js#L73
|
// https://github.com/Semantic-Org/Semantic-UI-React/blob/72c45080e4f20b531fda2e3e430e384083d6766b/test/specs/modules/Dropdown/Dropdown-test.js#L73
|
||||||
const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => {} } };
|
const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => {} } };
|
||||||
@ -205,6 +208,7 @@ describe('Table.filter', () => {
|
|||||||
wrapper.find('#confirm').simulate('click');
|
wrapper.find('#confirm').simulate('click');
|
||||||
expect(getFilterMenu().props().filterState.filteredKeys).toEqual([42]);
|
expect(getFilterMenu().props().filterState.filteredKeys).toEqual([42]);
|
||||||
wrapper.find('#reset').simulate('click');
|
wrapper.find('#reset').simulate('click');
|
||||||
|
wrapper.find('#confirm').simulate('click');
|
||||||
expect(getFilterMenu().props().filterState.filteredKeys).toBeFalsy();
|
expect(getFilterMenu().props().filterState.filteredKeys).toBeFalsy();
|
||||||
|
|
||||||
// try to use confirm btn
|
// try to use confirm btn
|
||||||
@ -688,15 +692,21 @@ describe('Table.filter', () => {
|
|||||||
const wrapper = mount(<App />);
|
const wrapper = mount(<App />);
|
||||||
|
|
||||||
wrapper.find('.ant-dropdown-trigger').first().simulate('click');
|
wrapper.find('.ant-dropdown-trigger').first().simulate('click');
|
||||||
|
expect(wrapper.find('Dropdown').first().props().visible).toBe(true);
|
||||||
wrapper.find('MenuItem').first().simulate('click');
|
wrapper.find('MenuItem').first().simulate('click');
|
||||||
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-primary').simulate('click');
|
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-primary').simulate('click');
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
expect(wrapper.find('Dropdown').first().props().visible).toBe(false);
|
||||||
expect(renderedNames(wrapper)).toEqual(['Jack']);
|
expect(renderedNames(wrapper)).toEqual(['Jack']);
|
||||||
|
|
||||||
|
wrapper.find('.ant-dropdown-trigger').first().simulate('click');
|
||||||
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-link').simulate('click');
|
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-link').simulate('click');
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
expect(wrapper.find('Dropdown').first().props().visible).toBe(true);
|
||||||
|
expect(renderedNames(wrapper)).toEqual(['Jack']);
|
||||||
|
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-primary').simulate('click');
|
||||||
expect(renderedNames(wrapper)).toEqual(['Jack', 'Lucy', 'Tom', 'Jerry']);
|
expect(renderedNames(wrapper)).toEqual(['Jack', 'Lucy', 'Tom', 'Jerry']);
|
||||||
|
expect(wrapper.find('Dropdown').first().props().visible).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with grouping columns in controlled mode', () => {
|
it('works with grouping columns in controlled mode', () => {
|
||||||
@ -1355,6 +1365,8 @@ describe('Table.filter', () => {
|
|||||||
{
|
{
|
||||||
...column,
|
...column,
|
||||||
filterDropdownVisible: true,
|
filterDropdownVisible: true,
|
||||||
|
filterSearch: true,
|
||||||
|
filterMode: 'tree',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@ -1366,6 +1378,12 @@ describe('Table.filter', () => {
|
|||||||
expect(wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-link').last().text()).toEqual(
|
expect(wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-link').last().text()).toEqual(
|
||||||
'Reset',
|
'Reset',
|
||||||
);
|
);
|
||||||
|
expect(wrapper.find('.ant-table-filter-dropdown-checkall').first().text()).toEqual(
|
||||||
|
'Select all items',
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.ant-input').getDOMNode().getAttribute('placeholder')).toEqual(
|
||||||
|
'Search in filters',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('filtered should work', () => {
|
it('filtered should work', () => {
|
||||||
@ -1456,7 +1474,7 @@ describe('Table.filter', () => {
|
|||||||
expect(checkbox.props().checked).toEqual(false);
|
expect(checkbox.props().checked).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not trigger onChange when filter is empty', () => {
|
it('should not trigger onChange when filters is empty', () => {
|
||||||
const onChange = jest.fn();
|
const onChange = jest.fn();
|
||||||
const Test = ({ filters }) => (
|
const Test = ({ filters }) => (
|
||||||
<Table
|
<Table
|
||||||
@ -1726,4 +1744,312 @@ describe('Table.filter', () => {
|
|||||||
wrapper.find('.ant-btn.ant-btn-primary.ant-btn-sm').simulate('click');
|
wrapper.find('.ant-btn.ant-btn-primary.ant-btn-sm').simulate('click');
|
||||||
expect(wrapper.find('.ant-table-tbody .ant-table-cell').first().text()).toEqual(`${66}`);
|
expect(wrapper.find('.ant-table-tbody .ant-table-cell').first().text()).toEqual(`${66}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('filter tree mode', () => {
|
||||||
|
it('supports filter tree', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
const wrapper = mount(
|
||||||
|
createTable({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
...column,
|
||||||
|
filterMode: 'tree',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
|
||||||
|
act(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
wrapper.update();
|
||||||
|
});
|
||||||
|
expect(wrapper.find(Tree).length).toBe(1);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').length).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports search input in filter tree', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
const wrapper = mount(
|
||||||
|
createTable({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
...column,
|
||||||
|
filterMode: 'tree',
|
||||||
|
filterSearch: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
|
||||||
|
act(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
wrapper.update();
|
||||||
|
});
|
||||||
|
expect(wrapper.find(Tree).length).toBe(1);
|
||||||
|
expect(wrapper.find(Input).length).toBe(1);
|
||||||
|
wrapper
|
||||||
|
.find(Input)
|
||||||
|
.find('input')
|
||||||
|
.simulate('change', { target: { value: '111' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports search input in filter menu', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
const wrapper = mount(
|
||||||
|
createTable({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
...column,
|
||||||
|
filterSearch: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
|
||||||
|
act(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
wrapper.update();
|
||||||
|
});
|
||||||
|
expect(wrapper.find(Menu).length).toBe(1);
|
||||||
|
expect(wrapper.find(Input).length).toBe(1);
|
||||||
|
wrapper
|
||||||
|
.find(Input)
|
||||||
|
.find('input')
|
||||||
|
.simulate('change', { target: { value: '111' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip search when filters[0].text is ReactNode', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
const wrapper = mount(
|
||||||
|
createTable({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
...column,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: '123',
|
||||||
|
value: '456',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 123456,
|
||||||
|
value: '456',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: <span>123</span>,
|
||||||
|
value: '456',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
filterSearch: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
|
||||||
|
act(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
wrapper.update();
|
||||||
|
});
|
||||||
|
expect(wrapper.find(Menu).length).toBe(1);
|
||||||
|
expect(wrapper.find(Input).length).toBe(1);
|
||||||
|
expect(wrapper.find('li.ant-dropdown-menu-item').length).toBe(3);
|
||||||
|
wrapper
|
||||||
|
.find(Input)
|
||||||
|
.find('input')
|
||||||
|
.simulate('change', { target: { value: '123' } });
|
||||||
|
expect(wrapper.find('li.ant-dropdown-menu-item').length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports check all items', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
const wrapper = mount(
|
||||||
|
createTable({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
...column,
|
||||||
|
filterMode: 'tree',
|
||||||
|
filterSearch: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
|
||||||
|
act(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
wrapper.update();
|
||||||
|
});
|
||||||
|
expect(wrapper.find(Checkbox).length).toBe(1);
|
||||||
|
expect(wrapper.find(Checkbox).text()).toBe('Select all items');
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(0);
|
||||||
|
wrapper
|
||||||
|
.find(Checkbox)
|
||||||
|
.find('input')
|
||||||
|
.simulate('change', { target: { checked: true } });
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(5);
|
||||||
|
wrapper
|
||||||
|
.find(Checkbox)
|
||||||
|
.find('input')
|
||||||
|
.simulate('change', { target: { checked: false } });
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports check item by selecting it', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
const wrapper = mount(
|
||||||
|
createTable({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
...column,
|
||||||
|
filterMode: 'tree',
|
||||||
|
filterSearch: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
|
||||||
|
act(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
wrapper.update();
|
||||||
|
});
|
||||||
|
expect(wrapper.find(Checkbox).length).toBe(1);
|
||||||
|
expect(wrapper.find(Checkbox).text()).toBe('Select all items');
|
||||||
|
wrapper.find('.ant-tree-node-content-wrapper').at(0).simulate('click');
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').at(0).hasClass('ant-tree-checkbox-checked')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filterMultiple is false - check item', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
const wrapper = mount(
|
||||||
|
createTable({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
...column,
|
||||||
|
filterMode: 'tree',
|
||||||
|
filterMultiple: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
|
||||||
|
act(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
wrapper.update();
|
||||||
|
});
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').length).toBe(5);
|
||||||
|
expect(wrapper.find('.ant-table-filter-dropdown-checkall').exists()).toBe(false);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(0);
|
||||||
|
wrapper.find('.ant-tree-checkbox').at(2).simulate('click');
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').at(2).hasClass('ant-tree-checkbox-checked')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(1);
|
||||||
|
wrapper.find('.ant-tree-checkbox').at(1).simulate('click');
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').at(1).hasClass('ant-tree-checkbox-checked')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(1);
|
||||||
|
wrapper.find('.ant-tree-checkbox').at(1).simulate('click');
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').at(1).hasClass('ant-tree-checkbox-checked')).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filterMultiple is false - select item', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
const wrapper = mount(
|
||||||
|
createTable({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
...column,
|
||||||
|
filterMode: 'tree',
|
||||||
|
filterMultiple: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
|
||||||
|
act(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
wrapper.update();
|
||||||
|
});
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').length).toBe(5);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(0);
|
||||||
|
wrapper.find('.ant-tree-node-content-wrapper').at(2).simulate('click');
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').at(2).hasClass('ant-tree-checkbox-checked')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(1);
|
||||||
|
wrapper.find('.ant-tree-node-content-wrapper').at(1).simulate('click');
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').at(1).hasClass('ant-tree-checkbox-checked')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(1);
|
||||||
|
wrapper.find('.ant-tree-node-content-wrapper').at(1).simulate('click');
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').at(1).hasClass('ant-tree-checkbox-checked')).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox-checked').length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select children when select parent', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
const wrapper = mount(
|
||||||
|
createTable({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
...column,
|
||||||
|
filters: [
|
||||||
|
{ text: 'Boy', value: 'boy' },
|
||||||
|
{ text: 'Girl', value: 'girl' },
|
||||||
|
{
|
||||||
|
text: 'Title',
|
||||||
|
value: 'title',
|
||||||
|
children: [
|
||||||
|
{ text: 'Jack', value: 'Jack' },
|
||||||
|
{ text: 'Coder', value: 'coder' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
filterMode: 'tree',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
|
||||||
|
act(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
wrapper.update();
|
||||||
|
});
|
||||||
|
// check parentnode
|
||||||
|
wrapper.find('.ant-tree-checkbox').at(2).simulate('click');
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').at(2).hasClass('ant-tree-checkbox-checked')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').at(3).hasClass('ant-tree-checkbox-checked')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.ant-tree-checkbox').at(4).hasClass('ant-tree-checkbox-checked')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-primary').simulate('click');
|
||||||
|
expect(renderedNames(wrapper)).toEqual(['Jack']);
|
||||||
|
wrapper.find('.ant-tree-checkbox').at(2).simulate('click');
|
||||||
|
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-primary').simulate('click');
|
||||||
|
expect(renderedNames(wrapper)).toEqual(['Jack', 'Lucy', 'Tom', 'Jerry']);
|
||||||
|
wrapper.find('.ant-tree-node-content-wrapper').at(2).simulate('click');
|
||||||
|
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-primary').simulate('click');
|
||||||
|
expect(renderedNames(wrapper)).toEqual(['Jack']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -5585,6 +5585,351 @@ exports[`renders ./components/table/demo/expand.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/table/demo/filter-in-tree.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-table-wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-spin-nested-loading"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-spin-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-table"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-table-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-table-content"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
style="table-layout:auto"
|
||||||
|
>
|
||||||
|
<colgroup>
|
||||||
|
<col
|
||||||
|
style="width:30%"
|
||||||
|
/>
|
||||||
|
<col />
|
||||||
|
<col
|
||||||
|
style="width:40%"
|
||||||
|
/>
|
||||||
|
</colgroup>
|
||||||
|
<thead
|
||||||
|
class="ant-table-thead"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-table-filter-column"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-table-column-title"
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-dropdown-trigger ant-table-filter-trigger"
|
||||||
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="filter"
|
||||||
|
class="anticon anticon-filter"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="filter"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M349 838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V642H349v196zm531.1-684H143.9c-24.5 0-39.8 26.7-27.5 48l221.3 376h348.8l221.3-376c12.1-21.3-3.2-48-27.7-48z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ant-table-cell ant-table-column-has-sorters"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-table-column-sorters"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-table-column-title"
|
||||||
|
>
|
||||||
|
Age
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-table-column-sorter ant-table-column-sorter-full"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-table-column-sorter-inner"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="caret-up"
|
||||||
|
class="anticon anticon-caret-up ant-table-column-sorter-up"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="caret-up"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M858.9 689L530.5 308.2c-9.4-10.9-27.5-10.9-37 0L165.1 689c-12.2 14.2-1.2 35 18.5 35h656.8c19.7 0 30.7-20.8 18.5-35z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
aria-label="caret-down"
|
||||||
|
class="anticon anticon-caret-down ant-table-column-sorter-down"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="caret-down"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-table-filter-column"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-table-column-title"
|
||||||
|
>
|
||||||
|
Address
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-dropdown-trigger ant-table-filter-trigger"
|
||||||
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="filter"
|
||||||
|
class="anticon anticon-filter"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="filter"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M349 838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V642H349v196zm531.1-684H143.9c-24.5 0-39.8 26.7-27.5 48l221.3 376h348.8l221.3-376c12.1-21.3-3.2-48-27.7-48z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
class="ant-table-tbody"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="ant-table-row ant-table-row-level-0"
|
||||||
|
data-row-key="1"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
John Brown
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
32
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
New York No. 1 Lake Park
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
class="ant-table-row ant-table-row-level-0"
|
||||||
|
data-row-key="2"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
Jim Green
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
42
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
London No. 1 Lake Park
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
class="ant-table-row ant-table-row-level-0"
|
||||||
|
data-row-key="3"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
Joe Black
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
32
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
Sidney No. 1 Lake Park
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
class="ant-table-row ant-table-row-level-0"
|
||||||
|
data-row-key="4"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
Jim Red
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
32
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="ant-table-cell"
|
||||||
|
>
|
||||||
|
London No. 2 Lake Park
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
class="ant-pagination ant-table-pagination ant-table-pagination-right"
|
||||||
|
unselectable="unselectable"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
aria-disabled="true"
|
||||||
|
class="ant-pagination-prev ant-pagination-disabled"
|
||||||
|
title="Previous Page"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-pagination-item-link"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="left"
|
||||||
|
class="anticon anticon-left"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="left"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
|
||||||
|
tabindex="0"
|
||||||
|
title="1"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
rel="nofollow"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
aria-disabled="true"
|
||||||
|
class="ant-pagination-next ant-pagination-disabled"
|
||||||
|
title="Next Page"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-pagination-item-link"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="right"
|
||||||
|
class="anticon anticon-right"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="right"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/table/demo/fixed-columns.md correctly 1`] = `
|
exports[`renders ./components/table/demo/fixed-columns.md correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ant-table-wrapper"
|
class="ant-table-wrapper"
|
||||||
|
122
components/table/demo/filter-in-tree.md
Normal file
122
components/table/demo/filter-in-tree.md
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
---
|
||||||
|
order: 6.1
|
||||||
|
title:
|
||||||
|
en-US: Filter in Tree
|
||||||
|
zh-CN: 树型筛选菜单
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
可以使用 `filterMode` 来修改筛选菜单的 UI,可选值有 `menu`(默认)和 `tree`。
|
||||||
|
|
||||||
|
> `filterSearch` 用于开启筛选项的搜索。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
You can use `filterMode` to change default filter interface, options: `menu`(default) and `tree`.
|
||||||
|
|
||||||
|
> `filterSearch` is used for making filter dropdown items searchable.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Table } from 'antd';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: 'Joe',
|
||||||
|
value: 'Joe',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Category 1',
|
||||||
|
value: 'Category 1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: 'Yellow',
|
||||||
|
value: 'Yellow',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Pink',
|
||||||
|
value: 'Pink',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Category 2',
|
||||||
|
value: 'Category 2',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: 'Green',
|
||||||
|
value: 'Green',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Black',
|
||||||
|
value: 'Black',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
filterMode: 'tree',
|
||||||
|
filterSearch: true,
|
||||||
|
onFilter: (value, record) => record.name.includes(value),
|
||||||
|
width: '30%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Age',
|
||||||
|
dataIndex: 'age',
|
||||||
|
sorter: (a, b) => a.age - b.age,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Address',
|
||||||
|
dataIndex: 'address',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: 'London',
|
||||||
|
value: 'London',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'New York',
|
||||||
|
value: 'New York',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onFilter: (value, record) => record.address.startsWith(value),
|
||||||
|
filterSearch: true,
|
||||||
|
width: '40%',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
name: 'John Brown',
|
||||||
|
age: 32,
|
||||||
|
address: 'New York No. 1 Lake Park',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
name: 'Jim Green',
|
||||||
|
age: 42,
|
||||||
|
address: 'London No. 1 Lake Park',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
name: 'Joe Black',
|
||||||
|
age: 32,
|
||||||
|
address: 'Sidney No. 1 Lake Park',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
name: 'Jim Red',
|
||||||
|
age: 32,
|
||||||
|
address: 'London No. 2 Lake Park',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function onChange(pagination, filters, sorter, extra) {
|
||||||
|
console.log('params', pagination, filters, sorter, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(<Table columns={columns} dataSource={data} onChange={onChange} />, mountNode);
|
||||||
|
```
|
@ -4,62 +4,50 @@ import isEqual from 'lodash/isEqual';
|
|||||||
import FilterFilled from '@ant-design/icons/FilterFilled';
|
import FilterFilled from '@ant-design/icons/FilterFilled';
|
||||||
import Button from '../../../button';
|
import Button from '../../../button';
|
||||||
import Menu from '../../../menu';
|
import Menu from '../../../menu';
|
||||||
|
import Tree from '../../../tree';
|
||||||
|
import type { DataNode, EventDataNode } from '../../../tree';
|
||||||
import Checkbox from '../../../checkbox';
|
import Checkbox from '../../../checkbox';
|
||||||
|
import type { CheckboxChangeEvent } from '../../../checkbox';
|
||||||
import Radio from '../../../radio';
|
import Radio from '../../../radio';
|
||||||
import Dropdown from '../../../dropdown';
|
import Dropdown from '../../../dropdown';
|
||||||
import Empty from '../../../empty';
|
import Empty from '../../../empty';
|
||||||
import { ColumnType, ColumnFilterItem, Key, TableLocale, GetPopupContainer } from '../../interface';
|
import { ColumnType, ColumnFilterItem, Key, TableLocale, GetPopupContainer } from '../../interface';
|
||||||
import FilterDropdownMenuWrapper from './FilterWrapper';
|
import FilterDropdownMenuWrapper from './FilterWrapper';
|
||||||
import { FilterState } from '.';
|
import FilterSearch from './FilterSearch';
|
||||||
|
import { FilterState, flattenKeys } from '.';
|
||||||
import useSyncState from '../../../_util/hooks/useSyncState';
|
import useSyncState from '../../../_util/hooks/useSyncState';
|
||||||
import { ConfigContext } from '../../../config-provider/context';
|
import { ConfigContext } from '../../../config-provider/context';
|
||||||
|
|
||||||
const { SubMenu, Item: MenuItem } = Menu;
|
|
||||||
|
|
||||||
function hasSubMenu(filters: ColumnFilterItem[]) {
|
function hasSubMenu(filters: ColumnFilterItem[]) {
|
||||||
return filters.some(({ children }) => children);
|
return filters.some(({ children }) => children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function searchValueMatched(searchValue: string, text: React.ReactNode) {
|
||||||
|
if (typeof text === 'string' || typeof text === 'number') {
|
||||||
|
return text?.toString().toLowerCase().includes(searchValue.trim().toLowerCase());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function renderFilterItems({
|
function renderFilterItems({
|
||||||
filters,
|
filters,
|
||||||
prefixCls,
|
prefixCls,
|
||||||
filteredKeys,
|
filteredKeys,
|
||||||
filterMultiple,
|
filterMultiple,
|
||||||
locale,
|
searchValue,
|
||||||
}: {
|
}: {
|
||||||
filters: ColumnFilterItem[];
|
filters: ColumnFilterItem[];
|
||||||
prefixCls: string;
|
prefixCls: string;
|
||||||
filteredKeys: Key[];
|
filteredKeys: Key[];
|
||||||
filterMultiple: boolean;
|
filterMultiple: boolean;
|
||||||
locale: TableLocale;
|
searchValue: string;
|
||||||
}) {
|
}) {
|
||||||
if (filters.length === 0) {
|
|
||||||
// wrapped with <div /> to avoid react warning
|
|
||||||
// https://github.com/ant-design/ant-design/issues/25979
|
|
||||||
return (
|
|
||||||
<MenuItem key="empty">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
margin: '16px 0',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Empty
|
|
||||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
||||||
description={locale.filterEmptyText}
|
|
||||||
imageStyle={{
|
|
||||||
height: 24,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return filters.map((filter, index) => {
|
return filters.map((filter, index) => {
|
||||||
const key = String(filter.value);
|
const key = String(filter.value);
|
||||||
|
|
||||||
if (filter.children) {
|
if (filter.children) {
|
||||||
return (
|
return (
|
||||||
<SubMenu
|
<Menu.SubMenu
|
||||||
key={key || index}
|
key={key || index}
|
||||||
title={filter.text}
|
title={filter.text}
|
||||||
popupClassName={`${prefixCls}-dropdown-submenu`}
|
popupClassName={`${prefixCls}-dropdown-submenu`}
|
||||||
@ -69,20 +57,24 @@ function renderFilterItems({
|
|||||||
prefixCls,
|
prefixCls,
|
||||||
filteredKeys,
|
filteredKeys,
|
||||||
filterMultiple,
|
filterMultiple,
|
||||||
locale,
|
searchValue,
|
||||||
})}
|
})}
|
||||||
</SubMenu>
|
</Menu.SubMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Component = filterMultiple ? Checkbox : Radio;
|
const Component = filterMultiple ? Checkbox : Radio;
|
||||||
|
|
||||||
return (
|
const item = (
|
||||||
<MenuItem key={filter.value !== undefined ? key : index}>
|
<Menu.Item key={filter.value !== undefined ? key : index}>
|
||||||
<Component checked={filteredKeys.includes(key)} />
|
<Component checked={filteredKeys.includes(key)} />
|
||||||
<span>{filter.text}</span>
|
<span>{filter.text}</span>
|
||||||
</MenuItem>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
if (searchValue.trim()) {
|
||||||
|
return searchValueMatched(searchValue, filter.text) ? item : undefined;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +85,8 @@ export interface FilterDropdownProps<RecordType> {
|
|||||||
column: ColumnType<RecordType>;
|
column: ColumnType<RecordType>;
|
||||||
filterState?: FilterState<RecordType>;
|
filterState?: FilterState<RecordType>;
|
||||||
filterMultiple: boolean;
|
filterMultiple: boolean;
|
||||||
|
filterMode?: 'menu' | 'tree';
|
||||||
|
filterSearch?: boolean;
|
||||||
columnKey: Key;
|
columnKey: Key;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
triggerFilter: (filterState: FilterState<RecordType>) => void;
|
triggerFilter: (filterState: FilterState<RecordType>) => void;
|
||||||
@ -108,6 +102,8 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
|
|||||||
dropdownPrefixCls,
|
dropdownPrefixCls,
|
||||||
columnKey,
|
columnKey,
|
||||||
filterMultiple,
|
filterMultiple,
|
||||||
|
filterMode = 'menu',
|
||||||
|
filterSearch = false,
|
||||||
filterState,
|
filterState,
|
||||||
triggerFilter,
|
triggerFilter,
|
||||||
locale,
|
locale,
|
||||||
@ -134,11 +130,22 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
|
|||||||
const propFilteredKeys = filterState?.filteredKeys;
|
const propFilteredKeys = filterState?.filteredKeys;
|
||||||
const [getFilteredKeysSync, setFilteredKeysSync] = useSyncState(propFilteredKeys || []);
|
const [getFilteredKeysSync, setFilteredKeysSync] = useSyncState(propFilteredKeys || []);
|
||||||
|
|
||||||
const onSelectKeys = ({ selectedKeys }: { selectedKeys?: Key[] }) => {
|
const onSelectKeys = ({ selectedKeys }: { selectedKeys: Key[] }) => {
|
||||||
setFilteredKeysSync(selectedKeys!);
|
setFilteredKeysSync(selectedKeys);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCheck = (keys: Key[], { node, checked }: { node: EventDataNode; checked: boolean }) => {
|
||||||
|
if (!filterMultiple) {
|
||||||
|
onSelectKeys({ selectedKeys: checked && node.key ? [node.key] : [] });
|
||||||
|
} else {
|
||||||
|
onSelectKeys({ selectedKeys: keys as Key[] });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
onSelectKeys({ selectedKeys: propFilteredKeys || [] });
|
onSelectKeys({ selectedKeys: propFilteredKeys || [] });
|
||||||
}, [propFilteredKeys]);
|
}, [propFilteredKeys]);
|
||||||
|
|
||||||
@ -160,6 +167,19 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// search in tree mode column filter
|
||||||
|
const [searchValue, setSearchValue] = React.useState('');
|
||||||
|
const onSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
setSearchValue(value);
|
||||||
|
};
|
||||||
|
// clear search value after close filter dropdown
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!visible) {
|
||||||
|
setSearchValue('');
|
||||||
|
}
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
// ======================= Submit ========================
|
// ======================= Submit ========================
|
||||||
const internalTriggerFilter = (keys: Key[] | undefined | null) => {
|
const internalTriggerFilter = (keys: Key[] | undefined | null) => {
|
||||||
const mergedKeys = keys && keys.length ? keys : null;
|
const mergedKeys = keys && keys.length ? keys : null;
|
||||||
@ -184,9 +204,8 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
|
setSearchValue('');
|
||||||
setFilteredKeysSync([]);
|
setFilteredKeysSync([]);
|
||||||
triggerVisible(false);
|
|
||||||
internalTriggerFilter([]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const doFilter = ({ closeDropdown } = { closeDropdown: true }) => {
|
const doFilter = ({ closeDropdown } = { closeDropdown: true }) => {
|
||||||
@ -215,8 +234,29 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
|
|||||||
[`${dropdownPrefixCls}-menu-without-submenu`]: !hasSubMenu(column.filters || []),
|
[`${dropdownPrefixCls}-menu-without-submenu`]: !hasSubMenu(column.filters || []),
|
||||||
});
|
});
|
||||||
|
|
||||||
let dropdownContent: React.ReactNode;
|
const onCheckAll = (e: CheckboxChangeEvent) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
const allFilterKeys = flattenKeys(column?.filters).map(key => String(key));
|
||||||
|
setFilteredKeysSync(allFilterKeys);
|
||||||
|
} else {
|
||||||
|
setFilteredKeysSync([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTreeData = ({ filters }: { filters?: ColumnFilterItem[] }) =>
|
||||||
|
(filters || []).map((filter, index) => {
|
||||||
|
const key = String(filter.value);
|
||||||
|
const item: DataNode = {
|
||||||
|
title: filter.text,
|
||||||
|
key: filter.value !== undefined ? key : index,
|
||||||
|
};
|
||||||
|
if (filter.children) {
|
||||||
|
item.children = getTreeData({ filters: filter.children });
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
let dropdownContent: React.ReactNode;
|
||||||
if (typeof column.filterDropdown === 'function') {
|
if (typeof column.filterDropdown === 'function') {
|
||||||
dropdownContent = column.filterDropdown({
|
dropdownContent = column.filterDropdown({
|
||||||
prefixCls: `${dropdownPrefixCls}-custom`,
|
prefixCls: `${dropdownPrefixCls}-custom`,
|
||||||
@ -231,28 +271,101 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
|
|||||||
dropdownContent = column.filterDropdown;
|
dropdownContent = column.filterDropdown;
|
||||||
} else {
|
} else {
|
||||||
const selectedKeys = (getFilteredKeysSync() || []) as any;
|
const selectedKeys = (getFilteredKeysSync() || []) as any;
|
||||||
|
const getFilterComponent = () => {
|
||||||
|
if ((column.filters || []).length === 0) {
|
||||||
|
return (
|
||||||
|
<Empty
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
description={locale.filterEmptyText}
|
||||||
|
imageStyle={{
|
||||||
|
height: 24,
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
padding: '16px 0',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (filterMode === 'tree') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterSearch
|
||||||
|
filterSearch={filterSearch}
|
||||||
|
value={searchValue}
|
||||||
|
onChange={onSearch}
|
||||||
|
tablePrefixCls={tablePrefixCls}
|
||||||
|
locale={locale}
|
||||||
|
/>
|
||||||
|
<div className={`${tablePrefixCls}-filter-dropdown-tree`}>
|
||||||
|
{filterMultiple ? (
|
||||||
|
<Checkbox
|
||||||
|
className={`${tablePrefixCls}-filter-dropdown-checkall`}
|
||||||
|
onChange={onCheckAll}
|
||||||
|
>
|
||||||
|
{locale.filterCheckall}
|
||||||
|
</Checkbox>
|
||||||
|
) : null}
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
selectable={false}
|
||||||
|
blockNode
|
||||||
|
multiple={filterMultiple}
|
||||||
|
checkStrictly={!filterMultiple}
|
||||||
|
className={`${dropdownPrefixCls}-menu`}
|
||||||
|
onCheck={onCheck}
|
||||||
|
checkedKeys={selectedKeys}
|
||||||
|
selectedKeys={selectedKeys}
|
||||||
|
showIcon={false}
|
||||||
|
treeData={getTreeData({ filters: column.filters })}
|
||||||
|
autoExpandParent
|
||||||
|
defaultExpandAll
|
||||||
|
filterTreeNode={
|
||||||
|
searchValue.trim()
|
||||||
|
? node => searchValueMatched(searchValue, node.title)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterSearch
|
||||||
|
filterSearch={filterSearch}
|
||||||
|
value={searchValue}
|
||||||
|
onChange={onSearch}
|
||||||
|
tablePrefixCls={tablePrefixCls}
|
||||||
|
locale={locale}
|
||||||
|
/>
|
||||||
|
<Menu
|
||||||
|
multiple={filterMultiple}
|
||||||
|
prefixCls={`${dropdownPrefixCls}-menu`}
|
||||||
|
className={dropdownMenuClass}
|
||||||
|
onClick={onMenuClick}
|
||||||
|
onSelect={onSelectKeys}
|
||||||
|
onDeselect={onSelectKeys}
|
||||||
|
selectedKeys={selectedKeys}
|
||||||
|
getPopupContainer={getPopupContainer}
|
||||||
|
openKeys={openKeys}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
|
{renderFilterItems({
|
||||||
|
filters: column.filters || [],
|
||||||
|
prefixCls,
|
||||||
|
filteredKeys: getFilteredKeysSync(),
|
||||||
|
filterMultiple,
|
||||||
|
searchValue,
|
||||||
|
})}
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
dropdownContent = (
|
dropdownContent = (
|
||||||
<>
|
<>
|
||||||
<Menu
|
{getFilterComponent()}
|
||||||
multiple={filterMultiple}
|
|
||||||
prefixCls={`${dropdownPrefixCls}-menu`}
|
|
||||||
className={dropdownMenuClass}
|
|
||||||
onClick={onMenuClick}
|
|
||||||
onSelect={onSelectKeys}
|
|
||||||
onDeselect={onSelectKeys}
|
|
||||||
selectedKeys={selectedKeys}
|
|
||||||
getPopupContainer={getPopupContainer}
|
|
||||||
openKeys={openKeys}
|
|
||||||
onOpenChange={onOpenChange}
|
|
||||||
>
|
|
||||||
{renderFilterItems({
|
|
||||||
filters: column.filters || [],
|
|
||||||
prefixCls,
|
|
||||||
filteredKeys: getFilteredKeysSync(),
|
|
||||||
filterMultiple,
|
|
||||||
locale,
|
|
||||||
})}
|
|
||||||
</Menu>
|
|
||||||
<div className={`${prefixCls}-dropdown-btns`}>
|
<div className={`${prefixCls}-dropdown-btns`}>
|
||||||
<Button type="link" size="small" disabled={selectedKeys.length === 0} onClick={onReset}>
|
<Button type="link" size="small" disabled={selectedKeys.length === 0} onClick={onReset}>
|
||||||
{locale.filterReset}
|
{locale.filterReset}
|
||||||
|
39
components/table/hooks/useFilter/FilterSearch.tsx
Normal file
39
components/table/hooks/useFilter/FilterSearch.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import SearchOutlined from '@ant-design/icons/SearchOutlined';
|
||||||
|
import Input from '../../../input';
|
||||||
|
import { TableLocale } from '../../interface';
|
||||||
|
|
||||||
|
interface FilterSearchProps {
|
||||||
|
value: string;
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
filterSearch: Boolean;
|
||||||
|
tablePrefixCls: string;
|
||||||
|
locale: TableLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilterSearch: React.FC<FilterSearchProps> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
filterSearch,
|
||||||
|
tablePrefixCls,
|
||||||
|
locale,
|
||||||
|
}) => {
|
||||||
|
if (!filterSearch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={`${tablePrefixCls}-filter-dropdown-search`}>
|
||||||
|
<Input
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
placeholder={locale.filterSearchPlaceholder}
|
||||||
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
// for skip min-width of input
|
||||||
|
htmlSize={1}
|
||||||
|
className={`${tablePrefixCls}-filter-dropdown-search-input`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilterSearch;
|
@ -78,7 +78,7 @@ function injectFilter<RecordType>(
|
|||||||
): ColumnsType<RecordType> {
|
): ColumnsType<RecordType> {
|
||||||
return columns.map((column, index) => {
|
return columns.map((column, index) => {
|
||||||
const columnPos = getColumnPos(index, pos);
|
const columnPos = getColumnPos(index, pos);
|
||||||
const { filterMultiple = true } = column as ColumnType<RecordType>;
|
const { filterMultiple = true, filterMode, filterSearch } = column as ColumnType<RecordType>;
|
||||||
|
|
||||||
let newColumn: ColumnsType<RecordType>[number] = column;
|
let newColumn: ColumnsType<RecordType>[number] = column;
|
||||||
|
|
||||||
@ -97,6 +97,8 @@ function injectFilter<RecordType>(
|
|||||||
columnKey={columnKey}
|
columnKey={columnKey}
|
||||||
filterState={filterState}
|
filterState={filterState}
|
||||||
filterMultiple={filterMultiple}
|
filterMultiple={filterMultiple}
|
||||||
|
filterMode={filterMode}
|
||||||
|
filterSearch={filterSearch}
|
||||||
triggerFilter={triggerFilter}
|
triggerFilter={triggerFilter}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
getPopupContainer={getPopupContainer}
|
getPopupContainer={getPopupContainer}
|
||||||
@ -127,7 +129,7 @@ function injectFilter<RecordType>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function flattenKeys(filters?: ColumnFilterItem[]) {
|
export function flattenKeys(filters?: ColumnFilterItem[]) {
|
||||||
let keys: FilterValue = [];
|
let keys: FilterValue = [];
|
||||||
(filters || []).forEach(({ value, children }) => {
|
(filters || []).forEach(({ value, children }) => {
|
||||||
keys.push(value);
|
keys.push(value);
|
||||||
|
@ -68,7 +68,7 @@ const columns = [
|
|||||||
| footer | Table footer renderer | function(currentPageData) | - | |
|
| footer | Table footer renderer | function(currentPageData) | - | |
|
||||||
| getPopupContainer | The render container of dropdowns in table | (triggerNode) => HTMLElement | () => TableHtmlElement | |
|
| getPopupContainer | The render container of dropdowns in table | (triggerNode) => HTMLElement | () => TableHtmlElement | |
|
||||||
| loading | Loading status of table | boolean \| [Spin Props](/components/spin/#API) | false | |
|
| loading | Loading status of table | boolean \| [Spin Props](/components/spin/#API) | false | |
|
||||||
| locale | The i18n text including filter, sort, empty text, etc | object | filterConfirm: `Ok` <br> filterReset: `Reset` <br> emptyText: `No Data` <br> [Default](https://github.com/ant-design/ant-design/blob/4ad1ccac277782d7ed14f7e5d02d6346aae0db67/components/locale/default.tsx#L19) | |
|
| locale | The i18n text including filter, sort, empty text, etc | object | [Default Value](https://github.com/ant-design/ant-design/blob/6dae4a7e18ad1ba193aedd5ab6867e1d823e2aa4/components/locale/default.tsx#L19-L37) | |
|
||||||
| pagination | Config of pagination. You can ref table pagination [config](#pagination) or full [`pagination`](/components/pagination/) document, hide it by setting it to `false` | object | - | |
|
| pagination | Config of pagination. You can ref table pagination [config](#pagination) or full [`pagination`](/components/pagination/) document, hide it by setting it to `false` | object | - | |
|
||||||
| rowClassName | Row's className | function(record, index): string | - | |
|
| rowClassName | Row's className | function(record, index): string | - | |
|
||||||
| rowKey | Row's unique key, could be a string or function that returns a string | string \| function(record): string | `key` | |
|
| rowKey | Row's unique key, could be a string or function that returns a string | string \| function(record): string | `key` | |
|
||||||
@ -129,6 +129,8 @@ One of the Table `columns` prop for describing the table's columns, Column has t
|
|||||||
| filteredValue | Controlled filtered value, filter icon will highlight | string\[] | - | |
|
| filteredValue | Controlled filtered value, filter icon will highlight | string\[] | - | |
|
||||||
| filterIcon | Customized filter icon | ReactNode \| (filtered: boolean) => ReactNode | - | |
|
| filterIcon | Customized filter icon | ReactNode \| (filtered: boolean) => ReactNode | - | |
|
||||||
| filterMultiple | Whether multiple filters can be selected | boolean | true | |
|
| filterMultiple | Whether multiple filters can be selected | boolean | true | |
|
||||||
|
| filterMode | To specify the filter interface | 'menu' \| 'tree' | 'menu' | 4.17.0 |
|
||||||
|
| filterSearch | Whether to be searchable for filter menu | Boolean | false | 4.17.0 |
|
||||||
| filters | Filter menu config | object\[] | - | |
|
| filters | Filter menu config | object\[] | - | |
|
||||||
| fixed | (IE not support) Set column to be fixed: `true`(same as left) `'left'` `'right'` | boolean \| string | false | |
|
| fixed | (IE not support) Set column to be fixed: `true`(same as left) `'left'` `'right'` | boolean \| string | false | |
|
||||||
| key | Unique key of this column, you can ignore this prop if you've set a unique `dataIndex` | string | - | |
|
| key | Unique key of this column, you can ignore this prop if you've set a unique `dataIndex` | string | - | |
|
||||||
|
@ -75,7 +75,7 @@ const columns = [
|
|||||||
| footer | 表格尾部 | function(currentPageData) | - | |
|
| footer | 表格尾部 | function(currentPageData) | - | |
|
||||||
| getPopupContainer | 设置表格内各类浮层的渲染节点,如筛选菜单 | (triggerNode) => HTMLElement | () => TableHtmlElement | |
|
| getPopupContainer | 设置表格内各类浮层的渲染节点,如筛选菜单 | (triggerNode) => HTMLElement | () => TableHtmlElement | |
|
||||||
| loading | 页面是否加载中 | boolean \| [Spin Props](/components/spin/#API) | false | |
|
| loading | 页面是否加载中 | boolean \| [Spin Props](/components/spin/#API) | false | |
|
||||||
| locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | filterConfirm: `确定` <br> filterReset: `重置` <br> emptyText: `暂无数据` <br> [默认值](https://github.com/ant-design/ant-design/blob/4ad1ccac277782d7ed14f7e5d02d6346aae0db67/components/locale/default.tsx#L19) | |
|
| locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | [默认值](https://github.com/ant-design/ant-design/blob/6dae4a7e18ad1ba193aedd5ab6867e1d823e2aa4/components/locale/zh_CN.tsx#L20-L37) | |
|
||||||
| pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination/) 文档,设为 false 时不展示和进行分页 | object | - | |
|
| pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination/) 文档,设为 false 时不展示和进行分页 | object | - | |
|
||||||
| rowClassName | 表格行的类名 | function(record, index): string | - | |
|
| rowClassName | 表格行的类名 | function(record, index): string | - | |
|
||||||
| rowKey | 表格行 key 的取值,可以是字符串或一个函数 | string \| function(record): string | `key` | |
|
| rowKey | 表格行 key 的取值,可以是字符串或一个函数 | string \| function(record): string | `key` | |
|
||||||
@ -136,6 +136,8 @@ const columns = [
|
|||||||
| filteredValue | 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 | string\[] | - | |
|
| filteredValue | 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 | string\[] | - | |
|
||||||
| filterIcon | 自定义 filter 图标。 | ReactNode \| (filtered: boolean) => ReactNode | false | |
|
| filterIcon | 自定义 filter 图标。 | ReactNode \| (filtered: boolean) => ReactNode | false | |
|
||||||
| filterMultiple | 是否多选 | boolean | true | |
|
| filterMultiple | 是否多选 | boolean | true | |
|
||||||
|
| filterMode | 指定筛选菜单的用户界面 | 'menu' \| 'tree' | 'menu' | 4.17.0 |
|
||||||
|
| filterSearch | 筛选菜单项是否可搜索 | Boolean | false | 4.17.0 |
|
||||||
| filters | 表头的筛选菜单项 | object\[] | - | |
|
| filters | 表头的筛选菜单项 | object\[] | - | |
|
||||||
| fixed | (IE 下无效)列是否固定,可选 true (等效于 left) `left` `right` | boolean \| string | false | |
|
| fixed | (IE 下无效)列是否固定,可选 true (等效于 left) `left` `right` | boolean \| string | false | |
|
||||||
| key | React 需要的 key,如果已经设置了唯一的 `dataIndex`,可以忽略这个属性 | string | - | |
|
| key | React 需要的 key,如果已经设置了唯一的 `dataIndex`,可以忽略这个属性 | string | - | |
|
||||||
|
@ -28,6 +28,8 @@ export interface TableLocale {
|
|||||||
filterConfirm?: React.ReactNode;
|
filterConfirm?: React.ReactNode;
|
||||||
filterReset?: React.ReactNode;
|
filterReset?: React.ReactNode;
|
||||||
filterEmptyText?: React.ReactNode;
|
filterEmptyText?: React.ReactNode;
|
||||||
|
filterCheckall?: React.ReactNode;
|
||||||
|
filterSearchPlaceholder?: string;
|
||||||
emptyText?: React.ReactNode | (() => React.ReactNode);
|
emptyText?: React.ReactNode | (() => React.ReactNode);
|
||||||
selectAll?: React.ReactNode;
|
selectAll?: React.ReactNode;
|
||||||
selectNone?: React.ReactNode;
|
selectNone?: React.ReactNode;
|
||||||
@ -108,6 +110,8 @@ export interface ColumnType<RecordType> extends RcColumnType<RecordType> {
|
|||||||
filteredValue?: FilterValue | null;
|
filteredValue?: FilterValue | null;
|
||||||
defaultFilteredValue?: FilterValue | null;
|
defaultFilteredValue?: FilterValue | null;
|
||||||
filterIcon?: React.ReactNode | ((filtered: boolean) => React.ReactNode);
|
filterIcon?: React.ReactNode | ((filtered: boolean) => React.ReactNode);
|
||||||
|
filterMode?: 'menu' | 'tree';
|
||||||
|
filterSearch?: boolean;
|
||||||
onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
|
onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
|
||||||
filterDropdownVisible?: boolean;
|
filterDropdownVisible?: boolean;
|
||||||
onFilterDropdownVisibleChange?: (visible: boolean) => void;
|
onFilterDropdownVisibleChange?: (visible: boolean) => void;
|
||||||
|
@ -4,12 +4,14 @@
|
|||||||
@import './bordered';
|
@import './bordered';
|
||||||
|
|
||||||
@table-prefix-cls: ~'@{ant-prefix}-table';
|
@table-prefix-cls: ~'@{ant-prefix}-table';
|
||||||
|
@tree-prefix-cls: ~'@{ant-prefix}-tree';
|
||||||
@dropdown-prefix-cls: ~'@{ant-prefix}-dropdown';
|
@dropdown-prefix-cls: ~'@{ant-prefix}-dropdown';
|
||||||
@descriptions-prefix-cls: ~'@{ant-prefix}-descriptions';
|
@descriptions-prefix-cls: ~'@{ant-prefix}-descriptions';
|
||||||
@table-header-icon-color: #bfbfbf;
|
@table-header-icon-color: #bfbfbf;
|
||||||
@table-header-icon-color-hover: darken(@table-header-icon-color, 10%);
|
@table-header-icon-color-hover: darken(@table-header-icon-color, 10%);
|
||||||
@table-sticky-zindex: calc(@zindex-table-fixed + 1);
|
@table-sticky-zindex: calc(@zindex-table-fixed + 1);
|
||||||
@table-sticky-scroll-bar-active-bg: fade(@table-sticky-scroll-bar-bg, 80%);
|
@table-sticky-scroll-bar-active-bg: fade(@table-sticky-scroll-bar-bg, 80%);
|
||||||
|
@table-filter-dropdown-max-height: 264px;
|
||||||
|
|
||||||
.@{table-prefix-cls}-wrapper {
|
.@{table-prefix-cls}-wrapper {
|
||||||
clear: both;
|
clear: both;
|
||||||
@ -330,21 +332,64 @@
|
|||||||
&-filter-dropdown {
|
&-filter-dropdown {
|
||||||
.reset-component();
|
.reset-component();
|
||||||
|
|
||||||
|
min-width: 120px;
|
||||||
|
background-color: @table-filter-dropdown-bg;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
box-shadow: @box-shadow-base;
|
||||||
|
|
||||||
// Reset menu
|
// Reset menu
|
||||||
.@{dropdown-prefix-cls}-menu {
|
.@{dropdown-prefix-cls}-menu {
|
||||||
// https://github.com/ant-design/ant-design/issues/4916
|
// https://github.com/ant-design/ant-design/issues/4916
|
||||||
// https://github.com/ant-design/ant-design/issues/19542
|
// https://github.com/ant-design/ant-design/issues/19542
|
||||||
max-height: 264px;
|
max-height: @table-filter-dropdown-max-height;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
border: 0;
|
border: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:empty::after {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 0;
|
||||||
|
color: @disabled-color;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
text-align: center;
|
||||||
|
content: 'Not Found';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
min-width: 120px;
|
&-tree {
|
||||||
background-color: @table-filter-dropdown-bg;
|
padding: 8px 8px 0 8px;
|
||||||
|
|
||||||
border-radius: @border-radius-base;
|
.@{tree-prefix-cls}-treenode .@{tree-prefix-cls}-node-content-wrapper:hover {
|
||||||
box-shadow: @box-shadow-base;
|
background-color: @tree-node-hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{tree-prefix-cls}-treenode-checkbox-checked .@{tree-prefix-cls}-node-content-wrapper {
|
||||||
|
&,
|
||||||
|
&:hover {
|
||||||
|
background-color: @tree-node-selected-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-search {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: @border-width-base @border-color-split @border-style-base;
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
input {
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
.@{iconfont-css-prefix} {
|
||||||
|
color: @disabled-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-checkall {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&-submenu > ul {
|
&-submenu > ul {
|
||||||
max-height: calc(100vh - 130px);
|
max-height: calc(100vh - 130px);
|
||||||
@ -364,7 +409,7 @@
|
|||||||
&-btns {
|
&-btns {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 7px 8px 7px 3px;
|
padding: 7px 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: @table-filter-btns-bg;
|
background-color: @table-filter-btns-bg;
|
||||||
border-top: @border-width-base @border-style-base @table-border-color;
|
border-top: @border-width-base @border-style-base @table-border-color;
|
||||||
|
@ -12,3 +12,5 @@ import '../../dropdown/style';
|
|||||||
import '../../spin/style';
|
import '../../spin/style';
|
||||||
import '../../pagination/style';
|
import '../../pagination/style';
|
||||||
import '../../tooltip/style';
|
import '../../tooltip/style';
|
||||||
|
import '../../input/style';
|
||||||
|
import '../../tree/style';
|
||||||
|
@ -60,33 +60,61 @@ exports[`renders ./components/transfer/demo/advanced.md correctly 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="ant-transfer-list-body-search-wrapper"
|
class="ant-transfer-list-body-search-wrapper"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
class="ant-input ant-transfer-list-search"
|
|
||||||
placeholder="Search here"
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<span
|
<span
|
||||||
class="ant-transfer-list-search-action"
|
class="ant-input-affix-wrapper ant-transfer-list-search"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-label="search"
|
class="ant-input-prefix"
|
||||||
class="anticon anticon-search"
|
|
||||||
role="img"
|
|
||||||
>
|
>
|
||||||
<svg
|
<span
|
||||||
aria-hidden="true"
|
aria-label="search"
|
||||||
data-icon="search"
|
class="anticon anticon-search"
|
||||||
fill="currentColor"
|
role="img"
|
||||||
focusable="false"
|
|
||||||
height="1em"
|
|
||||||
viewBox="64 64 896 896"
|
|
||||||
width="1em"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
aria-hidden="true"
|
||||||
/>
|
data-icon="search"
|
||||||
</svg>
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
class="ant-input"
|
||||||
|
placeholder="Search here"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-input-suffix"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="close-circle"
|
||||||
|
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
|
||||||
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="close-circle"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -271,33 +299,61 @@ exports[`renders ./components/transfer/demo/advanced.md correctly 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="ant-transfer-list-body-search-wrapper"
|
class="ant-transfer-list-body-search-wrapper"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
class="ant-input ant-transfer-list-search"
|
|
||||||
placeholder="Search here"
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<span
|
<span
|
||||||
class="ant-transfer-list-search-action"
|
class="ant-input-affix-wrapper ant-transfer-list-search"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-label="search"
|
class="ant-input-prefix"
|
||||||
class="anticon anticon-search"
|
|
||||||
role="img"
|
|
||||||
>
|
>
|
||||||
<svg
|
<span
|
||||||
aria-hidden="true"
|
aria-label="search"
|
||||||
data-icon="search"
|
class="anticon anticon-search"
|
||||||
fill="currentColor"
|
role="img"
|
||||||
focusable="false"
|
|
||||||
height="1em"
|
|
||||||
viewBox="64 64 896 896"
|
|
||||||
width="1em"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
aria-hidden="true"
|
||||||
/>
|
data-icon="search"
|
||||||
</svg>
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
class="ant-input"
|
||||||
|
placeholder="Search here"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-input-suffix"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="close-circle"
|
||||||
|
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
|
||||||
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="close-circle"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -2802,33 +2858,61 @@ exports[`renders ./components/transfer/demo/search.md correctly 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="ant-transfer-list-body-search-wrapper"
|
class="ant-transfer-list-body-search-wrapper"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
class="ant-input ant-transfer-list-search"
|
|
||||||
placeholder="Search here"
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<span
|
<span
|
||||||
class="ant-transfer-list-search-action"
|
class="ant-input-affix-wrapper ant-transfer-list-search"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-label="search"
|
class="ant-input-prefix"
|
||||||
class="anticon anticon-search"
|
|
||||||
role="img"
|
|
||||||
>
|
>
|
||||||
<svg
|
<span
|
||||||
aria-hidden="true"
|
aria-label="search"
|
||||||
data-icon="search"
|
class="anticon anticon-search"
|
||||||
fill="currentColor"
|
role="img"
|
||||||
focusable="false"
|
|
||||||
height="1em"
|
|
||||||
viewBox="64 64 896 896"
|
|
||||||
width="1em"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
aria-hidden="true"
|
||||||
/>
|
data-icon="search"
|
||||||
</svg>
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
class="ant-input"
|
||||||
|
placeholder="Search here"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-input-suffix"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="close-circle"
|
||||||
|
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
|
||||||
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="close-circle"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -2993,33 +3077,61 @@ exports[`renders ./components/transfer/demo/search.md correctly 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="ant-transfer-list-body-search-wrapper"
|
class="ant-transfer-list-body-search-wrapper"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
class="ant-input ant-transfer-list-search"
|
|
||||||
placeholder="Search here"
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<span
|
<span
|
||||||
class="ant-transfer-list-search-action"
|
class="ant-input-affix-wrapper ant-transfer-list-search"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-label="search"
|
class="ant-input-prefix"
|
||||||
class="anticon anticon-search"
|
|
||||||
role="img"
|
|
||||||
>
|
>
|
||||||
<svg
|
<span
|
||||||
aria-hidden="true"
|
aria-label="search"
|
||||||
data-icon="search"
|
class="anticon anticon-search"
|
||||||
fill="currentColor"
|
role="img"
|
||||||
focusable="false"
|
|
||||||
height="1em"
|
|
||||||
viewBox="64 64 896 896"
|
|
||||||
width="1em"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
aria-hidden="true"
|
||||||
/>
|
data-icon="search"
|
||||||
</svg>
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
class="ant-input"
|
||||||
|
placeholder="Search here"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-input-suffix"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="close-circle"
|
||||||
|
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
|
||||||
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="close-circle"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Transfer.Search should show cross icon when input value exists 1`] = `
|
exports[`Transfer.Search should show cross icon when input value exists 1`] = `
|
||||||
Array [
|
<span
|
||||||
<input
|
class="ant-input-affix-wrapper"
|
||||||
class="ant-input"
|
>
|
||||||
placeholder=""
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>,
|
|
||||||
<span
|
<span
|
||||||
class="undefined-action"
|
class="ant-input-prefix"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-label="search"
|
aria-label="search"
|
||||||
@ -30,25 +26,21 @@ Array [
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</span>,
|
</span>
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Transfer.Search should show cross icon when input value exists 2`] = `
|
|
||||||
Array [
|
|
||||||
<input
|
<input
|
||||||
class="ant-input"
|
class="ant-input"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
type="text"
|
type="text"
|
||||||
value="a"
|
value=""
|
||||||
/>,
|
/>
|
||||||
<a
|
<span
|
||||||
class="undefined-action"
|
class="ant-input-suffix"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-label="close-circle"
|
aria-label="close-circle"
|
||||||
class="anticon anticon-close-circle"
|
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
|
||||||
role="img"
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@ -64,6 +56,66 @@ Array [
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</a>,
|
</span>
|
||||||
]
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Transfer.Search should show cross icon when input value exists 2`] = `
|
||||||
|
<span
|
||||||
|
class="ant-input-affix-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-input-prefix"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="search"
|
||||||
|
class="anticon anticon-search"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="search"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
class="ant-input"
|
||||||
|
placeholder=""
|
||||||
|
type="text"
|
||||||
|
value="a"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-input-suffix"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="close-circle"
|
||||||
|
class="anticon anticon-close-circle ant-input-clear-icon"
|
||||||
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="close-circle"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
`;
|
`;
|
||||||
|
@ -59,7 +59,7 @@ describe('Transfer.Search', () => {
|
|||||||
.simulate('change', { target: { value: 'a' } });
|
.simulate('change', { target: { value: 'a' } });
|
||||||
expect(onSearch).toHaveBeenCalledWith('left', 'a');
|
expect(onSearch).toHaveBeenCalledWith('left', 'a');
|
||||||
onSearch.mockReset();
|
onSearch.mockReset();
|
||||||
wrapper.find('.ant-transfer-list-search-action').at(0).simulate('click');
|
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
|
||||||
expect(onSearch).toHaveBeenCalledWith('left', '');
|
expect(onSearch).toHaveBeenCalledWith('left', '');
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
|
|
||||||
import SearchOutlined from '@ant-design/icons/SearchOutlined';
|
import SearchOutlined from '@ant-design/icons/SearchOutlined';
|
||||||
|
|
||||||
import Input from '../input';
|
import Input from '../input';
|
||||||
@ -8,7 +7,7 @@ export interface TransferSearchProps {
|
|||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
onChange?: (e: React.FormEvent<HTMLElement>) => void;
|
onChange?: (e: React.FormEvent<HTMLElement>) => void;
|
||||||
handleClear?: (e: React.MouseEvent<HTMLElement>) => void;
|
handleClear?: () => void;
|
||||||
value?: string;
|
value?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
@ -19,35 +18,22 @@ export default function Search(props: TransferSearchProps) {
|
|||||||
const handleChange = React.useCallback(
|
const handleChange = React.useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
onChange?.(e);
|
onChange?.(e);
|
||||||
|
if (e.target.value === '') {
|
||||||
|
handleClear?.();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[onChange],
|
[onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClearFn = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!disabled && handleClear) {
|
|
||||||
handleClear(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Input
|
||||||
<Input
|
placeholder={placeholder}
|
||||||
placeholder={placeholder}
|
className={prefixCls}
|
||||||
className={prefixCls}
|
value={value}
|
||||||
value={value}
|
onChange={handleChange}
|
||||||
onChange={handleChange}
|
disabled={disabled}
|
||||||
disabled={disabled}
|
allowClear
|
||||||
/>
|
prefix={<SearchOutlined />}
|
||||||
{value && value.length > 0 ? (
|
/>
|
||||||
<a className={`${prefixCls}-action`} onClick={handleClearFn}>
|
|
||||||
<CloseCircleFilled />
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<span className={`${prefixCls}-action`}>
|
|
||||||
<SearchOutlined />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -36,28 +36,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-search {
|
&-search {
|
||||||
padding-right: 24px;
|
.anticon-search {
|
||||||
padding-left: @control-padding-horizontal-sm;
|
|
||||||
&-action {
|
|
||||||
position: absolute;
|
|
||||||
top: @transfer-list-search-icon-top;
|
|
||||||
right: 12px;
|
|
||||||
bottom: 12px;
|
|
||||||
width: 28px;
|
|
||||||
color: @disabled-color;
|
color: @disabled-color;
|
||||||
line-height: @input-height-base;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.@{iconfont-css-prefix} {
|
|
||||||
color: @disabled-color;
|
|
||||||
transition: all 0.3s;
|
|
||||||
&:hover {
|
|
||||||
color: @text-color-secondary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
span& {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user