playbook_ui 14.4.0.pre.rc.16 → 14.5.0.pre.alpha.PBNTR374multilevelselectPOC3946

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +2 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/SortIconButton.tsx +23 -4
  4. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +1 -1
  5. data/app/pb_kits/playbook/pb_button/_button.tsx +6 -2
  6. data/app/pb_kits/playbook/pb_contact/_contact.tsx +17 -5
  7. data/app/pb_kits/playbook/pb_contact/contact.html.erb +14 -6
  8. data/app/pb_kits/playbook/pb_contact/contact.rb +4 -0
  9. data/app/pb_kits/playbook/pb_contact/contact.test.js +1 -1
  10. data/app/pb_kits/playbook/pb_dashboard/pbChartsDarkTheme.ts +2 -6
  11. data/app/pb_kits/playbook/pb_dashboard/pbChartsLightTheme.ts +2 -7
  12. data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +4 -3
  13. data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +1 -1
  14. data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +4 -0
  15. data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +6 -3
  16. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_inline.html.erb +1 -1
  17. data/app/pb_kits/playbook/pb_date_picker/sass_partials/_header_styles.scss +6 -2
  18. data/app/pb_kits/playbook/pb_dialog/_dialog.scss +2 -0
  19. data/app/pb_kits/playbook/pb_dialog/dialog_header.html.erb +1 -1
  20. data/app/pb_kits/playbook/pb_drawer/_close_icon.tsx +25 -0
  21. data/app/pb_kits/playbook/pb_drawer/_drawer.scss +465 -0
  22. data/app/pb_kits/playbook/pb_drawer/_drawer.tsx +195 -0
  23. data/app/pb_kits/playbook/pb_drawer/_drawer_context.tsx +3 -0
  24. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_borders.jsx +117 -0
  25. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_breakpoints.jsx +43 -0
  26. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_default.html.erb +1 -0
  27. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_default.jsx +63 -0
  28. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_overlay.jsx +55 -0
  29. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_sizes.jsx +113 -0
  30. data/app/pb_kits/playbook/pb_drawer/docs/example.yml +12 -0
  31. data/app/pb_kits/playbook/pb_drawer/docs/index.js +5 -0
  32. data/app/pb_kits/playbook/pb_drawer/drawer.html.erb +12 -0
  33. data/app/pb_kits/playbook/pb_drawer/drawer.rb +8 -0
  34. data/app/pb_kits/playbook/pb_drawer/drawer.test.jsx +77 -0
  35. data/app/pb_kits/playbook/pb_filter/docs/_filter_default.html.erb +10 -2
  36. data/app/pb_kits/playbook/pb_filter/docs/_filter_max_height.html.erb +5 -1
  37. data/app/pb_kits/playbook/pb_filter/docs/_filter_max_width.html.erb +5 -1
  38. data/app/pb_kits/playbook/pb_filter/docs/_filter_no_background.html.erb +5 -1
  39. data/app/pb_kits/playbook/pb_filter/docs/_filter_no_sort.html.erb +5 -1
  40. data/app/pb_kits/playbook/pb_filter/docs/_filter_only.html.erb +5 -1
  41. data/app/pb_kits/playbook/pb_filter/docs/_filter_placement.html.erb +5 -1
  42. data/app/pb_kits/playbook/pb_filter/docs/_filter_single.html.erb +5 -1
  43. data/app/pb_kits/playbook/pb_map/_map_controls.tsx +7 -1
  44. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +210 -232
  45. data/app/pb_kits/playbook/pb_multi_level_select/context/index.tsx +5 -0
  46. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx +1 -1
  47. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.jsx +107 -0
  48. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.jsx +108 -0
  49. data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +3 -0
  50. data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +2 -0
  51. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select_options.tsx +149 -0
  52. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_page_change.jsx +12 -1
  53. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_page_change_react.md +3 -1
  54. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +10 -2
  55. data/app/pb_kits/playbook/pb_popover/docs/_popover_list.html.erb +14 -13
  56. data/app/pb_kits/playbook/pb_popover/docs/_popover_list.jsx +4 -4
  57. data/app/pb_kits/playbook/pb_radio/_radio.tsx +92 -33
  58. data/app/pb_kits/playbook/pb_radio/docs/_radio_custom_children.html.erb +2 -0
  59. data/app/pb_kits/playbook/pb_radio/docs/_radio_custom_children.jsx +62 -0
  60. data/app/pb_kits/playbook/pb_radio/docs/example.yml +1 -0
  61. data/app/pb_kits/playbook/pb_radio/docs/index.js +1 -0
  62. data/app/pb_kits/playbook/pb_rich_text_editor/TipTap/ToolbarDropdown.tsx +12 -5
  63. data/app/pb_kits/playbook/pb_select/_select.tsx +5 -2
  64. data/app/pb_kits/playbook/pb_select/select.html.erb +1 -1
  65. data/app/pb_kits/playbook/pb_select/select.rb +4 -0
  66. data/app/pb_kits/playbook/pb_text_input/_text_input.scss +0 -1
  67. data/app/pb_kits/playbook/pb_tooltip/_tooltip.tsx +17 -13
  68. data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +0 -1
  69. data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +14 -0
  70. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +3 -0
  71. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +6 -5
  72. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_margin_bottom.html.erb +88 -0
  73. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_margin_bottom.jsx +60 -0
  74. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  75. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +1 -0
  76. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +2 -1
  77. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +6 -1
  78. data/app/pb_kits/playbook/utilities/icons/allicons.tsx +136 -13
  79. data/app/pb_kits/playbook/utilities/icons/angle-down.svg +3 -0
  80. data/app/pb_kits/playbook/utilities/icons/envelope.svg +3 -0
  81. data/dist/chunks/_typeahead-DMIBeCYd.js +22 -0
  82. data/dist/chunks/_weekday_stacked-CviGlw2m.js +45 -0
  83. data/dist/chunks/lib-CEpcaI8y.js +29 -0
  84. data/dist/chunks/{pb_form_validation-zV9OpdSt.js → pb_form_validation-D9zkwt2b.js} +1 -1
  85. data/dist/chunks/vendor.js +1 -1
  86. data/dist/menu.yml +3 -1
  87. data/dist/playbook-doc.js +1 -1
  88. data/dist/playbook-rails-react-bindings.js +1 -1
  89. data/dist/playbook-rails.js +1 -1
  90. data/dist/playbook.css +1 -1
  91. data/lib/playbook/pagination_renderer.rb +10 -2
  92. data/lib/playbook/pb_doc_helper.rb +5 -5
  93. data/lib/playbook/version.rb +2 -2
  94. metadata +30 -6
  95. data/dist/chunks/_typeahead-B2zRxReA.js +0 -22
  96. data/dist/chunks/_weekday_stacked-Co95UEiW.js +0 -45
  97. 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 { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from "../utilities/props"
5
- import Checkbox from "../pb_checkbox/_checkbox"
6
- import Radio from "../pb_radio/_radio"
7
- import Body from "../pb_body/_body"
8
- import Icon from "../pb_icon/_icon"
9
- import FormPill from "../pb_form_pill/_form_pill"
10
- import CircleIconButton from "../pb_circle_icon_button/_circle_icon_button"
11
- import { cloneDeep } from "lodash"
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,152 @@ 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
- // Function to map over data and add parent_id + depth property to each item
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(selectedIds && selectedIds.length && selectedIds.includes(item.id)),
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(formattedData, selectedIds[0])
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 (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
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
-
188
-
201
+ window.removeEventListener("click", handleClickOutside);
202
+ };
203
+ }, []);
189
204
 
