Browse Source

Merge pull request #17806 from ant-design/feat-table-popup-container

feat: Table getPopupContainer
pull/17976/head
偏右 5 years ago
committed by GitHub
parent
commit
161def04aa
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 99
      components/table/Table.tsx
  2. 32
      components/table/__tests__/Table.filter.test.js
  3. 438
      components/table/__tests__/__snapshots__/Table.filter.test.js.snap
  4. 3
      components/table/filterDropdown.tsx
  5. 1
      components/table/index.en-US.md
  6. 1
      components/table/index.zh-CN.md
  7. 1
      components/table/interface.tsx

99
components/table/Table.tsx

@ -85,6 +85,7 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
locale: PropTypes.object,
dropdownPrefixCls: PropTypes.string,
sortDirections: PropTypes.array,
getPopupContainer: PropTypes.func,
};
static defaultProps = {
@ -762,18 +763,24 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
return recordKey === undefined ? index : recordKey;
};
getPopupContainer = () => {
return ReactDOM.findDOMNode(this) as HTMLElement;
};
generatePopupContainerFunc = () => {
generatePopupContainerFunc = (getPopupContainer: TableProps<T>['getPopupContainer']) => {
const { scroll } = this.props;
if (getPopupContainer) {
return getPopupContainer;
}
// Use undefined to let rc component use default logic.
return scroll ? this.getPopupContainer : undefined;
return scroll ? () => ReactDOM.findDOMNode(this) as HTMLElement : undefined;
};
renderRowSelection(prefixCls: string, locale: TableLocale) {
renderRowSelection({
prefixCls,
locale,
getPopupContainer,
}: {
prefixCls: string;
locale: TableLocale;
getPopupContainer: TableProps<T>['getPopupContainer'];
}) {
const { rowSelection } = this.props;
const columns = this.columns.concat();
if (rowSelection) {
@ -813,7 +820,7 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
onSelect={this.handleSelectRow}
selections={rowSelection.selections}
hideDefaultSelections={rowSelection.hideDefaultSelections}
getPopupContainer={this.generatePopupContainerFunc()}
getPopupContainer={this.generatePopupContainerFunc(getPopupContainer)}
/>
);
}
@ -853,12 +860,19 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
return this.getColumnKey(sortColumn) === this.getColumnKey(column);
}
renderColumnsDropdown(
prefixCls: string,
dropdownPrefixCls: string,
columns: ColumnProps<T>[],
locale: TableLocale,
) {
renderColumnsDropdown({
prefixCls,
dropdownPrefixCls,
columns,
locale,
getPopupContainer,
}: {
prefixCls: string;
dropdownPrefixCls: string;
columns: ColumnProps<T>[];
locale: TableLocale;
getPopupContainer: TableProps<T>['getPopupContainer'];
}) {
const { sortOrder, filters } = this.state;
return treeMap(columns, (column, i) => {
const key = this.getColumnKey(column, i) as string;
@ -876,7 +890,7 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
confirmFilter={this.handleFilter}
prefixCls={`${prefixCls}-filter`}
dropdownPrefixCls={dropdownPrefixCls || 'ant-dropdown'}
getPopupContainer={this.generatePopupContainerFunc()}
getPopupContainer={this.generatePopupContainerFunc(getPopupContainer)}
key="filter-dropdown"
/>
);
@ -1182,16 +1196,27 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
return null;
};
renderTable = (
prefixCls: string,
renderEmpty: RenderEmptyHandler,
dropdownPrefixCls: string,
contextLocale: TableLocale,
) => {
const { style, className, showHeader, locale, ...restProps } = this.props;
renderTable = ({
prefixCls,
renderEmpty,
dropdownPrefixCls,
contextLocale,
getPopupContainer: contextGetPopupContainer,
}: {
prefixCls: string;
renderEmpty: RenderEmptyHandler;
dropdownPrefixCls: string;
contextLocale: TableLocale;
getPopupContainer: TableProps<T>['getPopupContainer'];
}) => {
const { style, className, showHeader, locale, getPopupContainer, ...restProps } = this.props;
const data = this.getCurrentPageData();
const expandIconAsCell = this.props.expandedRowRender && this.props.expandIconAsCell !== false;
// use props.getPopupContainer first
const realGetPopupContainer = getPopupContainer || contextGetPopupContainer;
// Merge too locales
const mergedLocale = { ...contextLocale, ...locale };
if (!locale || !locale.emptyText) {
mergedLocale.emptyText = renderEmpty('Table');
@ -1204,13 +1229,23 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
[`${prefixCls}-without-column-header`]: !showHeader,
});
let columns = this.renderRowSelection(prefixCls, mergedLocale);
columns = this.renderColumnsDropdown(prefixCls, dropdownPrefixCls, columns, mergedLocale);
columns = columns.map((column, i) => {
const columnsWithRowSelection = this.renderRowSelection({
prefixCls,
locale: mergedLocale,
getPopupContainer: realGetPopupContainer,
});
const columns = this.renderColumnsDropdown({
columns: columnsWithRowSelection,
prefixCls,
dropdownPrefixCls,
locale: mergedLocale,
getPopupContainer: realGetPopupContainer,
}).map((column, i) => {
const newColumn = { ...column };
newColumn.key = this.getColumnKey(newColumn, i);
return newColumn;
});
let expandIconColumnIndex = columns[0] && columns[0].key === 'selection-column' ? 1 : 0;
if ('expandIconColumnIndex' in restProps) {
expandIconColumnIndex = restProps.expandIconColumnIndex as number;
@ -1235,7 +1270,7 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
);
};
renderComponent = ({ getPrefixCls, renderEmpty }: ConfigConsumerProps) => {
renderComponent = ({ getPrefixCls, renderEmpty, getPopupContainer }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
dropdownPrefixCls: customizeDropdownPrefixCls,
@ -1255,7 +1290,15 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
const dropdownPrefixCls = getPrefixCls('dropdown', customizeDropdownPrefixCls);
const table = (
<LocaleReceiver componentName="Table" defaultLocale={defaultLocale.Table}>
{locale => this.renderTable(prefixCls, renderEmpty, dropdownPrefixCls, locale)}
{locale =>
this.renderTable({
prefixCls,
renderEmpty,
dropdownPrefixCls,
contextLocale: locale,
getPopupContainer,
})
}
</LocaleReceiver>
);

32
components/table/__tests__/Table.filter.test.js

@ -4,6 +4,7 @@ import { render, mount } from 'enzyme';
import Table from '..';
import Input from '../../input';
import Button from '../../button';
import ConfigProvider from '../../config-provider';
function getDropdownWrapper(wrapper) {
return mount(
@ -686,4 +687,35 @@ describe('Table.filter', () => {
dropdownWrapper2.find('.confirm').simulate('click');
expect(onChange).toHaveBeenCalled();
});
it('should support getPopupContainer', () => {
const wrapper = mount(
createTable({
columns: [
{
...column,
filterDropdownVisible: true,
},
],
getPopupContainer: node => node.parentNode,
}),
);
expect(wrapper.render()).toMatchSnapshot();
});
it('should support getPopupContainer from ConfigProvider', () => {
const wrapper = mount(
<ConfigProvider getPopupContainer={node => node.parentNode}>
{createTable({
columns: [
{
...column,
filterDropdownVisible: true,
},
],
})}
</ConfigProvider>
);
expect(wrapper.render()).toMatchSnapshot();
});
});

438
components/table/__tests__/__snapshots__/Table.filter.test.js.snap

@ -376,3 +376,441 @@ exports[`Table.filter renders radio filter correctly 1`] = `
</div>
</div>
`;
exports[`Table.filter should support getPopupContainer 1`] = `
<div
class="ant-table-wrapper"
>
<div
class="ant-spin-nested-loading"
>
<div
class="ant-spin-container"
>
<div
class="ant-table ant-table-default ant-table-scroll-position-left"
>
<div
class="ant-table-content"
>
<div
class="ant-table-body"
>
<table
class=""
>
<colgroup>
<col />
</colgroup>
<thead
class="ant-table-thead"
>
<tr>
<th
class="ant-table-column-has-actions ant-table-column-has-filters"
>
<span
class="ant-table-header-column"
>
<div>
<span
class="ant-table-column-title"
>
Name
</span>
<span
class="ant-table-column-sorter"
/>
</div>
</span>
<i
aria-label="icon: filter"
class="anticon anticon-filter ant-table-filter-open ant-dropdown-trigger ant-dropdown-open"
style=""
tabindex="-1"
title="Filter menu"
>
<svg
aria-hidden="true"
class=""
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>
</i>
<div>
<div
class="ant-dropdown ant-dropdown-placement-bottomRight slide-up-appear"
style="left: -999px; top: -995px;"
>
<div
class="ant-table-filter-dropdown"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical"
role="menu"
tabindex="0"
>
<li
class="ant-dropdown-menu-item"
role="menuitem"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
value=""
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
<span>
Boy
</span>
</li>
<li
class="ant-dropdown-menu-item"
role="menuitem"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
value=""
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
<span>
Girl
</span>
</li>
<li
class="ant-dropdown-menu-submenu ant-dropdown-menu-submenu-vertical"
role="menuitem"
>
<div
aria-expanded="false"
aria-haspopup="true"
class="ant-dropdown-menu-submenu-title"
title="Title"
>
Title
<i
class="ant-dropdown-menu-submenu-arrow"
/>
</div>
</li>
</ul>
<div
class="ant-table-filter-dropdown-btns"
>
<a
class="ant-table-filter-dropdown-link confirm"
>
OK
</a>
<a
class="ant-table-filter-dropdown-link clear"
>
Reset
</a>
</div>
</div>
</div>
</div>
</th>
</tr>
</thead>
<tbody
class="ant-table-tbody"
>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="0"
>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
Jack
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="1"
>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
Lucy
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="2"
>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
Tom
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="3"
>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
Jerry
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Table.filter should support getPopupContainer from ConfigProvider 1`] = `
<div
class="ant-table-wrapper"
>
<div
class="ant-spin-nested-loading"
>
<div
class="ant-spin-container"
>
<div
class="ant-table ant-table-default ant-table-scroll-position-left"
>
<div
class="ant-table-content"
>
<div
class="ant-table-body"
>
<table
class=""
>
<colgroup>
<col />
</colgroup>
<thead
class="ant-table-thead"
>
<tr>
<th
class="ant-table-column-has-actions ant-table-column-has-filters"
>
<span
class="ant-table-header-column"
>
<div>
<span
class="ant-table-column-title"
>
Name
</span>
<span
class="ant-table-column-sorter"
/>
</div>
</span>
<i
aria-label="icon: filter"
class="anticon anticon-filter ant-table-filter-open ant-dropdown-trigger ant-dropdown-open"
style=""
tabindex="-1"
title="Filter menu"
>
<svg
aria-hidden="true"
class=""
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>
</i>
<div>
<div
class="ant-dropdown ant-dropdown-placement-bottomRight slide-up-appear"
style="left: -999px; top: -995px;"
>
<div
class="ant-table-filter-dropdown"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical"
role="menu"
tabindex="0"
>
<li
class="ant-dropdown-menu-item"
role="menuitem"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
value=""
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
<span>
Boy
</span>
</li>
<li
class="ant-dropdown-menu-item"
role="menuitem"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
value=""
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
<span>
Girl
</span>
</li>
<li
class="ant-dropdown-menu-submenu ant-dropdown-menu-submenu-vertical"
role="menuitem"
>
<div
aria-expanded="false"
aria-haspopup="true"
class="ant-dropdown-menu-submenu-title"
title="Title"
>
Title
<i
class="ant-dropdown-menu-submenu-arrow"
/>
</div>
</li>
</ul>
<div
class="ant-table-filter-dropdown-btns"
>
<a
class="ant-table-filter-dropdown-link confirm"
>
OK
</a>
<a
class="ant-table-filter-dropdown-link clear"
>
Reset
</a>
</div>
</div>
</div>
</div>
</th>
</tr>
</thead>
<tbody
class="ant-table-tbody"
>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="0"
>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
Jack
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="1"
>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
Lucy
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="2"
>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
Tom
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="3"
>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
Jerry
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
`;

3
components/table/filterDropdown.tsx

@ -263,7 +263,6 @@ class FilterMenu<T> extends React.Component<FilterMenuProps<T>, FilterMenuState<
confirm: this.handleConfirm,
clearFilters: this.handleClearFilters,
filters: column.filters,
getPopupContainer: (triggerNode: HTMLElement) => triggerNode.parentNode as HTMLElement,
});
}
@ -281,7 +280,7 @@ class FilterMenu<T> extends React.Component<FilterMenuProps<T>, FilterMenuState<
onSelect={this.setSelectedKeys}
onDeselect={this.setSelectedKeys}
selectedKeys={originSelectedKeys && originSelectedKeys.map(val => val.toString())}
getPopupContainer={(triggerNode: HTMLElement) => triggerNode.parentNode}
getPopupContainer={getPopupContainer}
>
{this.renderMenus(column.filters!)}
</Menu>

1
components/table/index.en-US.md

@ -87,6 +87,7 @@ const columns = [
| onExpandedRowsChange | Callback executed when the expanded rows change | Function(expandedRows) | | |
| onHeaderRow | Set props on per header row | Function(column, index) | - | |
| onRow | Set props on per row | Function(record, index) | - | |
| getPopupContainer | the render container of dropdowns in table | (triggerNode) => HTMLElement | `() => TableHtmlElement` | 3.21.0 |
#### onRow usage

1
components/table/index.zh-CN.md

@ -92,6 +92,7 @@ const columns = [
| onExpandedRowsChange | 展开的行变化时触发 | Function(expandedRows) | | |
| onHeaderRow | 设置头部行属性 | Function(column, index) | - | |
| onRow | 设置行属性 | Function(record, index) | - | |
| getPopupContainer | 设置表格内各类浮层的渲染节点,如筛选菜单 | (triggerNode) => HTMLElement | `() => TableHtmlElement` | 3.21.0 |
#### onRow 用法

1
components/table/interface.tsx

@ -190,6 +190,7 @@ export interface TableProps<T> {
style?: React.CSSProperties;
children?: React.ReactNode;
sortDirections?: SortOrder[];
getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
}
export interface TableStateFilters {

Loading…
Cancel
Save