mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
feat(Table): column filterDropdownProps support (#51297)
Co-authored-by: afc163 <afc163@users.noreply.github.com>
This commit is contained in:
parent
9f77bc576b
commit
badbd80eb4
@ -360,11 +360,15 @@ describe('Table.filter', () => {
|
||||
|
||||
const onFilterDropdownOpenChange = jest.fn();
|
||||
const onFilterDropdownVisibleChange = jest.fn();
|
||||
const onOpenChange = jest.fn();
|
||||
const { container } = render(
|
||||
createTable({
|
||||
columns: [
|
||||
{
|
||||
...column,
|
||||
filterDropdownProps: {
|
||||
onOpenChange,
|
||||
},
|
||||
onFilterDropdownOpenChange,
|
||||
onFilterDropdownVisibleChange,
|
||||
},
|
||||
@ -372,11 +376,12 @@ describe('Table.filter', () => {
|
||||
}),
|
||||
);
|
||||
fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
|
||||
expect(onOpenChange).toHaveBeenCalledWith(true);
|
||||
expect(onFilterDropdownOpenChange).toHaveBeenCalledWith(true);
|
||||
expect(onFilterDropdownVisibleChange).toHaveBeenCalledWith(true);
|
||||
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Table] `onFilterDropdownVisibleChange` is deprecated. Please use `onFilterDropdownOpenChange` instead.',
|
||||
'Warning: [antd: Table] `onFilterDropdownVisibleChange` is deprecated. Please use `filterDropdownProps.onOpenChange` instead.',
|
||||
);
|
||||
|
||||
errSpy.mockRestore();
|
||||
@ -3094,4 +3099,53 @@ describe('Table.filter', () => {
|
||||
expect(container.querySelector('.ant-table-filter-dropdown')!.childNodes).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/51151#issuecomment-2419116749
|
||||
describe('should support filterDropdownProps', () => {
|
||||
it('dropdownRender', () => {
|
||||
const dropdownRender = jest.fn((node) => (
|
||||
<>
|
||||
{node}
|
||||
<span>Foo</span>
|
||||
</>
|
||||
));
|
||||
|
||||
const { container, getByText } = render(
|
||||
createTable({
|
||||
columns: [
|
||||
{
|
||||
...column,
|
||||
filterDropdownProps: {
|
||||
dropdownRender,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
|
||||
expect(dropdownRender).toHaveBeenCalled();
|
||||
expect(dropdownRender.mock.calls[0][0]).toMatchSnapshot();
|
||||
expect(getByText('Foo')).toBeTruthy();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/51151
|
||||
it('placement', () => {
|
||||
const { container } = render(
|
||||
createTable({
|
||||
columns: [
|
||||
{
|
||||
...column,
|
||||
filterDropdownProps: {
|
||||
placement: 'topLeft',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
|
||||
expect(container.querySelector('.ant-dropdown-placement-topLeft')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -838,3 +838,126 @@ exports[`Table.filter renders radio filter correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Table.filter should support filterDropdownProps dropdownRender 1`] = `
|
||||
<FilterDropdownMenuWrapper
|
||||
className="ant-table-filter-dropdown"
|
||||
>
|
||||
<React.Fragment>
|
||||
<React.Fragment>
|
||||
<FilterSearch
|
||||
filterSearch={false}
|
||||
locale={
|
||||
{
|
||||
"cancelSort": "Click to cancel sorting",
|
||||
"collapse": "Collapse row",
|
||||
"emptyText": "No data",
|
||||
"expand": "Expand row",
|
||||
"filterCheckall": "Select all items",
|
||||
"filterConfirm": "OK",
|
||||
"filterEmptyText": "No filters",
|
||||
"filterReset": "Reset",
|
||||
"filterSearchPlaceholder": "Search in filters",
|
||||
"filterTitle": "Filter menu",
|
||||
"selectAll": "Select current page",
|
||||
"selectInvert": "Invert current page",
|
||||
"selectNone": "Clear all data",
|
||||
"selectionAll": "Select all data",
|
||||
"sortTitle": "Sort",
|
||||
"triggerAsc": "Click to sort ascending",
|
||||
"triggerDesc": "Click to sort descending",
|
||||
}
|
||||
}
|
||||
onChange={[Function]}
|
||||
tablePrefixCls="ant-table"
|
||||
value=""
|
||||
/>
|
||||
<Menu
|
||||
className=""
|
||||
items={
|
||||
[
|
||||
{
|
||||
"key": "boy",
|
||||
"label": <React.Fragment>
|
||||
<Checkbox
|
||||
checked={false}
|
||||
/>
|
||||
<span>
|
||||
Boy
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
},
|
||||
{
|
||||
"key": "girl",
|
||||
"label": <React.Fragment>
|
||||
<Checkbox
|
||||
checked={false}
|
||||
/>
|
||||
<span>
|
||||
Girl
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"key": "designer",
|
||||
"label": <React.Fragment>
|
||||
<Checkbox
|
||||
checked={false}
|
||||
/>
|
||||
<span>
|
||||
Designer
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
},
|
||||
{
|
||||
"key": "coder",
|
||||
"label": <React.Fragment>
|
||||
<Checkbox
|
||||
checked={false}
|
||||
/>
|
||||
<span>
|
||||
Coder
|
||||
</span>
|
||||
</React.Fragment>,
|
||||
},
|
||||
],
|
||||
"key": "title",
|
||||
"label": "Title",
|
||||
"popupClassName": "ant-table-filter-dropdown-submenu",
|
||||
},
|
||||
]
|
||||
}
|
||||
multiple={true}
|
||||
onDeselect={[Function]}
|
||||
onOpenChange={[Function]}
|
||||
onSelect={[Function]}
|
||||
openKeys={[]}
|
||||
prefixCls="ant-dropdown-menu"
|
||||
selectable={true}
|
||||
selectedKeys={[]}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<div
|
||||
className="ant-table-filter-dropdown-btns"
|
||||
>
|
||||
<Button
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
type="link"
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
type="primary"
|
||||
>
|
||||
OK
|
||||
</Button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</FilterDropdownMenuWrapper>
|
||||
`;
|
||||
|
@ -120,10 +120,12 @@ const App: React.FC = () => {
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes((value as string).toLowerCase()),
|
||||
onFilterDropdownOpenChange: (visible) => {
|
||||
if (visible) {
|
||||
setTimeout(() => searchInput.current?.select(), 100);
|
||||
}
|
||||
filterDropdownProps: {
|
||||
onOpenChange(open) {
|
||||
if (open) {
|
||||
setTimeout(() => searchInput.current?.select(), 100);
|
||||
}
|
||||
},
|
||||
},
|
||||
render: (text) =>
|
||||
searchedColumn === dataIndex ? (
|
||||
|
@ -165,14 +165,14 @@ const FilterDropdown = <RecordType extends AnyObject = AnyObject>(
|
||||
} = props;
|
||||
|
||||
const {
|
||||
filterDropdownOpen,
|
||||
onFilterDropdownOpenChange,
|
||||
filterResetToDefaultFilteredValue,
|
||||
defaultFilteredValue,
|
||||
|
||||
filterDropdownProps = {},
|
||||
// Deprecated
|
||||
filterDropdownOpen,
|
||||
filterDropdownVisible,
|
||||
onFilterDropdownVisibleChange,
|
||||
onFilterDropdownOpenChange,
|
||||
} = column;
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
|
||||
@ -182,30 +182,33 @@ const FilterDropdown = <RecordType extends AnyObject = AnyObject>(
|
||||
);
|
||||
const triggerVisible = (newVisible: boolean) => {
|
||||
setVisible(newVisible);
|
||||
filterDropdownProps.onOpenChange?.(newVisible);
|
||||
// deprecated
|
||||
onFilterDropdownOpenChange?.(newVisible);
|
||||
onFilterDropdownVisibleChange?.(newVisible);
|
||||
};
|
||||
|
||||
// =================Warning===================
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('Table');
|
||||
|
||||
[
|
||||
['filterDropdownVisible', 'filterDropdownOpen', filterDropdownVisible],
|
||||
[
|
||||
'onFilterDropdownVisibleChange',
|
||||
'onFilterDropdownOpenChange',
|
||||
onFilterDropdownVisibleChange,
|
||||
],
|
||||
].forEach(([deprecatedName, newName, prop]) => {
|
||||
warning.deprecated(
|
||||
prop === undefined || prop === null,
|
||||
deprecatedName as string,
|
||||
newName as string,
|
||||
);
|
||||
const deprecatedList: [keyof typeof column, string][] = [
|
||||
['filterDropdownOpen', 'filterDropdownProps.open'],
|
||||
['filterDropdownVisible', 'filterDropdownProps.open'],
|
||||
['onFilterDropdownOpenChange', 'filterDropdownProps.onOpenChange'],
|
||||
['onFilterDropdownVisibleChange', 'filterDropdownProps.onOpenChange'],
|
||||
];
|
||||
|
||||
deprecatedList.forEach(([deprecatedName, newName]) => {
|
||||
warning.deprecated(!(deprecatedName in column), deprecatedName, newName);
|
||||
});
|
||||
}
|
||||
|
||||
const mergedVisible = filterDropdownOpen ?? filterDropdownVisible ?? visible;
|
||||
const mergedVisible =
|
||||
filterDropdownProps.open ??
|
||||
filterDropdownOpen ?? // deprecated
|
||||
filterDropdownVisible ?? // deprecated
|
||||
visible; // inner state
|
||||
|
||||
// ===================== Select Keys =====================
|
||||
const propFilteredKeys = filterState?.filteredKeys;
|
||||
@ -514,46 +517,59 @@ const FilterDropdown = <RecordType extends AnyObject = AnyObject>(
|
||||
dropdownContent = <OverrideProvider selectable={undefined}>{dropdownContent}</OverrideProvider>;
|
||||
}
|
||||
|
||||
const menu = () => (
|
||||
dropdownContent = (
|
||||
<FilterDropdownMenuWrapper className={`${prefixCls}-dropdown`}>
|
||||
{dropdownContent}
|
||||
</FilterDropdownMenuWrapper>
|
||||
);
|
||||
|
||||
let filterIcon: React.ReactNode;
|
||||
if (typeof column.filterIcon === 'function') {
|
||||
filterIcon = column.filterIcon(filtered);
|
||||
} else if (column.filterIcon) {
|
||||
filterIcon = column.filterIcon;
|
||||
} else {
|
||||
filterIcon = <FilterFilled />;
|
||||
}
|
||||
const getDropdownTrigger = () => {
|
||||
let filterIcon: React.ReactNode;
|
||||
if (typeof column.filterIcon === 'function') {
|
||||
filterIcon = column.filterIcon(filtered);
|
||||
} else if (column.filterIcon) {
|
||||
filterIcon = column.filterIcon;
|
||||
} else {
|
||||
filterIcon = <FilterFilled />;
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className={classNames(`${prefixCls}-trigger`, {
|
||||
active: filtered,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{filterIcon}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const mergedDropdownProps: DropdownProps = {
|
||||
trigger: ['click'],
|
||||
placement: direction === 'rtl' ? 'bottomLeft' : 'bottomRight',
|
||||
children: getDropdownTrigger(),
|
||||
getPopupContainer,
|
||||
...filterDropdownProps,
|
||||
rootClassName: classNames(rootClassName, filterDropdownProps.rootClassName),
|
||||
open: mergedVisible,
|
||||
onOpenChange: onVisibleChange,
|
||||
dropdownRender: () => {
|
||||
if (typeof filterDropdownProps?.dropdownRender === 'function') {
|
||||
return filterDropdownProps.dropdownRender(dropdownContent);
|
||||
}
|
||||
return dropdownContent;
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${prefixCls}-column`}>
|
||||
<span className={`${tablePrefixCls}-column-title`}>{children}</span>
|
||||
<Dropdown
|
||||
dropdownRender={menu}
|
||||
trigger={['click']}
|
||||
open={mergedVisible}
|
||||
onOpenChange={onVisibleChange}
|
||||
getPopupContainer={getPopupContainer}
|
||||
placement={direction === 'rtl' ? 'bottomLeft' : 'bottomRight'}
|
||||
rootClassName={rootClassName}
|
||||
>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className={classNames(`${prefixCls}-trigger`, {
|
||||
active: filtered,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{filterIcon}
|
||||
</span>
|
||||
</Dropdown>
|
||||
<Dropdown {...mergedDropdownProps} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -192,7 +192,6 @@ One of the Table `columns` prop for describing the table's columns, Column has t
|
||||
| defaultSortOrder | Default order of sorted values | `ascend` \| `descend` | - | |
|
||||
| ellipsis | The ellipsis cell content, not working with sorter and filters for now.<br />tableLayout would be `fixed` when `ellipsis` is `true` or `{ showTitle?: boolean }` | boolean \| {showTitle?: boolean } | false | showTitle: 4.3.0 |
|
||||
| filterDropdown | Customized filter overlay | ReactNode \| (props: [FilterDropdownProps](https://github.com/ant-design/ant-design/blob/ecc54dda839619e921c0ace530408871f0281c2a/components/table/interface.tsx#L79)) => ReactNode | - | |
|
||||
| filterDropdownOpen | Whether `filterDropdown` is visible | boolean | - | |
|
||||
| filtered | Whether the `dataSource` is filtered | boolean | false | |
|
||||
| filteredValue | Controlled filtered value, filter icon will highlight | string\[] | - | |
|
||||
| filterIcon | Customized filter icon | ReactNode \| (filtered: boolean) => ReactNode | - | |
|
||||
@ -201,6 +200,7 @@ One of the Table `columns` prop for describing the table's columns, Column has t
|
||||
| filterMode | To specify the filter interface | 'menu' \| 'tree' | 'menu' | 4.17.0 |
|
||||
| filterSearch | Whether to be searchable for filter menu | boolean \| function(input, record):boolean | false | boolean:4.17.0 function:4.19.0 |
|
||||
| filters | Filter menu config | object\[] | - | |
|
||||
| filterDropdownProps | Customized dropdown props, `filterDropdownOpen` and `onFilterDropdownOpenChange` were available before `<5.22.0` | [DropdownProps](/components/dropdown#api) | - | 5.22.0 |
|
||||
| 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 | - | |
|
||||
| render | Renderer of the table cell. The return value should be a ReactNode | function(text, record, index) {} | - | |
|
||||
@ -218,7 +218,6 @@ One of the Table `columns` prop for describing the table's columns, Column has t
|
||||
| hidden | Hidden this column | boolean | false | 5.13.0 |
|
||||
| onCell | Set props on per cell | function(record, rowIndex) | - | |
|
||||
| onFilter | Function that determines if the row is displayed when filtered | function(value, record) => boolean | - | |
|
||||
| onFilterDropdownOpenChange | Callback executed when `filterDropdownOpen` is changed | function(visible) {} | - | |
|
||||
| onHeaderCell | Set props on per header cell | function(column) | - | |
|
||||
|
||||
### ColumnGroup
|
||||
|
@ -194,7 +194,6 @@ const columns = [
|
||||
| defaultSortOrder | 默认排序顺序 | `ascend` \| `descend` | - | |
|
||||
| ellipsis | 超过宽度将自动省略,暂不支持和排序筛选一起使用。<br />设置为 `true` 或 `{ showTitle?: boolean }` 时,表格布局将变成 `tableLayout="fixed"`。 | boolean \| { showTitle?: boolean } | false | showTitle: 4.3.0 |
|
||||
| filterDropdown | 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互 | ReactNode \| (props: [FilterDropdownProps](https://github.com/ant-design/ant-design/blob/ecc54dda839619e921c0ace530408871f0281c2a/components/table/interface.tsx#L79)) => ReactNode | - | |
|
||||
| filterDropdownOpen | 用于控制自定义筛选菜单是否可见 | boolean | - | |
|
||||
| filtered | 标识数据是否经过过滤,筛选图标会高亮 | boolean | false | |
|
||||
| filteredValue | 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 | string\[] | - | |
|
||||
| filterIcon | 自定义 filter 图标。 | ReactNode \| (filtered: boolean) => ReactNode | false | |
|
||||
@ -203,6 +202,7 @@ const columns = [
|
||||
| filterMode | 指定筛选菜单的用户界面 | 'menu' \| 'tree' | 'menu' | 4.17.0 |
|
||||
| filterSearch | 筛选菜单项是否可搜索 | boolean \| function(input, record):boolean | false | boolean:4.17.0 function:4.19.0 |
|
||||
| filters | 表头的筛选菜单项 | object\[] | - | |
|
||||
| filterDropdownProps | 自定义下拉属性,在 `<5.22.0` 之前可用 `filterDropdownOpen` 和 `onFilterDropdownOpenChange` | [DropdownProps](/components/dropdown#api) | - | 5.22.0 |
|
||||
| fixed | (IE 下无效)列是否固定,可选 `true` (等效于 `left`) `left` `right` | boolean \| string | false | |
|
||||
| key | React 需要的 key,如果已经设置了唯一的 `dataIndex`,可以忽略这个属性 | string | - | |
|
||||
| render | 生成复杂数据的渲染函数,参数分别为当前行的值,当前行数据,行索引 | function(text, record, index) {} | - | |
|
||||
@ -220,7 +220,6 @@ const columns = [
|
||||
| hidden | 隐藏列 | boolean | false | 5.13.0 |
|
||||
| onCell | 设置单元格属性 | function(record, rowIndex) | - | |
|
||||
| onFilter | 本地模式下,确定筛选的运行函数 | function | - | |
|
||||
| onFilterDropdownOpenChange | 自定义筛选菜单可见变化时调用 | function(visible) {} | - | |
|
||||
| onHeaderCell | 设置头部单元格属性 | function(column) | - | |
|
||||
|
||||
### ColumnGroup
|
||||
|
@ -11,6 +11,7 @@ import { ExpandableConfig, GetRowKey } from 'rc-table/lib/interface';
|
||||
import type { Breakpoint } from '../_util/responsiveObserver';
|
||||
import type { AnyObject } from '../_util/type';
|
||||
import type { CheckboxProps } from '../checkbox';
|
||||
import type { DropdownProps } from '../dropdown';
|
||||
import type { PaginationProps } from '../pagination';
|
||||
import type { TooltipProps } from '../tooltip';
|
||||
import type { INTERNAL_SELECTION_ITEM } from './hooks/useSelection';
|
||||
@ -114,6 +115,19 @@ export interface FilterDropdownProps {
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
// 非必要请勿导出
|
||||
interface CoverableDropdownProps
|
||||
extends Omit<
|
||||
DropdownProps,
|
||||
| 'onOpenChange'
|
||||
// === deprecated ===
|
||||
| 'overlay'
|
||||
| 'visible'
|
||||
| 'onVisibleChange'
|
||||
> {
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export interface ColumnType<RecordType = AnyObject>
|
||||
extends Omit<RcColumnType<RecordType>, 'title'> {
|
||||
title?: ColumnTitle<RecordType>;
|
||||
@ -144,17 +158,30 @@ export interface ColumnType<RecordType = AnyObject>
|
||||
filterMode?: 'menu' | 'tree';
|
||||
filterSearch?: FilterSearchType<ColumnFilterItem>;
|
||||
onFilter?: (value: React.Key | boolean, record: RecordType) => boolean;
|
||||
filterDropdownOpen?: boolean;
|
||||
onFilterDropdownOpenChange?: (visible: boolean) => void;
|
||||
/**
|
||||
* Can cover `<Dropdown>` props
|
||||
* @since 5.22.0
|
||||
*/
|
||||
filterDropdownProps?: CoverableDropdownProps;
|
||||
filterResetToDefaultFilteredValue?: boolean;
|
||||
|
||||
// Responsive
|
||||
responsive?: Breakpoint[];
|
||||
|
||||
// Deprecated
|
||||
/** @deprecated Please use `filterDropdownOpen` instead */
|
||||
/**
|
||||
* @deprecated Please use `filterDropdownProps.open` instead.
|
||||
* @since 4.23.0
|
||||
*/
|
||||
filterDropdownOpen?: boolean;
|
||||
/**
|
||||
* @deprecated Please use `filterDropdownProps.onOpenChange` instead.
|
||||
* @since 4.23.0
|
||||
*/
|
||||
onFilterDropdownOpenChange?: (visible: boolean) => void;
|
||||
/** @deprecated Please use `filterDropdownProps.open` instead. */
|
||||
filterDropdownVisible?: boolean;
|
||||
/** @deprecated Please use `onFilterDropdownOpenChange` instead */
|
||||
/** @deprecated Please use `filterDropdownProps.onOpenChange` instead */
|
||||
onFilterDropdownVisibleChange?: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user