playbook_ui 12.26.0.pre.alpha.multiselectfixes798 → 12.26.0.pre.alpha.railsmultilevelimprovements805

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 885259e20b787892699275c0691bde8b5e63bf0b010d7e447e51e945fc5f23f8
4
- data.tar.gz: 8ca06c90d70b8d2e5822a4be0b1882d7e22e847c72e0bb28d8afd4e520d6b00b
3
+ metadata.gz: 79c732fa4dc676050bdbc6dda57f9a180670d812ffeeb73ba5d410a683a2c6bf
4
+ data.tar.gz: 332c5b9b341d69f47d83af65dfd5936918ee3b7e4149884892a8c4926b7ec3e7
5
5
  SHA512:
6
- metadata.gz: cfc65879ed3c5beb563456c3bc791a8ad1335292b5448ecdfbcb90b8c72bbe1d2d9a275c11ecccea51309d9ccd727a2f09f29120468d9f646f76c7948f35377b
7
- data.tar.gz: 0b41fb441055d6619d7d8ff5391a4e1f979df876a0ad04ec3ee350f8a883ae6ae8ca223fd0ca9c39a41f2e2aa7e6cc82136affd88922d9e956140f64f61b6b4b
6
+ metadata.gz: bd51e6d5feffbe630c6e315e17e108d3f2c2d55a55b2ba12fbe14894950f61e9d457cb29899993a7f990564140e95e78c8251e675cfa7f3048dacfb7cb24d72f
7
+ data.tar.gz: fd53b4b633ea48c3b6db1f2f87c017509db18cf1d7732d9a1992ee3147e8df41d6e0b05fa1bf5f876c2304c3ae71ddb8627deb1e5abd65de8d8f14ccac9bc139
@@ -210,22 +210,3 @@ export const removeChildrenIfParentChecked = (
210
210
  );
211
211
  setDefaultReturn([...filteredDefaultArray, items]);
212
212
  };
213
-
214
- export const areAllCheckedFalse = (data:any) => {
215
- if (!Array.isArray(data)) {
216
- return;
217
- }
218
- for (const item of data) {
219
- if (item.checked !== false) {
220
- return false; // Return false if any item is not checked: false
221
- }
222
-
223
- if (item.children && item.children.length > 0) {
224
- if (!areAllCheckedFalse(item.children)) {
225
- return false; // Return false if any nested item is not checked: false
226
- }
227
- }
228
- }
229
-
230
- return true; // Return true if all items are checked: false
231
- };
@@ -1,11 +1,11 @@
1
- import React, { useState, useEffect, useRef } from "react";
2
- import classnames from "classnames";
3
- import { buildAriaProps, buildCss, buildDataProps } from "../utilities/props";
4
- import { globalProps, GlobalProps } from "../utilities/globalProps";
5
- import Icon from "../pb_icon/_icon";
6
- import Checkbox from "../pb_checkbox/_checkbox";
7
- import FormPill from "../pb_form_pill/_form_pill";
8
- import CircleIconButton from "../pb_circle_icon_button/_circle_icon_button";
1
+ import React, { useState, useEffect, useRef } from "react"
2
+ import classnames from "classnames"
3
+ import { buildAriaProps, buildCss, buildDataProps } from "../utilities/props"
4
+ import { globalProps, GlobalProps } from "../utilities/globalProps"
5
+ import Icon from "../pb_icon/_icon"
6
+ import Checkbox from "../pb_checkbox/_checkbox"
7
+ import FormPill from "../pb_form_pill/_form_pill"
8
+ import CircleIconButton from "../pb_circle_icon_button/_circle_icon_button"
9
9
  import {
10
10
  unCheckIt,
11
11
  getAncestorsOfUnchecked,
@@ -18,18 +18,19 @@ import {
18
18
  recursiveReturnOnlyParent,
19
19
  removeChildrenIfParentChecked,
20
20
  getChildIds,
21
- areAllCheckedFalse
22
- } from "./_helper_functions";
21
+ } from "./_helper_functions"
23
22
 
