playbook_ui 14.20.0.pre.alpha.PLAY2170checkboxrailsindeterminatelogicinkitPOC7980 → 14.20.0.pre.alpha.PLAY2178advancedtablerowpinning7978

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +82 -5
  3. data/app/pb_kits/playbook/pb_advanced_table/Context/AdvancedTableContext.tsx +58 -2
  4. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +1 -1
  5. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +13 -4
  6. data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableHeader.tsx +7 -3
  7. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +5 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +6 -2
  9. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +61 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows.jsx +57 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows_react.md +5 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +2 -1
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
  14. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +12 -8
  15. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +6 -3
  16. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_indeterminate.html.erb +48 -2
  17. data/dist/chunks/_weekday_stacked-yWpUc_c0.js +45 -0
  18. data/dist/chunks/vendor.js +1 -1
  19. data/dist/menu.yml +1 -1
  20. data/dist/playbook-doc.js +1 -1
  21. data/dist/playbook-rails.js +1 -1
  22. data/dist/playbook.css +1 -1
  23. data/lib/playbook/version.rb +1 -1
  24. metadata +4 -4
  25. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_indeterminate_rails.md +0 -1
  26. data/app/pb_kits/playbook/pb_checkbox/index.js +0 -56
  27. data/dist/chunks/_weekday_stacked-C4d17aYW.js +0 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f08c1401569be7ec360234581ac1f8e77974bd468dd40578153aa3b26de92f58
4
- data.tar.gz: 317f542180cb2ab211d2a509b2380f549023af74b9d28d854cc2c39fcbed451b
3
+ metadata.gz: c8e79a2664649f32a2195a4e9199e868f6c1ebac20764479066f12c2dbbd7e9c
4
+ data.tar.gz: da415e2485131587cc6a32a03c8145152df934616500a008ef06b4e49c6c049a
5
5
  SHA512:
