Created
July 12, 2018 12:31
-
-
Save justinobney/7c03cb31890eb5a71e9dcfe9862681d6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
<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