From 1dda096d4d518c94a31034b33423df3968003235 Mon Sep 17 00:00:00 2001
From: Brandon Dow <bdow@homebound.com>
Date: Thu, 14 Dec 2023 12:55:02 -0500
Subject: [PATCH] feat: Support indentation of rows

---
 src/components/Table/GridTable.stories.tsx | 27 ++++++++++++++++++++++
 src/components/Table/TableStyles.tsx       | 14 ++++++++++-
 src/components/Table/components/Row.tsx    |  8 ++++++-
 3 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/src/components/Table/GridTable.stories.tsx b/src/components/Table/GridTable.stories.tsx
index dcf020e31..e4a561509 100644
--- a/src/components/Table/GridTable.stories.tsx
+++ b/src/components/Table/GridTable.stories.tsx
@@ -517,6 +517,33 @@ export const StyleCard = newStory(() => {
   );
 }, {});
 
+export const LeveledStyleCard = newStory(() => {
+  const nameColumn: GridColumn<NestedRow> = {
+    header: () => "Name",
+    parent: (row) => row.name,
+    child: (row) => row.name,
+    grandChild: (row) => row.name,
+    add: () => "Add",
+  };
+  const valueColumn: GridColumn<NestedRow> = {
+    header: () => "Value",
+    parent: (row) => row.name,
+    child: (row) => row.name,
+    grandChild: (row) => row.name,
+    add: () => "Add",
+    w: "200px",
+  };
+  return (
+    <div css={Css.wPx(550).$}>
+      <GridTable
+        style={cardStyle}
+        columns={[collapseColumn<NestedRow>(), selectColumn<NestedRow>(), nameColumn, valueColumn]}
+        rows={rowsWithHeader}
+      />
+    </div>
+  );
+}, {});
+
 export const StyleCardWithOneColumn = newStory(() => {
   const nameColumn: GridColumn<Row> = { header: "Name", data: ({ name }) => name };
   return (
diff --git a/src/components/Table/TableStyles.tsx b/src/components/Table/TableStyles.tsx
index 97bfcd283..cf973f236 100644
--- a/src/components/Table/TableStyles.tsx
+++ b/src/components/Table/TableStyles.tsx
@@ -51,7 +51,15 @@ export interface GridStyle {
   /** Minimum table width in pixels. Used when calculating columns sizes */
   minWidthPx?: number;
   /** Css to apply at each level of a parent/child nested table. */
-  levels?: Record<number, { cellCss?: Properties; firstContentColumn?: Properties }>;
+  levels?: Record<
+    number,
+    {
+      /** Number of pixels to indent the row. This value will be subtracted from the "first content column" width. First content column is the first column that is not an 'action' column (i.e. non-checkbox or non-collapse button column) */
+      rowIndent?: number;
+      cellCss?: Properties;
+      firstContentColumn?: Properties;
+    }
+  >;
   /** Allows for customization of the background color used to denote an "active" row */
   activeBgColor?: Palette;
   /** Defines styles for the group row which holds the selected rows that have been filtered out */
@@ -223,6 +231,10 @@ export const cardStyle: GridStyle = {
   },
   rowHoverColor: "none",
   nonHeaderRowHoverCss: Css.bshHover.bGray700.$,
+  levels: {
+    1: { rowIndent: 24 },
+    2: { rowIndent: 48 },
+  },
 };
 
 export function resolveStyles(style: GridStyle | GridStyleDef): GridStyle {
diff --git a/src/components/Table/components/Row.tsx b/src/components/Table/components/Row.tsx
index 95da77496..06cfbc808 100644
--- a/src/components/Table/components/Row.tsx
+++ b/src/components/Table/components/Row.tsx
@@ -78,6 +78,8 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
   const showRowHoverColor = !reservedRowKinds.includes(row.kind) && !omitRowHover && style.rowHoverColor !== "none";
 
   const rowStyleCellCss = maybeApplyFunction(row as any, rowStyle?.cellCss);
+  const levelIndent = (style.levels && style.levels[level]?.rowIndent) ?? 0;
+
   const rowCss = {
     ...(!reservedRowKinds.includes(row.kind) && style.nonHeaderRowCss),
     // Optionally include the row hover styles, by default they should be turned on.
@@ -90,6 +92,7 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
         // Need to spread this to make TS happy.
         ":hover": { ...style.nonHeaderRowHoverCss },
       }),
+    ...(levelIndent && Css.mlPx(levelIndent).$),
     // For virtual tables use `display: flex` to keep all cells on the same row. For each cell in the row use `flexNone` to ensure they stay their defined widths
     ...(as === "table" ? {} : Css.relative.df.fg1.fs1.addIn("&>*", Css.flexNone.$).$),
     // Apply `cursorPointer` to the row if it has a link or `onClick` value.
@@ -289,7 +292,10 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
             // Apply cell highlight styles to active cell and hover
             ...Css.if(applyCellHighlight && isCellActive).br4.boxShadow(`inset 0 0 0 1px ${Palette.Blue700}`).$,
             // Define the width of the column on each cell. Supports col spans.
-            width: `calc(${columnSizes.slice(columnIndex, columnIndex + currentColspan).join(" + ")})`,
+            // If we have a 'levelIndent' defined, then subtract that amount from the first content column's width to ensure all columns will still line up properly
+            width: `calc(${columnSizes.slice(columnIndex, columnIndex + currentColspan).join(" + ")}${
+              applyFirstContentColumnStyles ? ` - ${levelIndent}px` : ""
+            })`,
             ...(typeof column.mw === "string" ? Css.mw(column.mw).$ : {}),
           };