6
- metadata.gz: 41b11eac6492594abe461ef0ee462353ba7b37b717d0edfe26a0f4356cf4eba17578036933c0ea97021263ac47e55b38435fcebe882b976b54e5a3e6ee28ca8b
7
- data.tar.gz: d36b7e00aa603dbe91fb5a068f68937a82bcb3f849a6fe8acf24209174f13bd7ca3d2750b211d88caa74dc92d7816eef36626d768668e576529c7574bec9beef
6
+ metadata.gz: fd7c9331ca4274f9e6e95d186ada4456ddfa8240d1cc48249dd0b899931bf3db83576abfbfed0ebd4105e2159d57cc9efb75a3a16e2d33cc6a37262dede08746
7
+ data.tar.gz: 9072a56af0c016514dd3a3f3d5b0e20fbdbaea20ca5b25d258f3c31c859fc0d4edc855c58ed297089453525b67df0789c6ff216b9dd500eaa5115664a4bba396
@@ -28,11 +28,14 @@ export const RegularTableView = ({
28
28
  handleExpandOrCollapse,
29
29
  inlineRowLoading,
30
30
  loading,
31
- responsive,
32
31
  table,
33
32
  selectableRows,
34
33
  hasAnySubRows,
35
- stickyLeftColumn
34
+ stickyLeftColumn,
35
+ pinnedRows,
36
+ headerHeight,
37
+ rowHeight,
38
+ sampleRowRef,
36
39
  } = useContext(AdvancedTableContext)
37
40
 
38
41
 
@@ -50,9 +53,81 @@ export const RegularTableView = ({
50
53
  const columnPinning = table.getState().columnPinning || { left: [] };
51
54
  const columnDefinitions = table.options.meta?.columnDefinitions || [];
52
55
 
56
+ // Row pinning
57
+ function PinnedRow({ row }: { row: Row<any> }) {
58
+ return (
59
+ <tr
60
+ className={classnames(
61
+ `pinned-row`,
62
+ )}
63
+ style={{
64
+ backgroundColor: 'white',
65
+ position: 'sticky',
66
+ top:
67
+ row.getIsPinned() === 'top'
68
+ ? `${row.getPinnedIndex() * rowHeight + headerHeight}px`
69
+ : undefined,
70
+ zIndex: '3'
71
+ }}
72
+ >
73
+ {row.getVisibleCells().map((cell: Cell<GenericObject, unknown>, i: number) => {
74
+ const isPinnedLeft = columnPinning.left.includes(cell.column.id);
75
+ const isLastCell = (() => {
76
+ const parent = cell.column.parent;
77
+ if (!parent) {
78
+ const last = row.getVisibleCells().at(-1);
79
+ return last?.column.id === cell.column.id;
80
+ }
81
+
82
+ const visibleSiblings = parent.columns.filter(col => col.getIsVisible());
83
+ return visibleSiblings.at(-1)?.id === cell.column.id;
84
+ })();
85
+
86
+ const { column } = cell;
87
+ return (
88
+ <td
89
+ align="right"
90
+ className={classnames(
91
+ `${cell.id}-cell position_relative`,
92
+ isChrome() ? "chrome-styles" : "",
93
+ isPinnedLeft && 'pinned-left',
94
+ stickyLeftColumn && stickyLeftColumn.length > 0 && isPinnedLeft && 'sticky-left',
95
+ isLastCell && 'last-cell',
96
+ )}
97
+ key={`${cell.id}-data`}
98
+ style={{
99
+ left: isPinnedLeft
100
+ ? i === 1 //Accounting for set min-width for first column
101
+ ? '180px'
102
+ : `${column.getStart("left")}px`
103
+ : undefined,
104
+ }}
105
+ >
106
+ {collapsibleTrail && i === 0 && row.depth > 0 && renderCollapsibleTrail(row.depth)}
107
+ <span id={`${cell.id}-span`}>
108
+ {loading ? (
109
+ <LoadingCell />
110
+ ) : (
111
+ flexRender(cell.column.columnDef.cell, cell.getContext())
112
+ )}
113
+ </span>
114
+ </td>
115
+ );
116
+ })}
117
+ </tr>
118
+ )
119
+ }
120
+
121
+ const totalRows = pinnedRows ? table.getCenterRows() : table.getRowModel().rows
122
+
53
123
  return (
54
124
  <>
55
- {table.getRowModel().rows.map((row: Row<GenericObject>) => {
125
+ {pinnedRows && table.getTopRows().map((row: Row<GenericObject>) => (
126
+ <PinnedRow key={row.id}
127
+ row={row}
128
+ />
129
+ ))}
130
+ {totalRows.map((row: Row<GenericObject>, rowIndex: number) => {
56
131
  const isExpandable = row.getIsExpanded();
57
132
  const isFirstChildofSubrow = row.depth > 0 && row.index === 0;
58
133
  const rowHasNoChildren = row.original?.children && !row.original.children.length ? true : false;
@@ -60,6 +135,7 @@ export const RegularTableView = ({
60
135
  const isDataLoading = isExpandable && (inlineRowLoading && rowHasNoChildren) && (row.depth < columnDefinitions[0]?.cellAccessors?.length);
61
136
  const rowBackground = isExpandable && ((!inlineRowLoading && row.getCanExpand()) || (inlineRowLoading && rowHasNoChildren));
62
137
  const rowColor = row.getIsSelected() ? "bg-row-selection" : rowBackground ? "bg-silver" : "bg-white";
138
+ const isFirstRegularRow = rowIndex === 0 && !row.getIsPinned();
63
139
 
64
140
  return (
65
141
  <React.Fragment key={`${row.index}-${row.id}-${row.depth}-row`}>
@@ -77,6 +153,7 @@ export const RegularTableView = ({
77
153
  <tr
78
154
  className={`${rowColor} ${row.depth > 0 ? `depth-sub-row-${row.depth}` : ""}`}
79
155
  id={`${row.index}-${row.id}-${row.depth}-row`}
156
+ ref={isFirstRegularRow ? sampleRowRef : null}
80
157
  >
81
158
  {/* Render custom checkbox column when we want selectableRows for non-expanding tables */}
82
159
  {selectableRows && !hasAnySubRows && (
@@ -99,7 +176,7 @@ export const RegularTableView = ({
99
176
  const last = row.getVisibleCells().at(-1);
100
177
  return last?.column.id === cell.column.id;
101
178
  }
102
-
179
+
103
180
  const visibleSiblings = parent.columns.filter(col => col.getIsVisible());
104
181
  return visibleSiblings.at(-1)?.id === cell.column.id;
105
182
  })();
@@ -154,4 +231,4 @@ export const RegularTableView = ({
154
231
  );
155
232
  }
156
233
 
157
- export default RegularTableView;
234
+ export default RegularTableView;
@@ -1,4 +1,4 @@
1
- import React, { createContext, useRef, useMemo, useEffect } from 'react';
1
+ import React, { createContext, useRef, useMemo, useEffect, useState, useCallback } from 'react';
2
2
  import { useVirtualizer } from '@tanstack/react-virtual';
3
3
 
4
4
  import { Row } from "@tanstack/react-table";
@@ -23,6 +23,54 @@ export const AdvancedTableProvider = ({ children, ...props }: {
23
23
 
24
24
  const table = props.table;
25
25
  const isVirtualized = props.virtualizedRows || props.enableVirtualization;
26
+
27
+ // Pinned Row: height calculations for Header and Row
28
+ const headerRef = useRef(null);
29
+ const sampleRowRef = useRef(null);
30
+
31
+ const [headerHeight, setHeaderHeight] = useState(44);
32
+ const [rowHeight, setRowHeight] = useState(38);
33
+
34
+ const measureHeights = useCallback(() => {
35
+ if (headerRef.current) {
36
+ const headerRect = headerRef.current.getBoundingClientRect();
37
+ if (headerRect.height > 0) {
38
+ setHeaderHeight(headerRect.height);
39
+ }
40
+ }
41
+ if (sampleRowRef.current) {
42
+ const rowRect = sampleRowRef.current.getBoundingClientRect();
43
+ if (rowRect.height > 0) {
44
+ setRowHeight(rowRect.height);
45
+ }
46
+ }
47
+ }, []);
48
+
49
+ useEffect(() => {
50
+ const resizeObserver = new ResizeObserver(() => {
51
+ measureHeights();
52
+ });
53
+
54
+ if (headerRef.current) {
55
+ resizeObserver.observe(headerRef.current);
56
+ }
57
+
58
+ if (sampleRowRef.current) {
59
+ resizeObserver.observe(sampleRowRef.current);
60
+ }
61
+
62
+ const timeoutId = setTimeout(measureHeights, 100);
63
+
64
+ return () => {
65
+ resizeObserver.disconnect();
66
+ clearTimeout(timeoutId);
67
+ };
68
+ }, [measureHeights]);
69
+
70
+ useEffect(() => {
71
+ measureHeights();
72
+ }, [table?.getRowModel().rows.length, measureHeights]);
73
+
26
74
 
27
75
  // Create a flattened data array that includes ALL components for virtualization
28
76
  const flattenedItems = useMemo(() => {
@@ -132,7 +180,15 @@ export const AdvancedTableProvider = ({ children, ...props }: {
132
180
  virtualizer: isVirtualized ? virtualizer : null,
133
181
  flattenedItems,
134
182
  virtualizedRows: isVirtualized,
135
- enableVirtualization: isVirtualized
183
+ enableVirtualization: isVirtualized,
184
+ rowPinning: props.rowPinning,
185
+ setRowPinning: props.setRowPinning,
186
+ keepPinnedRows: props.keepPinnedRows,
187
+ headerHeight,
188
+ rowHeight,
189
+ headerRef,
190
+ sampleRowRef,
191
+ measureHeights,
136
192
  };
137
193
 
138
194
  return (
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect } from 'react';
2
- import { Row } from "@tanstack/react-table";
2
+ import { Row, RowPinningState } from "@tanstack/react-table";
3
3
  import { GenericObject } from "../../types";
4
4
  import { updateExpandAndCollapseState } from "../Utilities/ExpansionControlHelpers";
5
5
 
@@ -6,7 +6,8 @@ import {
6
6
  getPaginationRowModel,
7
7
  getSortedRowModel,
8
8
  RowSelectionState,
9
- Row
9
+ Row,
10
+ RowPinningState
10
11
  } from "@tanstack/react-table";
11
12
  import { GenericObject } from "../../types";
12
13
  import { createColumnHelper } from "@tanstack/react-table";
@@ -23,11 +24,11 @@ interface UseTableStateProps {
23
24
  loading?: boolean | string;
24
25
  pagination?: boolean;
25
26
  paginationProps?: GenericObject;
27
+ pinnedRows?: any;
26
28
  virtualizedRows?: boolean;
27
29
  tableOptions?: GenericObject;
28
30
  onRowSelectionChange?: (arg: RowSelectionState) => void;
29
31
  columnVisibilityControl?: GenericObject;
30
-
31
32
  }
32
33
 
33
34
  export function useTableState({
@@ -43,18 +44,24 @@ export function useTableState({
43
44
  paginationProps,
44
45
  virtualizedRows = false,
45
46
  tableOptions,
46
- columnVisibilityControl
47
+ columnVisibilityControl,
48
+ pinnedRows,
47
49
  }: UseTableStateProps) {
48
50
  // Create a local state for expanded and setExpanded if expandedControl not used
49
51
  const [localExpanded, setLocalExpanded] = useState({});
50
52
  const [loadingStateRowCount, setLoadingStateRowCount] = useState(initialLoadingRowsCount);
51
53
  const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
52
54
  const [localColumnVisibility, setLocalColumnVisibility] = useState({});
55
+ const [localRowPinning, setLocalRowPinning] = useState<RowPinningState>({
56
+ top: [],
57
+ });
53
58
  // Determine whether to use the prop or the local state
54
59
  const expanded = expandedControl ? expandedControl.value : localExpanded;
55
60
  const setExpanded = expandedControl ? expandedControl.onChange : setLocalExpanded;
56
61
  const columnVisibility = (columnVisibilityControl && columnVisibilityControl.value) ? columnVisibilityControl.value : localColumnVisibility;
57
62
  const setColumnVisibility = (columnVisibilityControl && columnVisibilityControl.onChange) ? columnVisibilityControl.onChange : setLocalColumnVisibility;
63
+ const rowPinning = pinnedRows && pinnedRows.value || localRowPinning;
64
+ const setRowPinning = (pinnedRows && pinnedRows.onChange) ? pinnedRows.onChange : setLocalRowPinning;
58
65
 
59
66
  // Virtualized data handling (chunked loading)
60
67
  const fetchSize = 20; // Number of rows per "page"
@@ -115,6 +122,7 @@ export function useTableState({
115
122
  ...(sortControl && { sorting }),
116
123
  ...(selectableRows && { rowSelection }),
117
124
  ...(columnVisibility && { columnVisibility }),
125
+ ...(pinnedRows && { rowPinning }),
118
126
  },
119
127
  }), [
120
128
  expanded,
@@ -123,6 +131,7 @@ export function useTableState({
123
131
  selectableRows,
124
132
  rowSelection,
125
133
  columnVisibility,
134
+ rowPinning,
126
135
  ]);
127
136
 
128
137
  // Pagination configuration
@@ -153,7 +162,7 @@ export function useTableState({
153
162
  enableSortingRemoval: false,
154
163
  sortDescFirst: true,
155
164
  onRowSelectionChange: setRowSelection,
156
- getRowId: selectableRows ? row => row.id : undefined,
165
+ getRowId: (selectableRows || pinnedRows) ? row => row.id : undefined,
157
166
  onColumnVisibilityChange: setColumnVisibility,
158
167
  meta: {
159
168
  columnDefinitions
@@ -39,7 +39,8 @@ export const TableHeader = ({
39
39
  hasAnySubRows,
40
40
  showActionsBar,
41
41
  selectableRows,
42
- responsive
42
+ responsive,
43
+ headerRef
43
44
  } = useContext(AdvancedTableContext)
44
45
 
45
46
  const classes = classnames(
@@ -62,8 +63,11 @@ export const TableHeader = ({
62
63
  id={id}
63
64
  >
64
65
  {/* Get the header groups (only one in this example) */}
65
- {table.getHeaderGroups().map((headerGroup: HeaderGroup<GenericObject>) => (
66
- <tr key={`${headerGroup.id}-headerGroup`}>
66
+ {table.getHeaderGroups().map((headerGroup: HeaderGroup<GenericObject>, index: number) => (
67
+ <tr
68
+ key={`${headerGroup.id}-headerGroup`}
69
+ ref={index === 0 ? headerRef : null}
70
+ >
67
71
  {!hasAnySubRows && selectableRows && (
68
72
  <th className={customCellClassnames}>
69
73
  <Checkbox
@@ -580,6 +580,11 @@
580
580
  }
581
581
  }
582
582
 
583
+ // Row Pinning - additional inline styles in RegularTableView.tsx
584
+ .pinned-row {
585
+ box-shadow: 0 4px 10px 0 rgba($shadow, 0.16) !important;
586
+ }
587
+
583
588
  &.dark {
584
589
  // Override default border color for dark mode
585
590
  --column-border-color: #{$border_dark};
@@ -51,7 +51,8 @@ type AdvancedTableProps = {
51
51
  onRowToggleClick?: (arg: Row<GenericObject>) => void
52
52
  onToggleExpansionClick?: (arg: Row<GenericObject>) => void
53
53
  pagination?: boolean,
54
- paginationProps?: GenericObject
54
+ paginationProps?: GenericObject,
55
+ pinnedRows?: any,
55
56
  responsive?: "scroll" | "none",
56
57
  selectableRows?: boolean,
57
58
  showActionsBar?: boolean,
@@ -91,6 +92,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
91
92
  onToggleExpansionClick,
92
93
  pagination = false,
93
94
  paginationProps,
95
+ pinnedRows,
94
96
  responsive = "scroll",
95
97
  showActionsBar = true,
96
98
  selectableRows,
@@ -136,6 +138,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
136
138
  tableOptions,
137
139
  onRowSelectionChange,
138
140
  columnVisibilityControl,
141
+ pinnedRows,
139
142
  });
140
143
 
141
144
  // Initialize table actions
@@ -241,7 +244,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
241
244
  maxHeight ? `advanced-table-max-height-${maxHeight}` : '',
242
245
  {
243
246
  'advanced-table-fullscreen': isFullscreen,
244
- 'advanced-table-allow-fullscreen': allowFullScreen
247
+ 'advanced-table-allow-fullscreen': allowFullScreen,
245
248
  },
246
249
  {'advanced-table-sticky-left-columns': stickyLeftColumn && stickyLeftColumn.length > 0},
247
250
  columnGroupBorderColor ? `column-group-border-${columnGroupBorderColor}` : '',
@@ -302,6 +305,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
302
305
  isFullscreen={isFullscreen}
303
306
  loading={loading}
304
307
  onExpandByDepthClick={onExpandByDepthClick}
308
+ pinnedRows={pinnedRows}
305
309
  responsive={responsive}
306
310
  selectableRows={selectableRows}
307
311
  setExpanded={setExpanded}
@@ -3,6 +3,12 @@ import { render, screen, waitFor } from "../utilities/test-utils"
3
3
 
4
4
  import { AdvancedTable, Pill } from "playbook-ui"
5
5
 
6
+ global.ResizeObserver = class {
7
+ observe() {}
8
+ unobserve() {}
9
+ disconnect() {}
10
+ };
11
+
6
12
  const MOCK_DATA = [
7
13
  {
8
14
  year: "2021",
@@ -72,6 +78,36 @@ const MOCK_DATA_LOADING = [
72
78
  },
73
79
  ]
74
80
 
81
+ const MOCK_DATA_WITH_ID = [
82
+ {
83
+ id: "1",
84
+ year: "2021",
85
+ quarter: null,
86
+ month: null,
87
+ day: null,
88
+ newEnrollments: "20",
89
+ scheduledMeetings: "10",
90
+ },
91
+ {
92
+ id: "2",
93
+ year: "2022",
94
+ quarter: null,
95
+ month: null,
96
+ day: null,
97
+ newEnrollments: "25",
98
+ scheduledMeetings: "15",
99
+ },
100
+ {
101
+ id: "3",
102
+ year: "2023",
103
+ quarter: null,
104
+ month: null,
105
+ day: null,
106
+ newEnrollments: "30",
107
+ scheduledMeetings: "20",
108
+ },
109
+ ]
110
+
75
111
  const columnDefinitions = [
76
112
  {
77
113
  accessor: "year",
@@ -512,3 +548,28 @@ test("allowFullScreen prop adds fullscreen class", () => {
512
548
  const tableContainer = screen.getByRole("table").closest("div")
513
549
  expect(tableContainer).toHaveClass("advanced-table-allow-fullscreen")
514
550
  })
551
+
552
+ test("pinnedRows prop renders pinned rows at top", () => {
553
+ const pinnedRowsControl = {
554
+ value: { top: ["1", "3"] },
555
+ onChange: jest.fn()
556
+ }
557
+
558
+ render(
559
+ <AdvancedTable
560
+ columnDefinitions={columnDefinitions}
561
+ data={{ testid: testId }}
562
+ pinnedRows={pinnedRowsControl}
563
+ tableData={MOCK_DATA_WITH_ID}
564
+ />
565
+ )
566
+
567
+ const kit = screen.getByTestId(testId)
568
+ const pinnedRows = kit.querySelectorAll(".pinned-row")
569
+
570
+ expect(pinnedRows).toHaveLength(2)
571
+
572
+ const firstPinnedRow = pinnedRows[0]
573
+ expect(firstPinnedRow).toHaveStyle("position: sticky")
574
+ expect(firstPinnedRow).toHaveStyle("background-color: white")
575
+ })
@@ -0,0 +1,57 @@
1
+ import React, { useState } from "react"
2
+ import AdvancedTable from '../_advanced_table'
3
+ import MOCK_DATA from "./advanced_table_mock_data_with_id.json"
4
+
5
+ const AdvancedTableRowPinning = (props) => {
6
+ const columnDefinitions = [
7
+ {
8
+ accessor: "year",
9
+ label: "Year",
10
+ cellAccessors: ["quarter", "month", "day"],
11
+ },
12
+ {
13
+ accessor: "newEnrollments",
14
+ label: "New Enrollments",
15
+ },
16
+ {
17
+ accessor: "scheduledMeetings",
18
+ label: "Scheduled Meetings",
19
+ },
20
+ {
21
+ accessor: "attendanceRate",
22
+ label: "Attendance Rate",
23
+ },
24
+ {
25
+ accessor: "completedClasses",
26
+ label: "Completed Classes",
27
+ },
28
+ {
29
+ accessor: "classCompletionRate",
30
+ label: "Class Completion Rate",
31
+ },
32
+ {
33
+ accessor: "graduatedStudents",
34
+ label: "Graduated Students",
35
+ },
36
+ ]
37
+
38
+ const [pinnedRows, setPinnedRows] = useState({top: ["8", "9", "10", "11", "12", "13", "14"]})
39
+
40
+ return (
41
+ <div>
42
+ <AdvancedTable
43
+ columnDefinitions={columnDefinitions}
44
+ maxHeight="xs"
45
+ pinnedRows={{value: pinnedRows, onChange: setPinnedRows}}
46
+ tableData={MOCK_DATA}
47
+ tableProps={{sticky: true}}
48
+ {...props}
49
+ >
50
+ <AdvancedTable.Header enableSorting />
51
+ <AdvancedTable.Body />
52
+ </AdvancedTable>
53
+ </div>
54
+ )
55
+ }
56
+
57
+ export default AdvancedTableRowPinning
@@ -0,0 +1,5 @@
1
+ Use the `pinnedRows` prop to pin specific rows to the top of an Advanced Table. Pinned rows will remain at the top when scrolling through table data and reorganizing via sorting.
2
+
3
+ **NOTE:** This prop is in Beta. Current Requirements for V1:
4
+ - Sticky header required: Pinned rows must be used with `sticky: true` via `tableProps` (works with both responsive and non-responsive tables)
5
+ - Row ids required: Pass an array of row ids to the `top` property. For expandable rows, both parent and all its child row ids must be included individually
@@ -52,4 +52,5 @@ examples:
52
52
  - advanced_table_column_visibility: Column Visibility Control
53
53
  - advanced_table_column_visibility_with_state: Column Visibility Control With State
54
54
  - advanced_table_column_visibility_custom: Column Visibility Control with Custom Dropdown
55
- - advanced_table_column_visibility_multi: Column Visibility Control with Multi-Header Columns
55
+ - advanced_table_column_visibility_multi: Column Visibility Control with Multi-Header Columns
56
+ - advanced_table_pinned_rows: Pinned Rows
@@ -31,4 +31,5 @@ export { default as AdvancedTableColumnBorderColor} from './_advanced_table_colu
31
31
  export { default as AdvancedTableColumnVisibility } from './_advanced_table_column_visibility.jsx'
32
32
  export { default as AdvancedTableColumnVisibilityCustom } from './_advanced_table_column_visibility_custom.jsx'
33
33
  export { default as AdvancedTableColumnVisibilityMulti } from './_advanced_table_column_visibility_multi.jsx'
34
- export { default as AdvancedTableColumnVisibilityWithState } from './_advanced_table_column_visibility_with_state.jsx'
34
+ export { default as AdvancedTableColumnVisibilityWithState } from './_advanced_table_column_visibility_with_state.jsx'
35
+ export { default as AdvancedTablePinnedRows } from './_advanced_table_pinned_rows.jsx'
@@ -1,12 +1,16 @@
1
- <%= pb_content_tag(:label, data: {
2
- pb_checkbox_indeterminate_main: object.indeterminate_main,
3
- pb_checkbox_indeterminate_parent: object.indeterminate_parent,
4
- }) do %>
1
+ <%= pb_content_tag(:label) do %>
5
2
  <%= content.presence || object.input %>
6
- <span data-pb-checkbox-icon-span="true" class="pb_checkbox_checkmark">
7
- <%= pb_rails("icon", props: { icon: "check", classname: "check_icon", fixed_width: true}) %>
8
- <%= pb_rails("icon", props: { icon: "minus", classname: "indeterminate_icon hidden", fixed_width: true}) %>
9
- </span>
3
+ <% if object.indeterminate %>
4
+ <span data-pb-checkbox-icon-span="true" class="pb_checkbox_indeterminate">
5
+ <%= pb_rails("icon", props: { icon: "minus", classname: "indeterminate_icon", fixed_width: true}) %>
6
+ <%= pb_rails("icon", props: { icon: "check", classname: "check_icon hidden", fixed_width: true}) %>
7
+ </span>
8
+ <% else %>
9
+ <span data-pb-checkbox-icon-span="true" class="pb_checkbox_checkmark">
10
+ <%= pb_rails("icon", props: { icon: "check", classname: "check_icon", fixed_width: true}) %>
11
+ <%= pb_rails("icon", props: { icon: "minus", classname: "indeterminate_icon hidden", fixed_width: true}) %>
12
+ </span>
13
+ <% end %>
10
14
  <span class="pb_checkbox_label">
11
15
  <%= pb_rails("body", props: { status: object.checkbox_label_status, text: object.text, dark: object.dark, margin_right: object.form_spacing ? "xs" : "" }) %>
12
16
  </span>
@@ -5,8 +5,7 @@ module Playbook
5
5
  class Checkbox < Playbook::KitBase
6
6
  prop :error, type: Playbook::Props::Boolean, default: false
7
7
  prop :checked, type: Playbook::Props::Boolean, default: false
8
- prop :indeterminate_main, type: Playbook::Props::Boolean, default: false
9
- prop :indeterminate_parent
8
+ prop :indeterminate, type: Playbook::Props::Boolean, default: false
10
9
  prop :text
11
10
  prop :value
12
11
  prop :name
@@ -20,7 +19,7 @@ module Playbook
20
19
  default: false
21
20
 
22
21
  def classname
23
- generate_classname("pb_checkbox_kit", checked_class) + error_class
22
+ generate_classname("pb_checkbox_kit", checked_class) + indeterminate_class + error_class
24
23
  end
25
24
 
26
25
  def input
@@ -40,6 +39,10 @@ module Playbook
40
39
  def checked_class
41
40
  checked ? "on" : "off"
42
41
  end
42
+
43
+ def indeterminate_class
44
+ indeterminate ? " indeterminate" : ""
45
+ end
43
46
  end
44
47
  end
45
48
  end
@@ -9,10 +9,11 @@
9
9
  <tr>
10
10
  <th>
11
11
  <%= pb_rails("checkbox", props: {
12
+ checked: true,
12
13
  text: "Uncheck All",
13
14
  value: "checkbox-value",
14
15
  name: "main-checkbox",
15
- indeterminate_main: true,
16
+ indeterminate: true,
16
17
  id: "indeterminate-checkbox"
17
18
  }) %>
18
19
  </th>
@@ -29,10 +30,55 @@
29
30
  value: checkbox[:id],
30
31
  name: "#{checkbox[:id]}-indeterminate-checkbox",
31
32
  id: "#{checkbox[:id]}-indeterminate-checkbox",
32
- indeterminate_parent: "indeterminate-checkbox",
33
33
  }) %>
34
34
  </td>
35
35
  </tr>
36
36
  <% end %>
37
37
  </tbody>
38
38
  <% end %>
39
+
40
+ <script>
41
+ document.addEventListener('DOMContentLoaded', function() {
42
+ const mainCheckboxWrapper = document.getElementById('indeterminate-checkbox');
43
+ const mainCheckbox = document.getElementsByName("main-checkbox")[0];
44
+ const childCheckboxes = document.querySelectorAll('input[type="checkbox"][id$="indeterminate-checkbox"]');
45
+
46
+ const updateMainCheckbox = () => {
47
+ // Count the number of checked child checkboxes
48
+ const checkedCount = Array.from(childCheckboxes).filter(cb => cb.checked).length;
49
+ // Determine if the main checkbox should be in an indeterminate state
50
+ const indeterminate = checkedCount > 0 && checkedCount < childCheckboxes.length;
51
+
52
+ // Set the main checkbox states
53
+ mainCheckbox.indeterminate = indeterminate;
54
+ mainCheckbox.checked = checkedCount > 0;
55
+
56
+ // Determine the main checkbox label based on the number of checked checkboxes
57
+ const text = checkedCount === 0 ? 'Check All' : 'Uncheck All';
58
+
59
+ // Determine the icon class to add and remove based on the number of checked checkboxes
60
+ const iconClassToAdd = checkedCount === 0 ? 'pb_checkbox_checkmark' : 'pb_checkbox_indeterminate';
61
+ const iconClassToRemove = checkedCount === 0 ? 'pb_checkbox_indeterminate' : 'pb_checkbox_checkmark';
62
+
63
+ // Update main checkbox label
64
+ mainCheckboxWrapper.getElementsByClassName('pb_body_kit')[0].textContent = text;
65
+
66
+ // Add and remove the icon class to the main checkbox wrapper
67
+ mainCheckboxWrapper.querySelector('[data-pb-checkbox-icon-span]').classList.add(iconClassToAdd);
68
+ mainCheckboxWrapper.querySelector('[data-pb-checkbox-icon-span]').classList.remove(iconClassToRemove);
69
+
70
+ // Toggle the visibility of the checkbox icon based on the indeterminate state
71
+ mainCheckboxWrapper.getElementsByClassName("indeterminate_icon")[0].classList.toggle('hidden', !indeterminate);
72
+ mainCheckboxWrapper.getElementsByClassName("check_icon")[0].classList.toggle('hidden', indeterminate);
73
+ };
74
+
75
+ mainCheckbox.addEventListener('change', function() {
76
+ childCheckboxes.forEach(cb => cb.checked = this.checked);
77
+ updateMainCheckbox();
78
+ });
79
+
80
+ childCheckboxes.forEach(cb => {
81
+ cb.addEventListener('change', updateMainCheckbox);
82
+ });
83
+ });
84
+ </script>