190
205
  // Iterate over tree, find item and set checked or unchecked
191
206
  const modifyValue = (
@@ -194,234 +209,182 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
194
209
  check: boolean
195
210
  ) => {
196
211
  if (!Array.isArray(tree)) {
197
- return
212
+ return;
198
213
  }
199
214
  return tree.map((item: any) => {
200
- if (item.id != id) item.children = modifyValue(id, item.children, check)
215
+ if (item.id != id) item.children = modifyValue(id, item.children, check);
201
216
  else {
202
- item.checked = check
217
+ item.checked = check;
203
218
 
204
219
  if (variant === "single") {
205
220
  // Single select: no children should be checked
206
- item.children = modifyRecursive(item.children, !check)
221
+ item.children = modifyRecursive(item.children, !check);
207
222
  } else {
208
- item.children = modifyRecursive(item.children, check)
223
+ item.children = modifyRecursive(item.children, check);
209
224
  }
210
225
  }
211
226
 
212
- return item
213
- })
214
- }
227
+ return item;
228
+ });
229
+ };
215
230
 
216
231
  // Clone tree, check items + children
217
232
  const checkItem = (item: { [key: string]: any }) => {
218
- const tree = cloneDeep(formattedData)
233
+ const tree = cloneDeep(formattedData);
219
234
  if (returnAllSelected) {
220
- return modifyValue(item.id, tree, true)
235
+ return modifyValue(item.id, tree, true);
221
236
  } else {
222
- const checkedTree = modifyValue(item.id, tree, true)
223
- return recursiveCheckParent(item, checkedTree)
237
+ const checkedTree = modifyValue(item.id, tree, true);
238
+ return recursiveCheckParent(item, checkedTree);
224
239
  }
225
- }
240
+ };
226
241
 
