playbook_ui 14.4.0 → 14.5.0.pre.alpha.PBNTR606multilevelselectreset4035

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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;