playbook_ui 16.3.0.pre.alpha.play284914702 → 16.3.0.pre.alpha.play284914770
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/CustomCell.tsx +17 -4
- data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +3 -1
- data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +21 -9
- data/app/pb_kits/playbook/pb_advanced_table/Utilities/ExpansionControlHelpers.tsx +25 -1
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +5 -1
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +74 -1
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_cascade_collapse.jsx +50 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_cascade_collapse.md +1 -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_table/_table.tsx +24 -21
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props.jsx +152 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props.md +17 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props_rails.html.erb +121 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props_rails.md +17 -0
- data/app/pb_kits/playbook/pb_table/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_table/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_table/table.html.erb +12 -11
- data/app/pb_kits/playbook/pb_table/table.rb +4 -0
- data/app/pb_kits/playbook/pb_table/table.test.js +33 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +105 -3
- data/dist/chunks/{_typeahead-QhswHQnq.js → _typeahead-tG1K5JPP.js} +1 -1
- data/dist/chunks/componentRegistry-DRSp5D_e.js +1 -0
- data/dist/chunks/vendor.js +2 -2
- data/dist/menu.yml +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/lib/playbook/pb_forms_helper.rb +3 -0
- data/lib/playbook/version.rb +1 -1
- metadata +12 -4
- data/dist/chunks/componentRegistry-DzmmLR2x.js +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b00abe84561a81194a57761780d54f66b81d7242fbd9a5375ceb53f68168b31
|
|
4
|
+
data.tar.gz: 8915da7876e7d6e0541ac17f82fc7b2dedba1cd78f41b295b2a5162d19b74e8a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5bb0b7509dcbede294e117a06019480ab186eef8306eb8bdf08210b88535739526e0b96fa696cc36e3c05c4cfa03eed520b0e177496940b9dbb6fe6ca6ebf99a
|
|
7
|
+
data.tar.gz: e56a8eef5c977851fda3ccaa58058a577e352bb051a39bd933e38267f950fd7a611f5bb090d5243dfbf1c8d6287a7e6909a9f3f93489d83228dd765ed534e18c
|
|
@@ -11,6 +11,7 @@ import Icon from "../../pb_icon/_icon"
|
|
|
11
11
|
import Checkbox from "../../pb_checkbox/_checkbox"
|
|
12
12
|
|
|
13
13
|
import AdvancedTableContext from "../Context/AdvancedTableContext"
|
|
14
|
+
import { getDescendantRowIds } from "../Utilities/ExpansionControlHelpers"
|
|
14
15
|
|
|
15
16
|
interface CustomCellProps {
|
|
16
17
|
getValue?: Getter<string>
|
|
@@ -31,13 +32,25 @@ export const CustomCell = ({
|
|
|
31
32
|
selectableRows,
|
|
32
33
|
customStyle = {},
|
|
33
34
|
}: CustomCellProps & GlobalProps) => {
|
|
34
|
-
const { setExpanded, expanded, expandedControl, inlineRowLoading, hasAnySubRows } = useContext(AdvancedTableContext);
|
|
35
|
+
const { setExpanded, expanded, expandedControl, inlineRowLoading, hasAnySubRows, cascadeCollapse } = useContext(AdvancedTableContext);
|
|
35
36
|
|
|
36
37
|
const handleOnExpand = (row: Row<GenericObject>) => {
|
|
37
38
|
onRowToggleClick && onRowToggleClick(row);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
|
|
40
|
+
const willBeExpanded = !row.getIsExpanded();
|
|
41
|
+
if (willBeExpanded) {
|
|
42
|
+
if (!expandedControl) {
|
|
43
|
+
setExpanded({ ...expanded, [row.id]: true });
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
if (cascadeCollapse) {
|
|
47
|
+
const idsToRemove = new Set([row.id, ...getDescendantRowIds(row)]);
|
|
48
|
+
const nextExpanded = { ...expanded };
|
|
49
|
+
idsToRemove.forEach((id) => delete nextExpanded[id]);
|
|
50
|
+
setExpanded(nextExpanded);
|
|
51
|
+
} else if (!expandedControl) {
|
|
52
|
+
setExpanded({ ...expanded, [row.id]: false });
|
|
53
|
+
}
|
|
41
54
|
}
|
|
42
55
|
};
|
|
43
56
|
|
|
@@ -63,6 +63,7 @@ export const TableHeaderCell = ({
|
|
|
63
63
|
stickyLeftColumn,
|
|
64
64
|
inlineRowLoading,
|
|
65
65
|
isActionBarVisible,
|
|
66
|
+
cascadeCollapse,
|
|
66
67
|
} = useContext(AdvancedTableContext);
|
|
67
68
|
|
|
68
69
|
type justifyTypes = "none" | "center" | "start" | "end" | "between" | "around" | "evenly"
|
|
@@ -182,7 +183,8 @@ const isToggleExpansionEnabled =
|
|
|
182
183
|
table.getRowModel(),
|
|
183
184
|
expanded,
|
|
184
185
|
undefined,
|
|
185
|
-
depth
|
|
186
|
+
depth,
|
|
187
|
+
cascadeCollapse
|
|
186
188
|
)
|
|
187
189
|
setExpanded(updated)
|
|
188
190
|
}
|
|
@@ -12,6 +12,7 @@ interface UseTableActionsProps {
|
|
|
12
12
|
inlineRowLoading?: boolean;
|
|
13
13
|
localPagination?: { pageIndex: number; pageSize: number };
|
|
14
14
|
setLocalPagination?: (pagination: { pageIndex: number; pageSize: number }) => void;
|
|
15
|
+
cascadeCollapse?: boolean;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function useTableActions({
|
|
@@ -22,7 +23,8 @@ export function useTableActions({
|
|
|
22
23
|
onRowSelectionChange,
|
|
23
24
|
inlineRowLoading = false,
|
|
24
25
|
localPagination,
|
|
25
|
-
setLocalPagination
|
|
26
|
+
setLocalPagination,
|
|
27
|
+
cascadeCollapse = false
|
|
26
28
|
}: UseTableActionsProps) {
|
|
27
29
|
|
|
28
30
|
// State to achieve 1 second delay before fetching more rows
|
|
@@ -32,15 +34,25 @@ export function useTableActions({
|
|
|
32
34
|
// Handle expand/collapse
|
|
33
35
|
const handleExpandOrCollapse = useCallback(async (row: Row<GenericObject>) => {
|
|
34
36
|
if (onToggleExpansionClick) onToggleExpansionClick(row)
|
|
35
|
-
const updatedExpandedState = await updateExpandAndCollapseState(
|
|
36
|
-
table.getRowModel(),
|
|
37
|
-
expanded,
|
|
38
|
-
row?.parentId,
|
|
39
|
-
undefined
|
|
40
|
-
)
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const anyTopLevelExpanded = table.getRowModel().rows.some((r: Row<GenericObject>) => r.getIsExpanded())
|
|
39
|
+
const isHeaderCollapseAll = row == null && anyTopLevelExpanded
|
|
40
|
+
|
|
41
|
+
if (cascadeCollapse && isHeaderCollapseAll) {
|
|
42
|
+
setExpanded({})
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const updatedExpandedState = await updateExpandAndCollapseState(
|
|
47
|
+
table.getRowModel(),
|
|
48
|
+
expanded,
|
|
49
|
+
row?.parentId,
|
|
50
|
+
undefined,
|
|
51
|
+
cascadeCollapse
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
setExpanded(updatedExpandedState)
|
|
55
|
+
}, [expanded, setExpanded, onToggleExpansionClick, table, cascadeCollapse]);
|
|
44
56
|
|
|
45
57
|
// Handle pagination
|
|
46
58
|
const onPageChange = useCallback((page: number) => {
|
|
@@ -11,11 +11,21 @@ const filterExpandableRows = (expandedState: Record<string, boolean>) => {
|
|
|
11
11
|
return expandedState
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export const getDescendantRowIds = (row: Row<GenericObject>): string[] => {
|
|
15
|
+
const ids: string[] = []
|
|
16
|
+
for (const sub of row.subRows || []) {
|
|
17
|
+
ids.push(sub.id)
|
|
18
|
+
ids.push(...getDescendantRowIds(sub))
|
|
19
|
+
}
|
|
20
|
+
return ids
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
export const updateExpandAndCollapseState = (
|
|
15
24
|
tableRows: RowModel<GenericObject>,
|
|
16
25
|
expanded: Record<string, boolean>,
|
|
17
26
|
targetParent?: string,
|
|
18
27
|
targetDepth?: number,
|
|
28
|
+
cascadeCollapse?: boolean,
|
|
19
29
|
) => {
|
|
20
30
|
const updateExpandedRows: Record<string, boolean> = {};
|
|
21
31
|
const rows = targetDepth !== undefined ? tableRows.flatRows : tableRows.rows;
|
|
@@ -51,8 +61,22 @@ export const updateExpandAndCollapseState = (
|
|
|
51
61
|
}
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
|
|
64
|
+
const updatedExpandedState = filterExpandableRows({
|
|
55
65
|
...(expanded as ExpandedStateObject),
|
|
56
66
|
...updateExpandedRows,
|
|
57
67
|
});
|
|
68
|
+
|
|
69
|
+
if (cascadeCollapse && !isExpandAction) {
|
|
70
|
+
const idsToRemove = new Set<string>();
|
|
71
|
+
for (const row of rowsToToggle) {
|
|
72
|
+
const shouldUpdate =
|
|
73
|
+
targetDepth === undefined ? true : row.depth === targetDepth;
|
|
74
|
+
if (shouldUpdate) {
|
|
75
|
+
getDescendantRowIds(row).forEach((id) => idsToRemove.add(id));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
idsToRemove.forEach((id) => delete updatedExpandedState[id]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return updatedExpandedState;
|
|
58
82
|
};
|
|
@@ -31,6 +31,7 @@ type FullscreenControls = {
|
|
|
31
31
|
type AdvancedTableProps = {
|
|
32
32
|
aria?: { [key: string]: string }
|
|
33
33
|
actions?: React.ReactNode[] | React.ReactNode
|
|
34
|
+
cascadeCollapse?: boolean
|
|
34
35
|
children?: React.ReactNode | React.ReactNode[]
|
|
35
36
|
className?: string
|
|
36
37
|
columnDefinitions: GenericObject[]
|
|
@@ -80,6 +81,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
|
|
|
80
81
|
const {
|
|
81
82
|
aria = {},
|
|
82
83
|
actions,
|
|
84
|
+
cascadeCollapse = false,
|
|
83
85
|
children,
|
|
84
86
|
className,
|
|
85
87
|
columnDefinitions,
|
|
@@ -173,7 +175,8 @@ const AdvancedTable = (props: AdvancedTableProps) => {
|
|
|
173
175
|
onRowSelectionChange,
|
|
174
176
|
inlineRowLoading,
|
|
175
177
|
localPagination,
|
|
176
|
-
setLocalPagination
|
|
178
|
+
setLocalPagination,
|
|
179
|
+
cascadeCollapse
|
|
177
180
|
});
|
|
178
181
|
|
|
179
182
|
// Set table row count for loading state
|
|
@@ -339,6 +342,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
|
|
|
339
342
|
>
|
|
340
343
|
{renderFullscreenHeader()}
|
|
341
344
|
<AdvancedTableProvider
|
|
345
|
+
cascadeCollapse={cascadeCollapse}
|
|
342
346
|
columnDefinitions={columnDefinitions}
|
|
343
347
|
columnGroupBorderColor={columnGroupBorderColor}
|
|
344
348
|
columnVisibilityControl={columnVisibilityControl}
|
|
@@ -1015,4 +1015,77 @@ test("columnStyling.headerFontColor works as excpected", () => {
|
|
|
1015
1015
|
|
|
1016
1016
|
const firstEnrollmentHeader = screen.getAllByText("New Enrollments")[0].closest("th");
|
|
1017
1017
|
expect(firstEnrollmentHeader).toHaveStyle({ color: colors.white });
|
|
1018
|
-
});
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
test("cascadeCollapse=false (default) preserves existing behavior when parent is re-expanded", () => {
|
|
1021
|
+
render(
|
|
1022
|
+
<AdvancedTable
|
|
1023
|
+
columnDefinitions={columnDefinitions}
|
|
1024
|
+
data={{ testid: testId }}
|
|
1025
|
+
tableData={MOCK_DATA}
|
|
1026
|
+
/>
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
const kit = screen.getByTestId(testId)
|
|
1030
|
+
const getParentExpandButton = () => kit.querySelector("tbody tr .gray-icon.expand-toggle-icon")
|
|
1031
|
+
const parentButton = getParentExpandButton()
|
|
1032
|
+
expect(parentButton).toBeInTheDocument()
|
|
1033
|
+
parentButton.click()
|
|
1034
|
+
let subRow = kit.querySelector(".pb-bg-row-white.depth-sub-row-1")
|
|
1035
|
+
expect(subRow).toBeInTheDocument()
|
|
1036
|
+
getParentExpandButton().click()
|
|
1037
|
+
subRow = kit.querySelector(".pb-bg-row-white.depth-sub-row-1")
|
|
1038
|
+
expect(subRow).not.toBeInTheDocument()
|
|
1039
|
+
getParentExpandButton().click()
|
|
1040
|
+
subRow = kit.querySelector(".pb-bg-row-white.depth-sub-row-1")
|
|
1041
|
+
expect(subRow).toBeInTheDocument()
|
|
1042
|
+
})
|
|
1043
|
+
|
|
1044
|
+
test("cascadeCollapse=true collapses all descendants when parent is collapsed", () => {
|
|
1045
|
+
render(
|
|
1046
|
+
<AdvancedTable
|
|
1047
|
+
cascadeCollapse
|
|
1048
|
+
columnDefinitions={columnDefinitions}
|
|
1049
|
+
data={{ testid: testId }}
|
|
1050
|
+
tableData={MOCK_DATA}
|
|
1051
|
+
/>
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
const kit = screen.getByTestId(testId)
|
|
1055
|
+
const getParentExpandButton = () => kit.querySelector("tbody tr .gray-icon.expand-toggle-icon")
|
|
1056
|
+
const parentButton = getParentExpandButton()
|
|
1057
|
+
expect(parentButton).toBeInTheDocument()
|
|
1058
|
+
parentButton.click()
|
|
1059
|
+
expect(kit.querySelector(".depth-sub-row-1")).toBeInTheDocument()
|
|
1060
|
+
getParentExpandButton().click()
|
|
1061
|
+
expect(kit.querySelector(".depth-sub-row-1")).not.toBeInTheDocument()
|
|
1062
|
+
getParentExpandButton().click()
|
|
1063
|
+
expect(kit.querySelector(".depth-sub-row-1")).toBeInTheDocument()
|
|
1064
|
+
})
|
|
1065
|
+
|
|
1066
|
+
test("cascadeCollapse=true with header toggle all: collapse all then expand all shows only direct children", async () => {
|
|
1067
|
+
render(
|
|
1068
|
+
<AdvancedTable
|
|
1069
|
+
cascadeCollapse
|
|
1070
|
+
columnDefinitions={columnDefinitions}
|
|
1071
|
+
data={{ testid: testId }}
|
|
1072
|
+
tableData={MOCK_DATA}
|
|
1073
|
+
/>
|
|
1074
|
+
)
|
|
1075
|
+
|
|
1076
|
+
const kit = screen.getByTestId(testId)
|
|
1077
|
+
const toggleAllButton = kit.querySelector(".gray-icon.toggle-all-icon")
|
|
1078
|
+
expect(toggleAllButton).toBeInTheDocument()
|
|
1079
|
+
toggleAllButton.click()
|
|
1080
|
+
await waitFor(() => {
|
|
1081
|
+
expect(kit.querySelector(".depth-sub-row-1")).toBeInTheDocument()
|
|
1082
|
+
})
|
|
1083
|
+
toggleAllButton.click()
|
|
1084
|
+
await waitFor(() => {
|
|
1085
|
+
expect(kit.querySelector(".depth-sub-row-1")).not.toBeInTheDocument()
|
|
1086
|
+
})
|
|
1087
|
+
toggleAllButton.click()
|
|
1088
|
+
await waitFor(() => {
|
|
1089
|
+
expect(kit.querySelector(".depth-sub-row-1")).toBeInTheDocument()
|
|
1090
|
+
})
|
|
1091
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
const AdvancedTableCascadeCollapse = (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
|
+
return (
|
|
39
|
+
<div>
|
|
40
|
+
<AdvancedTable
|
|
41
|
+
cascadeCollapse
|
|
42
|
+
columnDefinitions={columnDefinitions}
|
|
43
|
+
tableData={MOCK_DATA}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default AdvancedTableCascadeCollapse
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
`cascadeCollapse` is an optional prop that is set to 'false' by default. If set to 'true', collapsing any parent row itself or by using the toggle exapansion buttons in any header or subheader row also collapses all descendants and clears their expansion state. Re-expanding then shows only direct children until the user expands deeper levels again.
|
|
@@ -42,6 +42,7 @@ examples:
|
|
|
42
42
|
- advanced_table_expanded_control: Expanded Control
|
|
43
43
|
- advanced_table_expand_by_depth: Expand by Depth
|
|
44
44
|
- advanced_table_subrow_headers: SubRow Headers
|
|
45
|
+
- advanced_table_cascade_collapse: Cascade Collapse
|
|
45
46
|
- advanced_table_collapsible_trail: Collapsible Trail
|
|
46
47
|
- advanced_table_table_options: Table Options
|
|
47
48
|
- advanced_table_table_props: Table Props
|
|
@@ -48,4 +48,5 @@ export { default as AdvancedTablePaddingControl } from './_advanced_table_paddin
|
|
|
48
48
|
export { default as AdvancedTablePaddingControlPerRow } from './_advanced_table_padding_control_per_row.jsx'
|
|
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
|
-
export { default as AdvancedTableColumnStylingBackgroundCustom } from './_advanced_table_column_styling_background_custom.jsx'
|
|
51
|
+
export { default as AdvancedTableColumnStylingBackgroundCustom } from './_advanced_table_column_styling_background_custom.jsx'
|
|
52
|
+
export { default as AdvancedTableCascadeCollapse } from './_advanced_table_cascade_collapse.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.
|
|
@@ -32,6 +32,7 @@ examples:
|
|
|
32
32
|
- dropdown_quickpick_with_date_pickers_rails: Quick Pick with Date Pickers
|
|
33
33
|
- dropdown_quickpick_with_date_pickers_default_rails: Quick Pick with Date Pickers (Default Value)
|
|
34
34
|
- dropdown_required_indicator: Required Indicator
|
|
35
|
+
- dropdown_custom_event_type: Custom Event Type
|
|
35
36
|
|
|
36
37
|
react:
|
|
37
38
|
- dropdown_default: Default
|
|
@@ -52,6 +52,8 @@ module Playbook
|
|
|
52
52
|
default: false
|
|
53
53
|
prop :required_indicator, type: Playbook::Props::Boolean,
|
|
54
54
|
default: false
|
|
55
|
+
prop :custom_event_type, type: Playbook::Props::String,
|
|
56
|
+
default: ""
|
|
55
57
|
|
|
56
58
|
def data
|
|
57
59
|
Hash(prop(:data)).merge(
|
|
@@ -63,7 +65,8 @@ module Playbook
|
|
|
63
65
|
start_date_id: variant == "quickpick" ? start_date_id : nil,
|
|
64
66
|
end_date_id: variant == "quickpick" ? end_date_id : nil,
|
|
65
67
|
controls_start_id: variant == "quickpick" && controls_start_id.present? ? controls_start_id : nil,
|
|
66
|
-
controls_end_id: variant == "quickpick" && controls_end_id.present? ? controls_end_id : nil
|
|
68
|
+
controls_end_id: variant == "quickpick" && controls_end_id.present? ? controls_end_id : nil,
|
|
69
|
+
custom_event_type: custom_event_type.presence
|
|
67
70
|
).compact
|
|
68
71
|
end
|
|
69
72
|
|