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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +82 -5
- data/app/pb_kits/playbook/pb_advanced_table/Context/AdvancedTableContext.tsx +58 -2
- data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +1 -1
- data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +13 -4
- data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableHeader.tsx +7 -3
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +5 -0
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +6 -2
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +61 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows.jsx +57 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows_react.md +5 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +2 -1
- data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
- data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +12 -8
- data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +6 -3
- data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_indeterminate.html.erb +48 -2
- data/dist/chunks/_weekday_stacked-yWpUc_c0.js +45 -0
- data/dist/chunks/vendor.js +1 -1
- data/dist/menu.yml +1 -1
- data/dist/playbook-doc.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +4 -4
- data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_indeterminate_rails.md +0 -1
- data/app/pb_kits/playbook/pb_checkbox/index.js +0 -56
- data/dist/chunks/_weekday_stacked-C4d17aYW.js +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8e79a2664649f32a2195a4e9199e868f6c1ebac20764479066f12c2dbbd7e9c
|
4
|
+
data.tar.gz: da415e2485131587cc6a32a03c8145152df934616500a008ef06b4e49c6c049a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
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
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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 :
|
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
|
-
|
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>
|