Skip to content

Commit e0f4c19

Browse files
authored
[PBNTR-749] SelectableRows for AdvancedTable- React (#4131)
[Runway Story](https://runway.powerhrg.com/backlog_items/PBNTR-749) This PR: - Creates `selectableRows` props for adding checkboxes - Creates `onRowSelectionChange` prop for giving devs access to selected items - Creates `showActionsBar` prop to optionally show actions header - Creates `actions` prop to pass in custom actions - Creates selection pattern for expanded and non-expanded tables <img width="980" alt="Screenshot 2025-01-20 at 3 04 07 PM" src="https://github.com/user-attachments/assets/6e7b13a2-643f-4022-b1cb-061b1b1075bf" /> <img width="973" alt="Screenshot 2025-01-20 at 3 04 16 PM" src="https://github.com/user-attachments/assets/350e02cd-1b42-4fa3-80fc-c5078bd4b06e" /> <img width="967" alt="Screenshot 2025-01-20 at 3 04 25 PM" src="https://github.com/user-attachments/assets/defdce1b-6c80-4e8d-9b43-f17c3da1aa9b" /> <img width="971" alt="Screenshot 2025-01-20 at 3 04 31 PM" src="https://github.com/user-attachments/assets/16778038-fc37-4b15-a4ba-5cc61d8771e1" />
1 parent 19fe238 commit e0f4c19

21 files changed

Lines changed: 830 additions & 15 deletions

playbook/app/pb_kits/playbook/pb_advanced_table/Components/CustomCell.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { GlobalProps } from "../../utilities/globalProps"
88
import Flex from "../../pb_flex/_flex"
99
import FlexItem from "../../pb_flex/_flex_item"
1010
import Icon from "../../pb_icon/_icon"
11+
import Checkbox from "../../pb_checkbox/_checkbox"
1112

1213
import AdvancedTableContext from "../Context/AdvancedTableContext"
1314

@@ -17,6 +18,7 @@ interface CustomCellProps {
1718
row: Row<GenericObject>
1819
value?: string
1920
customRenderer?: (row: Row<GenericObject>, value: string | undefined) => React.ReactNode
21+
selectableRows?: boolean
2022
}
2123

2224
export const CustomCell = ({
@@ -25,8 +27,9 @@ export const CustomCell = ({
2527
row,
2628
value,
2729
customRenderer,
30+
selectableRows,
2831
}: CustomCellProps & GlobalProps) => {
29-
const { setExpanded, expanded, expandedControl, inlineRowLoading } = useContext(AdvancedTableContext);
32+
const { setExpanded, expanded, expandedControl, inlineRowLoading, hasAnySubRows } = useContext(AdvancedTableContext);
3033

3134
const handleOnExpand = (row: Row<GenericObject>) => {
3235
onRowToggleClick && onRowToggleClick(row);
@@ -41,10 +44,23 @@ export const CustomCell = ({
4144

4245
return (
4346
<div style={{ paddingLeft: `${row.depth * 1.25}em` }}>
44-
<Flex alignItems="center"
47+
<Flex
48+
alignItems="center"
4549
columnGap="xs"
50+
justify={!hasAnySubRows ? "end" : "start"}
4651
orientation="row"
4752
>
53+
{
54+
selectableRows && hasAnySubRows && (
55+
<Checkbox
56+
checked={row.getIsSelected()}
57+
disabled={!row.getCanSelect()}
58+
indeterminate={row.getIsSomeSelected()}
59+
name={row.id}
60+
onChange={row.getToggleSelectedHandler()}
61+
/>
62+
)
63+
}
4864
{renderButton ? (
4965
<button
5066
className="gray-icon expand-toggle-icon"

playbook/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React, { useContext } from "react"
22
import classnames from "classnames"
3-
import { flexRender, Header } from "@tanstack/react-table"
3+
import { flexRender, Header, Table } from "@tanstack/react-table"
44

55
import { GenericObject } from "../../types"
66

77
import { GlobalProps } from "../../utilities/globalProps"
88

99
import Flex from "../../pb_flex/_flex"
10+
import Checkbox from "../../pb_checkbox/_checkbox"
1011

1112
import { SortIconButton } from "./SortIconButton"
1213
import { ToggleIconButton } from "./ToggleIconButton"
@@ -24,6 +25,7 @@ type TableHeaderCellProps = {
2425
isPinnedLeft?: boolean
2526
loading?: boolean
2627
sortIcon?: string | string[]
28+
table?: Table<GenericObject>
2729
} & GlobalProps
2830

2931
export const TableHeaderCell = ({
@@ -35,9 +37,13 @@ export const TableHeaderCell = ({
3537
isPinnedLeft = false,
3638
loading,
3739
sortIcon,
40+
table
3841
}: TableHeaderCellProps) => {
39-
const { sortControl, responsive } = useContext(AdvancedTableContext)
42+
const { sortControl, responsive, selectableRows, hasAnySubRows, showActionsBar } =
43+
useContext(AdvancedTableContext);
4044

45+
type justifyTypes = "none" | "center" | "start" | "end" | "between" | "around" | "evenly"
46+
4147
const toggleSortButton = (event: React.SyntheticEvent) => {
4248
if (sortControl) {
4349
const sortIsDesc = header?.column.getIsSorted() === "desc"
@@ -59,6 +65,7 @@ export const TableHeaderCell = ({
5965

6066
const cellClassName = classnames(
6167
"table-header-cells",
68+
`${showActionsBar && "header-cells-with-actions"}`,
6269
`${isChrome() ? "chrome-styles" : ""}`,
6370
`${enableSorting ? "table-header-cells-active" : ""}`,
6471
{ "pinned-left": responsive === "scroll" && isPinnedLeft },
@@ -82,8 +89,14 @@ const isToggleExpansionEnabled =
8289
(enableToggleExpansion === "all" || "header") &&
8390
enableToggleExpansion !== "none"
8491

85-
const justifyHeader = isLeafColumn ? "end" : "center"
92+
let justifyHeader:justifyTypes;
8693

94+
if (header?.index === 0 && hasAnySubRows) {
95+
justifyHeader = enableSorting ? "between" : "start";
96+
} else {
97+
justifyHeader = isLeafColumn ? "end" : "center";
98+
}
99+
87100
return (
88101
<th
89102
align="right"
@@ -102,9 +115,18 @@ const justifyHeader = isLeafColumn ? "end" : "center"
102115
) : (
103116
<Flex
104117
alignItems="center"
105-
justify={header?.index === 0 && enableSorting ? "between" : header?.index === 0 && !enableSorting ? "start" : justifyHeader}
118+
justify={justifyHeader}
106119
>
107-
{isToggleExpansionEnabled && (
120+
{
121+
selectableRows && header?.index === 0 && hasAnySubRows && (
122+
<Checkbox
123+
checked={table?.getIsAllRowsSelected()}
124+
indeterminate={table?.getIsSomeRowsSelected()}
125+
onChange={table?.getToggleAllRowsSelectedHandler()}
126+
/>
127+
)
128+
}
129+
{isToggleExpansionEnabled && hasAnySubRows && (
108130
<ToggleIconButton onClick={handleExpandOrCollapse} />
109131
)}
110132

playbook/app/pb_kits/playbook/pb_advanced_table/SubKits/TableBody.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { globalProps } from "../../utilities/globalProps"
99
import { isChrome } from "../Utilities/BrowserCheck"
1010

1111
import LoadingInline from "../../pb_loading_inline/_loading_inline"
12+
import Checkbox from "../../pb_checkbox/_checkbox"
1213

1314
import { SubRowHeaderRow } from "../Components/SubRowHeaderRow"
1415
import { LoadingCell } from "../Components/LoadingCell"
@@ -42,6 +43,8 @@ export const TableBody = ({
4243
loading,
4344
responsive,
4445
table,
46+
selectableRows,
47+
hasAnySubRows
4548
} = useContext(AdvancedTableContext)
4649

4750
const classes = classnames(
@@ -65,7 +68,7 @@ export const TableBody = ({
6568
const numberOfColumns = table.getAllFlatColumns().length
6669
const isDataLoading = isExpandable && (inlineRowLoading && rowHasNoChildren) && (row.depth < columnDefinitions[0].cellAccessors?.length)
6770
const rowBackground = isExpandable && ((!inlineRowLoading && row.getCanExpand()) || (inlineRowLoading && rowHasNoChildren))
68-
71+
const rowColor = row.getIsSelected() ? "bg-row-selection" : rowBackground ? "bg-silver" : "bg-white"
6972
return (
7073
<React.Fragment key={`${row.index}-${row.id}-${row.depth}-row`}>
7174
{isFirstChildofSubrow && subRowHeaders && (
@@ -79,11 +82,23 @@ export const TableBody = ({
7982
/>
8083
)}
8184
<tr
82-
className={`${rowBackground ? "bg-silver" : "bg-white"} ${
85+
className={`${rowColor} ${
8386
row.depth > 0 ? `depth-sub-row-${row.depth}` : ""
8487
}`}
8588
id={`${row.index}-${row.id}-${row.depth}-row`}
8689
>
90+
{/* Render custom checkbox column when we want selectableRows for non-expanding tables */}
91+
{selectableRows && !hasAnySubRows && (
92+
<td className="checkbox-cell">
93+
<Checkbox
94+
checked={row.getIsSelected()}
95+
disabled={!row.getCanSelect()}
96+
indeterminate={row.getIsSomeSelected()}
97+
name={row.id}
98+
onChange={row.getToggleSelectedHandler()}
99+
/>
100+
</td>
101+
)}
87102
{row.getVisibleCells().map((cell, i) => {
88103
const isPinnedLeft = columnPinning.left.includes(cell.column.id)
89104
const isLastCell = cell.column.parent?.columns.at(-1)?.id === cell.column.id

playbook/app/pb_kits/playbook/pb_advanced_table/SubKits/TableHeader.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import { GenericObject } from "../../types"
77
import { buildCss } from "../../utilities/props"
88
import { globalProps } from "../../utilities/globalProps"
99

10-
import { TableHeaderCell } from "../Components/TableHeaderCell"
10+
import Checkbox from "../../pb_checkbox/_checkbox"
1111

12+
import { TableHeaderCell } from "../Components/TableHeaderCell"
13+
import { isChrome } from "../Utilities/BrowserCheck"
1214
import AdvancedTableContext from "../Context/AdvancedTableContext"
1315

1416
type TableHeaderProps = {
@@ -34,6 +36,10 @@ export const TableHeader = ({
3436
handleExpandOrCollapse,
3537
loading,
3638
table,
39+
hasAnySubRows,
40+
showActionsBar,
41+
selectableRows,
42+
responsive
3743
} = useContext(AdvancedTableContext)
3844

3945
const classes = classnames(
@@ -44,6 +50,12 @@ export const TableHeader = ({
4450

4551
const columnPinning = table.getState().columnPinning;
4652

53+
const customCellClassnames = classnames(
54+
"table-header-cells-custom",
55+
`${showActionsBar && "header-cells-with-actions"}`,
56+
`${isChrome() ? "chrome-styles" : ""}`,
57+
`${responsive === "scroll" && "pinned-left"}`,
58+
);
4759
return (
4860
<>
4961
<thead className={classes}
@@ -52,6 +64,15 @@ export const TableHeader = ({
5264
{/* Get the header groups (only one in this example) */}
5365
{table.getHeaderGroups().map((headerGroup: HeaderGroup<GenericObject>) => (
5466
<tr key={`${headerGroup.id}-headerGroup`}>
67+
{!hasAnySubRows && selectableRows && (
68+
<th className={customCellClassnames}>
69+
<Checkbox
70+
checked={table?.getIsAllRowsSelected()}
71+
indeterminate={table?.getIsSomeRowsSelected()}
72+
onChange={table?.getToggleAllRowsSelectedHandler()}
73+
/>
74+
</th>
75+
)}
5576
{headerGroup.headers.map(header => {
5677
const isPinnedLeft = columnPinning.left.includes(header.id)
5778
return (
@@ -65,6 +86,7 @@ export const TableHeader = ({
6586
key={`${header.id}-header`}
6687
loading={loading}
6788
sortIcon={sortIcon}
89+
table={table}
6890
/>
6991
)
7092
})}

playbook/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,20 @@
2323
background-color: $white;
2424
}
2525

26+
.bg-row-selection {
27+
background-color: $info_subtle;
28+
}
29+
2630
.full-width {
2731
width: 100%;
2832
}
2933

34+
.row-selection-actions-card {
35+
border-bottom-right-radius: 0px !important;
36+
border-bottom-left-radius: 0px !important;
37+
border-bottom-color: transparent;
38+
}
39+
3040
.table-header-cells:first-child {
3141
min-width: 180px;
3242
}
@@ -47,6 +57,16 @@
4757
th[colspan]:not([colspan="1"]) {
4858
border-right: 1px solid $border_light !important;
4959
}
60+
.table-header-cells-custom {
61+
text-align:center;
62+
[class^=pb_checkbox_kit] .pb_checkbox_label {
63+
padding-left: 0px;
64+
}
65+
}
66+
.header-cells-with-actions {
67+
border-top-left-radius: 0px !important;
68+
border-top-right-radius: 0px !important;
69+
}
5070
}
5171

5272
.pb_advanced_table_body {
@@ -59,6 +79,14 @@
5979
tr .pb_table_td:last-child {
6080
padding-right: 8px !important;
6181
}
82+
83+
.checkbox-cell {
84+
display: flex;
85+
justify-content: center;
86+
[class^=pb_checkbox_kit] .pb_checkbox_label {
87+
padding-left: 0px;
88+
}
89+
}
6290
}
6391

6492

@@ -116,6 +144,7 @@
116144

117145
// Vertical separator
118146
.table-header-cells:first-child,
147+
.table-header-cells-custom:first-child,
119148
td:first-child,
120149
.pb_table_td:first-child {
121150
box-shadow: 1px 0px 0px 0px $border_light !important;

0 commit comments

Comments
 (0)