From 87b7616d2c2748d7e3b9fd404c5bd785de19c2bd Mon Sep 17 00:00:00 2001 From: Ayesha Mazumdar Date: Mon, 22 Feb 2021 22:38:08 -0800 Subject: [PATCH 1/5] Table: Add ability to freeze columns --- docs/src/Table.doc.js | 202 +++++++++++++++++++ packages/gestalt/src/Table.css | 11 + packages/gestalt/src/Table.js | 11 +- packages/gestalt/src/TableCell.js | 16 +- packages/gestalt/src/TableContextProvider.js | 15 ++ packages/gestalt/src/TableHeaderCell.js | 9 +- packages/gestalt/src/TableRow.js | 47 ++++- 7 files changed, 302 insertions(+), 9 deletions(-) create mode 100644 packages/gestalt/src/TableContextProvider.js diff --git a/docs/src/Table.doc.js b/docs/src/Table.doc.js index e88ef0b87ef..47401c5baad 100644 --- a/docs/src/Table.doc.js +++ b/docs/src/Table.doc.js @@ -259,6 +259,208 @@ card( />, ); +card( + + + + + + Image + + + Name + + + House + + + jfh + + + fgreg + + + yrwey + + + + + + + + + + + + + Luna Lovegood + Ravenclaw + Ravenclaw + + + + + + + + + + + Draco Malfoy + Slytherin + Slytherin + + + + + + + + + + + Neville Longbottom + Gryffindor + Gryffindor + + + +
+ + +`} + />, +); + +card( + + + + + + Image + + + Name + + + House + + + jfh + + + fgreg + + + yrwey + + + jfh + + + fgreg + + + yrwey + + + + + + + + + + + + + Luna Lovegood + Ravenclaw + Ravenclaw + Ravenclaw + Ravenclaw + Ravenclaw + + + + + + + + + + + Draco Malfoy + Slytherin + Slytherin + Slytherin + Slytherin + Slytherin + + + + + + + + + + + Neville Longbottom + Gryffindor + Gryffindor + Gryffindor + Gryffindor + Gryffindor + + + +
+ + +`} + />, +); + card( - {children}
+ + + {children} + +
); } diff --git a/packages/gestalt/src/TableCell.js b/packages/gestalt/src/TableCell.js index 692316a3fdc..9cafc96e846 100644 --- a/packages/gestalt/src/TableCell.js +++ b/packages/gestalt/src/TableCell.js @@ -1,19 +1,29 @@ // @flow strict import React, { type Node } from 'react'; +import cx from 'classnames'; import styles from './Table.css'; type Props = {| children: Node, colSpan?: number, rowSpan?: number, + shouldBeSticky?: boolean, + previousTotalWidth?: number, |}; export default function TableCell(props: Props): Node { - const { children, colSpan, rowSpan } = props; - + const { children, colSpan, rowSpan, shouldBeSticky, previousTotalWidth } = props; + const cs = cx(styles.td, shouldBeSticky && styles.columnSticky); return ( - + {children} ); } + +TableCell.displayName = 'TableCell'; diff --git a/packages/gestalt/src/TableContextProvider.js b/packages/gestalt/src/TableContextProvider.js new file mode 100644 index 00000000000..664056af42d --- /dev/null +++ b/packages/gestalt/src/TableContextProvider.js @@ -0,0 +1,15 @@ +// @flow strict +import { createContext, type Context } from 'react'; + +type TableContextType = {| + stickyColumn?: number, + stickyInclusive?: boolean, +|}; + +const initialState = { + stickyColumn: -1, +}; + +const TableContext: Context = createContext(initialState); + +export default TableContext; diff --git a/packages/gestalt/src/TableHeaderCell.js b/packages/gestalt/src/TableHeaderCell.js index 9fe998d23ca..9b84f766d6b 100644 --- a/packages/gestalt/src/TableHeaderCell.js +++ b/packages/gestalt/src/TableHeaderCell.js @@ -1,20 +1,25 @@ // @flow strict import React, { type Node } from 'react'; +import cx from 'classnames'; import styles from './Table.css'; type Props = {| children: Node, + shouldBeSticky?: Boolean, colSpan?: number, rowSpan?: number, scope?: 'col' | 'colgroup' | 'row' | 'rowgroup', |}; export default function TableHeaderCell(props: Props): Node { - const { children, colSpan, scope, rowSpan } = props; + const { children, colSpan, scope, rowSpan, shouldBeSticky } = props; + const cs = cx(styles.th, shouldBeSticky && styles.columnSticky); return ( - + {children} ); } + +TableHeaderCell.displayName = 'TableHeaderCell'; diff --git a/packages/gestalt/src/TableRow.js b/packages/gestalt/src/TableRow.js index 170fa717cd0..ae7e366df01 100644 --- a/packages/gestalt/src/TableRow.js +++ b/packages/gestalt/src/TableRow.js @@ -1,10 +1,53 @@ // @flow strict -import React, { type Node } from 'react'; +import React, { Children, type Node, cloneElement, useContext, useEffect, useState } from 'react'; +import TableContext from './TableContextProvider.js'; type Props = {| children: Node, |}; export default function TableRow(props: Props): Node { - return {props.children}; + const { stickyColumn = -1, stickyInclusive } = useContext(TableContext); + const rowRef = React.useRef(); + const [columnWidths, setColumnWidths] = useState([]); + + useEffect(() => { + if (rowRef && rowRef.current) { + const colWidths = []; + const tableRowChildrenArray = [...rowRef.current.children]; + tableRowChildrenArray.forEach((child) => { + colWidths.push(child.clientWidth); + }); + setColumnWidths(colWidths); + } + }, [columnWidths, props.children]); + + const renderCellsWithIndex = React.useCallback(() => { + const cells = []; + const tableRowChildrenArray = Children.toArray(props.children); + + tableRowChildrenArray.forEach((child, index) => { + const shouldBeSticky = + stickyColumn >= 0 && + ((index < stickyColumn && stickyInclusive) || + (!stickyInclusive && index === stickyColumn - 1)); + console.log({ columnWidths }); + // this doesn't re-run after columnWidths changes + // or + // columnWidths isn't changing + if (columnWidths.length > 0) { + if ( + child.type.displayName === 'TableCell' || + child.type.displayName === 'TableHeaderCell' + ) { + const previousWidths = columnWidths.slice(0, index); + const previousTotalWidth = previousWidths.reduce((a, b) => a + b); + cells.push(cloneElement(child, { shouldBeSticky, previousTotalWidth })); + } + } + }); + return cells; + }, [columnWidths, props.children, stickyInclusive, stickyColumn]); + + return {renderCellsWithIndex()}; } From 3f3ca067220bd3bf4047866c9391ec6cc3684e10 Mon Sep 17 00:00:00 2001 From: Ayesha Mazumdar Date: Tue, 23 Feb 2021 20:41:59 -0800 Subject: [PATCH 2/5] Working --- docs/src/Table.doc.js | 17 ++++++---- packages/gestalt/src/Table.js | 7 ++-- packages/gestalt/src/TableContextProvider.js | 1 - packages/gestalt/src/TableHeaderCell.js | 11 ++++-- packages/gestalt/src/TableRow.js | 35 ++++++-------------- packages/gestalt/src/TableRowExpandable.js | 6 ++-- 6 files changed, 36 insertions(+), 41 deletions(-) diff --git a/docs/src/Table.doc.js b/docs/src/Table.doc.js index 47401c5baad..6b9b4d04a89 100644 --- a/docs/src/Table.doc.js +++ b/docs/src/Table.doc.js @@ -264,7 +264,7 @@ card( id="stickyColumn" name="Example: Sticky Column" defaultCode={` - + @@ -278,13 +278,13 @@ card( House - jfh + Favorite Spell - fgreg + Patronus - yrwey + Birthday @@ -304,8 +304,9 @@ card( Luna LovegoodRavenclaw - Ravenclaw - + Expelliarmus + Hare + June 25, 1993 @@ -577,7 +578,8 @@ card( }; return( -
+ +
@@ -765,6 +767,7 @@ card(
+
); } `} diff --git a/packages/gestalt/src/Table.js b/packages/gestalt/src/Table.js index 661b3e8e12a..2e7db03d3b3 100644 --- a/packages/gestalt/src/Table.js +++ b/packages/gestalt/src/Table.js @@ -17,11 +17,10 @@ type Props = {| borderStyle?: 'sm' | 'none', maxHeight?: number | string, stickyColumn?: number, - stickyInclusive?: boolean, |}; export default function Table(props: Props): Node { - const { borderStyle, children, maxHeight, stickyColumn = -1, stickyInclusive = true } = props; + const { borderStyle, children, maxHeight, stickyColumn = -1 } = props; return ( - - {children} - + {children}
); diff --git a/packages/gestalt/src/TableContextProvider.js b/packages/gestalt/src/TableContextProvider.js index 664056af42d..eec1ac691c9 100644 --- a/packages/gestalt/src/TableContextProvider.js +++ b/packages/gestalt/src/TableContextProvider.js @@ -3,7 +3,6 @@ import { createContext, type Context } from 'react'; type TableContextType = {| stickyColumn?: number, - stickyInclusive?: boolean, |}; const initialState = { diff --git a/packages/gestalt/src/TableHeaderCell.js b/packages/gestalt/src/TableHeaderCell.js index 9b84f766d6b..2c61cccb98d 100644 --- a/packages/gestalt/src/TableHeaderCell.js +++ b/packages/gestalt/src/TableHeaderCell.js @@ -9,14 +9,21 @@ type Props = {| colSpan?: number, rowSpan?: number, scope?: 'col' | 'colgroup' | 'row' | 'rowgroup', + previousTotalWidth?: number, |}; export default function TableHeaderCell(props: Props): Node { - const { children, colSpan, scope, rowSpan, shouldBeSticky } = props; + const { children, colSpan, scope, rowSpan, shouldBeSticky, previousTotalWidth } = props; const cs = cx(styles.th, shouldBeSticky && styles.columnSticky); return ( - + {children} ); diff --git a/packages/gestalt/src/TableRow.js b/packages/gestalt/src/TableRow.js index ae7e366df01..5f5a4d18eb8 100644 --- a/packages/gestalt/src/TableRow.js +++ b/packages/gestalt/src/TableRow.js @@ -7,12 +7,12 @@ type Props = {| |}; export default function TableRow(props: Props): Node { - const { stickyColumn = -1, stickyInclusive } = useContext(TableContext); + const { stickyColumn = -1 } = useContext(TableContext); const rowRef = React.useRef(); const [columnWidths, setColumnWidths] = useState([]); useEffect(() => { - if (rowRef && rowRef.current) { + if (rowRef && rowRef.current && stickyColumn > 0) { const colWidths = []; const tableRowChildrenArray = [...rowRef.current.children]; tableRowChildrenArray.forEach((child) => { @@ -20,34 +20,21 @@ export default function TableRow(props: Props): Node { }); setColumnWidths(colWidths); } - }, [columnWidths, props.children]); + }, [rowRef, stickyColumn]); - const renderCellsWithIndex = React.useCallback(() => { + const renderCellsWithIndex = () => { const cells = []; const tableRowChildrenArray = Children.toArray(props.children); tableRowChildrenArray.forEach((child, index) => { - const shouldBeSticky = - stickyColumn >= 0 && - ((index < stickyColumn && stickyInclusive) || - (!stickyInclusive && index === stickyColumn - 1)); - console.log({ columnWidths }); - // this doesn't re-run after columnWidths changes - // or - // columnWidths isn't changing - if (columnWidths.length > 0) { - if ( - child.type.displayName === 'TableCell' || - child.type.displayName === 'TableHeaderCell' - ) { - const previousWidths = columnWidths.slice(0, index); - const previousTotalWidth = previousWidths.reduce((a, b) => a + b); - cells.push(cloneElement(child, { shouldBeSticky, previousTotalWidth })); - } - } + const shouldBeSticky = stickyColumn >= 0 && index < stickyColumn; + const previousWidths = columnWidths.slice(0, index); + const previousTotalWidth = + previousWidths.length > 0 ? previousWidths.reduce((a, b) => a + b) : 0; + cells.push(cloneElement(child, { shouldBeSticky, previousTotalWidth })); }); return cells; - }, [columnWidths, props.children, stickyInclusive, stickyColumn]); + }; - return {renderCellsWithIndex()}; + return {stickyColumn > 0 ? renderCellsWithIndex() : props.children}; } diff --git a/packages/gestalt/src/TableRowExpandable.js b/packages/gestalt/src/TableRowExpandable.js index 615077d0610..ae79ebf8fa6 100644 --- a/packages/gestalt/src/TableRowExpandable.js +++ b/packages/gestalt/src/TableRowExpandable.js @@ -1,11 +1,12 @@ // @flow strict -import React, { Children, Fragment, type Node, useState } from 'react'; +import React, { Children, Fragment, type Node, useState, useContext } from 'react'; import cx from 'classnames'; import styles from './Table.css'; import Box from './Box.js'; import IconButton from './IconButton.js'; import TableCell from './TableCell.js'; import { type AbstractEventHandler } from './AbstractEventHandler.js'; +import TableContext from './TableContextProvider.js'; type Props = {| accessibilityExpandLabel: string, @@ -35,6 +36,7 @@ export default function TableRowExpandable(props: Props): Node { const [expanded, setExpanded] = useState(false); const hoverStyle = props.hoverStyle || 'gray'; const cs = hoverStyle === 'gray' ? cx(styles.hoverShadeGray) : null; + const { stickyColumn = -1 } = useContext(TableContext); const handleButtonClick = ({ event }) => { setExpanded(!expanded); @@ -46,7 +48,7 @@ export default function TableRowExpandable(props: Props): Node { return ( - + 0} previousTotalWidth={0}> Date: Wed, 24 Feb 2021 17:35:00 -0800 Subject: [PATCH 3/5] RTL --- packages/gestalt/src/TableCell.js | 5 ++++- packages/gestalt/src/TableHeaderCell.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/gestalt/src/TableCell.js b/packages/gestalt/src/TableCell.js index 9cafc96e846..6efbb571643 100644 --- a/packages/gestalt/src/TableCell.js +++ b/packages/gestalt/src/TableCell.js @@ -19,7 +19,10 @@ export default function TableCell(props: Props): Node { className={cs} colSpan={colSpan} rowSpan={rowSpan} - style={{ left: shouldBeSticky ? previousTotalWidth : undefined }} + style={{ + left: shouldBeSticky ? previousTotalWidth : undefined, + right: shouldBeSticky ? previousTotalWidth : undefined, + }} > {children} diff --git a/packages/gestalt/src/TableHeaderCell.js b/packages/gestalt/src/TableHeaderCell.js index 2c61cccb98d..8e9d9802e08 100644 --- a/packages/gestalt/src/TableHeaderCell.js +++ b/packages/gestalt/src/TableHeaderCell.js @@ -22,7 +22,10 @@ export default function TableHeaderCell(props: Props): Node { scope={scope || 'col'} colSpan={colSpan} rowSpan={rowSpan} - style={{ left: shouldBeSticky ? previousTotalWidth : undefined }} + style={{ + left: shouldBeSticky ? previousTotalWidth : undefined, + right: shouldBeSticky ? previousTotalWidth : undefined, + }} > {children} From b109e9dfcfd3514a0a0d323bf0c2e3ed484d1770 Mon Sep 17 00:00:00 2001 From: Ayesha Mazumdar Date: Thu, 11 Mar 2021 11:42:21 -0800 Subject: [PATCH 4/5] WIP - white line --- docs/src/Table.doc.js | 8 ++-- packages/gestalt/src/Table.css | 49 ++++++++++++++++++-- packages/gestalt/src/Table.js | 6 +-- packages/gestalt/src/TableCell.js | 16 ++++++- packages/gestalt/src/TableContextProvider.js | 4 +- packages/gestalt/src/TableHeaderCell.js | 19 ++++++-- packages/gestalt/src/TableRow.js | 13 +++--- packages/gestalt/src/TableRowExpandable.js | 4 +- 8 files changed, 93 insertions(+), 26 deletions(-) diff --git a/docs/src/Table.doc.js b/docs/src/Table.doc.js index 6b9b4d04a89..e17a9d6f1da 100644 --- a/docs/src/Table.doc.js +++ b/docs/src/Table.doc.js @@ -264,8 +264,8 @@ card( id="stickyColumn" name="Example: Sticky Column" defaultCode={` - - + +
@@ -357,8 +357,8 @@ card( id="stickyColumn2" name="Example: Sticky 2nd or 3rd Column" defaultCode={` - -
+ +
diff --git a/packages/gestalt/src/Table.css b/packages/gestalt/src/Table.css index 3572d248888..a6b2d230eb7 100644 --- a/packages/gestalt/src/Table.css +++ b/packages/gestalt/src/Table.css @@ -2,6 +2,7 @@ border-collapse: separate; border-spacing: 0; width: 100%; + isolation: isolate; } .th { @@ -13,24 +14,64 @@ .td { composes: paddingY3 from "./boxWhitespace.css"; composes: paddingX3 from "./boxWhitespace.css"; + position: relative; +} + +td div{ + z-index: 1; + position: relative; } .sticky tr th { background-color: var(--g-colorGray0); position: sticky; top: 0; - z-index: 2; + z-index: 6; } .columnSticky { - background-color: yellow; + background-color: var(--g-colorGray0); position:sticky; left: 0; - z-index: 1; + z-index: 3; } -.sticky th.columnSticky { +.columnSticky div { + z-index: 4; + isolation: isolate; +} + +.columnStickyShadow div { z-index: 3; + position: relative; +} + + +.columnStickyShadow::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + box-shadow: 8px 0px 8px -8px var(--g-colorGray150); +} + + +.columnStickyShadow + td::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 4; + background-color: transparent; + box-shadow: inset 8px -8px white; +} + +.sticky th.columnSticky { + z-index: 7; } .thead tr:last-child th { diff --git a/packages/gestalt/src/Table.js b/packages/gestalt/src/Table.js index 2e7db03d3b3..03baf9fdc30 100644 --- a/packages/gestalt/src/Table.js +++ b/packages/gestalt/src/Table.js @@ -16,11 +16,11 @@ type Props = {| children: Node, borderStyle?: 'sm' | 'none', maxHeight?: number | string, - stickyColumn?: number, + stickyColumns?: number, |}; export default function Table(props: Props): Node { - const { borderStyle, children, maxHeight, stickyColumn = -1 } = props; + const { borderStyle, children, maxHeight, stickyColumns } = props; return (
- {children} + {children}
); diff --git a/packages/gestalt/src/TableCell.js b/packages/gestalt/src/TableCell.js index 6efbb571643..a6b4ff77fc2 100644 --- a/packages/gestalt/src/TableCell.js +++ b/packages/gestalt/src/TableCell.js @@ -8,12 +8,24 @@ type Props = {| colSpan?: number, rowSpan?: number, shouldBeSticky?: boolean, + shouldHaveShadow?: boolean, previousTotalWidth?: number, |}; export default function TableCell(props: Props): Node { - const { children, colSpan, rowSpan, shouldBeSticky, previousTotalWidth } = props; - const cs = cx(styles.td, shouldBeSticky && styles.columnSticky); + const { + children, + colSpan, + rowSpan, + shouldBeSticky, + previousTotalWidth, + shouldHaveShadow, + } = props; + const cs = cx( + styles.td, + shouldBeSticky && styles.columnSticky, + shouldHaveShadow && styles.columnStickyShadow, + ); return ( = createContext(initialState); diff --git a/packages/gestalt/src/TableHeaderCell.js b/packages/gestalt/src/TableHeaderCell.js index 8e9d9802e08..fde04e463fd 100644 --- a/packages/gestalt/src/TableHeaderCell.js +++ b/packages/gestalt/src/TableHeaderCell.js @@ -5,7 +5,8 @@ import styles from './Table.css'; type Props = {| children: Node, - shouldBeSticky?: Boolean, + shouldBeSticky?: boolean, + shouldHaveShadow?: boolean, colSpan?: number, rowSpan?: number, scope?: 'col' | 'colgroup' | 'row' | 'rowgroup', @@ -13,8 +14,20 @@ type Props = {| |}; export default function TableHeaderCell(props: Props): Node { - const { children, colSpan, scope, rowSpan, shouldBeSticky, previousTotalWidth } = props; - const cs = cx(styles.th, shouldBeSticky && styles.columnSticky); + const { + children, + colSpan, + scope, + rowSpan, + shouldBeSticky, + previousTotalWidth, + shouldHaveShadow, + } = props; + const cs = cx( + styles.th, + shouldBeSticky && styles.columnSticky, + shouldHaveShadow && styles.columnStickyShadow, + ); return ( { - if (rowRef && rowRef.current && stickyColumn > 0) { + if (rowRef && rowRef.current && stickyColumns) { const colWidths = []; const tableRowChildrenArray = [...rowRef.current.children]; tableRowChildrenArray.forEach((child) => { @@ -20,21 +20,22 @@ export default function TableRow(props: Props): Node { }); setColumnWidths(colWidths); } - }, [rowRef, stickyColumn]); + }, [rowRef, stickyColumns]); const renderCellsWithIndex = () => { const cells = []; const tableRowChildrenArray = Children.toArray(props.children); tableRowChildrenArray.forEach((child, index) => { - const shouldBeSticky = stickyColumn >= 0 && index < stickyColumn; + const shouldBeSticky = stickyColumns >= 0 && index < stickyColumns; + const shouldHaveShadow = stickyColumns - 1 === index; const previousWidths = columnWidths.slice(0, index); const previousTotalWidth = previousWidths.length > 0 ? previousWidths.reduce((a, b) => a + b) : 0; - cells.push(cloneElement(child, { shouldBeSticky, previousTotalWidth })); + cells.push(cloneElement(child, { shouldBeSticky, previousTotalWidth, shouldHaveShadow })); }); return cells; }; - return {stickyColumn > 0 ? renderCellsWithIndex() : props.children}; + return {stickyColumns > 0 ? renderCellsWithIndex() : props.children}; } diff --git a/packages/gestalt/src/TableRowExpandable.js b/packages/gestalt/src/TableRowExpandable.js index ae79ebf8fa6..c4bffe8b027 100644 --- a/packages/gestalt/src/TableRowExpandable.js +++ b/packages/gestalt/src/TableRowExpandable.js @@ -36,7 +36,7 @@ export default function TableRowExpandable(props: Props): Node { const [expanded, setExpanded] = useState(false); const hoverStyle = props.hoverStyle || 'gray'; const cs = hoverStyle === 'gray' ? cx(styles.hoverShadeGray) : null; - const { stickyColumn = -1 } = useContext(TableContext); + const { stickyColumns } = useContext(TableContext); const handleButtonClick = ({ event }) => { setExpanded(!expanded); @@ -48,7 +48,7 @@ export default function TableRowExpandable(props: Props): Node { return ( - 0} previousTotalWidth={0}> + 0} previousTotalWidth={0}> Date: Thu, 11 Mar 2021 12:17:42 -0800 Subject: [PATCH 5/5] update --- packages/gestalt/src/Table.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/gestalt/src/Table.css b/packages/gestalt/src/Table.css index a6b2d230eb7..93006c40a62 100644 --- a/packages/gestalt/src/Table.css +++ b/packages/gestalt/src/Table.css @@ -38,7 +38,6 @@ td div{ .columnSticky div { z-index: 4; - isolation: isolate; } .columnStickyShadow div { @@ -57,7 +56,6 @@ td div{ box-shadow: 8px 0px 8px -8px var(--g-colorGray150); } - .columnStickyShadow + td::before { content: ""; position: absolute;