playbook_ui 16.4.0.pre.rc.3 → 16.4.0.pre.rc.4
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.tsx +4 -1
- 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_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 +1 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -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/vendor.js +2 -2
- data/dist/menu.yml +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +11 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ad1b7d3456e544a5d908b0fd556bc44c61607a27fdcebd55013054591ced1851
|
|
4
|
+
data.tar.gz: 65ed19c014844a0aceaa592e61f53018d09f2a835f05f58aa19c7f3805653ef5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 24b3221a2717bf9263f089a7b11811e9999aea7c5868e999d4b87a07b8dc7ad6e0330b801a4ff8c9cdde55539e31b602718837895f00a4b989b8d3bcd047b28d
|
|
7
|
+
data.tar.gz: 41309667045531ef713db565a84257662f781309f23247a9f154c4e91ba05277b3d5e0d903c7ca7ada074af99250f58405eee8f1e504b61286406d5e13f0a7ac
|
|
@@ -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
|
+
}
|
|
@@ -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
|
|
@@ -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,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.
|
|
@@ -39,6 +39,7 @@ examples:
|
|
|
39
39
|
- advanced_table_sort_per_column: Enable Sort By Column
|
|
40
40
|
- advanced_table_sort_per_column_for_multi_column: Enable Sort By Column (Multi-Column)
|
|
41
41
|
- advanced_table_custom_sort: Custom Sort
|
|
42
|
+
- advanced_table_sort_parent_only: Sort Parent Only
|
|
42
43
|
- advanced_table_expanded_control: Expanded Control
|
|
43
44
|
- advanced_table_expand_by_depth: Expand by Depth
|
|
44
45
|
- 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'
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<%
|
|
2
|
+
default_options = [
|
|
3
|
+
{ label: 'United States', value: 'unitedStates', id: 'us' },
|
|
4
|
+
{ label: 'Canada', value: 'canada', id: 'ca' },
|
|
5
|
+
{ label: 'Pakistan', value: 'pakistan', id: 'pk' },
|
|
6
|
+
{ label: 'India', value: 'India', id: 'in' },
|
|
7
|
+
{ label: 'Mexico', value: 'Mexico', id: 'mx' },
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
multi_options = [
|
|
11
|
+
{ label: 'United States', value: 'unitedStates', id: 'us' },
|
|
12
|
+
{ label: 'Canada', value: 'canada', id: 'ca' },
|
|
13
|
+
{ label: 'Pakistan', value: 'pakistan', id: 'pk' },
|
|
14
|
+
{ label: 'India', value: 'india', id: 'in' },
|
|
15
|
+
{ label: 'United Kingdom', value: 'unitedKingdom', id: 'uk' },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
autocomplete_options = [
|
|
19
|
+
{ label: "United States", value: "unitedStates", areaCode: "+1", icon: "🇺🇸", id: "us" },
|
|
20
|
+
{ label: "United Kingdom", value: "unitedKingdom", areaCode: "+44", icon: "🇬🇧", id: "gb" },
|
|
21
|
+
{ label: "Pakistan", value: "pakistan", areaCode: "+92", icon: "🇵🇰", id: "pk" },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
custom_display_options = [
|
|
25
|
+
{ label: "Strong Bad", value: "strongBad", id: "strong-bad", status: "Offline" },
|
|
26
|
+
{ label: "Strong Mad", value: "strongMad", id: "strong-mad", status: "Online" },
|
|
27
|
+
{ label: "Strong Sad", value: "strongSad", id: "strong-sad", status: "Away" },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
custom_display_content = capture do
|
|
31
|
+
pb_rails("flex", props: { align: "center" }) do
|
|
32
|
+
concat(pb_rails("avatar", props: { name: "", size: "xs", id: "cet-dropdown-avatar" }))
|
|
33
|
+
concat(pb_rails("body", props: { text: "", size: "xs", margin_x: "xs", id: "cet-dropdown-avatar-name" }))
|
|
34
|
+
concat(pb_rails("badge", props: { text: "", id: "cet-dropdown-avatar-status" }))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
%>
|
|
38
|
+
|
|
39
|
+
<!-- Example 1: Default dropdown -->
|
|
40
|
+
<%= pb_rails("dropdown", props: {
|
|
41
|
+
custom_event_type: "form:submitted,pb:dropdown:clearRequest",
|
|
42
|
+
id: "dropdown-default-cet",
|
|
43
|
+
label: "Default dropdown",
|
|
44
|
+
margin_bottom: "sm",
|
|
45
|
+
options: default_options,
|
|
46
|
+
}) %>
|
|
47
|
+
<%= pb_rails("flex", props: { wrap: true, gap: "sm", align: "center", margin_bottom: "md" }) do %>
|
|
48
|
+
<%= pb_rails("button", props: { id: "clear-default-cet", text: "Clear", variant: "primary" }) %>
|
|
49
|
+
<%= pb_rails("button", props: { id: "select-default-cet", text: "Select Canada", variant: "secondary" }) %>
|
|
50
|
+
<%= pb_rails("button", props: { id: "simulate-default-cet", text: "Simulate form submit", variant: "secondary" }) %>
|
|
51
|
+
<% end %>
|
|
52
|
+
|
|
53
|
+
<script>
|
|
54
|
+
(function() {
|
|
55
|
+
var id = "dropdown-default-cet";
|
|
56
|
+
document.getElementById("clear-default-cet").addEventListener("click", function() {
|
|
57
|
+
document.dispatchEvent(new CustomEvent("pb:dropdown:clearRequest", { detail: { dropdownId: id } }));
|
|
58
|
+
});
|
|
59
|
+
document.getElementById("select-default-cet").addEventListener("click", function() {
|
|
60
|
+
document.dispatchEvent(new CustomEvent("pb:dropdown:select", { detail: { dropdownId: id, optionId: "ca" } }));
|
|
61
|
+
});
|
|
62
|
+
document.getElementById("simulate-default-cet").addEventListener("click", function() {
|
|
63
|
+
document.dispatchEvent(new CustomEvent("form:submitted", { detail: { dropdownId: id } }));
|
|
64
|
+
});
|
|
65
|
+
})();
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<!-- Example 2: Multi select -->
|
|
69
|
+
<%= pb_rails("dropdown", props: {
|
|
70
|
+
custom_event_type: "form:submitted,pb:dropdown:clearRequest",
|
|
71
|
+
id: "dropdown-multi-cet",
|
|
72
|
+
label: "Multi select dropdown",
|
|
73
|
+
margin_bottom: "sm",
|
|
74
|
+
multi_select: true,
|
|
75
|
+
options: multi_options,
|
|
76
|
+
}) %>
|
|
77
|
+
<%= pb_rails("flex", props: { wrap: true, gap: "sm", align: "center", margin_bottom: "md" }) do %>
|
|
78
|
+
<%= pb_rails("button", props: { id: "clear-multi-cet", text: "Clear", variant: "primary" }) %>
|
|
79
|
+
<%= pb_rails("button", props: { id: "select-multi-cet", text: "Select US + UK", variant: "secondary" }) %>
|
|
80
|
+
<%= pb_rails("button", props: { id: "simulate-multi-cet", text: "Simulate form submit", variant: "secondary" }) %>
|
|
81
|
+
<% end %>
|
|
82
|
+
|
|
83
|
+
<script>
|
|
84
|
+
(function() {
|
|
85
|
+
var id = "dropdown-multi-cet";
|
|
86
|
+
document.getElementById("clear-multi-cet").addEventListener("click", function() {
|
|
87
|
+
document.dispatchEvent(new CustomEvent("pb:dropdown:clearRequest", { detail: { dropdownId: id } }));
|
|
88
|
+
});
|
|
89
|
+
document.getElementById("select-multi-cet").addEventListener("click", function() {
|
|
90
|
+
document.dispatchEvent(new CustomEvent("pb:dropdown:select", { detail: { dropdownId: id, optionIds: ["us", "uk"] } }));
|
|
91
|
+
});
|
|
92
|
+
document.getElementById("simulate-multi-cet").addEventListener("click", function() {
|
|
93
|
+
document.dispatchEvent(new CustomEvent("form:submitted", { detail: { dropdownId: id } }));
|
|
94
|
+
});
|
|
95
|
+
})();
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<!-- Example 3: Autocomplete -->
|
|
99
|
+
<%= pb_rails("dropdown", props: {
|
|
100
|
+
autocomplete: true,
|
|
101
|
+
custom_event_type: "form:submitted,pb:dropdown:clearRequest",
|
|
102
|
+
id: "dropdown-autocomplete-cet",
|
|
103
|
+
label: "Autocomplete dropdown",
|
|
104
|
+
margin_bottom: "sm",
|
|
105
|
+
options: autocomplete_options,
|
|
106
|
+
}) %>
|
|
107
|
+
<%= pb_rails("flex", props: { wrap: true, gap: "sm", align: "center", margin_bottom: "md" }) do %>
|
|
108
|
+
<%= pb_rails("button", props: { id: "clear-autocomplete-cet", text: "Clear", variant: "primary" }) %>
|
|
109
|
+
<%= pb_rails("button", props: { id: "select-autocomplete-cet", text: "Select Pakistan", variant: "secondary" }) %>
|
|
110
|
+
<%= pb_rails("button", props: { id: "simulate-autocomplete-cet", text: "Simulate form submit", variant: "secondary" }) %>
|
|
111
|
+
<% end %>
|
|
112
|
+
|
|
113
|
+
<script>
|
|
114
|
+
(function() {
|
|
115
|
+
var id = "dropdown-autocomplete-cet";
|
|
116
|
+
document.getElementById("clear-autocomplete-cet").addEventListener("click", function() {
|
|
117
|
+
document.dispatchEvent(new CustomEvent("pb:dropdown:clearRequest", { detail: { dropdownId: id } }));
|
|
118
|
+
});
|
|
119
|
+
document.getElementById("select-autocomplete-cet").addEventListener("click", function() {
|
|
120
|
+
document.dispatchEvent(new CustomEvent("pb:dropdown:select", { detail: { dropdownId: id, optionId: "pk" } }));
|
|
121
|
+
});
|
|
122
|
+
document.getElementById("simulate-autocomplete-cet").addEventListener("click", function() {
|
|
123
|
+
document.dispatchEvent(new CustomEvent("form:submitted", { detail: { dropdownId: id } }));
|
|
124
|
+
});
|
|
125
|
+
})();
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<!-- Example 4: Quick pick (Date Range) -->
|
|
129
|
+
<%= pb_rails("dropdown", props: {
|
|
130
|
+
custom_event_type: "form:submitted,pb:dropdown:clearRequest",
|
|
131
|
+
id: "dropdown-quickpick-cet",
|
|
132
|
+
label: "Quickpick dropdown",
|
|
133
|
+
margin_bottom: "sm",
|
|
134
|
+
variant: "quickpick",
|
|
135
|
+
}) %>
|
|
136
|
+
<%= pb_rails("flex", props: { wrap: true, gap: "sm", align: "center", margin_bottom: "md" }) do %>
|
|
137
|
+
<%= pb_rails("button", props: { id: "clear-quickpick-cet", text: "Clear", variant: "primary" }) %>
|
|
138
|
+
<%= pb_rails("button", props: { id: "select-quickpick-cet", text: "Select This Week", variant: "secondary" }) %>
|
|
139
|
+
<%= pb_rails("button", props: { id: "simulate-quickpick-cet", text: "Simulate form submit", variant: "secondary" }) %>
|
|
140
|
+
<% end %>
|
|
141
|
+
|
|
142
|
+
<script>
|
|
143
|
+
(function() {
|
|
144
|
+
var id = "dropdown-quickpick-cet";
|
|
145
|
+
document.getElementById("clear-quickpick-cet").addEventListener("click", function() {
|
|
146
|
+
document.dispatchEvent(new CustomEvent("pb:dropdown:clearRequest", { detail: { dropdownId: id } }));
|
|
147
|
+
});
|
|
148
|
+
document.getElementById("select-quickpick-cet").addEventListener("click", function() {
|
|
149
|
+
document.dispatchEvent(new CustomEvent("pb:dropdown:select", { detail: { dropdownId: id, optionId: "quickpick-this-week" } }));
|
|
150
|
+
});
|
|
151
|
+
document.getElementById("simulate-quickpick-cet").addEventListener("click", function() {
|
|
152
|
+
document.dispatchEvent(new CustomEvent("form:submitted", { detail: { dropdownId: id } }));
|
|
153
|
+
});
|
|
154
|
+
})();
|
|
155
|
+
</script>
|
|
156
|
+
|
|
157
|
+
<!-- Example 5: Custom display -->
|
|
158
|
+
<%= pb_rails("dropdown", props: {
|
|
159
|
+
custom_event_type: "form:submitted,pb:dropdown:clearRequest",
|
|
160
|
+
id: "dropdown-custom-display-cet",
|
|
161
|
+
label: "Custom display (Subcomponent) dropdown",
|
|
162
|
+
margin_bottom: "sm",
|
|
163
|
+
options: custom_display_options,
|
|
164
|
+
}) do %>
|
|
165
|
+
<%= pb_rails("dropdown/dropdown_trigger", props: { placeholder: "Select a User", custom_display: custom_display_content }) %>
|
|
166
|
+
<%= pb_rails("dropdown/dropdown_container") do %>
|
|
167
|
+
<% custom_display_options.each do |option| %>
|
|
168
|
+
<%= pb_rails("dropdown/dropdown_option", props: { option: option }) do %>
|
|
169
|
+
<%= pb_rails("flex", props: { align: "center", justify: "between" }) do %>
|
|
170
|
+
<%= pb_rails("flex/flex_item") do %>
|
|
171
|
+
<%= pb_rails("user", props: { name: option[:label], align: "left", avatar: true, orientation: "horizontal" }) %>
|
|
172
|
+
<% end %>
|
|
173
|
+
<%= pb_rails("flex/flex_item") do %>
|
|
174
|
+
<%= pb_rails("badge", props: { rounded: true, dark: true, text: option[:status], variant: option[:status] == "Offline" ? "neutral" : option[:status] == "Online" ? "success" : "warning" }) %>
|
|
175
|
+
<% end %>
|
|
176
|
+
<% end %>
|
|
177
|
+
<% end %>
|
|
178
|
+
<% end %>
|
|
179
|
+
<% end %>
|
|
180
|
+
<% end %>
|
|
181
|
+
<%= pb_rails("flex", props: { wrap: true, gap: "sm", align: "center", margin_bottom: "md" }) do %>
|
|
182
|
+
<%= pb_rails("button", props: { id: "clear-custom-display-cet", text: "Clear", variant: "primary" }) %>
|
|
183
|
+
<%= pb_rails("button", props: { id: "select-custom-display-cet", text: "Select Strong Sad", variant: "secondary" }) %>
|
|
184
|
+
<%= pb_rails("button", props: { id: "simulate-custom-display-cet", text: "Simulate form submit", variant: "secondary" }) %>
|
|
185
|
+
<% end %>
|
|
186
|
+
|
|
187
|
+
<script>
|
|
188
|
+
(function() {
|
|
189
|
+
var id = "dropdown-custom-display-cet";
|
|
190
|
+
document.getElementById("clear-custom-display-cet").addEventListener("click", function() {
|
|
191
|
+
document.dispatchEvent(new CustomEvent("pb:dropdown:clearRequest", { detail: { dropdownId: id } }));
|
|
192
|
+
});
|
|
193
|
+
document.getElementById("select-custom-display-cet").addEventListener("click", function() {
|
|
194
|
+
document.dispatchEvent(new CustomEvent("pb:dropdown:select", { detail: { dropdownId: id, optionId: "strong-sad" } }));
|
|
195
|
+
});
|
|
196
|
+
document.getElementById("simulate-custom-display-cet").addEventListener("click", function() {
|
|
197
|
+
document.dispatchEvent(new CustomEvent("form:submitted", { detail: { dropdownId: id } }));
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
document.addEventListener("pb:dropdown:selected", function(e) {
|
|
201
|
+
if (e.target && e.target.id === id && e.detail) {
|
|
202
|
+
var display = e.target.querySelector("[data-dropdown-trigger-custom-display]");
|
|
203
|
+
if (!display) return;
|
|
204
|
+
var nameEl = display.querySelector("#cet-dropdown-avatar-name");
|
|
205
|
+
if (nameEl) nameEl.textContent = e.detail.label;
|
|
206
|
+
var avatarEl = display.querySelector("#cet-dropdown-avatar");
|
|
207
|
+
if (avatarEl) {
|
|
208
|
+
var wrapper = avatarEl.querySelector(".avatar_wrapper");
|
|
209
|
+
if (wrapper) {
|
|
210
|
+
var initials = (e.detail.label[0] + (e.detail.label.split(" ").pop() || "")[0]).toUpperCase();
|
|
211
|
+
wrapper.dataset.name = e.detail.label;
|
|
212
|
+
wrapper.setAttribute("data-initials", initials);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
var badgeEl = display.querySelector("#cet-dropdown-avatar-status");
|
|
216
|
+
if (badgeEl && e.detail.status) {
|
|
217
|
+
var variant = e.detail.status === "Online" ? "success" : e.detail.status === "Offline" ? "neutral" : "warning";
|
|
218
|
+
badgeEl.querySelector("span").textContent = e.detail.status;
|
|
219
|
+
badgeEl.className = "pb_badge_kit_" + variant;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
})();
|
|
224
|
+
</script>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
The `custom_event_type` prop lets the dropdown clear when specific events are dispatched. Set it to a comma-separated list of event names (e.g. Turbo or app-specific); when any of those events fire with optional `detail: { dropdownId }`, the matching dropdown clears. You can also listen for selection changes (`pb:dropdown:selected`), clear by dispatching `pb:dropdown:clear`, or set the selection by dispatching `pb:dropdown:select` with `detail: { dropdownId, optionId }` (or `optionIds` for multi-select).
|
|
2
|
+
|
|
3
|
+
The examples show five distinct variants (default, multi select, autocomplete, quick pick, custom display). In each example, the first button clears the dropdown by dispatching a custom event that the dropdown listens for via `custom_event_type`. The second button sets the selection by dispatching `pb:dropdown:select`. The third button simulates a form submit by dispatching another custom event in `custom_event_type`, so the dropdown clears as it would after a real form submission.
|
|
4
|
+
|
|
5
|
+
Turbo events (e.g. `turbo:frame-load`, `turbo:frame-render`, `turbo:submit-end`) often fire for many actions or across the page, so using them as `custom_event_type` can clear the dropdown more often than intended. Use them when that scope is what you want; otherwise use app-specific event names and dispatch with `detail: { dropdownId }` when the action happens, or dispatch `pb:dropdown:clear` from your own handler.
|
|
6
|
+
|
|
7
|
+
Dropdowns with subcomponent structures that show custom displays (e.g. custom trigger or custom display) require a `pb:dropdown:selected` listener to update the visible UI from `event.detail` when a selection is made; clear is handled by the kit.
|
|
@@ -33,6 +33,7 @@ examples:
|
|
|
33
33
|
- dropdown_quickpick_with_date_pickers_rails: Quick Pick with Date Pickers
|
|
34
34
|
- dropdown_quickpick_with_date_pickers_default_rails: Quick Pick with Date Pickers (Default Value)
|
|
35
35
|
- dropdown_required_indicator: Required Indicator
|
|
36
|
+
- dropdown_custom_event_type: Custom Event Type
|
|
36
37
|
|
|
37
38
|
react:
|
|
38
39
|
- dropdown_default: Default
|