Skip to content

Instantly share code, notes, and snippets.

@justinobney
Created July 12, 2018 12:31
Show Gist options
  • Save justinobney/7c03cb31890eb5a71e9dcfe9862681d6 to your computer and use it in GitHub Desktop.
Save justinobney/7c03cb31890eb5a71e9dcfe9862681d6 to your computer and use it in GitHub Desktop.
/**
<div> -- track 'top' of scrollable parent
-- track 'scrollLeft' of container to move fixed header
<Table.Container>
<Table.Header>
<Table.Row> -- clone row and set 'top' and 'position: fixed'
<Table.HeaderCell /> -- track dimensions of original row cells
-- bind dimensions of cloned row cells to tracked cells
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
</Table.Row>
</Table.Body>
</Table.Container>
</div>
*/
import React, {Children, Component} from 'react';
import {findDOMNode} from 'react-dom';
import {Ref, Table} from 'semantic-ui-react'; // Ref is used by semantic-ui to
// get refs to underlying components
import Measure from 'react-measure';
class TableContainer extends Component {
state = {
dimensions: {},
parentDimensions: {},
scrollLeft: 0,
widths: {},
};
componentDidMount() {
this._parentElement = findDOMNode(this).parentElement;
if (this._parentElement) {
this._parentElement.addEventListener('scroll', this._handleParentScroll);
}
}
componentWillUnmount() {
if (this._parentElement) {
this._parentElement.removeEventListener(
'scroll',
this._handleParentScroll
);
}
}
_createClonedTableCell = (cell, idx) => {
return React.cloneElement(cell, {
key: idx,
style: {
width: this.state.widths[idx] || 'initial',
minWidth: this.state.widths[idx] || 'initial', // needed to keep cell fro shrinking
},
});
};
_createMeasuredTableCell = (cell, idx) => (
<Measure
key={idx}
bounds
onResize={({bounds}) => {
this.setState(state => ({
widths: {
...state.widths,
[idx]: bounds.width,
},
}));
}}
>
{({measureRef}) => <Ref innerRef={measureRef}>{cell}</Ref>}
</Measure>
);
_handleParentScroll = e => {
const scrollLeft = this._parentElement.scrollLeft;
if (this.state.scrollLeft !== scrollLeft) {
this.setState({scrollLeft: scrollLeft});
}
};
_handleTableResized = contentRect => {
const parentDimensions = this._measureParent();
this.setState({dimensions: contentRect.bounds, parentDimensions});
};
_measureParent() {
try {
const {
bottom,
height,
left,
right,
top,
width,
} = this._parentElement.getBoundingClientRect();
// you need to destructure this to be able to inspect the bounds
// outside of the closure. (Ex: DevTools)
return {bottom, height, left, right, top, width};
} catch (error) {
console.log(error);
return {};
}
}
render() {
const {children, ...props} = this.props;
const {dimensions, parentDimensions, scrollLeft} = this.state;
const right = document.documentElement.clientWidth - dimensions.width;
const renderClone = !window.isNaN(right);
const rowStyle = {
right,
top: parentDimensions.top,
left: dimensions.left - scrollLeft, // takes horizontal scrolling into account
position: 'fixed',
overflow: 'hidden', // this prevents the fixed header for floating over the scrollbar
};
return (
<Measure bounds onResize={this._handleTableResized}>
{({measureRef}) => (
<div ref={measureRef}>
<Table {...props} unstackable>
{Children.map(children, child => {
if (child.type.displayName === 'TableHeader') {
const TableHeaderRow = Children.only(child.props.children); // ensure single row in header
const TableHeaderRowCells = TableHeaderRow.props.children;
/**
* Clone the Table.Row in the Table.Header.
* Clone and measure the original Table.Cells and track their dimensions.
* We will keep this in sync with the Table.Cells of the floating header.
*/
const originalRow = React.cloneElement(TableHeaderRow, {
children: Children.map(
TableHeaderRowCells,
this._createMeasuredTableCell
),
});
/**
* This will be a position:fixed clone of the TableHeaderRow
* All the children cells will be bound to the widths of the
* corresponding measured cells above
*/
const row = React.cloneElement(TableHeaderRow, {
style: rowStyle,
children: Children.map(
TableHeaderRowCells,
this._createClonedTableCell
),
});
return renderClone
? React.cloneElement(child, {
children: (
<React.Fragment>
{originalRow}
{row}
</React.Fragment>
),
})
: child;
}
return child;
})}
</Table>
</div>
)}
</Measure>
);
}
}
Table.Container = TableContainer;
export default Table;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment