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

Sign up to get free protection for your applications and to get access to all the features.
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