playbook_ui 16.3.0.pre.alpha.play285814889 → 16.3.0.pre.alpha.railspinnedrows14948
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/Hooks/useTableState.ts +5 -2
- data/app/pb_kits/playbook/pb_advanced_table/Utilities/RowModelUtils.ts +100 -0
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +46 -1
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +4 -1
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +2 -2
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +2 -0
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +35 -1
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows_rails.html.erb +57 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows_rails.md +7 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort_parent_only.jsx +175 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort_parent_only.md +5 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
- data/app/pb_kits/playbook/pb_advanced_table/index.js +82 -0
- data/app/pb_kits/playbook/pb_advanced_table/table_body.html.erb +21 -4
- data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +115 -9
- data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +3 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +12 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.html.erb +4 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.rb +9 -1
- data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +3 -1
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_custom_event_type.html.erb +224 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_custom_event_type.md +7 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +4 -1
- data/app/pb_kits/playbook/pb_dropdown/index.js +161 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.scss +4 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.tsx +3 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin.html.erb +46 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin.jsx +42 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin_rails.md +1 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin_react.md +1 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/index.js +2 -1
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.rb +7 -1
- data/dist/chunks/{_pb_line_graph-BI5wY8Wj.js → _pb_line_graph-D6s5rymw.js} +1 -1
- data/dist/chunks/{_typeahead-8CvXJGlb.js → _typeahead-Bh0RF1X-.js} +1 -1
- data/dist/chunks/{globalProps-Bn1WUHLp.js → globalProps-Ds_6HBhX.js} +1 -1
- data/dist/chunks/{lib-qwWYiGtH.js → lib-BaO72ugL.js} +1 -1
- data/dist/chunks/vendor.js +3 -3
- data/dist/menu.yml +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +17 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4bcbf435e4eba2bfaadf18bb80b6f03e9aeaa464fdca4521d1943d70f2b58506
|
|
4
|
+
data.tar.gz: f59ca24bf67f770d0e13ed0ca8c4770c41001a10dd029de232d1437e4b96d96c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 04cb664168bba64dfcd8018edaf8e114beb74c1e1d231765b3ad154dcd2d1e5cb80955b81f8c6ed523e48fa6059a639c9b0ecd4ab5e42da0fa267e7a01ae8d68
|
|
7
|
+
data.tar.gz: 3b2b50c314d40092c160fc971b1481ecf7f5e9dcbe918b9c126849e498d31d5953c1f270500f449f687507259e0bf631f0571f0976a59101e596eceecb4c5deb
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { GenericObject } from "../../types";
|
|
13
13
|
import { createColumnHelper } from "@tanstack/react-table";
|
|
14
14
|
import { createCellFunction } from "../Utilities/CellRendererUtils";
|
|
15
|
+
import { getParentOnlySortedRowModel } from "../Utilities/RowModelUtils";
|
|
15
16
|
|
|
16
17
|
interface UseTableStateProps {
|
|
17
18
|
tableData: GenericObject[];
|
|
@@ -36,6 +37,7 @@ interface UseTableStateProps {
|
|
|
36
37
|
columnVisibilityControl?: GenericObject;
|
|
37
38
|
rowStyling?: GenericObject;
|
|
38
39
|
inlineRowLoading?: boolean;
|
|
40
|
+
sortParentOnly?: boolean;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export function useTableState({
|
|
@@ -55,7 +57,8 @@ export function useTableState({
|
|
|
55
57
|
columnVisibilityControl,
|
|
56
58
|
pinnedRows,
|
|
57
59
|
rowStyling,
|
|
58
|
-
inlineRowLoading = false
|
|
60
|
+
inlineRowLoading = false,
|
|
61
|
+
sortParentOnly = false
|
|
59
62
|
}: UseTableStateProps) {
|
|
60
63
|
|
|
61
64
|
// Create a local state for expanded and setExpanded if expandedControl not used
|
|
@@ -190,7 +193,7 @@ export function useTableState({
|
|
|
190
193
|
getSubRows: (row: GenericObject) => row.children,
|
|
191
194
|
getCoreRowModel: getCoreRowModel(),
|
|
192
195
|
getExpandedRowModel: getExpandedRowModel(),
|
|
193
|
-
getSortedRowModel: getSortedRowModel(),
|
|
196
|
+
getSortedRowModel: sortParentOnly ? getParentOnlySortedRowModel() : getSortedRowModel(),
|
|
194
197
|
enableSortingRemoval: enableSortingRemoval,
|
|
195
198
|
sortDescFirst: true,
|
|
196
199
|
onRowSelectionChange: setRowSelection,
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Returns a row model getter that sorts only depth-0 (parent) rows so children and grandchild rows keep their original order under each parent.
|
|
2
|
+
|
|
3
|
+
import type { Table, Row, RowModel, RowData } from "@tanstack/react-table";
|
|
4
|
+
|
|
5
|
+
export function getParentOnlySortedRowModel<TData extends RowData>(): (
|
|
6
|
+
table: Table<TData>
|
|
7
|
+
) => () => RowModel<TData> {
|
|
8
|
+
return (table) => () => {
|
|
9
|
+
const sortingState = table.getState().sorting;
|
|
10
|
+
const rowModel = table.getPreSortedRowModel();
|
|
11
|
+
|
|
12
|
+
if (!rowModel.rows.length || !sortingState?.length) {
|
|
13
|
+
return rowModel;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sortedFlatRows: Row<TData>[] = [];
|
|
17
|
+
const availableSorting = sortingState.filter((sort) =>
|
|
18
|
+
table.getColumn(sort.id)?.getCanSort()
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const columnInfoById: Record<
|
|
22
|
+
string,
|
|
23
|
+
{
|
|
24
|
+
sortUndefined?: false | -1 | 1 | "first" | "last";
|
|
25
|
+
invertSorting?: boolean;
|
|
26
|
+
sortingFn: (rowA: Row<TData>, rowB: Row<TData>, columnId: string) => number;
|
|
27
|
+
}
|
|
28
|
+
> = {};
|
|
29
|
+
|
|
30
|
+
availableSorting.forEach((sortEntry) => {
|
|
31
|
+
const column = table.getColumn(sortEntry.id);
|
|
32
|
+
if (!column) return;
|
|
33
|
+
columnInfoById[sortEntry.id] = {
|
|
34
|
+
sortUndefined: column.columnDef.sortUndefined,
|
|
35
|
+
invertSorting: column.columnDef.invertSorting,
|
|
36
|
+
sortingFn: column.getSortingFn(),
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const parentRows = rowModel.rows.map((row) => ({ ...row }));
|
|
41
|
+
parentRows.sort((rowA, rowB) => {
|
|
42
|
+
for (let i = 0; i < availableSorting.length; i += 1) {
|
|
43
|
+
const sortEntry = availableSorting[i]!;
|
|
44
|
+
const columnInfo = columnInfoById[sortEntry.id]!;
|
|
45
|
+
const sortUndefined = columnInfo.sortUndefined;
|
|
46
|
+
const isDesc = sortEntry?.desc ?? false;
|
|
47
|
+
let sortInt = 0;
|
|
48
|
+
|
|
49
|
+
if (sortUndefined) {
|
|
50
|
+
const aValue = rowA.getValue(sortEntry.id);
|
|
51
|
+
const bValue = rowB.getValue(sortEntry.id);
|
|
52
|
+
const aUndefined = aValue === undefined;
|
|
53
|
+
const bUndefined = bValue === undefined;
|
|
54
|
+
if (aUndefined || bUndefined) {
|
|
55
|
+
if (sortUndefined === "first") return aUndefined ? -1 : 1;
|
|
56
|
+
if (sortUndefined === "last") return aUndefined ? 1 : -1;
|
|
57
|
+
sortInt =
|
|
58
|
+
aUndefined && bUndefined
|
|
59
|
+
? 0
|
|
60
|
+
: aUndefined
|
|
61
|
+
? sortUndefined
|
|
62
|
+
: -sortUndefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (sortInt === 0) {
|
|
67
|
+
sortInt = columnInfo.sortingFn(rowA, rowB, sortEntry.id);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (sortInt !== 0) {
|
|
71
|
+
if (isDesc) sortInt *= -1;
|
|
72
|
+
if (columnInfo.invertSorting) sortInt *= -1;
|
|
73
|
+
return sortInt;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return rowA.index - rowB.index;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function flattenRowsInOrder(rows: Row<TData>[]): void {
|
|
80
|
+
rows.forEach((row) => {
|
|
81
|
+
sortedFlatRows.push(row);
|
|
82
|
+
if (row.subRows?.length) {
|
|
83
|
+
flattenRowsInOrder(row.subRows);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
flattenRowsInOrder(parentRows);
|
|
88
|
+
|
|
89
|
+
const rowsById: Record<string, Row<TData>> = {};
|
|
90
|
+
sortedFlatRows.forEach((row) => {
|
|
91
|
+
rowsById[row.id] = row;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
rows: parentRows,
|
|
96
|
+
flatRows: sortedFlatRows,
|
|
97
|
+
rowsById,
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -63,6 +63,51 @@
|
|
|
63
63
|
width: 100%;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Override pb_table endcap: first and last column use same padding as rest of row
|
|
67
|
+
// First column
|
|
68
|
+
.pb_table.table-sm tbody tr td:first-child,
|
|
69
|
+
.pb_table.table-sm tbody tr .pb_table_td:first-child,
|
|
70
|
+
.pb_table.table-sm tbody .pb_table_tr td:first-child,
|
|
71
|
+
.pb_table.table-sm tbody .pb_table_tr .pb_table_td:first-child,
|
|
72
|
+
.pb_table.table-sm .pb_table_tbody tr td:first-child,
|
|
73
|
+
.pb_table.table-sm .pb_table_tbody tr .pb_table_td:first-child,
|
|
74
|
+
.pb_table.table-sm .pb_table_tbody .pb_table_tr td:first-child,
|
|
75
|
+
.pb_table.table-sm .pb_table_tbody .pb_table_tr .pb_table_td:first-child {
|
|
76
|
+
padding-left: $space-xs;
|
|
77
|
+
}
|
|
78
|
+
.pb_table.table-md tbody tr td:first-child,
|
|
79
|
+
.pb_table.table-md tbody tr .pb_table_td:first-child,
|
|
80
|
+
.pb_table.table-md tbody .pb_table_tr td:first-child,
|
|
81
|
+
.pb_table.table-md tbody .pb_table_tr .pb_table_td:first-child,
|
|
82
|
+
.pb_table.table-md .pb_table_tbody tr td:first-child,
|
|
83
|
+
.pb_table.table-md .pb_table_tbody tr .pb_table_td:first-child,
|
|
84
|
+
.pb_table.table-md .pb_table_tbody .pb_table_tr td:first-child,
|
|
85
|
+
.pb_table.table-md .pb_table_tbody .pb_table_tr .pb_table_td:first-child {
|
|
86
|
+
padding-left: $space-sm;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Last column
|
|
90
|
+
.pb_table.table-sm tbody tr td:last-child,
|
|
91
|
+
.pb_table.table-sm tbody tr .pb_table_td:last-child,
|
|
92
|
+
.pb_table.table-sm tbody .pb_table_tr td:last-child,
|
|
93
|
+
.pb_table.table-sm tbody .pb_table_tr .pb_table_td:last-child,
|
|
94
|
+
.pb_table.table-sm .pb_table_tbody tr td:last-child,
|
|
95
|
+
.pb_table.table-sm .pb_table_tbody tr .pb_table_td:last-child,
|
|
96
|
+
.pb_table.table-sm .pb_table_tbody .pb_table_tr td:last-child,
|
|
97
|
+
.pb_table.table-sm .pb_table_tbody .pb_table_tr .pb_table_td:last-child {
|
|
98
|
+
padding-right: $space-xs;
|
|
99
|
+
}
|
|
100
|
+
.pb_table.table-md tbody tr td:last-child,
|
|
101
|
+
.pb_table.table-md tbody tr .pb_table_td:last-child,
|
|
102
|
+
.pb_table.table-md tbody .pb_table_tr td:last-child,
|
|
103
|
+
.pb_table.table-md tbody .pb_table_tr .pb_table_td:last-child,
|
|
104
|
+
.pb_table.table-md .pb_table_tbody tr td:last-child,
|
|
105
|
+
.pb_table.table-md .pb_table_tbody tr .pb_table_td:last-child,
|
|
106
|
+
.pb_table.table-md .pb_table_tbody .pb_table_tr td:last-child,
|
|
107
|
+
.pb_table.table-md .pb_table_tbody .pb_table_tr .pb_table_td:last-child {
|
|
108
|
+
padding-right: $space-sm;
|
|
109
|
+
}
|
|
110
|
+
|
|
66
111
|
// Virtualized Table and Rows for Infinite Scroll
|
|
67
112
|
scrollbar-gutter: stable right-edges;
|
|
68
113
|
.virtualized-header-row-header {
|
|
@@ -720,7 +765,7 @@
|
|
|
720
765
|
}
|
|
721
766
|
}
|
|
722
767
|
|
|
723
|
-
// Row Pinning -
|
|
768
|
+
// Row Pinning - React uses inline style; Rails passes same style via html_options from table_body
|
|
724
769
|
.pinned-row {
|
|
725
770
|
box-shadow: 0 4px 10px 0 rgba($shadow, 0.16) !important;
|
|
726
771
|
}
|
|
@@ -66,6 +66,7 @@ type AdvancedTableProps = {
|
|
|
66
66
|
showActionsBar?: boolean,
|
|
67
67
|
persistToggleExpansionButton?: boolean,
|
|
68
68
|
sortControl?: GenericObject
|
|
69
|
+
sortParentOnly?: boolean
|
|
69
70
|
tableData: GenericObject[]
|
|
70
71
|
tableOptions?: GenericObject
|
|
71
72
|
tableProps?: GenericObject
|
|
@@ -114,6 +115,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
|
|
|
114
115
|
selectableRows,
|
|
115
116
|
persistToggleExpansionButton = false,
|
|
116
117
|
sortControl,
|
|
118
|
+
sortParentOnly = false,
|
|
117
119
|
stickyLeftColumn,
|
|
118
120
|
tableData,
|
|
119
121
|
tableOptions,
|
|
@@ -159,7 +161,8 @@ const AdvancedTable = (props: AdvancedTableProps) => {
|
|
|
159
161
|
columnVisibilityControl,
|
|
160
162
|
pinnedRows,
|
|
161
163
|
rowStyling,
|
|
162
|
-
inlineRowLoading
|
|
164
|
+
inlineRowLoading,
|
|
165
|
+
sortParentOnly
|
|
163
166
|
});
|
|
164
167
|
|
|
165
168
|
// Initialize table actions
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
}) %>
|
|
8
8
|
<% end %>
|
|
9
9
|
|
|
10
|
-
<%= pb_rails("table", props: { size: "sm", data_table: true, number_spacing:"tabular", responsive:"none", dark: dark, classname: object.loading ? "content-loading" : "" }.merge(object.table_props)) do %>
|
|
10
|
+
<%= pb_rails("table", props: { size: "sm", data_table: true, number_spacing:"tabular", responsive:"none", dark: dark, classname: object.loading ? "content-loading" : "" }.merge(object.table_props || {})) do %>
|
|
11
11
|
<% if content.present? %>
|
|
12
12
|
<% content.presence %>
|
|
13
13
|
<% else %>
|
|
14
14
|
<%= pb_rails("advanced_table/table_header", props: { table_id: object.id, column_definitions: object.column_definitions, enable_toggle_expansion: object.enable_toggle_expansion, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, show_actions_bar: object.show_actions_bar, inline_row_loading: object.inline_row_loading, persist_toggle_expansion_button: object.persist_toggle_expansion_button, table_data: object.table_data }) %>
|
|
15
|
-
<%= pb_rails("advanced_table/table_body", props: { table_id: object.id, table_data: object.table_data, column_definitions: object.column_definitions, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, enable_toggle_expansion: object.enable_toggle_expansion, row_styling: object.row_styling, inline_row_loading: object.inline_row_loading }) %>
|
|
15
|
+
<%= pb_rails("advanced_table/table_body", props: { table_id: object.id, table_data: object.table_data, column_definitions: object.column_definitions, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, enable_toggle_expansion: object.enable_toggle_expansion, row_styling: object.row_styling, inline_row_loading: object.inline_row_loading, pinned_rows: object.pinned_rows }) %>
|
|
16
16
|
<% end %>
|
|
17
17
|
<% end %>
|
|
18
18
|
<% end %>
|
|
@@ -495,7 +495,41 @@ test("sort button exists and sorts column data", () => {
|
|
|
495
495
|
|
|
496
496
|
const row2 = kit.getElementsByTagName('tr')[2]
|
|
497
497
|
expect(row2.id).toBe("0-0-0-row")
|
|
498
|
-
})
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
test("sortParentOnly sorts only parent rows and keeps children grouped under parent", () => {
|
|
501
|
+
render(
|
|
502
|
+
<AdvancedTable
|
|
503
|
+
columnDefinitions={columnDefinitions}
|
|
504
|
+
data={{ testid: testId }}
|
|
505
|
+
sortParentOnly
|
|
506
|
+
tableData={MOCK_DATA}
|
|
507
|
+
>
|
|
508
|
+
<AdvancedTable.Header enableSorting />
|
|
509
|
+
<AdvancedTable.Body />
|
|
510
|
+
</AdvancedTable>
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
const kit = screen.getByTestId(testId)
|
|
514
|
+
const sortButton = kit.querySelector(".header-sort-button.pb_th_link")
|
|
515
|
+
expect(sortButton).toBeInTheDocument()
|
|
516
|
+
|
|
517
|
+
const tbody = kit.querySelector('tbody')
|
|
518
|
+
const rowsBefore = tbody.getElementsByTagName('tr')
|
|
519
|
+
expect(rowsBefore[0]).toHaveTextContent('2021')
|
|
520
|
+
|
|
521
|
+
sortButton.click()
|
|
522
|
+
|
|
523
|
+
const rowsAfter = tbody.getElementsByTagName('tr')
|
|
524
|
+
expect(rowsAfter[0]).toHaveTextContent('2022')
|
|
525
|
+
|
|
526
|
+
const expandButton = kit.querySelector(".gray-icon.expand-toggle-icon")
|
|
527
|
+
expandButton.click()
|
|
528
|
+
|
|
529
|
+
const rowsExpanded = tbody.getElementsByTagName('tr')
|
|
530
|
+
expect(rowsExpanded.length).toBeGreaterThan(1)
|
|
531
|
+
expect(rowsExpanded[1]).toHaveTextContent('Q1')
|
|
532
|
+
})
|
|
499
533
|
|
|
500
534
|
test("Generates Table.Header default + custom classname", () => {
|
|
501
535
|
render(
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<%# Example sort method for demonstration purposes %>
|
|
2
|
+
<% if params["sort"] %>
|
|
3
|
+
<% sort_param = params["sort"].gsub(/_(asc|desc)\z/, "") %>
|
|
4
|
+
<% sort_direction = params["sort"].end_with?("_asc") ? 1 : -1 %>
|
|
5
|
+
<% @table_data_with_id.sort! do |a, b|
|
|
6
|
+
value_a = a[sort_param] || a[sort_param.to_sym]
|
|
7
|
+
value_b = b[sort_param] || b[sort_param.to_sym]
|
|
8
|
+
|
|
9
|
+
value_a = value_a.to_i if value_a.is_a?(String) && value_a.match?(/^\d+$/)
|
|
10
|
+
value_b = value_b.to_i if value_b.is_a?(String) && value_b.match?(/^\d+$/)
|
|
11
|
+
|
|
12
|
+
sort_direction * (value_a <=> value_b)
|
|
13
|
+
end %>
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
<% column_definitions = [
|
|
17
|
+
{
|
|
18
|
+
accessor: "year",
|
|
19
|
+
label: "Year",
|
|
20
|
+
cellAccessors: ["quarter", "month", "day"],
|
|
21
|
+
sort_menu: [
|
|
22
|
+
{ item: "Year", link: "?sort=year_asc#pinned_rows_table", active: params["sort"] == "year_asc", direction: "asc" },
|
|
23
|
+
{ item: "Year", link: "?sort=year_desc#pinned_rows_table", active: params["sort"] == "year_desc", direction: "desc" }
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
accessor: "newEnrollments",
|
|
28
|
+
label: "New Enrollments",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
accessor: "scheduledMeetings",
|
|
32
|
+
label: "Scheduled Meetings",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
accessor: "attendanceRate",
|
|
36
|
+
label: "Attendance Rate",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
accessor: "completedClasses",
|
|
40
|
+
label: "Completed Classes",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
accessor: "classCompletionRate",
|
|
44
|
+
label: "Class Completion Rate",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
accessor: "graduatedStudents",
|
|
48
|
+
label: "Graduated Students",
|
|
49
|
+
}
|
|
50
|
+
] %>
|
|
51
|
+
|
|
52
|
+
<% pinned_rows = { top: ["8"] } %>
|
|
53
|
+
|
|
54
|
+
<%= pb_rails("advanced_table", props: { id: "pinned_rows_table", table_data: @table_data_with_id, column_definitions: column_definitions, max_height: "xs", pinned_rows: pinned_rows, responsive: "none", table_props: { sticky: true }}) do %>
|
|
55
|
+
<%= pb_rails("advanced_table/table_header", props: { table_id: "pinned_rows_table", column_definitions: column_definitions }) %>
|
|
56
|
+
<%= pb_rails("advanced_table/table_body", props: { table_id: "pinned_rows_table", table_data: @table_data_with_id, column_definitions: column_definitions, pinned_rows: pinned_rows }) %>
|
|
57
|
+
<% end %>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Use the `pinned_rows` 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 will not change position if sorting is used.
|
|
2
|
+
|
|
3
|
+
**NOTE:**
|
|
4
|
+
- Sticky header required: Pinned rows must be used with `sticky: true` via `table_props` (works with both responsive and non-responsive tables)
|
|
5
|
+
- Row ids required: Each object within the `table_data` array must contain a unique `id` in order to attach an id to all Rows for this to function.
|
|
6
|
+
- `pinned_rows` takes a hash with a `top` key containing an array of row ids, as shown in the code snippet below.
|
|
7
|
+
- For expandable rows, use the parent id in `pinned_rows[:top]`; all its children will automatically be pinned with it. If a child id is passed without the parent being pinned, nothing will be pinned.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import AdvancedTable from '../../pb_advanced_table/_advanced_table'
|
|
3
|
+
import MOCK_DATA from "./advanced_table_mock_data.json"
|
|
4
|
+
|
|
5
|
+
import Caption from "../../pb_caption/_caption"
|
|
6
|
+
|
|
7
|
+
const sharedColumnDefinitions = [
|
|
8
|
+
{
|
|
9
|
+
accessor: "year",
|
|
10
|
+
label: "Year",
|
|
11
|
+
cellAccessors: ["quarter", "month", "day"],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
accessor: "newEnrollments",
|
|
15
|
+
label: "New Enrollments",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
accessor: "scheduledMeetings",
|
|
19
|
+
label: "Scheduled Meetings",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
accessor: "attendanceRate",
|
|
23
|
+
label: "Attendance Rate",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
accessor: "completedClasses",
|
|
27
|
+
label: "Completed Classes",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
accessor: "classCompletionRate",
|
|
31
|
+
label: "Class Completion Rate",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
accessor: "graduatedStudents",
|
|
35
|
+
label: "Graduated Students",
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const sortByColumnDefinitions = [
|
|
40
|
+
{
|
|
41
|
+
accessor: "year",
|
|
42
|
+
label: "Year",
|
|
43
|
+
cellAccessors: ["quarter", "month", "day"],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
accessor: "newEnrollments",
|
|
47
|
+
label: "New Enrollments",
|
|
48
|
+
enableSort: true,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
accessor: "scheduledMeetings",
|
|
52
|
+
label: "Scheduled Meetings",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
accessor: "attendanceRate",
|
|
56
|
+
label: "Attendance Rate",
|
|
57
|
+
enableSort: true,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
accessor: "completedClasses",
|
|
61
|
+
label: "Completed Classes",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
accessor: "classCompletionRate",
|
|
65
|
+
label: "Class Completion Rate",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
accessor: "graduatedStudents",
|
|
69
|
+
label: "Graduated Students",
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
const sortByColumnMultiDefinitions = [
|
|
74
|
+
{
|
|
75
|
+
accessor: "year",
|
|
76
|
+
label: "Year",
|
|
77
|
+
cellAccessors: ["quarter", "month", "day"],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: "Enrollment Data",
|
|
81
|
+
columns: [
|
|
82
|
+
{
|
|
83
|
+
label: "Enrollment Stats",
|
|
84
|
+
columns: [
|
|
85
|
+
{
|
|
86
|
+
accessor: "newEnrollments",
|
|
87
|
+
label: "New Enrollments",
|
|
88
|
+
enableSort: true,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
accessor: "scheduledMeetings",
|
|
92
|
+
label: "Scheduled Meetings",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: "Performance Data",
|
|
100
|
+
columns: [
|
|
101
|
+
{
|
|
102
|
+
label: "Completion Metrics",
|
|
103
|
+
columns: [
|
|
104
|
+
{
|
|
105
|
+
accessor: "completedClasses",
|
|
106
|
+
label: "Completed Classes",
|
|
107
|
+
enableSort: true,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
accessor: "classCompletionRate",
|
|
111
|
+
label: "Class Completion Rate",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
label: "Attendance",
|
|
117
|
+
columns: [
|
|
118
|
+
{
|
|
119
|
+
accessor: "attendanceRate",
|
|
120
|
+
label: "Attendance Rate",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
accessor: "scheduledMeetings",
|
|
124
|
+
label: "Scheduled Meetings",
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
const AdvancedTableSortParentOnly = (props) => {
|
|
133
|
+
return (
|
|
134
|
+
<div>
|
|
135
|
+
<Caption text="Enable Sorting (first column) + sortParentOnly" />
|
|
136
|
+
<AdvancedTable
|
|
137
|
+
columnDefinitions={sharedColumnDefinitions}
|
|
138
|
+
sortParentOnly
|
|
139
|
+
tableData={MOCK_DATA}
|
|
140
|
+
{...props}
|
|
141
|
+
>
|
|
142
|
+
<AdvancedTable.Header enableSorting />
|
|
143
|
+
<AdvancedTable.Body />
|
|
144
|
+
</AdvancedTable>
|
|
145
|
+
<Caption marginTop="md"
|
|
146
|
+
text="Sort by column + sortParentOnly"
|
|
147
|
+
/>
|
|
148
|
+
<AdvancedTable
|
|
149
|
+
columnDefinitions={sortByColumnDefinitions}
|
|
150
|
+
enableSortingRemoval
|
|
151
|
+
sortParentOnly
|
|
152
|
+
tableData={MOCK_DATA}
|
|
153
|
+
{...props}
|
|
154
|
+
>
|
|
155
|
+
<AdvancedTable.Header />
|
|
156
|
+
<AdvancedTable.Body />
|
|
157
|
+
</AdvancedTable>
|
|
158
|
+
<Caption marginTop="md"
|
|
159
|
+
text="Sort by column (multi-column) + sortParentOnly"
|
|
160
|
+
/>
|
|
161
|
+
<AdvancedTable
|
|
162
|
+
columnDefinitions={sortByColumnMultiDefinitions}
|
|
163
|
+
enableSortingRemoval
|
|
164
|
+
sortParentOnly
|
|
165
|
+
tableData={MOCK_DATA}
|
|
166
|
+
{...props}
|
|
167
|
+
>
|
|
168
|
+
<AdvancedTable.Header enableSorting />
|
|
169
|
+
<AdvancedTable.Body />
|
|
170
|
+
</AdvancedTable>
|
|
171
|
+
</div>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default AdvancedTableSortParentOnly
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
The `sortParentOnly` prop is a boolean set to `false` by default. When set to `true`, only parent (depth-0) rows are re-ordered when sorting; children and grandchildren stay grouped under their parent and keep their original order.
|
|
2
|
+
|
|
3
|
+
`sortParentOnly` works with every sorting mode: `enableSorting` on the header, per-column `enableSort: true`, and sortable leaf columns in the multi-header variant. Sort indicators behave as usual.
|
|
4
|
+
|
|
5
|
+
When omitted or `false`, sorting applies to all levels.
|
|
@@ -7,6 +7,7 @@ examples:
|
|
|
7
7
|
- advanced_table_table_props: Table Props
|
|
8
8
|
- advanced_table_sticky_header_rails: Sticky Header
|
|
9
9
|
- advanced_table_table_props_sticky_header: Sticky Header for Responsive Table
|
|
10
|
+
- advanced_table_pinned_rows_rails: Pinned Rows
|
|
10
11
|
- advanced_table_beta_sort: Enable Sorting
|
|
11
12
|
- advanced_table_responsive: Responsive Tables
|
|
12
13
|
- advanced_table_custom_cell_rails: Custom Components for Cells
|
|
@@ -39,6 +40,7 @@ examples:
|
|
|
39
40
|
- advanced_table_sort_per_column: Enable Sort By Column
|
|
40
41
|
- advanced_table_sort_per_column_for_multi_column: Enable Sort By Column (Multi-Column)
|
|
41
42
|
- advanced_table_custom_sort: Custom Sort
|
|
43
|
+
- advanced_table_sort_parent_only: Sort Parent Only
|
|
42
44
|
- advanced_table_expanded_control: Expanded Control
|
|
43
45
|
- advanced_table_expand_by_depth: Expand by Depth
|
|
44
46
|
- advanced_table_subrow_headers: SubRow Headers
|
|
@@ -49,4 +49,5 @@ export { default as AdvancedTablePaddingControlPerRow } from './_advanced_table_
|
|
|
49
49
|
export { default as AdvancedTableColumnStylingBackground } from './_advanced_table_column_styling_background.jsx'
|
|
50
50
|
export { default as AdvancedTableColumnStylingBackgroundMulti } from './_advanced_table_column_styling_background_multi.jsx'
|
|
51
51
|
export { default as AdvancedTableColumnStylingBackgroundCustom } from './_advanced_table_column_styling_background_custom.jsx'
|
|
52
|
-
export { default as AdvancedTableCascadeCollapse } from './_advanced_table_cascade_collapse.jsx'
|
|
52
|
+
export { default as AdvancedTableCascadeCollapse } from './_advanced_table_cascade_collapse.jsx'
|
|
53
|
+
export { default as AdvancedTableSortParentOnly } from './_advanced_table_sort_parent_only.jsx'
|
|
@@ -196,6 +196,17 @@ export default class PbAdvancedTable extends PbEnhancedElement {
|
|
|
196
196
|
if (table.dataset.pbAdvancedTableInitialized) return;
|
|
197
197
|
table.dataset.pbAdvancedTableInitialized = "true";
|
|
198
198
|
|
|
199
|
+
// Measure header height so pinned rows don't overlap when header wraps (e.g. mobile)
|
|
200
|
+
if (mainTable) {
|
|
201
|
+
PbAdvancedTable.updateStickyHeaderRowHeights(mainTable);
|
|
202
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
203
|
+
PbAdvancedTable.updateStickyHeaderRowHeights(mainTable);
|
|
204
|
+
PbAdvancedTable.updatePinnedRowsStickyTops(mainTable);
|
|
205
|
+
});
|
|
206
|
+
resizeObserver.observe(table);
|
|
207
|
+
mainTable._advancedTableHeaderResizeObserver = resizeObserver;
|
|
208
|
+
}
|
|
209
|
+
|
|
199
210
|
// Delegate checkbox changes
|
|
200
211
|
table.addEventListener("change", (event) => {
|
|
201
212
|
const checkbox = event.target.closest('input[type="checkbox"]');
|
|
@@ -315,9 +326,64 @@ export default class PbAdvancedTable extends PbEnhancedElement {
|
|
|
315
326
|
lastVisibleRow.classList.add("last-visible-row");
|
|
316
327
|
lastVisibleRow.classList.add("last-row-cell");
|
|
317
328
|
}
|
|
329
|
+
|
|
330
|
+
PbAdvancedTable.updateStickyHeaderRowHeights(parentElement);
|
|
331
|
+
PbAdvancedTable.updatePinnedRowsStickyTops(table);
|
|
318
332
|
}
|
|
319
333
|
}
|
|
320
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Measure thead height and set --advanced-table-header-height so pinned rows and
|
|
337
|
+
* multi-row sticky headers use the correct offset. Re-run when header wraps (e.g. mobile).
|
|
338
|
+
*/
|
|
339
|
+
static updateStickyHeaderRowHeights(advancedTableWrapper) {
|
|
340
|
+
if (!advancedTableWrapper) return;
|
|
341
|
+
const table = advancedTableWrapper.querySelector("table.pb_table");
|
|
342
|
+
const thead = table?.querySelector("thead");
|
|
343
|
+
if (!thead) return;
|
|
344
|
+
|
|
345
|
+
const rows = Array.from(thead.querySelectorAll("tr"));
|
|
346
|
+
let totalHeight = 0;
|
|
347
|
+
rows.forEach((tr, index) => {
|
|
348
|
+
const h = tr.offsetHeight;
|
|
349
|
+
if (index === 0) {
|
|
350
|
+
advancedTableWrapper.style.setProperty(
|
|
351
|
+
"--advanced-table-header-row-0-height",
|
|
352
|
+
`${h}px`
|
|
353
|
+
);
|
|
354
|
+
} else if (index === 1) {
|
|
355
|
+
advancedTableWrapper.style.setProperty(
|
|
356
|
+
"--advanced-table-header-row-1-height",
|
|
357
|
+
`${h}px`
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
totalHeight += h;
|
|
361
|
+
});
|
|
362
|
+
advancedTableWrapper.style.setProperty(
|
|
363
|
+
"--advanced-table-header-height",
|
|
364
|
+
`${totalHeight}px`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Recompute sticky top for visible pinned rows so collapsed rows don't leave a gap.
|
|
370
|
+
* Call after expand/collapse and on load.
|
|
371
|
+
*/
|
|
372
|
+
static updatePinnedRowsStickyTops(advancedTableWrapper) {
|
|
373
|
+
const pinnedTbody = advancedTableWrapper?.querySelector("tbody.pinned-rows-tbody");
|
|
374
|
+
if (!pinnedTbody) return;
|
|
375
|
+
|
|
376
|
+
const pinnedRows = Array.from(pinnedTbody.querySelectorAll("tr.pinned-row"));
|
|
377
|
+
const visibleRows = pinnedRows.filter(
|
|
378
|
+
(tr) => tr.style.display !== "none" && tr.offsetParent !== null
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
const headerOffset = "var(--advanced-table-header-height, 44px)";
|
|
382
|
+
visibleRows.forEach((tr, index) => {
|
|
383
|
+
tr.style.top = `calc(${headerOffset} + 2.5em * ${index})`;
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
321
387
|
hideCloseIcon() {
|
|
322
388
|
const closeIcon = this.element.querySelector(UP_ARROW_SELECTOR);
|
|
323
389
|
closeIcon.style.display = "none";
|
|
@@ -519,3 +585,19 @@ window.expandAllRows = (element) => {
|
|
|
519
585
|
window.expandAllSubRows = (element, rowDepth) => {
|
|
520
586
|
PbAdvancedTable.handleToggleAllSubRows(element, rowDepth);
|
|
521
587
|
};
|
|
588
|
+
|
|
589
|
+
// Fix header height and pinned row sticky tops on load (header wrap + collapsed rows)
|
|
590
|
+
function updateAllAdvancedTableStickyHeights() {
|
|
591
|
+
document.querySelectorAll(".pb_advanced_table").forEach((wrapper) => {
|
|
592
|
+
PbAdvancedTable.updateStickyHeaderRowHeights(wrapper);
|
|
593
|
+
PbAdvancedTable.updatePinnedRowsStickyTops(wrapper);
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (typeof document !== "undefined") {
|
|
598
|
+
if (document.readyState === "loading") {
|
|
599
|
+
document.addEventListener("DOMContentLoaded", updateAllAdvancedTableStickyHeights);
|
|
600
|
+
} else {
|
|
601
|
+
updateAllAdvancedTableStickyHeights();
|
|
602
|
+
}
|
|
603
|
+
}
|