24
23
  type MultiLevelSelectProps = {
25
- aria?: { [key: string]: string };
26
- className?: string;
27
- data?: { [key: string]: string };
28
- id?: string;
29
- returnAllSelected?: boolean;
30
- treeData?: { [key: string]: string }[];
31
- onSelect?: (prop: { [key: string]: any }) => void;
32
- } & GlobalProps;
24
+ aria?: { [key: string]: string }
25
+ className?: string
26
+ data?: { [key: string]: string }
27
+ id?: string
28
+ name?: string
29
+ returnCompleteData?: boolean
30
+ returnAllSelected?: boolean
31
+ treeData?: { [key: string]: string }[]
32
+ onSelect?: (prop: { [key: string]: any }) => void
33
+ } & GlobalProps
33
34
 
34
35
  const MultiLevelSelect = (props: MultiLevelSelectProps) => {
35
36
  const {
@@ -37,209 +38,228 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
37
38
  className,
38
39
  data = {},
39
40
  id,
41
+ name,
40
42
  returnAllSelected = false,
43
+ returnCompleteData = false,
41
44
  treeData,
42
45
  onSelect = () => {},
43
- } = props;
46
+ } = props
44
47
 
45
- const ariaProps = buildAriaProps(aria);
46
- const dataProps = buildDataProps(data);
48
+ const ariaProps = buildAriaProps(aria)
49
+ const dataProps = buildDataProps(data)
47
50
  const classes = classnames(
48
51
  buildCss("pb_multi_level_select"),
49
52
  globalProps(props),
50
53
  className
51
- );
54
+ )
52
55
 
53
- const dropdownRef = useRef(null);
56
+ const dropdownRef = useRef(null)
54
57
 
55
58
  //state for whether dropdown is open or closed
56
- const [isClosed, setIsClosed] = useState(true);
59
+ const [isClosed, setIsClosed] = useState(true)
57
60
  //state from onchange for textinput, to use for filtering to create typeahead
58
- const [filterItem, setFilterItem] = useState("");
61
+ const [filterItem, setFilterItem] = useState("")
59
62
  //this is essentially the return that the user will get when they use the kit
60
- const [returnedArray, setReturnedArray] = useState([]);
63
+ const [returnedArray, setReturnedArray] = useState([])
61
64
  //formattedData with checked and parent_id added
62
- const [formattedData, setFormattedData] = useState([]);
65
+ const [formattedData, setFormattedData] = useState(treeData)
63
66
  //toggle chevron in dropdown
64
67
  //@ts-ignore
65
- const [isToggled, setIsToggled] = useState<{ [id: number]: boolean }>({});
68
+ const [isToggled, setIsToggled] = useState<{ [id: number]: boolean }>({})
66
69
  //state for return for default
67
- const [defaultReturn, setDefaultReturn] = useState([]);
70
+ const [defaultReturn, setDefaultReturn] = useState([])
68
71
 
