playbook_ui 14.4.0 → 14.5.0.pre.alpha.PBNTR374multilevelselectPOC3946

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.
Files changed (99) 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/lazysizes-B7xYodB-.js +1 -0
  84. data/dist/chunks/lib-CEpcaI8y.js +29 -0
  85. data/dist/chunks/{pb_form_validation-zV9OpdSt.js → pb_form_validation-D9zkwt2b.js} +1 -1
  86. data/dist/chunks/vendor.js +1 -1
  87. data/dist/menu.yml +3 -1
  88. data/dist/playbook-doc.js +1 -1
  89. data/dist/playbook-rails-react-bindings.js +1 -1
  90. data/dist/playbook-rails.js +1 -1
  91. data/dist/playbook.css +1 -1
  92. data/lib/playbook/pagination_renderer.rb +10 -2
  93. data/lib/playbook/pb_doc_helper.rb +5 -5
  94. data/lib/playbook/version.rb +2 -2
  95. metadata +34 -10
  96. data/dist/chunks/_typeahead-B2zRxReA.js +0 -22
  97. data/dist/chunks/_weekday_stacked-BIfZDNDm.js +0 -45
  98. data/dist/chunks/lazysizes-DHz07jlL.js +0 -1
  99. 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;