playbook_ui 14.4.0 → 14.5.0.pre.alpha.PBNTR606multilevelselectreset4035

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) 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_form_pill/_form_pill.tsx +9 -1
  44. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.html.erb +19 -0
  45. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.jsx +27 -0
  46. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.md +1 -0
  47. data/app/pb_kits/playbook/pb_form_pill/docs/example.yml +2 -0
  48. data/app/pb_kits/playbook/pb_form_pill/docs/index.js +1 -0
  49. data/app/pb_kits/playbook/pb_map/_map_controls.tsx +7 -1
  50. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +226 -231
  51. data/app/pb_kits/playbook/pb_multi_level_select/context/index.tsx +5 -0
  52. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx +1 -1
  53. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_reset.html.erb +93 -0
  54. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.jsx +105 -0
  55. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.md +1 -0
  56. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.jsx +106 -0
  57. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.md +1 -0
  58. data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +4 -0
  59. data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +2 -0
  60. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select_options.tsx +149 -0
  61. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_page_change.jsx +12 -1
  62. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_page_change_react.md +3 -1
  63. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +10 -2
  64. data/app/pb_kits/playbook/pb_popover/docs/_popover_list.html.erb +14 -13
  65. data/app/pb_kits/playbook/pb_popover/docs/_popover_list.jsx +4 -4
  66. data/app/pb_kits/playbook/pb_radio/_radio.tsx +92 -33
  67. data/app/pb_kits/playbook/pb_radio/docs/_radio_custom_children.html.erb +2 -0
  68. data/app/pb_kits/playbook/pb_radio/docs/_radio_custom_children.jsx +62 -0
  69. data/app/pb_kits/playbook/pb_radio/docs/example.yml +1 -0
  70. data/app/pb_kits/playbook/pb_radio/docs/index.js +1 -0
  71. data/app/pb_kits/playbook/pb_rich_text_editor/TipTap/ToolbarDropdown.tsx +12 -5
  72. data/app/pb_kits/playbook/pb_select/_select.tsx +5 -2
  73. data/app/pb_kits/playbook/pb_select/select.html.erb +1 -1
  74. data/app/pb_kits/playbook/pb_select/select.rb +4 -0
  75. data/app/pb_kits/playbook/pb_text_input/_text_input.scss +0 -1
  76. data/app/pb_kits/playbook/pb_tooltip/_tooltip.tsx +17 -13
  77. data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +0 -1
  78. data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +14 -0
  79. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +7 -1
  80. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +6 -5
  81. data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +3 -1
  82. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_margin_bottom.html.erb +88 -0
  83. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_margin_bottom.jsx +60 -0
  84. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  85. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +1 -0
  86. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +2 -1
  87. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +9 -2
  88. data/app/pb_kits/playbook/utilities/icons/allicons.tsx +136 -13
  89. data/app/pb_kits/playbook/utilities/icons/angle-down.svg +3 -0
  90. data/app/pb_kits/playbook/utilities/icons/envelope.svg +3 -0
  91. data/dist/chunks/_typeahead-C9g4qCcE.js +22 -0
  92. data/dist/chunks/_weekday_stacked-Div3Fpd3.js +45 -0
  93. data/dist/chunks/lazysizes-B7xYodB-.js +1 -0
  94. data/dist/chunks/lib-CEpcaI8y.js +29 -0
  95. data/dist/chunks/{pb_form_validation-zV9OpdSt.js → pb_form_validation-D9zkwt2b.js} +1 -1
  96. data/dist/chunks/vendor.js +1 -1
  97. data/dist/menu.yml +3 -1
  98. data/dist/playbook-doc.js +1 -1
  99. data/dist/playbook-rails-react-bindings.js +1 -1
  100. data/dist/playbook-rails.js +1 -1
  101. data/dist/playbook.css +1 -1
  102. data/lib/playbook/pagination_renderer.rb +10 -2
  103. data/lib/playbook/pb_doc_helper.rb +5 -5
  104. data/lib/playbook/version.rb +2 -2
  105. metadata +40 -10
  106. data/dist/chunks/_typeahead-B2zRxReA.js +0 -22
  107. data/dist/chunks/_weekday_stacked-BIfZDNDm.js +0 -45
  108. data/dist/chunks/lazysizes-DHz07jlL.js +0 -1
  109. 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,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
- // 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
-
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
- e: React.ChangeEvent<HTMLInputElement>,
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(selectedItemID, treeWithNoSelections, true)
299
- const selectedItem = filterFormattedDataById(treeWithSelectedItem, selectedItemID)
300
-
301
- setFormattedData(treeWithSelectedItem)
302
- setSingleSelectedItem({id: [selectedItemID], value: inputText, item: selectedItem})
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]: 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
- }
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
- <div
435
- className="wrapper"
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()} ${itemsSelectedLength() === 1 ? "item" : "items"} selected`
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 key="chevron-down">
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 key="chevron-up">
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
- {renderNestedOptions(
539
- filterItem
540
- ? findByFilter(formattedData, filterItem)
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;