227
242
  // Clone tree, uncheck items + children
228
243
  const unCheckItem = (item: { [key: string]: any }) => {
229
- const tree = cloneDeep(formattedData)
244
+ const tree = cloneDeep(formattedData);
230
245
  if (returnAllSelected) {
231
- return modifyValue(item.id, tree, false)
246
+ return modifyValue(item.id, tree, false);
232
247
  } else {
233
- const uncheckedTree = modifyValue(item.id, tree, false)
234
- return getAncestorsOfUnchecked(uncheckedTree, item)
248
+ const uncheckedTree = modifyValue(item.id, tree, false);
249
+ return getAncestorsOfUnchecked(uncheckedTree, item);
235
250
  }
236
- }
251
+ };
237
252
 
238
253
  // setFormattedData with proper properties
239
254
  const changeItem = (item: { [key: string]: any }, check: boolean) => {
240
- const tree = check ? checkItem(item) : unCheckItem(item)
241
- setFormattedData(tree)
255
+ const tree = check ? checkItem(item) : unCheckItem(item);
256
+ setFormattedData(tree);
242
257
 
243
- return tree
244
- }
245
-
246
-
258
+ return tree;
259
+ };
247
260
 
248
261
  // Click event for x on form pill
249
262
  const handlePillClose = (event: any, clickedItem: { [key: string]: any }) => {
250
263
  // Prevents the dropdown from closing when clicking on the pill
251
- event.stopPropagation()
252
- const updatedTree = changeItem(clickedItem, false)
264
+ event.stopPropagation();
265
+ const updatedTree = changeItem(clickedItem, false);
253
266
  // Logic for removing items from returnArray or defaultReturn when pills clicked
254
267
  if (returnAllSelected) {
255
- onSelect(getCheckedItems(updatedTree))
268
+ onSelect(getCheckedItems(updatedTree));
256
269
  } else {
257
- onSelect(getDefaultCheckedItems(updatedTree))
270
+ onSelect(getDefaultCheckedItems(updatedTree));
258
271
  }
259
- }
272
+ };
260
273
 
261
274
  // Handle click on input wrapper(entire div with pills, typeahead, etc) so it doesn't close when input or form pill is clicked
262
275
  const handleInputWrapperClick = (e: any) => {
263
- e.stopPropagation()
264
276
  if (
265
277
  e.target.id === "multiselect_input" ||
266
278
  e.target.classList.contains("pb_form_pill_tag")
267
279
  ) {
268
- return
280
+ return;
269
281
  }
270
- setIsDropdownClosed(!isDropdownClosed)
271
- }
282
+ setIsDropdownClosed(!isDropdownClosed);
283
+ };
272
284
 
273
285
  // Main function to handle any click inside dropdown
274
286
  const handledropdownItemClick = (e: any, check: boolean) => {
275
- const clickedItem = e.target.parentNode.id
287
+ const clickedItem = e.target.parentNode.id;
276
288
  // Setting filterItem to "" will clear textinput and clear typeahead
277
- setFilterItem("")
289
+ setFilterItem("");
278
290
 
279
- const filtered = filterFormattedDataById(formattedData, clickedItem)
280
- const updatedTree = changeItem(filtered[0], check)
291
+ const filtered = filterFormattedDataById(formattedData, clickedItem);
292
+ const updatedTree = changeItem(filtered[0], check);
281
293
  if (returnAllSelected) {
282
- onSelect(getCheckedItems(updatedTree))
294
+ onSelect(getCheckedItems(updatedTree));
283
295
  } else {
284
- onSelect(getDefaultCheckedItems(updatedTree))
296
+ onSelect(getDefaultCheckedItems(updatedTree));
285
297
  }
286
- }
298
+ };
287
299
 
288
300
  // Single select
