diff --git a/src/Table/elements.js b/src/Table/elements.js index 9c4f73b6..9d981c0e 100644 --- a/src/Table/elements.js +++ b/src/Table/elements.js @@ -1,4 +1,6 @@ import styled, { css } from 'styled-components'; +import { transparentize } from 'polished'; +import breakpoint from 'styled-components-breakpoint'; const borderRight = height => css` &:after { @@ -18,10 +20,8 @@ const borderRight = height => css` // Table export const TableElement = styled.table` - min-width: 100%; - - border-collapse: collapse; - table-layout: fixed; + width: 100%; + border-spacing: 0; position: relative; `; @@ -59,32 +59,25 @@ export const TableHeader = styled.thead` export const TableHeaderCell = styled.th` position: relative; height: 4.4rem; - padding: 0 0.5rem; - + padding: 0 1.2rem; + box-sizing: border-box; + vertical-align: middle; + border-bottom: 1px solid ${({ theme: { palette } }) => palette.veryLightBlue}; background-color: ${({ theme: { palette } }) => palette.white}; - + white-space: nowrap; text-align: ${({ align }) => align || 'left'}; - &:before { - content: ''; - position: absolute; - right: 0; - bottom: 0; - width: 100%; - border-bottom: 1px solid ${({ theme: { palette } }) => palette.veryLightBlue}; - } - &:first-child { padding-left: 2rem; - padding-right: 0.5rem; + padding-right: 1.2rem; z-index: 2; left: 0; ${({ isScrollable }) => isScrollable && borderRight('4.4rem')} } &:last-child { - padding-right: 2.2rem; - padding-left: 0.5rem; + padding-right: 2rem; + padding-left: 1.2rem; } ${({ isScrollable }) => @@ -109,14 +102,28 @@ export const HeaderLabel = styled.span` // Table Body export const Body = styled.tbody` + tr:not(:last-child) { + td { + border-bottom: 1px solid ${({ theme: { palette } }) => palette.veryLightBlue}; + } + th { + border-bottom: 1px solid ${({ theme: { palette } }) => palette.veryLightBlue}; + } + } td { height: 5.2rem; - - padding: 0 0.5rem; + padding: 0 1.2rem; + white-space: nowrap; + vertical-align: middle; font-feature-settings: 'tnum'; - min-width: 10rem; + ${({ colsLength }) => + colsLength && + css` + width: calc(100% / ${colsLength}); + `}; + box-sizing: border-box; &:first-child { padding-left: 2rem; @@ -134,7 +141,6 @@ export const ChildRow = styled(Row)` (hasChildren || selectable) && css` cursor: pointer; - } `}; ${({ striped }) => @@ -174,8 +180,16 @@ export const BodyRow = styled(ChildRow)` export const RowHeader = styled.th` height: 5.2rem; - display: flex; - align-items: center; + + /* Avoid cell to grow when the cell's text overflow */ + max-width: 30rem; + min-width: 20rem; + ${breakpoint('xs', 'sm')` + max-width: 50vw; + min-width: 0; + `}; + white-space: nowrap; + box-sizing: border-box; ${({ isScrollable }) => isScrollable && @@ -190,7 +204,7 @@ export const RowHeader = styled.th` white-space: nowrap; overflow: hidden; - padding: 0 0.5rem 0 2rem; + padding: 0 1.2rem 0 2rem; ${({ isChild }) => isChild && @@ -198,11 +212,17 @@ export const RowHeader = styled.th` padding-left: 4rem; `} - max-width: 30rem; - min-width: 15rem; background-color: ${({ theme: { palette } }) => palette.white}; `; +export const TextEllipsis = styled.div` + display: inline-block; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: 100%; +`; + // Table Footer export const Footer = styled.tfoot` position: sticky; @@ -212,6 +232,7 @@ export const Footer = styled.tfoot` td { font-feature-settings: 'tnum'; + box-sizing: border-box; } th, @@ -235,7 +256,7 @@ export const Footer = styled.tfoot` height: 5.2rem; - padding: 0 0.5rem; + padding: 0 1.2rem; background-color: ${({ theme: { palette } }) => palette.white}; color: ${({ theme: { palette } }) => palette.primary.default}; @@ -251,11 +272,11 @@ export const Footer = styled.tfoot` ${borderRight('5.2rem')} `} - padding: 0 0.5rem 0 2rem; + padding: 0 1.2rem 0 2rem; } td:last-of-type { - padding: 0 3rem 0 0.5rem; + padding: 0 2rem 0 1.2rem; } ${({ isHoverable }) => @@ -280,3 +301,32 @@ export const Container = styled.div` `} position: relative; `; + +// Shadow Container +export const ShadowContainer = styled.div` + pointer-events: none; + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: ${({ firstCellWidth }) => firstCellWidth && `${firstCellWidth}px`}; + z-index: 10; + box-shadow: ${({ side, theme: { palette } }) => { + const shadowColor = transparentize(0.9, palette.black); + if (side === 'left') { + return `inset -8px 0 6px -6px ${shadowColor};`; + } else if (side === 'right') { + return `inset 8px 0 6px -6px ${shadowColor};`; + } else if (side === 'both') { + return `inset -8px 0 6px -6px ${shadowColor}, inset 8px 0 6px -6px ${shadowColor};`; + } + return null; + }} + background-repeat: no-repeat; + background-size: 10px 100%; +`; + +// Shadow Container +export const ShadowWrapped = styled.div` + position: relative; +`; diff --git a/src/Table/index.js b/src/Table/index.js index 49ca6a51..9b03607a 100644 --- a/src/Table/index.js +++ b/src/Table/index.js @@ -1,4 +1,4 @@ -import React, { Fragment, PureComponent } from 'react'; +import React, { Fragment, PureComponent, createRef } from 'react'; import PropTypes from 'prop-types'; import { Icon, Theme } from '..'; @@ -15,6 +15,9 @@ import { RowHeader, Footer, ChildRow, + ShadowContainer, + TextEllipsis, + ShadowWrapped, } from './elements'; /** Lookup object for next sorting direction. */ @@ -51,6 +54,59 @@ class Table extends PureComponent { // Use `-1` for no row selected. selected: -1, selectedRows: [], + firstCellWidth: 0, + shadowSide: null, + }; + + rowHeaderRef = createRef(); + containerRef = createRef(); + bodyRef = createRef(); + + componentDidMount() { + const { isScrollable } = this.props; + if (isScrollable) { + this.setFirstCellWidth(); + addEventListener('resize', this.onResize); + this.containerRef.current.addEventListener('scroll', this.onScroll); + } + } + + componentWillUnmount() { + removeEventListener('resize', this.onResize); + this.containerRef.current.removeEventListener('scroll', this.onScroll); + } + + onResize = () => { + this.setFirstCellWidth(); + this.setContainerShadow(); + }; + + onScroll = () => { + this.setContainerShadow(); + }; + + setFirstCellWidth = () => { + const firstCellWidth = + this.rowHeaderRef && this.rowHeaderRef.current && this.rowHeaderRef.current.offsetWidth; + this.setState({ firstCellWidth }); + }; + + setContainerShadow = () => { + const containerRefRect = this.containerRef.current.getBoundingClientRect(); + const bodyRefRect = this.bodyRef.current.getBoundingClientRect(); + + let shadowSide = 'both'; + if ( + containerRefRect.left === bodyRefRect.left && + containerRefRect.right === bodyRefRect.right + ) { + shadowSide = null; + } else if (containerRefRect.left === bodyRefRect.left) { + shadowSide = 'left'; + } else if (containerRefRect.right === bodyRefRect.right) { + shadowSide = 'right'; + } + this.setState({ shadowSide }); }; /** @@ -193,8 +249,14 @@ class Table extends PureComponent { return sortedData; }; + // Rule: + // first cell count 2 fractions of the table + // normal cell count 1 fractions of the table + // To calculate the cell width we need to know the column's number and add it one to take care of the first cell which take 2 fractions. + const colsLength = colsDef.length + 1; + return ( - + {sortData(data).map(({ key, item }, index) => ( {colsDef.map(({ isRowHeader, value, format, align }, columnIndex) => isRowHeader ? ( - + {item.children && ( )} - {value(item, index)} + {value(item, index)} ) : ( @@ -248,7 +315,7 @@ class Table extends PureComponent { key={`row-header-${key}-${index}`} isChild > - {value(childrenItem, index)} + {value(childrenItem, index)} ) : ( @@ -295,6 +362,16 @@ class Table extends PureComponent { ); } + /** + * Renders the shadow of the table. + * + * @return {jsx} + */ + renderShadow() { + const { firstCellWidth, shadowSide } = this.state; + return ; + } + /** * Renders the component. * @@ -304,13 +381,21 @@ class Table extends PureComponent { const { dataTotal, height, isScrollable, width } = this.props; return ( - - - {this.renderHeader()} - {this.renderBody()} - {dataTotal && this.renderFooter()} - - + + {this.renderShadow()} + + + {this.renderHeader()} + {this.renderBody()} + {dataTotal && this.renderFooter()} + + + ); } }