playbook_ui 12.25.0.pre.alpha.railsmultilevelimprovements785 → 12.25.0
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 +0 -1
- data/app/pb_kits/playbook/index.js +0 -1
- data/app/pb_kits/playbook/pb_avatar/docs/_avatar_swift.md +1 -82
- data/app/pb_kits/playbook/pb_docs/kit_example.html.erb +13 -14
- data/app/pb_kits/playbook/pb_form_pill/_form_pill.tsx +2 -3
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +98 -58
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +86 -356
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_select_helper.tsx +31 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.md +1 -1
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_return_all_selected.html.erb +1 -1
- data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +0 -1
- data/app/pb_kits/playbook/pb_multi_level_select/helper_functions.ts +87 -0
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +0 -3
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +1 -1
- data/app/pb_kits/playbook/playbook-doc.js +0 -2
- data/dist/menu.yml +0 -1
- data/dist/playbook-rails.js +7 -7
- data/lib/playbook/forms/builder.rb +0 -1
- data/lib/playbook/version.rb +2 -2
- metadata +9 -29
- data/app/pb_kits/playbook/pb_detail/_detail.scss +0 -44
- data/app/pb_kits/playbook/pb_detail/_detail.tsx +0 -55
- data/app/pb_kits/playbook/pb_detail/_detail_mixins.scss +0 -29
- data/app/pb_kits/playbook/pb_detail/detail.html.erb +0 -7
- data/app/pb_kits/playbook/pb_detail/detail.rb +0 -31
- data/app/pb_kits/playbook/pb_detail/detail.test.jsx +0 -46
- data/app/pb_kits/playbook/pb_detail/docs/_description.md +0 -1
- data/app/pb_kits/playbook/pb_detail/docs/_detail_bold.html.erb +0 -34
- data/app/pb_kits/playbook/pb_detail/docs/_detail_bold.jsx +0 -49
- data/app/pb_kits/playbook/pb_detail/docs/_detail_bold.md +0 -1
- data/app/pb_kits/playbook/pb_detail/docs/_detail_colors.html.erb +0 -24
- data/app/pb_kits/playbook/pb_detail/docs/_detail_colors.jsx +0 -38
- data/app/pb_kits/playbook/pb_detail/docs/_detail_colors.md +0 -6
- data/app/pb_kits/playbook/pb_detail/docs/_detail_default.html.erb +0 -3
- data/app/pb_kits/playbook/pb_detail/docs/_detail_default.jsx +0 -13
- data/app/pb_kits/playbook/pb_detail/docs/_detail_styled.html.erb +0 -22
- data/app/pb_kits/playbook/pb_detail/docs/_detail_styled.jsx +0 -32
- data/app/pb_kits/playbook/pb_detail/docs/example.yml +0 -11
- data/app/pb_kits/playbook/pb_detail/docs/index.js +0 -4
- data/app/pb_kits/playbook/pb_multi_level_select/_helper_functions.tsx +0 -212
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_form.html.erb +0 -72
- data/lib/playbook/forms/builder/multi_level_select_field.rb +0 -12
@@ -1,35 +1,19 @@
|
|
1
|
-
import React, { useState, useEffect,
|
1
|
+
import React, { useState, useEffect, useMemo } from "react";
|
2
2
|
import classnames from "classnames";
|
3
3
|
import { buildAriaProps, buildCss, buildDataProps } from "../utilities/props";
|
4
|
-
import { globalProps
|
5
|
-
import
|
6
|
-
import
|
7
|
-
import FormPill from "../pb_form_pill/_form_pill";
|
8
|
-
import CircleIconButton from "../pb_circle_icon_button/_circle_icon_button";
|
9
|
-
import {
|
10
|
-
unCheckIt,
|
11
|
-
getAncestorsOfUnchecked,
|
12
|
-
unCheckedRecursive,
|
13
|
-
checkedRecursive,
|
14
|
-
filterFormattedDataById,
|
15
|
-
findByFilter,
|
16
|
-
getCheckedItems,
|
17
|
-
updateReturnItems,
|
18
|
-
recursiveReturnOnlyParent,
|
19
|
-
removeChildrenIfParentChecked,
|
20
|
-
getChildIds,
|
21
|
-
} from "./_helper_functions";
|
4
|
+
import { globalProps } from "../utilities/globalProps";
|
5
|
+
import { findItemById, checkIt, unCheckIt, getParentAndAncestorsIds } from "./helper_functions";
|
6
|
+
import MultiSelectHelper from "./_multi_select_helper";
|
22
7
|
|
23
8
|
type MultiLevelSelectProps = {
|
24
9
|
aria?: { [key: string]: string };
|
25
10
|
className?: string;
|
26
11
|
data?: { [key: string]: string };
|
27
12
|
id?: string;
|
28
|
-
name?: string;
|
29
13
|
returnAllSelected?: boolean;
|
30
14
|
treeData?: { [key: string]: string }[];
|
31
15
|
onSelect?: (prop: { [key: string]: any }) => void;
|
32
|
-
}
|
16
|
+
};
|
33
17
|
|
34
18
|
const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
35
19
|
const {
|
@@ -37,7 +21,6 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
37
21
|
className,
|
38
22
|
data = {},
|
39
23
|
id,
|
40
|
-
name,
|
41
24
|
returnAllSelected = false,
|
42
25
|
treeData,
|
43
26
|
onSelect = () => {},
|
@@ -51,357 +34,104 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
51
34
|
className
|
52
35
|
);
|
53
36
|
|
54
|
-
const dropdownRef = useRef(null);
|
55
|
-
|
56
|
-
//state for whether dropdown is open or closed
|
57
|
-
const [isClosed, setIsClosed] = useState(true);
|
58
|
-
//state from onchange for textinput, to use for filtering to create typeahead
|
59
|
-
const [filterItem, setFilterItem] = useState("");
|
60
|
-
//this is essentially the return that the user will get when they use the kit
|
61
|
-
const [returnedArray, setReturnedArray] = useState([]);
|
62
|
-
//formattedData with checked and parent_id added
|
63
37
|
const [formattedData, setFormattedData] = useState(treeData);
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
if (el) {
|
73
|
-
el.setAttribute(
|
74
|
-
"data-tree",
|
75
|
-
JSON.stringify(returnAllSelected ? returnedArray : defaultReturn)
|
76
|
-
);
|
77
|
-
}
|
78
|
-
|
79
|
-
const updateHiddenInputValue = (value: any) => {
|
80
|
-
console.log("value", value)
|
81
|
-
const hiddenInput = document.querySelector(
|
82
|
-
"input#" + id
|
83
|
-
) as HTMLInputElement
|
84
|
-
console.log("hiddenInput", hiddenInput)
|
85
|
-
if (hiddenInput) {
|
86
|
-
hiddenInput.value = JSON.stringify(value)
|
87
|
-
}
|
88
|
-
}
|
89
|
-
|
90
|
-
updateHiddenInputValue(returnAllSelected ? returnedArray : defaultReturn);
|
91
|
-
returnAllSelected
|
92
|
-
? onSelect(returnedArray)
|
93
|
-
: onSelect(
|
94
|
-
defaultReturn.filter(
|
95
|
-
(item, index, self) =>
|
96
|
-
index === self.findIndex((obj) => obj.id === item.id)
|
97
|
-
)
|
98
|
-
);
|
99
|
-
}, [returnedArray, defaultReturn]);
|
100
|
-
|
101
|
-
useEffect(() => {
|
102
|
-
//Create new formattedData array for use
|
103
|
-
setFormattedData(addCheckedAndParentProperty(treeData));
|
104
|
-
// Function to handle clicks outside the dropdown
|
105
|
-
const handleClickOutside = (event: any) => {
|
106
|
-
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
107
|
-
setIsClosed(true);
|
108
|
-
}
|
109
|
-
};
|
110
|
-
//if any items already checked in first render, set return accordingly
|
111
|
-
const initialChecked = getCheckedItems(treeData)
|
112
|
-
initialChecked && returnAllSelected && setReturnedArray(initialChecked)
|
113
|
-
initialChecked && !returnAllSelected && setDefaultReturn(initialChecked)
|
114
|
-
|
115
|
-
// Attach the event listener
|
116
|
-
window.addEventListener("click", handleClickOutside);
|
117
|
-
// Clean up the event listener on unmount
|
118
|
-
return () => {
|
119
|
-
window.removeEventListener("click", handleClickOutside);
|
120
|
-
};
|
121
|
-
}, []);
|
122
|
-
|
123
|
-
//function to map over data and add parent_id + depth property to each item
|
124
|
-
const addCheckedAndParentProperty = (
|
125
|
-
treeData: { [key: string]: any }[],
|
126
|
-
parent_id: string = null,
|
127
|
-
depth: number = 0,
|
128
|
-
) => {
|
129
|
-
if (!Array.isArray(treeData)) {
|
130
|
-
return;
|
131
|
-
}
|
132
|
-
return treeData.map((item: { [key: string]: any } | any) => {
|
133
|
-
const newItem = {
|
134
|
-
...item,
|
135
|
-
parent_id,
|
136
|
-
depth,
|
137
|
-
};
|
138
|
-
if (newItem.children && newItem.children.length > 0) {
|
139
|
-
newItem.children = addCheckedAndParentProperty(
|
140
|
-
newItem.children,
|
141
|
-
newItem.id,
|
142
|
-
depth + 1,
|
143
|
-
);
|
144
|
-
}
|
145
|
-
return newItem;
|
146
|
-
});
|
147
|
-
};
|
148
|
-
|
149
|
-
//click event for x on form pill
|
150
|
-
const handlePillClose = (event: any, clickedItem: { [key: string]: any }) => {
|
151
|
-
// prevents the dropdown from closing when clicking on the pill
|
152
|
-
event.stopPropagation();
|
153
|
-
//logic for removing items from returnArray or defaultReturn when pills clicked
|
154
|
-
if (returnAllSelected) {
|
155
|
-
if (returnedArray.includes(clickedItem)) {
|
156
|
-
if (clickedItem.children && clickedItem.children.length > 0) {
|
157
|
-
const childrenOfChecked = getChildIds(clickedItem, returnedArray);
|
158
|
-
const updatedFiltered = returnedArray
|
159
|
-
.filter((item) => item !== clickedItem)
|
160
|
-
.filter((item) => !childrenOfChecked.includes(item.id));
|
161
|
-
setReturnedArray(updatedFiltered);
|
38
|
+
const [selectedItems, setSelectedItems] = useState([]);
|
39
|
+
const [checkedData, setCheckedData] = useState([]);
|
40
|
+
|
41
|
+
const onChange = (currentNode: { [key: string]: any }) => {
|
42
|
+
const updatedData = formattedData.map((item: any) => {
|
43
|
+
if (item.id === currentNode._id) {
|
44
|
+
if (currentNode.checked) {
|
45
|
+
checkIt(item, selectedItems, setSelectedItems, false);
|
162
46
|
} else {
|
163
|
-
|
164
|
-
(item) => item !== clickedItem
|
165
|
-
);
|
166
|
-
setReturnedArray(updatedFiltered);
|
47
|
+
unCheckIt(item, selectedItems, setSelectedItems, false);
|
167
48
|
}
|
168
|
-
}
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
e.target.id === "multiselect_input" ||
|
191
|
-
e.target.classList.contains("pb_form_pill_tag")
|
192
|
-
) {
|
193
|
-
return;
|
194
|
-
}
|
195
|
-
setIsClosed(!isClosed);
|
196
|
-
};
|
197
|
-
|
198
|
-
//Main function to handle any click inside dropdown
|
199
|
-
const handledropdownItemClick = (e: any) => {
|
200
|
-
const clickedItem = e.target.parentNode.id;
|
201
|
-
//setting filterItem to "" will clear textinput and clear typeahead
|
202
|
-
setFilterItem("");
|
203
|
-
|
204
|
-
const filtered = filterFormattedDataById(formattedData, clickedItem);
|
205
|
-
//check and uncheck all children of checked/unchecked parent item
|
206
|
-
if (filtered[0].children && filtered[0].children.length > 0) {
|
207
|
-
if (filtered[0].checked) {
|
208
|
-
filtered[0].children.forEach((item: { [key: string]: any }) => {
|
209
|
-
checkedRecursive(item);
|
210
|
-
});
|
211
|
-
} else if (!filtered[0].checked) {
|
212
|
-
filtered[0].children.forEach((item: { [key: string]: any }) => {
|
213
|
-
unCheckedRecursive(item);
|
214
|
-
});
|
215
|
-
}
|
216
|
-
}
|
217
|
-
|
218
|
-
const checkedItems = getCheckedItems(formattedData);
|
219
|
-
|
220
|
-
//checking and unchecking items for returnAllSelected variant
|
221
|
-
if (returnedArray.includes(filtered[0])) {
|
222
|
-
if (!filtered[0].checked) {
|
223
|
-
if (filtered[0].children && filtered[0].children.length > 0) {
|
224
|
-
const childrenOfChecked = getChildIds(filtered[0], returnedArray);
|
225
|
-
const updatedFiltered = returnedArray
|
226
|
-
.filter((item) => item !== filtered[0])
|
227
|
-
.filter((item) => !childrenOfChecked.includes(item.id));
|
228
|
-
|
229
|
-
setReturnedArray(updatedFiltered);
|
230
|
-
} else {
|
231
|
-
const updatedFiltered = returnedArray.filter(
|
232
|
-
(item) => item !== filtered[0]
|
233
|
-
);
|
234
|
-
setReturnedArray(updatedFiltered);
|
49
|
+
} else if (item.children) {
|
50
|
+
const foundItem = findItemById(item.children, currentNode._id);
|
51
|
+
if (foundItem) {
|
52
|
+
if (currentNode.checked) {
|
53
|
+
checkIt(foundItem, selectedItems, setSelectedItems, false);
|
54
|
+
if (currentNode._parent) {
|
55
|
+
const parents = getParentAndAncestorsIds(currentNode._parent, formattedData)
|
56
|
+
parents.forEach((item:string) => {
|
57
|
+
const ancestor = findItemById(formattedData,item)
|
58
|
+
ancestor.expanded = true
|
59
|
+
});
|
60
|
+
}
|
61
|
+
} else {
|
62
|
+
unCheckIt(foundItem, selectedItems, setSelectedItems, false);
|
63
|
+
if (currentNode._parent) {
|
64
|
+
const parents = getParentAndAncestorsIds(currentNode._parent, formattedData)
|
65
|
+
parents.forEach((item:string) => {
|
66
|
+
const ancestor = findItemById(formattedData,item)
|
67
|
+
ancestor.expanded = true
|
68
|
+
});
|
69
|
+
}
|
70
|
+
}
|
235
71
|
}
|
236
72
|
}
|
237
|
-
} else {
|
238
|
-
setReturnedArray(checkedItems);
|
239
|
-
}
|
240
73
|
|
241
|
-
|
242
|
-
|
243
|
-
//uncheck parent and grandparent if any child unchecked
|
244
|
-
getAncestorsOfUnchecked(formattedData, filtered[0]);
|
245
|
-
|
246
|
-
const newChecked = getCheckedItems(formattedData);
|
247
|
-
//get all checked items, and filter to check if all children checked, if yes return only parent
|
248
|
-
const filteredReturn = updateReturnItems(newChecked);
|
249
|
-
setDefaultReturn(filteredReturn);
|
250
|
-
}
|
251
|
-
|
252
|
-
//when item is checked for default variant
|
253
|
-
if (!returnAllSelected && filtered[0].checked) {
|
254
|
-
//if checked item has children
|
255
|
-
if (filtered[0].children && filtered[0].children.length > 0) {
|
256
|
-
removeChildrenIfParentChecked(
|
257
|
-
filtered[0],
|
258
|
-
defaultReturn,
|
259
|
-
setDefaultReturn
|
260
|
-
);
|
261
|
-
}
|
74
|
+
return item;
|
75
|
+
});
|
262
76
|
|
263
|
-
|
264
|
-
if (filtered[0].parent_id !== null) {
|
265
|
-
recursiveReturnOnlyParent(
|
266
|
-
filtered[0],
|
267
|
-
formattedData,
|
268
|
-
defaultReturn,
|
269
|
-
setDefaultReturn
|
270
|
-
);
|
271
|
-
} else {
|
272
|
-
setDefaultReturn([filtered[0]]);
|
273
|
-
}
|
274
|
-
}
|
77
|
+
setFormattedData(updatedData);
|
275
78
|
};
|
276
79
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
80
|
+
useEffect(() => {
|
81
|
+
if (returnAllSelected) {
|
82
|
+
const selected = selectedItems.filter(
|
83
|
+
(item: { [key: string]: any }) => item.checked
|
84
|
+
);
|
85
|
+
//filter to remove duplicate items
|
86
|
+
const uniqueSelected = selected.filter(
|
87
|
+
(obj, index, self) => index === self.findIndex((t) => t.id === obj.id)
|
88
|
+
);
|
89
|
+
setCheckedData(uniqueSelected);
|
90
|
+
}
|
91
|
+
}, [selectedItems]);
|
285
92
|
|
286
|
-
|
287
|
-
|
93
|
+
useEffect(() => {
|
94
|
+
let el = document.getElementById(`pb_data_wrapper_${id}`);
|
95
|
+
if (el) {
|
96
|
+
el.setAttribute("data-tree", JSON.stringify(checkedData));
|
288
97
|
}
|
289
|
-
|
98
|
+
if (returnAllSelected) {
|
99
|
+
onSelect(checkedData);
|
100
|
+
}
|
101
|
+
}, [checkedData]);
|
290
102
|
|
291
|
-
|
292
|
-
const renderNestedOptions = (items: { [key: string]: any }[]) => {
|
103
|
+
const DropDownSelectComponent = useMemo(() => {
|
293
104
|
return (
|
294
|
-
<
|
295
|
-
{
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
}
|
309
|
-
>
|
310
|
-
<CircleIconButton
|
311
|
-
icon={
|
312
|
-
item.expanded ? "chevron-down" : "chevron-right"
|
313
|
-
}
|
314
|
-
className={item.children && item.children.length > 0 ? "" : "toggle_icon"}
|
315
|
-
onClick={(event) => handleToggleClick(item.id, event)}
|
316
|
-
variant="link"
|
317
|
-
/>
|
318
|
-
</div>
|
319
|
-
<Checkbox text={item.label} id={item.id}>
|
320
|
-
<input
|
321
|
-
checked={item.checked}
|
322
|
-
type="checkbox"
|
323
|
-
name={item.label}
|
324
|
-
value={item.label}
|
325
|
-
onChange={(e) => {
|
326
|
-
item.checked = !item.checked;
|
327
|
-
handledropdownItemClick(e);
|
328
|
-
}}
|
329
|
-
/>
|
330
|
-
</Checkbox>
|
331
|
-
</div>
|
332
|
-
{item.expanded &&
|
333
|
-
item.children &&
|
334
|
-
item.children.length > 0 &&
|
335
|
-
!filterItem && ( // Show children if expanded is true
|
336
|
-
<div>{renderNestedOptions(item.children)}</div>
|
337
|
-
)}
|
338
|
-
</li>
|
339
|
-
</>
|
340
|
-
);
|
341
|
-
})}
|
342
|
-
</ul>
|
105
|
+
<MultiSelectHelper
|
106
|
+
treeData={formattedData}
|
107
|
+
onChange={(
|
108
|
+
// @ts-ignore
|
109
|
+
selectedNodes: { [key: string]: any }[],
|
110
|
+
currentNode: { [key: string]: any }[]
|
111
|
+
) => {
|
112
|
+
setCheckedData(currentNode);
|
113
|
+
onSelect(currentNode);
|
114
|
+
|
115
|
+
}}
|
116
|
+
id={id}
|
117
|
+
{...props}
|
118
|
+
/>
|
343
119
|
);
|
344
|
-
}
|
120
|
+
}, [formattedData])
|
345
121
|
|
346
122
|
return (
|
347
123
|
<div {...ariaProps} {...dataProps} className={classes} id={id}>
|
348
|
-
|
349
|
-
<
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
/>
|
360
|
-
))
|
361
|
-
: null}
|
362
|
-
{!returnAllSelected &&
|
363
|
-
defaultReturn.length !== 0 &&
|
364
|
-
defaultReturn
|
365
|
-
.filter(
|
366
|
-
(item, index, self) =>
|
367
|
-
index === self.findIndex((obj) => obj.id === item.id)
|
368
|
-
)
|
369
|
-
.map((item, index) => (
|
370
|
-
<FormPill
|
371
|
-
key={index}
|
372
|
-
text={item.label}
|
373
|
-
size="small"
|
374
|
-
onClick={(event) => handlePillClose(event, item)}
|
375
|
-
/>
|
376
|
-
))}
|
377
|
-
{returnedArray.length !== 0 && returnAllSelected && <br />}
|
378
|
-
{defaultReturn.length !== 0 && !returnAllSelected && <br />}
|
379
|
-
<input
|
380
|
-
id="multiselect_input"
|
381
|
-
onChange={(e) => {
|
382
|
-
setFilterItem(e.target.value);
|
383
|
-
}}
|
384
|
-
placeholder="Start typing..."
|
385
|
-
value={filterItem}
|
386
|
-
onClick={() => setIsClosed(false)}
|
387
|
-
/>
|
388
|
-
</div>
|
389
|
-
{isClosed ? (
|
390
|
-
<div key="chevron-down">
|
391
|
-
<Icon icon="chevron-down" />
|
392
|
-
</div>
|
393
|
-
) : (
|
394
|
-
<div key="chevron-up">
|
395
|
-
<Icon icon="chevron-up" />
|
396
|
-
</div>
|
397
|
-
)}
|
398
|
-
</div>
|
399
|
-
<div className={`dropdown_menu ${isClosed ? "close" : "open"}`}>
|
400
|
-
{renderNestedOptions(
|
401
|
-
filterItem ? findByFilter(formattedData, filterItem) : formattedData
|
402
|
-
)}
|
403
|
-
</div>
|
404
|
-
</div>
|
124
|
+
{returnAllSelected ? (
|
125
|
+
<MultiSelectHelper
|
126
|
+
treeData={formattedData}
|
127
|
+
treeMode={returnAllSelected}
|
128
|
+
id={id}
|
129
|
+
onChange={onChange}
|
130
|
+
{...props}
|
131
|
+
/>
|
132
|
+
) : (
|
133
|
+
<>{DropDownSelectComponent}</>
|
134
|
+
)}
|
405
135
|
</div>
|
406
136
|
);
|
407
137
|
};
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import React from "react"
|
2
|
+
import DropdownTreeSelect from "react-dropdown-tree-select"
|
3
|
+
import "react-dropdown-tree-select/dist/styles.css"
|
4
|
+
|
5
|
+
type HelperProps = {
|
6
|
+
id?: string
|
7
|
+
treeData?: { [key: string]: string }[]
|
8
|
+
treeMode?: boolean
|
9
|
+
onChange?: any
|
10
|
+
|
11
|
+
}
|
12
|
+
|
13
|
+
const MultiSelectHelper = (props: HelperProps) => {
|
14
|
+
const { id, treeData, onChange, treeMode } = props
|
15
|
+
|
16
|
+
|
17
|
+
return (
|
18
|
+
<DropdownTreeSelect
|
19
|
+
data={treeData}
|
20
|
+
id={id}
|
21
|
+
keepOpenOnSelect
|
22
|
+
keepTreeOnSearch
|
23
|
+
keepChildrenOnSearch
|
24
|
+
onChange={onChange}
|
25
|
+
texts={{ placeholder: "Select..." }}
|
26
|
+
mode={treeMode ? 'hierarchical' : 'multiSelect'}
|
27
|
+
/>
|
28
|
+
)
|
29
|
+
}
|
30
|
+
|
31
|
+
export default MultiSelectHelper
|
@@ -2,4 +2,4 @@ The MultiLevelSelect kit renders a multi leveled select dropdown based on data f
|
|
2
2
|
|
3
3
|
For the React version of the kit, the `onSelect` prop returns an array of all checked items, irrespective of whether it is a parent, child or grandchild. Open the console on this example and check and uncheck checkboxes to see this is action!
|
4
4
|
|
5
|
-
For the Rails version, the array of checked items is attached to the DOM in a data attribute titled `data-tree` on the wrapping div around
|
5
|
+
For the Rails version, the array of checked items is attached to the DOM in a data attribute titled `data-tree` on the wrapping div around the MultiLevelSelect.
|
@@ -3,7 +3,6 @@
|
|
3
3
|
value: "Power Home Remodeling",
|
4
4
|
id: "powerhome1",
|
5
5
|
expanded: true,
|
6
|
-
|
7
6
|
children: [
|
8
7
|
{
|
9
8
|
label: "People",
|
@@ -64,6 +63,7 @@
|
|
64
63
|
],
|
65
64
|
}] %>
|
66
65
|
|
66
|
+
|
67
67
|
<%= pb_rails("multi_level_select", props: {
|
68
68
|
id: "parent-persistence-multi-level-select",
|
69
69
|
tree_data:treeData,
|
@@ -0,0 +1,87 @@
|
|
1
|
+
export const findItemById = (
|
2
|
+
items: { [key: string]: any }[],
|
3
|
+
id: string
|
4
|
+
): any => {
|
5
|
+
for (const item of items) {
|
6
|
+
if (item.id === id) {
|
7
|
+
return item;
|
8
|
+
}
|
9
|
+
if (item.children) {
|
10
|
+
const found = findItemById(item.children, id);
|
11
|
+
if (found) {
|
12
|
+
return found;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
}
|
16
|
+
return null;
|
17
|
+
};
|
18
|
+
|
19
|
+
export const checkIt = (
|
20
|
+
foundItem: { [key: string]: any },
|
21
|
+
selectedItems: any[],
|
22
|
+
setSelectedItems: Function,
|
23
|
+
expand: boolean
|
24
|
+
) => {
|
25
|
+
if (!foundItem) {
|
26
|
+
return;
|
27
|
+
}
|
28
|
+
|
29
|
+
foundItem.checked = true;
|
30
|
+
foundItem.expanded = expand;
|
31
|
+
selectedItems.push(foundItem);
|
32
|
+
|
33
|
+
if (foundItem.children) {
|
34
|
+
foundItem.children.map((x: any) => {
|
35
|
+
checkIt(x, selectedItems, setSelectedItems, expand);
|
36
|
+
});
|
37
|
+
}
|
38
|
+
|
39
|
+
setSelectedItems([...selectedItems]);
|
40
|
+
};
|
41
|
+
|
42
|
+
export const unCheckIt = (
|
43
|
+
foundItem: { [key: string]: any },
|
44
|
+
selectedItems: any,
|
45
|
+
setSelectedItems: any,
|
46
|
+
expand: boolean
|
47
|
+
) => {
|
48
|
+
if (!foundItem) {
|
49
|
+
return;
|
50
|
+
}
|
51
|
+
|
52
|
+
foundItem.checked = false;
|
53
|
+
foundItem.expanded = false;
|
54
|
+
const newSelectedItems = selectedItems.filter(
|
55
|
+
(item: any) => item.id !== foundItem.id
|
56
|
+
);
|
57
|
+
if (foundItem.children) {
|
58
|
+
foundItem.children.map((x: any) => {
|
59
|
+
unCheckIt(x, selectedItems, setSelectedItems, expand);
|
60
|
+
});
|
61
|
+
}
|
62
|
+
setSelectedItems([...newSelectedItems]);
|
63
|
+
};
|
64
|
+
|
65
|
+
|
66
|
+
export const getParentAndAncestorsIds = (itemId:string, items:{ [key: string]: string; }[], ancestors:string[] = []):any => {
|
67
|
+
for (let i = 0; i < items.length; i++) {
|
68
|
+
const item:any = items[i];
|
69
|
+
if (item.id === itemId) {
|
70
|
+
// item found in current level of items array
|
71
|
+
return [...ancestors, item.id];
|
72
|
+
}
|
73
|
+
if (item.children && item.children.length > 0) {
|
74
|
+
// recursively search through children
|
75
|
+
const foundAncestors = getParentAndAncestorsIds(
|
76
|
+
itemId,
|
77
|
+
item.children,
|
78
|
+
[...ancestors, item.id]
|
79
|
+
);
|
80
|
+
if (foundAncestors) {
|
81
|
+
return foundAncestors;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
// item not found in this level of items array or its children
|
86
|
+
return null;
|
87
|
+
}
|
@@ -3,8 +3,6 @@
|
|
3
3
|
module Playbook
|
4
4
|
module PbMultiLevelSelect
|
5
5
|
class MultiLevelSelect < Playbook::KitBase
|
6
|
-
prop :id
|
7
|
-
prop :name
|
8
6
|
prop :tree_data, type: Playbook::Props::Array,
|
9
7
|
default: []
|
10
8
|
prop :return_all_selected, type: Playbook::Props::Boolean,
|
@@ -17,7 +15,6 @@ module Playbook
|
|
17
15
|
def multi_level_select_options
|
18
16
|
{
|
19
17
|
id: id,
|
20
|
-
name: name,
|
21
18
|
treeData: tree_data,
|
22
19
|
returnAllSelected: return_all_selected,
|
23
20
|
}
|
@@ -30,7 +30,6 @@ import * as DateStacked from 'pb_date_stacked/docs'
|
|
30
30
|
import * as DateTime from 'pb_date_time/docs'
|
31
31
|
import * as DateTimeStacked from 'pb_date_time_stacked/docs'
|
32
32
|
import * as DateYearStacked from 'pb_date_year_stacked/docs'
|
33
|
-
import * as Detail from 'pb_detail/docs'
|
34
33
|
import * as Dialog from 'pb_dialog/docs'
|
35
34
|
import * as DistributionBarDocs from 'pb_distribution_bar/docs'
|
36
35
|
import * as FileUpload from 'pb_file_upload/docs'
|
@@ -131,7 +130,6 @@ WebpackerReact.setup({
|
|
131
130
|
...DateTime,
|
132
131
|
...DateTimeStacked,
|
133
132
|
...DateYearStacked,
|
134
|
-
...Detail,
|
135
133
|
...Dialog,
|
136
134
|
...DistributionBarDocs,
|
137
135
|
...FileUpload,
|