289
- const handleRadioButtonClick = (
290
- e: React.ChangeEvent<HTMLInputElement>,
291
- ) => {
292
- const { id, value: inputText } = e.target
301
+ const handleRadioButtonClick = (e: React.ChangeEvent<HTMLInputElement>) => {
302
+ const { id, value: inputText } = e.target;
293
303
  // The radio button needs a unique ID, this grabs the ID before the hyphen
294
- const selectedItemID = id.match(/^[^-]*/)[0]
304
+ const selectedItemID = id.match(/^[^-]*/)[0];
295
305
  // Reset tree checked state, triggering useEffect
296
- const treeWithNoSelections = modifyRecursive(formattedData, false)
306
+ const treeWithNoSelections = modifyRecursive(formattedData, false);
297
307
  // Update tree with single selection
298
- const treeWithSelectedItem = modifyValue(selectedItemID, treeWithNoSelections, true)
299
- const selectedItem = filterFormattedDataById(treeWithSelectedItem, selectedItemID)
300
-
301
- setFormattedData(treeWithSelectedItem)
302
- setSingleSelectedItem({id: [selectedItemID], value: inputText, item: selectedItem})
308
+ const treeWithSelectedItem = modifyValue(
309
+ selectedItemID,
310
+ treeWithNoSelections,
311
+ true
312
+ );
313
+ const selectedItem = filterFormattedDataById(
314
+ treeWithSelectedItem,
315
+ selectedItemID
316
+ );
317
+
318
+ setFormattedData(treeWithSelectedItem);
319
+ setSingleSelectedItem({
320
+ id: [selectedItemID],
321
+ value: inputText,
322
+ item: selectedItem,
323
+ });
303
324
  // Reset the filter to always display dropdown options on click
304
- setFilterItem("")
305
- setIsDropdownClosed(true)
325
+ setFilterItem("");
326
+ setIsDropdownClosed(true);
306
327
 
307
- onSelect(selectedItem)
328
+ onSelect(selectedItem);
308
329
  };
309
330
 
310
331
  // Single select: reset the tree state upon typing
311
332
  const handleRadioInputChange = (inputText: string) => {
312
- modifyRecursive(formattedData, false)
313
- setDefaultReturn([])
314
- setSingleSelectedItem({id: [], value: inputText, item: []})
315
- setFilterItem(inputText)
333
+ modifyRecursive(formattedData, false);
334
+ setDefaultReturn([]);
335
+ setSingleSelectedItem({ id: [], value: inputText, item: [] });
336
+ setFilterItem(inputText);
316
337
  };
317
338
 
318
- const isTreeRowExpanded = (item: any) => expanded.indexOf(item.id) > -1
339
+ const isTreeRowExpanded = (item: any) => expanded.indexOf(item.id) > -1;
319
340
 
320
341
  // Handle click on chevron toggles in dropdown
321
342
  const handleToggleClick = (id: string, event: React.MouseEvent) => {
322
- event.stopPropagation()
323
- const clickedItem = filterFormattedDataById(formattedData, id)
343
+ event.stopPropagation();
344
+ const clickedItem = filterFormattedDataById(formattedData, id);
324
345
  if (clickedItem) {
325
- let expandedArray = [...expanded]
326
- const itemExpanded = isTreeRowExpanded(clickedItem[0])
346
+ let expandedArray = [...expanded];
347
+ const itemExpanded = isTreeRowExpanded(clickedItem[0]);
327
348
 
328
349
  if (itemExpanded)
329
- expandedArray = expandedArray.filter((i) => i != clickedItem[0].id)
330
- else expandedArray.push(clickedItem[0].id)
350
+ expandedArray = expandedArray.filter((i) => i != clickedItem[0].id);
351
+ else expandedArray.push(clickedItem[0].id);
331
352
 
332
- setExpanded(expandedArray)
353
+ setExpanded(expandedArray);
333
354
  }
334
- }
355
+ };
335
356
 
336
357
  const itemsSelectedLength = () => {
337
- let items
358
+ let items;
338
359
  if (returnAllSelected && returnedArray && returnedArray.length) {
339
- items = returnedArray.length
360
+ items = returnedArray.length;
340
361
  } else if (!returnAllSelected && defaultReturn && defaultReturn.length) {
341
- items = defaultReturn.length
362
+ items = defaultReturn.length;
342
363
  }
343
- return items
344
- }
364
+ return items;
365
+ };
345
366
 
346
367
  // Rendering formattedData to UI based on typeahead