69
- useEffect(() => {
70
- let el = document.getElementById(`pb_data_wrapper_${id}`);
71
- if (el) {
72
- el.setAttribute(
73
- "data-tree",
74
- JSON.stringify(returnAllSelected ? returnedArray : defaultReturn)
75
- );
72
+ const [formattedReturn, setFormattedReturn] = useState([])
73
+
74
+ const getSelectedIds = (selectedData: { [key: string]: any }[]) => {
75
+ return selectedData
76
+ .map((item) => item.id)
77
+ .filter((value, index, self) => self.indexOf(value) === index)
78
+ }
79
+
80
+ const getValues = () => {
81
+ if(returnCompleteData){
82
+ if(returnAllSelected){
83
+ return setFormattedReturn(returnedArray)
84
+ } else {
85
+ return setFormattedReturn(defaultReturn)
76
86
  }
77
- returnAllSelected
78
- ? onSelect(returnedArray)
79
- : onSelect(
80
- defaultReturn.filter(
81
- (item, index, self) =>
82
- index === self.findIndex((obj) => obj.id === item.id)
83
- )
84
- );
85
- }, [returnedArray, defaultReturn]);
87
+ } else {
88
+ if(returnAllSelected){
89
+ setFormattedReturn(getSelectedIds(returnedArray))
90
+ } else {
91
+ setFormattedReturn(getSelectedIds(defaultReturn))
92
+ }
93
+ }
94
+ }
95
+
96
+ const updateHiddenInputValue = (value: any) => {
97
+ const hiddenInput = document.querySelector(
98
+ "input#" + id
99
+ ) as HTMLInputElement
100
+ if (hiddenInput) {
101
+ hiddenInput.value = JSON.stringify(value)
102
+ }
103
+ }
86
104
 
87
105
  useEffect(() => {
88
- console.log("TREEDATA", treeData)
89
- //Create new formattedData array for use
90
- setFormattedData(addCheckedAndParentProperty(treeData));
91
- //if any items already checked in first render, set return accordingly
92
- const initialChecked = getCheckedItems(treeData)
93
- console.log("INITIAl CHECKED", initialChecked)
94
- const initialUnchecked = areAllCheckedFalse(treeData)
95
- console.log("INIITAL UNCHECKED", initialUnchecked)
96
- initialChecked && returnAllSelected && setReturnedArray(initialChecked)
97
- initialChecked && !returnAllSelected && setDefaultReturn(initialChecked)
106
+ getValues()
107
+ }, [returnedArray, defaultReturn])
98
108
 
99
- }, [treeData]);
109
+ useEffect(() => {
110
+ onSelect(formattedReturn)
111
+ updateHiddenInputValue(formattedReturn)
112
+ }, [formattedReturn])
100
113
 
101
- useEffect(()=> {
114
+ useEffect(() => {
115
+ //Create new formattedData array for use
116
+ setFormattedData(addCheckedAndParentProperty(treeData))
102
117
  // Function to handle clicks outside the dropdown
103
118
  const handleClickOutside = (event: any) => {
104
119
  if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
105
- setIsClosed(true);
120
+ setIsClosed(true)
106
121
  }
107
- };
108
- // Attach the event listener
109
- window.addEventListener("click", handleClickOutside);
110
- // Clean up the event listener on unmount
111
- return () => {
112
- window.removeEventListener("click", handleClickOutside);
113
- };
114
- },[])
122
+ }
123
+ //if any items already checked in first render, set return accordingly
124
+ const initialChecked = getCheckedItems(treeData)
125
+ initialChecked && returnAllSelected && setReturnedArray(initialChecked)
126
+ initialChecked && !returnAllSelected && setDefaultReturn(initialChecked)
127
+
128
+ // Attach the event listener
129
+ window.addEventListener("click", handleClickOutside)
130
+ // Clean up the event listener on unmount
131
+ return () => {
132
+ window.removeEventListener("click", handleClickOutside)
133
+ }
134
+ }, [])
115
135
 
116
136
  //function to map over data and add parent_id + depth property to each item
117
137
  const addCheckedAndParentProperty = (
118
138
  treeData: { [key: string]: any }[],
119
139
  parent_id: string = null,
120
- depth: number = 0,
140
+ depth: number = 0
121
141
  ) => {
122
142
  if (!Array.isArray(treeData)) {
123
- return;
143
+ return
124
144
  }
125
145
  return treeData.map((item: { [key: string]: any } | any) => {
126
146
  const newItem = {
127
147
  ...item,
128
148
  parent_id,
129
149
  depth,
130
- };
150
+ }
131
151
  if (newItem.children && newItem.children.length > 0) {
132
152
  newItem.children = addCheckedAndParentProperty(
133
153
  newItem.children,
134
154
  newItem.id,
135
- depth + 1,
136
- );
155
+ depth + 1
156
+ )
137
157
  }
138
- return newItem;
139
- });
140
- };
158
+ return newItem
159
+ })
160
+ }
141
161
 
142
162
  //click event for x on form pill
