horizontal scrolling

Summary:
Adding a property `horizontallyScrollable` the `ManagedTable` that enables horizontal scrolling, if that is required by the table's contents.
By default this behaviour is disabled to not break existing tables.

Reviewed By: jknoxville

Differential Revision: D15166144

fbshipit-source-id: 7a9b76facdbe717deb3d097e0b8883d4b0732d51
This commit is contained in:
Daniel Büchele
2019-05-01 09:28:18 -07:00
committed by Facebook Github Bot
parent da44a02cad
commit fca7bc93ee
3 changed files with 67 additions and 19 deletions

View File

@@ -116,6 +116,10 @@ export type ManagedTableProps = {|
* Callback when sorting changes * Callback when sorting changes
*/ */
onSort?: (order: TableRowSortOrder) => void, onSort?: (order: TableRowSortOrder) => void,
/**
* Table scroll horizontally, if needed
*/
horizontallyScrollable?: boolean,
|}; |};
type ManagedTableState = {| type ManagedTableState = {|
@@ -126,9 +130,10 @@ type ManagedTableState = {|
shouldScrollToBottom: boolean, shouldScrollToBottom: boolean,
|}; |};
const Container = styled(FlexColumn)({ const Container = styled(FlexColumn)(props => ({
overflow: props.overflow ? 'scroll' : 'visible',
flexGrow: 1, flexGrow: 1,
}); }));
class ManagedTable extends React.Component< class ManagedTable extends React.Component<
ManagedTableProps, ManagedTableProps,
@@ -318,8 +323,13 @@ class ManagedTable extends React.Component<
); );
}; };
onColumnResize = (columnSizes: TableColumnSizes) => { onColumnResize = (id: string, width: number | string) => {
this.setState({columnSizes}); this.setState(({columnSizes}) => ({
columnSizes: {
...columnSizes,
[id]: width,
},
}));
}; };
scrollToBottom() { scrollToBottom() {
@@ -499,7 +509,13 @@ class ManagedTable extends React.Component<
); );
getRow = ({index, style}) => { getRow = ({index, style}) => {
const {onAddFilter, multiline, zebra, rows} = this.props; const {
onAddFilter,
multiline,
zebra,
rows,
horizontallyScrollable,
} = this.props;
const {columnOrder, columnSizes, highlightedRows} = this.state; const {columnOrder, columnSizes, highlightedRows} = this.state;
const columnKeys = columnOrder const columnKeys = columnOrder
.map(k => (k.visible ? k.key : null)) .map(k => (k.visible ? k.key : null))
@@ -520,16 +536,37 @@ class ManagedTable extends React.Component<
style={style} style={style}
onAddFilter={onAddFilter} onAddFilter={onAddFilter}
zebra={zebra} zebra={zebra}
horizontallyScrollable={horizontallyScrollable}
/> />
); );
}; };
render() { render() {
const {columns, rows, rowLineHeight, hideHeader} = this.props; const {
columns,
rows,
rowLineHeight,
hideHeader,
horizontallyScrollable,
} = this.props;
const {columnOrder, columnSizes} = this.state; const {columnOrder, columnSizes} = this.state;
let computedWidth = 0;
if (horizontallyScrollable) {
for (const col in columnSizes) {
const width = columnSizes[col];
if (isNaN(width)) {
// non-numeric columns with, can't caluclate
computedWidth = 0;
break;
} else {
computedWidth += parseInt(width, 10);
}
}
}
return ( return (
<Container> <Container overflow={horizontallyScrollable}>
{hideHeader !== true && ( {hideHeader !== true && (
<TableHead <TableHead
columnOrder={columnOrder} columnOrder={columnOrder}
@@ -539,6 +576,7 @@ class ManagedTable extends React.Component<
sortOrder={this.state.sortOrder} sortOrder={this.state.sortOrder}
columnSizes={columnSizes} columnSizes={columnSizes}
onSort={this.onSort} onSort={this.onSort}
horizontallyScrollable={horizontallyScrollable}
/> />
)} )}
<Container> <Container>
@@ -560,7 +598,7 @@ class ManagedTable extends React.Component<
DEFAULT_ROW_HEIGHT DEFAULT_ROW_HEIGHT
} }
ref={this.tableRef} ref={this.tableRef}
width={width} width={Math.max(width, computedWidth)}
estimatedItemSize={rowLineHeight || DEFAULT_ROW_HEIGHT} estimatedItemSize={rowLineHeight || DEFAULT_ROW_HEIGHT}
overscanCount={5} overscanCount={5}
innerRef={this.scrollRef} innerRef={this.scrollRef}

View File

@@ -43,7 +43,7 @@ const TableHeaderColumnContainer = styled('div')({
padding: '0 8px', padding: '0 8px',
}); });
const TableHeadContainer = styled(FlexRow)({ const TableHeadContainer = styled(FlexRow)(props => ({
borderBottom: `1px solid ${colors.sectionHeaderBorder}`, borderBottom: `1px solid ${colors.sectionHeaderBorder}`,
color: colors.light50, color: colors.light50,
flexShrink: 0, flexShrink: 0,
@@ -53,7 +53,8 @@ const TableHeadContainer = styled(FlexRow)({
textAlign: 'left', textAlign: 'left',
top: 0, top: 0,
zIndex: 2, zIndex: 2,
}); minWidth: props.horizontallyScrollable ? 'min-content' : 0,
}));
const TableHeadColumnContainer = styled('div')(props => ({ const TableHeadColumnContainer = styled('div')(props => ({
position: 'relative', position: 'relative',
@@ -97,9 +98,17 @@ class TableHeadColumn extends PureComponent<{
onColumnResize: ?TableOnColumnResize, onColumnResize: ?TableOnColumnResize,
children?: React$Node, children?: React$Node,
title?: string, title?: string,
horizontallyScrollable?: boolean,
}> { }> {
ref: HTMLElement; ref: HTMLElement;
componentDidMount() {
if (this.props.horizontallyScrollable) {
// measure initial width
this.onResize(this.ref.offsetWidth);
}
}
onClick = () => { onClick = () => {
const {id, onSort, sortOrder} = this.props; const {id, onSort, sortOrder} = this.props;
@@ -117,7 +126,7 @@ class TableHeadColumn extends PureComponent<{
}; };
onResize = (newWidth: number) => { onResize = (newWidth: number) => {
const {id, columnSizes, onColumnResize, width} = this.props; const {id, onColumnResize, width} = this.props;
if (!onColumnResize) { if (!onColumnResize) {
return; return;
} }
@@ -143,10 +152,7 @@ class TableHeadColumn extends PureComponent<{
} }
} }
onColumnResize({ onColumnResize(id, normalizedWidth);
...columnSizes,
[id]: normalizedWidth,
});
}; };
setRef = (ref: HTMLElement) => { setRef = (ref: HTMLElement) => {
@@ -191,6 +197,7 @@ export default class TableHead extends PureComponent<{
onSort: ?TableOnSort, onSort: ?TableOnSort,
columnSizes: TableColumnSizes, columnSizes: TableColumnSizes,
onColumnResize: ?TableOnColumnResize, onColumnResize: ?TableOnColumnResize,
horizontallyScrollable?: boolean,
}> { }> {
buildContextMenu = (): MenuTemplate => { buildContextMenu = (): MenuTemplate => {
const visibles = this.props.columnOrder const visibles = this.props.columnOrder
@@ -237,6 +244,7 @@ export default class TableHead extends PureComponent<{
onColumnResize, onColumnResize,
onSort, onSort,
sortOrder, sortOrder,
horizontallyScrollable,
} = this.props; } = this.props;
const elems = []; const elems = [];
@@ -284,7 +292,8 @@ export default class TableHead extends PureComponent<{
onSort={onSort} onSort={onSort}
columnSizes={columnSizes} columnSizes={columnSizes}
onColumnResize={onColumnResize} onColumnResize={onColumnResize}
title={key}> title={key}
horizontallyScrollable={horizontallyScrollable}>
{col.value} {col.value}
{arrow} {arrow}
</TableHeadColumn> </TableHeadColumn>
@@ -296,10 +305,11 @@ export default class TableHead extends PureComponent<{
lastResizable = isResizable; lastResizable = isResizable;
} }
return ( return (
<ContextMenu buildItems={this.buildContextMenu}> <ContextMenu buildItems={this.buildContextMenu}>
<TableHeadContainer>{elems}</TableHeadContainer> <TableHeadContainer horizontallyScrollable={horizontallyScrollable}>
{elems}
</TableHeadContainer>
</ContextMenu> </ContextMenu>
); );
} }

View File

@@ -26,7 +26,7 @@ export type TableHighlightedRows = Array<string>;
export type TableColumnKeys = Array<string>; export type TableColumnKeys = Array<string>;
export type TableOnColumnResize = (sizes: TableColumnSizes) => void; export type TableOnColumnResize = (id: string, size: number | string) => void;
export type TableOnColumnOrder = (order: TableColumnOrder) => void; export type TableOnColumnOrder = (order: TableColumnOrder) => void;
export type TableOnSort = (order: TableRowSortOrder) => void; export type TableOnSort = (order: TableRowSortOrder) => void;
export type TableOnHighlight = ( export type TableOnHighlight = (