347
- const renderNestedOptions = (items: { [key: string]: any }[]) => {
348
- return (
349
- <ul>
350
- {Array.isArray(items) &&
351
- items.map((item: { [key: string]: any }) => {
352
- return (
353
- <div key={item.id}>
354
- <li
355
- className={"dropdown_item"}
356
- data-name={item.id}
357
- >
358
- <div className="dropdown_item_checkbox_row">
359
- { !item.parent_id && !item.children ? null :
360
- <div
361
- key={isTreeRowExpanded(item) ? "chevron-down" : "chevron-right"}
362
- >
363
- <CircleIconButton
364
- className={
365
- item.children && item.children.length > 0
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
- }
368
+ const renderNestedOptions = (items: { [key: string]: string; }[] | any ) => {
369
+ const hasOptionsChild = React.Children.toArray(props.children).some(
370
+ child => child.type === MultiLevelSelect.Options
371
+ );
372
+
373
+ if (hasOptionsChild) {
374
+ return React.Children.map(props.children, (child) => {
375
+ if (child.type === MultiLevelSelect.Options) {
376
+ return React.cloneElement(child, { items });
377
+ }
378
+ return null;
379
+ });
380
+ } else {
381
+ // If nochildren, use the default rendering
382
+ return (
383
+ <MultiLevelSelectOptions items={items} />
384
+ );
385
+ }
386
+ };
387
+
425
388
 
426
389
  return (
427
390
  <div
@@ -431,12 +394,20 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
431
394
  className={classes}
432
395
  id={id}
433
396
  >
434
- <div
435
- className="wrapper"
397
+ <MultiLevelSelectContext.Provider value={{
398
+ variant,
399
+ inputName,
400
+ renderNestedOptions,
401
+ isTreeRowExpanded,
402
+ handleToggleClick,
403
+ handleRadioButtonClick,
404
+ handledropdownItemClick,
405
+ filterItem,
406
+ }}>
407
+ <div className="wrapper"
436
408
  ref={dropdownRef}
437
409
  >
438
- <div
439
- className="input_wrapper"
410
+ <div className="input_wrapper"
440
411
  onClick={handleInputWrapperClick}
441
412
  >
442
413
  <div className="input_inner_container">
@@ -502,15 +473,17 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
502
473
 
503
474
  <input
504
475
  id="multiselect_input"
505
- onChange={(e) =>{
476
+ onChange={(e) => {
506
477
  variant === "single"
507
478
  ? handleRadioInputChange(e.target.value)
508
- : setFilterItem(e.target.value)
479
+ : setFilterItem(e.target.value);
509
480
  }}
510
481
  onClick={() => setIsDropdownClosed(false)}
511
482
  placeholder={
512
483
  inputDisplay === "none" && itemsSelectedLength()
513
- ? `${itemsSelectedLength()} ${itemsSelectedLength() === 1 ? "item" : "items"} selected`
484
+ ? `${itemsSelectedLength()} ${
485
+ itemsSelectedLength() === 1 ? "item" : "items"
486
+ } selected`
514
487
  : "Start typing..."
515
488
  }
516
489
  value={singleSelectedItem.value || filterItem}
@@ -518,16 +491,20 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
518
491
  </div>
519
492
 
520
493
  {isDropdownClosed ? (
521
- <div key="chevron-down">
494
+ <div id={arrowDownElementId}
495
+ key="chevron-down">
522
496
  <Icon
523
497
  icon="chevron-down"
498
+ id={arrowDownElementId}
524
499
  size="xs"
525
500
  />
526
501
  </div>
527
502
  ) : (
528
- <div key="chevron-up">
503
+ <div id={arrowUpElementId}
504
+ key="chevron-up">
529
505
  <Icon
530
506
  icon="chevron-up"
507
+ id={arrowUpElementId}
531
508
  size="xs"
532
509
  />
533
510
  </div>
@@ -535,15 +512,16 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
535
512
  </div>
536
513
 
537
514
  <div className={`dropdown_menu ${isDropdownClosed ? "close" : "open"}`}>
538
- {renderNestedOptions(
539
- filterItem
540
- ? findByFilter(formattedData, filterItem)
541
- : formattedData
542
- )}
515
+ {renderNestedOptions(
516
+ filterItem ? findByFilter(formattedData, filterItem) : formattedData
517
+ )}
543
518
  </div>
544
519
  </div>
520
+ </MultiLevelSelectContext.Provider>
545
521
  </div>
546
- )
547
- }
522
+ );
523
+ };
524
+
525
+ MultiLevelSelect.Options = MultiLevelSelectOptions;
548
526
 
549
- export default MultiLevelSelect
527
+ export default MultiLevelSelect;