143
163
  const handlePillClose = (event: any, clickedItem: { [key: string]: any }) => {
144
164
  // prevents the dropdown from closing when clicking on the pill
145
- event.stopPropagation();
165
+ event.stopPropagation()
146
166
  //logic for removing items from returnArray or defaultReturn when pills clicked
147
167
  if (returnAllSelected) {
148
168
  if (returnedArray.includes(clickedItem)) {
149
169
  if (clickedItem.children && clickedItem.children.length > 0) {
150
- const childrenOfChecked = getChildIds(clickedItem, returnedArray);
170
+ const childrenOfChecked = getChildIds(clickedItem, returnedArray)
151
171
  const updatedFiltered = returnedArray
152
172
  .filter((item) => item !== clickedItem)
153
- .filter((item) => !childrenOfChecked.includes(item.id));
154
- setReturnedArray(updatedFiltered);
173
+ .filter((item) => !childrenOfChecked.includes(item.id))
174
+ setReturnedArray(updatedFiltered)
155
175
  } else {
156
176
  const updatedFiltered = returnedArray.filter(
157
177
  (item) => item !== clickedItem
158
- );
159
- setReturnedArray(updatedFiltered);
178
+ )
179
+ setReturnedArray(updatedFiltered)
160
180
  }
161
181
  }
162
182
  } else {
163
183
  if (defaultReturn.includes(clickedItem)) {
164
- getAncestorsOfUnchecked(formattedData, clickedItem);
165
- const newChecked = getCheckedItems(formattedData);
184
+ getAncestorsOfUnchecked(formattedData, clickedItem)
185
+ const newChecked = getCheckedItems(formattedData)
166
186
  const filteredReturn = updateReturnItems(newChecked).filter(
167
187
  (item) => item.id !== clickedItem.id
168
- );
169
- setDefaultReturn(filteredReturn);
188
+ )
189
+ setDefaultReturn(filteredReturn)
170
190
  }
171
191
  }
172
192
  if (clickedItem.children && clickedItem.children.length > 0) {
173
- unCheckedRecursive(clickedItem);
193
+ unCheckedRecursive(clickedItem)
174
194
  }
175
195
  //logic to uncheck clickedItem in formattedData
176
- unCheckIt(formattedData, clickedItem.id);
177
- };
196
+ unCheckIt(formattedData, clickedItem.id)
197
+ }
178
198
 
179
199
  //handle click on input wrapper(entire div with pills, typeahead, etc) so it doesn't close when input or form pill is clicked
180
200
  const handleInputWrapperClick = (e: any) => {
181
- e.stopPropagation();
201
+ e.stopPropagation()
182
202
  if (
183
203
  e.target.id === "multiselect_input" ||
184
204
  e.target.classList.contains("pb_form_pill_tag")
185
205
  ) {
186
- return;
206
+ return
187
207
  }
188
- setIsClosed(!isClosed);
189
- };
208
+ setIsClosed(!isClosed)
209
+ }
190
210
 
191
211
  //Main function to handle any click inside dropdown
192
212
  const handledropdownItemClick = (e: any) => {
193
- const clickedItem = e.target.parentNode.id;
213
+ const clickedItem = e.target.parentNode.id
194
214
  //setting filterItem to "" will clear textinput and clear typeahead
195
- setFilterItem("");
215
+ setFilterItem("")
196
216
 
197
- const filtered = filterFormattedDataById(formattedData, clickedItem);
217
+ const filtered = filterFormattedDataById(formattedData, clickedItem)
198
218
  //check and uncheck all children of checked/unchecked parent item
199
219
  if (filtered[0].children && filtered[0].children.length > 0) {
200
220
  if (filtered[0].checked) {
201
221
  filtered[0].children.forEach((item: { [key: string]: any }) => {
202
- checkedRecursive(item);
203
- });
222
+ checkedRecursive(item)
223
+ })
204
224
  } else if (!filtered[0].checked) {
205
225
  filtered[0].children.forEach((item: { [key: string]: any }) => {
206
- unCheckedRecursive(item);
207
- });
226
+ unCheckedRecursive(item)
227
+ })
208
228
  }
209
229
  }
210
230
 
211
- const checkedItems = getCheckedItems(formattedData);
231
+ const checkedItems = getCheckedItems(formattedData)
212
232
 
213
233
  //checking and unchecking items for returnAllSelected variant
