playbook_ui 14.4.0 → 14.5.0.pre.alpha.PBNTR606multilevelselectreset4035
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/_playbook.scss +2 -0
- data/app/pb_kits/playbook/pb_advanced_table/Components/SortIconButton.tsx +23 -4
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +1 -1
- data/app/pb_kits/playbook/pb_button/_button.tsx +6 -2
- data/app/pb_kits/playbook/pb_contact/_contact.tsx +17 -5
- data/app/pb_kits/playbook/pb_contact/contact.html.erb +14 -6
- data/app/pb_kits/playbook/pb_contact/contact.rb +4 -0
- data/app/pb_kits/playbook/pb_contact/contact.test.js +1 -1
- data/app/pb_kits/playbook/pb_dashboard/pbChartsDarkTheme.ts +2 -6
- data/app/pb_kits/playbook/pb_dashboard/pbChartsLightTheme.ts +2 -7
- data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +4 -3
- data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +1 -1
- data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +4 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +6 -3
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_inline.html.erb +1 -1
- data/app/pb_kits/playbook/pb_date_picker/sass_partials/_header_styles.scss +6 -2
- data/app/pb_kits/playbook/pb_dialog/_dialog.scss +2 -0
- data/app/pb_kits/playbook/pb_dialog/dialog_header.html.erb +1 -1
- data/app/pb_kits/playbook/pb_drawer/_close_icon.tsx +25 -0
- data/app/pb_kits/playbook/pb_drawer/_drawer.scss +465 -0
- data/app/pb_kits/playbook/pb_drawer/_drawer.tsx +195 -0
- data/app/pb_kits/playbook/pb_drawer/_drawer_context.tsx +3 -0
- data/app/pb_kits/playbook/pb_drawer/docs/_drawer_borders.jsx +117 -0
- data/app/pb_kits/playbook/pb_drawer/docs/_drawer_breakpoints.jsx +43 -0
- data/app/pb_kits/playbook/pb_drawer/docs/_drawer_default.html.erb +1 -0
- data/app/pb_kits/playbook/pb_drawer/docs/_drawer_default.jsx +63 -0
- data/app/pb_kits/playbook/pb_drawer/docs/_drawer_overlay.jsx +55 -0
- data/app/pb_kits/playbook/pb_drawer/docs/_drawer_sizes.jsx +113 -0
- data/app/pb_kits/playbook/pb_drawer/docs/example.yml +12 -0
- data/app/pb_kits/playbook/pb_drawer/docs/index.js +5 -0
- data/app/pb_kits/playbook/pb_drawer/drawer.html.erb +12 -0
- data/app/pb_kits/playbook/pb_drawer/drawer.rb +8 -0
- data/app/pb_kits/playbook/pb_drawer/drawer.test.jsx +77 -0
- data/app/pb_kits/playbook/pb_filter/docs/_filter_default.html.erb +10 -2
- data/app/pb_kits/playbook/pb_filter/docs/_filter_max_height.html.erb +5 -1
- data/app/pb_kits/playbook/pb_filter/docs/_filter_max_width.html.erb +5 -1
- data/app/pb_kits/playbook/pb_filter/docs/_filter_no_background.html.erb +5 -1
- data/app/pb_kits/playbook/pb_filter/docs/_filter_no_sort.html.erb +5 -1
- data/app/pb_kits/playbook/pb_filter/docs/_filter_only.html.erb +5 -1
- data/app/pb_kits/playbook/pb_filter/docs/_filter_placement.html.erb +5 -1
- data/app/pb_kits/playbook/pb_filter/docs/_filter_single.html.erb +5 -1
- data/app/pb_kits/playbook/pb_form_pill/_form_pill.tsx +9 -1
- data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.html.erb +19 -0
- data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.jsx +27 -0
- data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.md +1 -0
- data/app/pb_kits/playbook/pb_form_pill/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_form_pill/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_map/_map_controls.tsx +7 -1
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +226 -231
- data/app/pb_kits/playbook/pb_multi_level_select/context/index.tsx +5 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx +1 -1
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_reset.html.erb +93 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.jsx +105 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.md +1 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.jsx +106 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.md +1 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +4 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select_options.tsx +149 -0
- data/app/pb_kits/playbook/pb_pagination/docs/_pagination_page_change.jsx +12 -1
- data/app/pb_kits/playbook/pb_pagination/docs/_pagination_page_change_react.md +3 -1
- data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +10 -2
- data/app/pb_kits/playbook/pb_popover/docs/_popover_list.html.erb +14 -13
- data/app/pb_kits/playbook/pb_popover/docs/_popover_list.jsx +4 -4
- data/app/pb_kits/playbook/pb_radio/_radio.tsx +92 -33
- data/app/pb_kits/playbook/pb_radio/docs/_radio_custom_children.html.erb +2 -0
- data/app/pb_kits/playbook/pb_radio/docs/_radio_custom_children.jsx +62 -0
- data/app/pb_kits/playbook/pb_radio/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_radio/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/TipTap/ToolbarDropdown.tsx +12 -5
- data/app/pb_kits/playbook/pb_select/_select.tsx +5 -2
- data/app/pb_kits/playbook/pb_select/select.html.erb +1 -1
- data/app/pb_kits/playbook/pb_select/select.rb +4 -0
- data/app/pb_kits/playbook/pb_text_input/_text_input.scss +0 -1
- data/app/pb_kits/playbook/pb_tooltip/_tooltip.tsx +17 -13
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +0 -1
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +14 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +7 -1
- data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +6 -5
- data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +3 -1
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_margin_bottom.html.erb +88 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_margin_bottom.jsx +60 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +2 -1
- data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +9 -2
- data/app/pb_kits/playbook/utilities/icons/allicons.tsx +136 -13
- data/app/pb_kits/playbook/utilities/icons/angle-down.svg +3 -0
- data/app/pb_kits/playbook/utilities/icons/envelope.svg +3 -0
- data/dist/chunks/_typeahead-C9g4qCcE.js +22 -0
- data/dist/chunks/_weekday_stacked-Div3Fpd3.js +45 -0
- data/dist/chunks/lazysizes-B7xYodB-.js +1 -0
- data/dist/chunks/lib-CEpcaI8y.js +29 -0
- data/dist/chunks/{pb_form_validation-zV9OpdSt.js → pb_form_validation-D9zkwt2b.js} +1 -1
- data/dist/chunks/vendor.js +1 -1
- data/dist/menu.yml +3 -1
- data/dist/playbook-doc.js +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/pagination_renderer.rb +10 -2
- data/lib/playbook/pb_doc_helper.rb +5 -5
- data/lib/playbook/version.rb +2 -2
- metadata +40 -10
- data/dist/chunks/_typeahead-B2zRxReA.js +0 -22
- data/dist/chunks/_weekday_stacked-BIfZDNDm.js +0 -45
- data/dist/chunks/lazysizes-DHz07jlL.js +0 -1
- data/dist/chunks/lib-D2U4I1U6.js +0 -16
@@ -1,14 +1,17 @@
|
|
1
|
-
import React, { useState, useEffect, useRef } from "react"
|
2
|
-
import classnames from "classnames"
|
3
|
-
import { globalProps, GlobalProps } from "../utilities/globalProps"
|
4
|
-
import {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
import
|
11
|
-
import
|
1
|
+
import React, { useState, useEffect, useRef } from "react";
|
2
|
+
import classnames from "classnames";
|
3
|
+
import { globalProps, GlobalProps } from "../utilities/globalProps";
|
4
|
+
import {
|
5
|
+
buildAriaProps,
|
6
|
+
buildCss,
|
7
|
+
buildDataProps,
|
8
|
+
buildHtmlProps,
|
9
|
+
} from "../utilities/props";
|
10
|
+
import Icon from "../pb_icon/_icon";
|
11
|
+
import FormPill from "../pb_form_pill/_form_pill";
|
12
|
+
import { cloneDeep } from "lodash";
|
13
|
+
import MultiLevelSelectOptions from "./multi_level_select_options";
|
14
|
+
import MultiLevelSelectContext from "./context";
|
12
15
|
|
13
16
|
import {
|
14
17
|
getAncestorsOfUnchecked,
|
@@ -18,7 +21,7 @@ import {
|
|
18
21
|
getDefaultCheckedItems,
|
19
22
|
recursiveCheckParent,
|
20
23
|
getExpandedItems,
|
21
|
-
} from "./_helper_functions"
|
24
|
+
} from "./_helper_functions";
|
22
25
|
|
23
26
|
type MultiLevelSelectProps = {
|
24
27
|
aria?: { [key: string]: string }
|
@@ -30,9 +33,9 @@ type MultiLevelSelectProps = {
|
|
30
33
|
inputName?: string
|
31
34
|
name?: string
|
32
35
|
returnAllSelected?: boolean
|
33
|
-
treeData?: { [key: string]: string }[]
|
36
|
+
treeData?: { [key: string]: string; }[] | any
|
34
37
|
onSelect?: (prop: { [key: string]: any }) => void
|
35
|
-
selectedIds?: string[]
|
38
|
+
selectedIds?: string[] | any
|
36
39
|
variant?: "multi" | "single"
|
37
40
|
pillColor?: "primary" | "neutral" | "success" | "warning" | "error" | "info" | "data_1" | "data_2" | "data_3" | "data_4" | "data_5" | "data_6" | "data_7" | "data_8" | "windows" | "siding" | "roofing" | "doors" | "gutters" | "solar" | "insulation" | "accessories",
|
38
41
|
} & GlobalProps
|
@@ -52,140 +55,169 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
52
55
|
onSelect = () => null,
|
53
56
|
selectedIds,
|
54
57
|
variant = "multi",
|
58
|
+
children,
|
55
59
|
pillColor = "primary"
|
56
60
|
} = props
|
57
61
|
|
58
|
-
const ariaProps = buildAriaProps(aria)
|
59
|
-
const dataProps = buildDataProps(data)
|
60
|
-
const htmlProps = buildHtmlProps(htmlOptions)
|
62
|
+
const ariaProps = buildAriaProps(aria);
|
63
|
+
const dataProps = buildDataProps(data);
|
64
|
+
const htmlProps = buildHtmlProps(htmlOptions);
|
61
65
|
const classes = classnames(
|
62
66
|
buildCss("pb_multi_level_select"),
|
63
67
|
globalProps(props),
|
64
68
|
className
|
65
|
-
)
|
69
|
+
);
|
66
70
|
|
67
|
-
const dropdownRef = useRef(null)
|
71
|
+
const dropdownRef = useRef(null);
|
68
72
|
|
69
73
|
// State for whether dropdown is open or closed
|
70
|
-
const [isDropdownClosed, setIsDropdownClosed] = useState(true)
|
74
|
+
const [isDropdownClosed, setIsDropdownClosed] = useState(true);
|
71
75
|
// State from onChange for textinput, to use for filtering to create typeahead
|
72
|
-
const [filterItem, setFilterItem] = useState("")
|
76
|
+
const [filterItem, setFilterItem] = useState("");
|
73
77
|
// FormattedData with checked and parent_id added
|
74
|
-
const [formattedData, setFormattedData] = useState([])
|
78
|
+
const [formattedData, setFormattedData] = useState([]);
|
75
79
|
// State for the return of returnAllSelected
|
76
|
-
const [returnedArray, setReturnedArray] = useState([])
|
80
|
+
const [returnedArray, setReturnedArray] = useState([]);
|
77
81
|
// State for default return
|
78
|
-
const [defaultReturn, setDefaultReturn] = useState([])
|
82
|
+
const [defaultReturn, setDefaultReturn] = useState([]);
|
79
83
|
// Get expanded items from treeData
|
80
|
-
const initialExpandedItems = getExpandedItems(treeData, selectedIds)
|
84
|
+
const initialExpandedItems = getExpandedItems(treeData, selectedIds);
|
81
85
|
// Initialize state with expanded items
|
82
|
-
const [expanded, setExpanded] = useState(initialExpandedItems)
|
86
|
+
const [expanded, setExpanded] = useState(initialExpandedItems);
|
83
87
|
|
84
88
|
// Single Select specific state
|
85
89
|
const [singleSelectedItem, setSingleSelectedItem] = useState({
|
86
90
|
id: [],
|
87
91
|
value: "",
|
88
|
-
item: []
|
89
|
-
})
|
92
|
+
item: [],
|
93
|
+
});
|
94
|
+
|
95
|
+
const arrowDownElementId = `arrow_down_${id}`
|
96
|
+
const arrowUpElementId = `arrow_up_${id}`
|
90
97
|
|
91
98
|
const modifyRecursive = (tree: { [key: string]: any }[], check: boolean) => {
|
92
99
|
if (!Array.isArray(tree)) {
|
93
|
-
return
|
100
|
+
return;
|
94
101
|
}
|
95
102
|
return tree.map((item: { [key: string]: any }) => {
|
96
|
-
item.checked = check
|
97
|
-
item.children = modifyRecursive(item.children, check)
|
98
|
-
return item
|
99
|
-
})
|
100
|
-
}
|
103
|
+
item.checked = check;
|
104
|
+
item.children = modifyRecursive(item.children, check);
|
105
|
+
return item;
|
106
|
+
});
|
107
|
+
};
|
101
108
|
|
102
|
-
|
109
|
+
// Function to map over data and add parent_id + depth property to each item
|
103
110
|
const addCheckedAndParentProperty = (
|
104
111
|
treeData: { [key: string]: any }[],
|
105
112
|
selectedIds: string[],
|
106
|
-
parent_id: string = null,
|
107
|
-
depth = 0
|
113
|
+
parent_id: string | null = null,
|
114
|
+
depth = 0
|
108
115
|
) => {
|
109
116
|
if (!Array.isArray(treeData)) {
|
110
|
-
return
|
117
|
+
return;
|
111
118
|
}
|
112
119
|
return treeData.map((item: { [key: string]: any } | any) => {
|
113
120
|
const newItem = {
|
114
121
|
...item,
|
115
|
-
checked: Boolean(
|
122
|
+
checked: Boolean(
|
123
|
+
selectedIds && selectedIds.length && selectedIds.includes(item.id)
|
124
|
+
),
|
116
125
|
parent_id,
|
117
126
|
depth,
|
118
|
-
}
|
127
|
+
};
|
119
128
|
if (newItem.children && newItem.children.length > 0) {
|
120
129
|
const children =
|
121
130
|
item.checked && !returnAllSelected
|
122
131
|
? modifyRecursive(item.children, true)
|
123
|
-
: item.children
|
132
|
+
: item.children;
|
124
133
|
newItem.children = addCheckedAndParentProperty(
|
125
134
|
children,
|
126
135
|
selectedIds,
|
127
136
|
newItem.id,
|
128
137
|
depth + 1
|
129
|
-
)
|
138
|
+
);
|
130
139
|
}
|
131
|
-
return newItem
|
132
|
-
})
|
133
|
-
}
|
140
|
+
return newItem;
|
141
|
+
});
|
142
|
+
};
|
134
143
|
|
135
144
|
useEffect(() => {
|
136
145
|
const formattedData = addCheckedAndParentProperty(
|
137
146
|
treeData,
|
138
147
|
variant === "single" ? [selectedIds?.[0]] : selectedIds
|
139
|
-
)
|
148
|
+
);
|
140
149
|
|
141
|
-
setFormattedData(formattedData)
|
150
|
+
setFormattedData(formattedData);
|
142
151
|
|
143
152
|
if (variant === "single") {
|
144
153
|
// No selectedIds, reset state
|
145
154
|
if (selectedIds?.length === 0 || !selectedIds?.length) {
|
146
|
-
setSingleSelectedItem({ id: [], value: "", item: []})
|
155
|
+
setSingleSelectedItem({ id: [], value: "", item: [] });
|
147
156
|
} else {
|
148
157
|
// If there is a selectedId but no current item, set the selectedItem
|
149
158
|
if (selectedIds?.length !== 0 && !singleSelectedItem.value) {
|
150
|
-
const selectedItem = filterFormattedDataById(
|
159
|
+
const selectedItem = filterFormattedDataById(
|
160
|
+
formattedData,
|
161
|
+
selectedIds[0]
|
162
|
+
);
|
151
163
|
|
152
164
|
if (!selectedItem.length) {
|
153
|
-
setSingleSelectedItem({ id: [], value: "", item: []})
|
165
|
+
setSingleSelectedItem({ id: [], value: "", item: [] });
|
154
166
|
} else {
|
155
|
-
const { id, value } = selectedItem[0]
|
156
|
-
setSingleSelectedItem({ id: [id], value, item: selectedItem})
|
167
|
+
const { id, value } = selectedItem[0];
|
168
|
+
setSingleSelectedItem({ id: [id], value, item: selectedItem });
|
157
169
|
}
|
158
170
|
}
|
159
171
|
}
|
160
172
|
}
|
161
|
-
}, [treeData, selectedIds])
|
173
|
+
}, [treeData, selectedIds]);
|
162
174
|
|
163
175
|
useEffect(() => {
|
164
176
|
if (returnAllSelected) {
|
165
|
-
setReturnedArray(getCheckedItems(formattedData))
|
177
|
+
setReturnedArray(getCheckedItems(formattedData));
|
166
178
|
} else if (variant === "single") {
|
167
|
-
setDefaultReturn(singleSelectedItem.item)
|
179
|
+
setDefaultReturn(singleSelectedItem.item);
|
168
180
|
} else {
|
169
|
-
setDefaultReturn(getDefaultCheckedItems(formattedData))
|
181
|
+
setDefaultReturn(getDefaultCheckedItems(formattedData));
|
170
182
|
}
|
171
|
-
}, [formattedData])
|
183
|
+
}, [formattedData]);
|
172
184
|
|
173
185
|
useEffect(() => {
|
174
186
|
// Function to handle clicks outside the dropdown
|
175
187
|
const handleClickOutside = (event: any) => {
|
176
|
-
if (
|
188
|
+
if (
|
189
|
+
dropdownRef.current &&
|
190
|
+
!dropdownRef.current.contains(event.target) &&
|
191
|
+
event.target.id !== arrowDownElementId &&
|
192
|
+
event.target.id !== arrowUpElementId
|
193
|
+
) {
|
177
194
|
setIsDropdownClosed(true)
|
178
195
|
}
|
179
|
-
}
|
196
|
+
};
|
180
197
|
// Attach the event listener
|
181
|
-
window.addEventListener("click", handleClickOutside)
|
198
|
+
window.addEventListener("click", handleClickOutside);
|
182
199
|
// Clean up the event listener on unmount
|
183
200
|
return () => {
|
184
|
-
window.removeEventListener("click", handleClickOutside)
|
185
|
-
}
|
186
|
-
}, [])
|
187
|
-
|
201
|
+
window.removeEventListener("click", handleClickOutside);
|
202
|
+
};
|
203
|
+
}, []);
|
188
204
|
|
205
|
+
useEffect(() => {
|
206
|
+
if (id) {
|
207
|
+
// Attach the clear function to the window, scoped by the id
|
208
|
+
(window as any)[`clearMultiLevelSelect_${id}`] = () => {
|
209
|
+
const resetData = modifyRecursive(formattedData, false);
|
210
|
+
setFormattedData(resetData);
|
211
|
+
setReturnedArray([]);
|
212
|
+
setDefaultReturn([]);
|
213
|
+
setSingleSelectedItem({ id: [], value: "", item: [] });
|
214
|
+
onSelect([]);
|
215
|
+
};
|
216
|
+
return () => {
|
217
|
+
delete (window as any)[`clearMultiLevelSelect_${id}`];
|
218
|
+
};
|
219
|
+
}
|
220
|
+
}, [formattedData, id, onSelect]);
|
189
221
|
|
190
222
|
// Iterate over tree, find item and set checked or unchecked
|
191
223
|
const modifyValue = (
|
@@ -194,234 +226,182 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
194
226
|
check: boolean
|
195
227
|
) => {
|
196
228
|
if (!Array.isArray(tree)) {
|
197
|
-
return
|
229
|
+
return;
|
198
230
|
}
|
199
231
|
return tree.map((item: any) => {
|
200
|
-
if (item.id != id) item.children = modifyValue(id, item.children, check)
|
232
|
+
if (item.id != id) item.children = modifyValue(id, item.children, check);
|
201
233
|
else {
|
202
|
-
item.checked = check
|
234
|
+
item.checked = check;
|
203
235
|
|
204
236
|
if (variant === "single") {
|
205
237
|
// Single select: no children should be checked
|
206
|
-
item.children = modifyRecursive(item.children, !check)
|
238
|
+
item.children = modifyRecursive(item.children, !check);
|
207
239
|
} else {
|
208
|
-
item.children = modifyRecursive(item.children, check)
|
240
|
+
item.children = modifyRecursive(item.children, check);
|
209
241
|
}
|
210
242
|
}
|
211
243
|
|
212
|
-
return item
|
213
|
-
})
|
214
|
-
}
|
244
|
+
return item;
|
245
|
+
});
|
246
|
+
};
|
215
247
|
|
216
248
|
// Clone tree, check items + children
|
217
249
|
const checkItem = (item: { [key: string]: any }) => {
|
218
|
-
const tree = cloneDeep(formattedData)
|
250
|
+
const tree = cloneDeep(formattedData);
|
219
251
|
if (returnAllSelected) {
|
220
|
-
return modifyValue(item.id, tree, true)
|
252
|
+
return modifyValue(item.id, tree, true);
|
221
253
|
} else {
|
222
|
-
const checkedTree = modifyValue(item.id, tree, true)
|
223
|
-
return recursiveCheckParent(item, checkedTree)
|
254
|
+
const checkedTree = modifyValue(item.id, tree, true);
|
255
|
+
return recursiveCheckParent(item, checkedTree);
|
224
256
|
}
|
225
|
-
}
|
257
|
+
};
|
226
258
|
|
227
259
|
// Clone tree, uncheck items + children
|
228
260
|
const unCheckItem = (item: { [key: string]: any }) => {
|
229
|
-
const tree = cloneDeep(formattedData)
|
261
|
+
const tree = cloneDeep(formattedData);
|
230
262
|
if (returnAllSelected) {
|
231
|
-
return modifyValue(item.id, tree, false)
|
263
|
+
return modifyValue(item.id, tree, false);
|
232
264
|
} else {
|
233
|
-
const uncheckedTree = modifyValue(item.id, tree, false)
|
234
|
-
return getAncestorsOfUnchecked(uncheckedTree, item)
|
265
|
+
const uncheckedTree = modifyValue(item.id, tree, false);
|
266
|
+
return getAncestorsOfUnchecked(uncheckedTree, item);
|
235
267
|
}
|
236
|
-
}
|
268
|
+
};
|
237
269
|
|
238
270
|
// setFormattedData with proper properties
|
239
271
|
const changeItem = (item: { [key: string]: any }, check: boolean) => {
|
240
|
-
const tree = check ? checkItem(item) : unCheckItem(item)
|
241
|
-
setFormattedData(tree)
|
242
|
-
|
243
|
-
return tree
|
244
|
-
}
|
272
|
+
const tree = check ? checkItem(item) : unCheckItem(item);
|
273
|
+
setFormattedData(tree);
|
245
274
|
|
246
|
-
|
275
|
+
return tree;
|
276
|
+
};
|
247
277
|
|
248
278
|
// Click event for x on form pill
|
249
279
|
const handlePillClose = (event: any, clickedItem: { [key: string]: any }) => {
|
250
280
|
// Prevents the dropdown from closing when clicking on the pill
|
251
|
-
event.stopPropagation()
|
252
|
-
const updatedTree = changeItem(clickedItem, false)
|
281
|
+
event.stopPropagation();
|
282
|
+
const updatedTree = changeItem(clickedItem, false);
|
253
283
|
// Logic for removing items from returnArray or defaultReturn when pills clicked
|
254
284
|
if (returnAllSelected) {
|
255
|
-
onSelect(getCheckedItems(updatedTree))
|
285
|
+
onSelect(getCheckedItems(updatedTree));
|
256
286
|
} else {
|
257
|
-
onSelect(getDefaultCheckedItems(updatedTree))
|
287
|
+
onSelect(getDefaultCheckedItems(updatedTree));
|
258
288
|
}
|
259
|
-
}
|
289
|
+
};
|
260
290
|
|
261
291
|
// Handle click on input wrapper(entire div with pills, typeahead, etc) so it doesn't close when input or form pill is clicked
|
262
292
|
const handleInputWrapperClick = (e: any) => {
|
263
|
-
e.stopPropagation()
|
264
293
|
if (
|
265
294
|
e.target.id === "multiselect_input" ||
|
266
295
|
e.target.classList.contains("pb_form_pill_tag")
|
267
296
|
) {
|
268
|
-
return
|
297
|
+
return;
|
269
298
|
}
|
270
|
-
setIsDropdownClosed(!isDropdownClosed)
|
271
|
-
}
|
299
|
+
setIsDropdownClosed(!isDropdownClosed);
|
300
|
+
};
|
272
301
|
|
273
302
|
// Main function to handle any click inside dropdown
|
274
303
|
const handledropdownItemClick = (e: any, check: boolean) => {
|
275
|
-
const clickedItem = e.target.parentNode.id
|
304
|
+
const clickedItem = e.target.parentNode.id;
|
276
305
|
// Setting filterItem to "" will clear textinput and clear typeahead
|
277
|
-
setFilterItem("")
|
306
|
+
setFilterItem("");
|
278
307
|
|
279
|
-
const filtered = filterFormattedDataById(formattedData, clickedItem)
|
280
|
-
const updatedTree = changeItem(filtered[0], check)
|
308
|
+
const filtered = filterFormattedDataById(formattedData, clickedItem);
|
309
|
+
const updatedTree = changeItem(filtered[0], check);
|
281
310
|
if (returnAllSelected) {
|
282
|
-
onSelect(getCheckedItems(updatedTree))
|
311
|
+
onSelect(getCheckedItems(updatedTree));
|
283
312
|
} else {
|
284
|
-
onSelect(getDefaultCheckedItems(updatedTree))
|
313
|
+
onSelect(getDefaultCheckedItems(updatedTree));
|
285
314
|
}
|
286
|
-
}
|
315
|
+
};
|
287
316
|
|
288
317
|
// Single select
|
289
|
-
const handleRadioButtonClick = (
|
290
|
-
|
291
|
-
) => {
|
292
|
-
const { id, value: inputText } = e.target
|
318
|
+
const handleRadioButtonClick = (e: React.ChangeEvent<HTMLInputElement>) => {
|
319
|
+
const { id, value: inputText } = e.target;
|
293
320
|
// The radio button needs a unique ID, this grabs the ID before the hyphen
|
294
|
-
const selectedItemID = id.match(/^[^-]*/)[0]
|
321
|
+
const selectedItemID = id.match(/^[^-]*/)[0];
|
295
322
|
// Reset tree checked state, triggering useEffect
|
296
|
-
const treeWithNoSelections = modifyRecursive(formattedData, false)
|
323
|
+
const treeWithNoSelections = modifyRecursive(formattedData, false);
|
297
324
|
// Update tree with single selection
|
298
|
-
const treeWithSelectedItem = modifyValue(
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
325
|
+
const treeWithSelectedItem = modifyValue(
|
326
|
+
selectedItemID,
|
327
|
+
treeWithNoSelections,
|
328
|
+
true
|
329
|
+
);
|
330
|
+
const selectedItem = filterFormattedDataById(
|
331
|
+
treeWithSelectedItem,
|
332
|
+
selectedItemID
|
333
|
+
);
|
334
|
+
|
335
|
+
setFormattedData(treeWithSelectedItem);
|
336
|
+
setSingleSelectedItem({
|
337
|
+
id: [selectedItemID],
|
338
|
+
value: inputText,
|
339
|
+
item: selectedItem,
|
340
|
+
});
|
303
341
|
// Reset the filter to always display dropdown options on click
|
304
|
-
setFilterItem("")
|
305
|
-
setIsDropdownClosed(true)
|
342
|
+
setFilterItem("");
|
343
|
+
setIsDropdownClosed(true);
|
306
344
|
|
307
|
-
onSelect(selectedItem)
|
345
|
+
onSelect(selectedItem);
|
308
346
|
};
|
309
347
|
|
310
348
|
// Single select: reset the tree state upon typing
|
311
349
|
const handleRadioInputChange = (inputText: string) => {
|
312
|
-
modifyRecursive(formattedData, false)
|
313
|
-
setDefaultReturn([])
|
314
|
-
setSingleSelectedItem({id: [], value: inputText, item: []})
|
315
|
-
setFilterItem(inputText)
|
350
|
+
modifyRecursive(formattedData, false);
|
351
|
+
setDefaultReturn([]);
|
352
|
+
setSingleSelectedItem({ id: [], value: inputText, item: [] });
|
353
|
+
setFilterItem(inputText);
|
316
354
|
};
|
317
355
|
|
318
|
-
const isTreeRowExpanded = (item: any) => expanded.indexOf(item.id) > -1
|
356
|
+
const isTreeRowExpanded = (item: any) => expanded.indexOf(item.id) > -1;
|
319
357
|
|
320
358
|
// Handle click on chevron toggles in dropdown
|
321
359
|
const handleToggleClick = (id: string, event: React.MouseEvent) => {
|
322
|
-
event.stopPropagation()
|
323
|
-
const clickedItem = filterFormattedDataById(formattedData, id)
|
360
|
+
event.stopPropagation();
|
361
|
+
const clickedItem = filterFormattedDataById(formattedData, id);
|
324
362
|
if (clickedItem) {
|
325
|
-
let expandedArray = [...expanded]
|
326
|
-
const itemExpanded = isTreeRowExpanded(clickedItem[0])
|
363
|
+
let expandedArray = [...expanded];
|
364
|
+
const itemExpanded = isTreeRowExpanded(clickedItem[0]);
|
327
365
|
|
328
366
|
if (itemExpanded)
|
329
|
-
expandedArray = expandedArray.filter((i) => i != clickedItem[0].id)
|
330
|
-
else expandedArray.push(clickedItem[0].id)
|
367
|
+
expandedArray = expandedArray.filter((i) => i != clickedItem[0].id);
|
368
|
+
else expandedArray.push(clickedItem[0].id);
|
331
369
|
|
332
|
-
setExpanded(expandedArray)
|
370
|
+
setExpanded(expandedArray);
|
333
371
|
}
|
334
|
-
}
|
372
|
+
};
|
335
373
|
|
336
374
|
const itemsSelectedLength = () => {
|
337
|
-
let items
|
375
|
+
let items;
|
338
376
|
if (returnAllSelected && returnedArray && returnedArray.length) {
|
339
|
-
items = returnedArray.length
|
377
|
+
items = returnedArray.length;
|
340
378
|
} else if (!returnAllSelected && defaultReturn && defaultReturn.length) {
|
341
|
-
items = defaultReturn.length
|
379
|
+
items = defaultReturn.length;
|
342
380
|
}
|
343
|
-
return items
|
344
|
-
}
|
381
|
+
return items;
|
382
|
+
};
|
345
383
|
|
346
384
|
// Rendering formattedData to UI based on typeahead
|
347
|
-
const renderNestedOptions = (items: { [key: string]:
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
: "toggle_icon"
|
368
|
-
}
|
369
|
-
icon={
|
370
|
-
isTreeRowExpanded(item) ? "chevron-down" : "chevron-right"
|
371
|
-
}
|
372
|
-
onClick={(event: any) =>
|
373
|
-
handleToggleClick(item.id, event)
|
374
|
-
}
|
375
|
-
variant="link"
|
376
|
-
/>
|
377
|
-
</div>
|
378
|
-
}
|
379
|
-
{ variant === "single" ? (
|
380
|
-
item.hideRadio ? (
|
381
|
-
<Body>{item.label}</Body>
|
382
|
-
) :
|
383
|
-
<Radio
|
384
|
-
checked={item.checked}
|
385
|
-
id={`${item.id}-${item.label}`}
|
386
|
-
label={item.label}
|
387
|
-
name={inputName}
|
388
|
-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => (
|
389
|
-
handleRadioButtonClick(e)
|
390
|
-
)}
|
391
|
-
padding={item.children ? 'none' : 'xs'}
|
392
|
-
type="radio"
|
393
|
-
value={item.label}
|
394
|
-
/>
|
395
|
-
) : (
|
396
|
-
<Checkbox
|
397
|
-
id={item.id}
|
398
|
-
text={item.label}
|
399
|
-
>
|
400
|
-
<input
|
401
|
-
checked={item.checked}
|
402
|
-
name={item.label}
|
403
|
-
onChange={(e) => {
|
404
|
-
handledropdownItemClick(e, !item.checked)
|
405
|
-
}}
|
406
|
-
type="checkbox"
|
407
|
-
value={item.label}
|
408
|
-
/>
|
409
|
-
</Checkbox>
|
410
|
-
)}
|
411
|
-
</div>
|
412
|
-
{isTreeRowExpanded(item) &&
|
413
|
-
item.children &&
|
414
|
-
item.children.length > 0 &&
|
415
|
-
(variant === "single" || !filterItem) && ( // Show children if expanded is true
|
416
|
-
<div>{renderNestedOptions(item.children)}</div>
|
417
|
-
)}
|
418
|
-
</li>
|
419
|
-
</div>
|
420
|
-
)
|
421
|
-
})}
|
422
|
-
</ul>
|
423
|
-
)
|
424
|
-
}
|
385
|
+
const renderNestedOptions = (items: { [key: string]: string; }[] | any ) => {
|
386
|
+
const hasOptionsChild = React.Children.toArray(props.children).some(
|
387
|
+
(child: any) => child.type === MultiLevelSelect.Options
|
388
|
+
);
|
389
|
+
|
390
|
+
if (hasOptionsChild) {
|
391
|
+
return React.Children.map(props.children, (child) => {
|
392
|
+
if (child.type === MultiLevelSelect.Options) {
|
393
|
+
return React.cloneElement(child, { items });
|
394
|
+
}
|
395
|
+
return null;
|
396
|
+
});
|
397
|
+
} else {
|
398
|
+
// If no children, use the default rendering
|
399
|
+
return (
|
400
|
+
<MultiLevelSelectOptions items={items} />
|
401
|
+
);
|
402
|
+
}
|
403
|
+
};
|
404
|
+
|
425
405
|
|
426
406
|
return (
|
427
407
|
<div
|
@@ -431,12 +411,20 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
431
411
|
className={classes}
|
432
412
|
id={id}
|
433
413
|
>
|
434
|
-
<
|
435
|
-
|
414
|
+
<MultiLevelSelectContext.Provider value={{
|
415
|
+
variant,
|
416
|
+
inputName,
|
417
|
+
renderNestedOptions,
|
418
|
+
isTreeRowExpanded,
|
419
|
+
handleToggleClick,
|
420
|
+
handleRadioButtonClick,
|
421
|
+
handledropdownItemClick,
|
422
|
+
filterItem,
|
423
|
+
}}>
|
424
|
+
<div className="wrapper"
|
436
425
|
ref={dropdownRef}
|
437
426
|
>
|
438
|
-
<div
|
439
|
-
className="input_wrapper"
|
427
|
+
<div className="input_wrapper"
|
440
428
|
onClick={handleInputWrapperClick}
|
441
429
|
>
|
442
430
|
<div className="input_inner_container">
|
@@ -502,15 +490,17 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
502
490
|
|
503
491
|
<input
|
504
492
|
id="multiselect_input"
|
505
|
-
onChange={(e) =>{
|
493
|
+
onChange={(e) => {
|
506
494
|
variant === "single"
|
507
495
|
? handleRadioInputChange(e.target.value)
|
508
|
-
: setFilterItem(e.target.value)
|
496
|
+
: setFilterItem(e.target.value);
|
509
497
|
}}
|
510
498
|
onClick={() => setIsDropdownClosed(false)}
|
511
499
|
placeholder={
|
512
500
|
inputDisplay === "none" && itemsSelectedLength()
|
513
|
-
? `${itemsSelectedLength()} ${
|
501
|
+
? `${itemsSelectedLength()} ${
|
502
|
+
itemsSelectedLength() === 1 ? "item" : "items"
|
503
|
+
} selected`
|
514
504
|
: "Start typing..."
|
515
505
|
}
|
516
506
|
value={singleSelectedItem.value || filterItem}
|
@@ -518,16 +508,20 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
518
508
|
</div>
|
519
509
|
|
520
510
|
{isDropdownClosed ? (
|
521
|
-
<div
|
511
|
+
<div id={arrowDownElementId}
|
512
|
+
key="chevron-down">
|
522
513
|
<Icon
|
523
514
|
icon="chevron-down"
|
515
|
+
id={arrowDownElementId}
|
524
516
|
size="xs"
|
525
517
|
/>
|
526
518
|
</div>
|
527
519
|
) : (
|
528
|
-
<div
|
520
|
+
<div id={arrowUpElementId}
|
521
|
+
key="chevron-up">
|
529
522
|
<Icon
|
530
523
|
icon="chevron-up"
|
524
|
+
id={arrowUpElementId}
|
531
525
|
size="xs"
|
532
526
|
/>
|
533
527
|
</div>
|
@@ -535,15 +529,16 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
535
529
|
</div>
|
536
530
|
|
537
531
|
<div className={`dropdown_menu ${isDropdownClosed ? "close" : "open"}`}>
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
: formattedData
|
542
|
-
)}
|
532
|
+
{renderNestedOptions(
|
533
|
+
filterItem ? findByFilter(formattedData, filterItem) : formattedData
|
534
|
+
)}
|
543
535
|
</div>
|
544
536
|
</div>
|
537
|
+
</MultiLevelSelectContext.Provider>
|
545
538
|
</div>
|
546
|
-
)
|
547
|
-
}
|
539
|
+
);
|
540
|
+
};
|
541
|
+
|
542
|
+
MultiLevelSelect.Options = MultiLevelSelectOptions;
|
548
543
|
|
549
|
-
export default MultiLevelSelect
|
544
|
+
export default MultiLevelSelect;
|