214
234
  if (returnedArray.includes(filtered[0])) {
215
235
  if (!filtered[0].checked) {
216
236
  if (filtered[0].children && filtered[0].children.length > 0) {
217
- const childrenOfChecked = getChildIds(filtered[0], returnedArray);
237
+ const childrenOfChecked = getChildIds(filtered[0], returnedArray)
218
238
  const updatedFiltered = returnedArray
219
239
  .filter((item) => item !== filtered[0])
220
- .filter((item) => !childrenOfChecked.includes(item.id));
240
+ .filter((item) => !childrenOfChecked.includes(item.id))
221
241
 
222
- setReturnedArray(updatedFiltered);
242
+ setReturnedArray(updatedFiltered)
223
243
  } else {
224
244
  const updatedFiltered = returnedArray.filter(
225
245
  (item) => item !== filtered[0]
226
- );
227
- setReturnedArray(updatedFiltered);
246
+ )
247
+ setReturnedArray(updatedFiltered)
228
248
  }
229
249
  }
230
250
  } else {
231
- setReturnedArray(checkedItems);
251
+ setReturnedArray(checkedItems)
232
252
  }
233
253
 
234
254
  //when item is unchecked for default variant
235
255
  if (!filtered[0].checked && !returnAllSelected) {
236
256
  //uncheck parent and grandparent if any child unchecked
237
- getAncestorsOfUnchecked(formattedData, filtered[0]);
257
+ getAncestorsOfUnchecked(formattedData, filtered[0])
238
258
 
239
- const newChecked = getCheckedItems(formattedData);
259
+ const newChecked = getCheckedItems(formattedData)
240
260
  //get all checked items, and filter to check if all children checked, if yes return only parent
241
- const filteredReturn = updateReturnItems(newChecked);
242
- setDefaultReturn(filteredReturn);
261
+ const filteredReturn = updateReturnItems(newChecked)
262
+ setDefaultReturn(filteredReturn)
243
263
  }
244
264
 
245
265
  //when item is checked for default variant
@@ -250,7 +270,7 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
250
270
  filtered[0],
251
271
  defaultReturn,
252
272
  setDefaultReturn
253
- );
273
+ )
254
274
  }
255
275
 
256
276
  //if clicked item has parent_id, find parent and check if all children checked or not
@@ -260,26 +280,26 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
260
280
  formattedData,
261
281
  defaultReturn,
262
282
  setDefaultReturn
263
- );
283
+ )
264
284
  } else {
265
- setDefaultReturn([filtered[0]]);
285
+ setDefaultReturn([filtered[0]])
266
286
  }
267
287
  }
268
- };
288
+ }
269
289
 
270
290
  //handle click on chevron toggles in dropdown
271
291
  const handleToggleClick = (id: string, event: React.MouseEvent) => {
272
- event.stopPropagation();
292
+ event.stopPropagation()
273
293
  setIsToggled((prevState: { [id: string]: boolean }) => ({
274
294
  ...prevState,
275
295
  [id]: !prevState[id],
276
- }));
277
- const clickedItem = filterFormattedDataById(formattedData, id);
296
+ }))
297
+ const clickedItem = filterFormattedDataById(formattedData, id)
278
298
 
279
299
  if (clickedItem) {
280
- clickedItem[0].expanded = !clickedItem[0].expanded;
300
+ clickedItem[0].expanded = !clickedItem[0].expanded
281
301
  }
282
- };
302
+ }
283
303
 
284
304
  //rendering formattedData to UI based on typeahead
285
305
  const renderNestedOptions = (items: { [key: string]: any }[]) => {
@@ -289,35 +309,29 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
289
309
  items.map((item: { [key: string]: any }) => {
290
310
  return (
291
311
  <>
292
- <li
293
- key={item.id}
294
- className="dropdown_item"
295
- data-name={item.id}
296
- >
297
- <div className="dropdown_item_checkbox_row">
298
- <div
299
- key={
300
- item.expanded ? "chevron-down" : "chevron-right"
301
- }
302
- >
312
+ <li key={item.id} className='dropdown_item' data-name={item.id}>
313
+ <div className='dropdown_item_checkbox_row'>
314
+ <div key={item.expanded ? "chevron-down" : "chevron-right"}>
303
315
  <CircleIconButton
304
- icon={
305
- item.expanded ? "chevron-down" : "chevron-right"
316
+ icon={item.expanded ? "chevron-down" : "chevron-right"}
317
+ className={
318
+ item.children && item.children.length > 0
319
+ ? ""
320
+ : "toggle_icon"
306
321
  }
307
- className={item.children && item.children.length > 0 ? "" : "toggle_icon"}
308
322
  onClick={(event) => handleToggleClick(item.id, event)}
309
- variant="link"
323
+ variant='link'
310
324
  />
311
325
  </div>
312
326
  <Checkbox text={item.label} id={item.id}>
313
327
  <input
314
328
  checked={item.checked}
315
- type="checkbox"
329
+ type='checkbox'
316
330
  name={item.label}
317
331
  value={item.label}
318
332
  onChange={(e) => {
319
- item.checked = !item.checked;
320
- handledropdownItemClick(e);
333
+ item.checked = !item.checked
334
+ handledropdownItemClick(e)
321
335
  }}
322
336
  />
323
337
  </Checkbox>
@@ -330,23 +344,29 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
330
344
  )}
331
345
  </li>
332
346
  </>
333
- );
347
+ )
334
348
  })}
335
349
  </ul>
336
- );
337
- };
350
+ )
351
+ }
338
352
 
339
353
  return (
340
354
  <div {...ariaProps} {...dataProps} className={classes} id={id}>
341
- <div ref={dropdownRef} className="wrapper">
342
- <div className="input_wrapper" onClick={handleInputWrapperClick}>
343
- <div className="input_inner_container">
355
+ <div ref={dropdownRef} className='wrapper'>
356
+ <div className='input_wrapper' onClick={handleInputWrapperClick}>
357
+ <div className='input_inner_container'>
358
+ <input
359
+ type='hidden'
360
+ id={id}
361
+ name={name}
362
+ value={JSON.stringify(formattedReturn)}
363
+ />
344
364
  {returnedArray.length !== 0 && returnAllSelected
345
365
  ? returnedArray.map((item, index) => (
346
366
  <FormPill
347
367
  key={index}
348
368
  text={item.label}
349
- size="small"
369
+ size='small'
350
370
  onClick={(event) => handlePillClose(event, item)}
351
371
  />
352
372
  ))
@@ -362,29 +382,29 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
362
382
  <FormPill
363
383
  key={index}
364
384
  text={item.label}
365
- size="small"
385
+ size='small'
366
386
  onClick={(event) => handlePillClose(event, item)}
367
387
  />
368
388
  ))}
369
389
  {returnedArray.length !== 0 && returnAllSelected && <br />}
370
390
  {defaultReturn.length !== 0 && !returnAllSelected && <br />}
371
391
  <input
372
- id="multiselect_input"
392
+ id='multiselect_input'
373
393
  onChange={(e) => {
374
- setFilterItem(e.target.value);
394
+ setFilterItem(e.target.value)
375
395
  }}
376
- placeholder="Start typing..."
396
+ placeholder='Start typing...'
377
397
  value={filterItem}
378
398
  onClick={() => setIsClosed(false)}
379
399
  />
380
400
  </div>
381
401
  {isClosed ? (
382
- <div key="chevron-down">
383
- <Icon icon="chevron-down" />
402
+ <div key='chevron-down'>
403
+ <Icon icon='chevron-down' />
384
404
  </div>
385
405
  ) : (
386
- <div key="chevron-up">
387
- <Icon icon="chevron-up" />
406
+ <div key='chevron-up'>
407
+ <Icon icon='chevron-up' />
388
408
  </div>
389
409
  )}
390
410
  </div>
@@ -395,7 +415,7 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
395
415
  </div>
396
416
  </div>
397
417
  </div>
398
- );
399
- };
418
+ )
419
+ }
400
420
 
401
- export default MultiLevelSelect;
421
+ export default MultiLevelSelect
@@ -61,12 +61,12 @@
61
61
  ],
62
62
  },
63
63
  ],
64
- }] %>
64
+ }] %>
65
65
 
66
66
 
67
67
  <%= pb_rails("multi_level_select", props: {
68
68
  id: "default-multi-level-select",
69
69
  tree_data:treeData
70
- }) %>
70
+ }) %>
71
71
 
72
72