playbook_ui 12.26.0 → 12.26.1.pre.alpha.PLAY603datepickerquickpickinputpresetdropdown831

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 (26) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_date_picker/_date_picker.scss +26 -0
  3. data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +102 -95
  4. data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +18 -2
  5. data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +15 -4
  6. data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +84 -1
  7. data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +38 -4
  8. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_rails.html.erb +12 -0
  9. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_rails.md +3 -0
  10. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_range_limit.html.erb +12 -0
  11. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_range_limit.jsx +18 -0
  12. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_range_limit.md +1 -0
  13. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_react.jsx +17 -0
  14. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_react.md +1 -0
  15. data/app/pb_kits/playbook/pb_date_picker/docs/example.yml +4 -0
  16. data/app/pb_kits/playbook/pb_date_picker/docs/index.js +3 -1
  17. data/app/pb_kits/playbook/pb_date_picker/plugins/quickPick.tsx +168 -0
  18. data/app/pb_kits/playbook/pb_date_picker/sass_partials/_calendar_input_icon.scss +3 -2
  19. data/app/pb_kits/playbook/pb_date_picker/sass_partials/_quick_pick_styles.scss +75 -0
  20. data/app/pb_kits/playbook/pb_multi_level_select/_helper_functions.tsx +47 -125
  21. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +125 -166
  22. data/app/pb_kits/playbook/pb_nav/_item.tsx +1 -1
  23. data/app/pb_kits/playbook/pb_nav/_subtle_mixin.scss +1 -1
  24. data/dist/playbook-rails.js +279 -7
  25. data/lib/playbook/version.rb +2 -2
  26. metadata +16 -7
@@ -6,18 +6,15 @@ import Icon from "../pb_icon/_icon";
6
6
  import Checkbox from "../pb_checkbox/_checkbox";
7
7
  import FormPill from "../pb_form_pill/_form_pill";
8
8
  import CircleIconButton from "../pb_circle_icon_button/_circle_icon_button";
9
+ import { cloneDeep } from "lodash";
10
+
9
11
  import {
10
- unCheckIt,
11
12
  getAncestorsOfUnchecked,
12
- unCheckedRecursive,
13
- checkedRecursive,
14
13
  filterFormattedDataById,
15
14
  findByFilter,
16
15
  getCheckedItems,
17
- updateReturnItems,
18
- recursiveReturnOnlyParent,
19
- removeChildrenIfParentChecked,
20
- getChildIds,
16
+ getDefaultCheckedItems,
17
+ recursiveCheckParent,
21
18
  } from "./_helper_functions";
22
19
 
23
20
  type MultiLevelSelectProps = {
@@ -51,6 +48,8 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
51
48
 
52
49
  const dropdownRef = useRef(null);
53
50
 
51
+ //state for expanded property
52
+ const [expanded, setExpanded] = useState([]);
54
53
  //state for whether dropdown is open or closed
55
54
  const [isClosed, setIsClosed] = useState(true);
56
55
  //state from onchange for textinput, to use for filtering to create typeahead
@@ -58,45 +57,29 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
58
57
  //this is essentially the return that the user will get when they use the kit
59
58
  const [returnedArray, setReturnedArray] = useState([]);
60
59
  //formattedData with checked and parent_id added
61
- const [formattedData, setFormattedData] = useState(treeData);
62
- //toggle chevron in dropdown
63
- //@ts-ignore
64
- const [isToggled, setIsToggled] = useState<{ [id: number]: boolean }>({});
60
+ const [formattedData, setFormattedData] = useState([]);
65
61
  //state for return for default
66
62
  const [defaultReturn, setDefaultReturn] = useState([]);
67
63
 
68
64
  useEffect(() => {
69
- let el = document.getElementById(`pb_data_wrapper_${id}`);
70
- if (el) {
71
- el.setAttribute(
72
- "data-tree",
73
- JSON.stringify(returnAllSelected ? returnedArray : defaultReturn)
74
- );
65
+ setFormattedData(addCheckedAndParentProperty(treeData));
66
+ }, [treeData]);
67
+
68
+ useEffect(() => {
69
+ if (returnAllSelected) {
70
+ setReturnedArray(getCheckedItems(formattedData));
71
+ } else {
72
+ setDefaultReturn(getDefaultCheckedItems(formattedData));
75
73
  }
76
- returnAllSelected
77
- ? onSelect(returnedArray)
78
- : onSelect(
79
- defaultReturn.filter(
80
- (item, index, self) =>
81
- index === self.findIndex((obj) => obj.id === item.id)
82
- )
83
- );
84
- }, [returnedArray, defaultReturn]);
74
+ }, [formattedData]);
85
75
 
86
76
  useEffect(() => {
87
- //Create new formattedData array for use
88
- setFormattedData(addCheckedAndParentProperty(treeData));
89
77
  // Function to handle clicks outside the dropdown
90
78
  const handleClickOutside = (event: any) => {
91
79
  if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
92
80
  setIsClosed(true);
93
81
  }
94
82
  };
95
- //if any items already checked in first render, set return accordingly
96
- const initialChecked = getCheckedItems(treeData)
97
- initialChecked && returnAllSelected && setReturnedArray(initialChecked)
98
- initialChecked && !returnAllSelected && setDefaultReturn(initialChecked)
99
-
100
83
  // Attach the event listener
101
84
  window.addEventListener("click", handleClickOutside);
102
85
  // Clean up the event listener on unmount
@@ -105,11 +88,72 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
105
88
  };
106
89
  }, []);
107
90
 
91
+ const modifyRecursive = (tree: { [key: string]: any }[], check: boolean) => {
92
+ if (!Array.isArray(tree)) {
93
+ return;
94
+ }
95
+ return tree.map((item: { [key: string]: any }) => {
96
+ item.checked = check;
97
+ item.children = modifyRecursive(item.children, check);
98
+ return item;
99
+ });
100
+ };
101
+
102
+ //iterate over tree, find item and set checked or unchecked
103
+ const modifyValue = (
104
+ id: string,
105
+ tree: { [key: string]: any }[],
106
+ check: boolean
107
+ ) => {
108
+ if (!Array.isArray(tree)) {
109
+ return;
110
+ }
111
+ return tree.map((item: any) => {
112
+ if (item.id != id) item.children = modifyValue(id, item.children, check);
113
+ else {
114
+ item.checked = check;
115
+ item.children = modifyRecursive(item.children, check);
116
+ }
117
+
118
+ return item;
119
+ });
120
+ };
121
+
122
+ //clone tree, check items + children
123
+ const checkItem = (item: { [key: string]: any }) => {
124
+ const tree = cloneDeep(formattedData);
125
+ if (returnAllSelected) {
126
+ return modifyValue(item.id, tree, true);
127
+ } else {
128
+ const checkedTree = modifyValue(item.id, tree, true);
129
+ return recursiveCheckParent(item, checkedTree);
130
+ }
131
+ };
132
+
133
+ //clone tree, uncheck items + children
134
+ const unCheckItem = (item: { [key: string]: any }) => {
135
+ const tree = cloneDeep(formattedData);
136
+ if (returnAllSelected) {
137
+ return modifyValue(item.id, tree, false);
138
+ } else {
139
+ const uncheckedTree = modifyValue(item.id, tree, false);
140
+ return getAncestorsOfUnchecked(uncheckedTree, item);
141
+ }
142
+ };
143
+
144
+ //setformattedData with proper properties
145
+ const changeItem = (item: { [key: string]: any }, check: boolean) => {
146
+ const tree = check ? checkItem(item) : unCheckItem(item);
147
+ setFormattedData(tree);
148
+
149
+ return tree;
150
+ };
151
+
108
152
  //function to map over data and add parent_id + depth property to each item
109
153
  const addCheckedAndParentProperty = (
110
154
  treeData: { [key: string]: any }[],
111
155
  parent_id: string = null,
112
- depth: number = 0,
156
+ depth: number = 0
113
157
  ) => {
114
158
  if (!Array.isArray(treeData)) {
115
159
  return;
@@ -121,10 +165,14 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
121
165
  depth,
122
166
  };
123
167
  if (newItem.children && newItem.children.length > 0) {
168
+ const children =
169
+ item.checked && !returnAllSelected
170
+ ? modifyRecursive(item.children, true)
171
+ : item.children;
124
172
  newItem.children = addCheckedAndParentProperty(
125
- newItem.children,
173
+ children,
126
174
  newItem.id,
127
- depth + 1,
175
+ depth + 1
128
176
  );
129
177
  }
130
178
  return newItem;
@@ -135,37 +183,13 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
135
183
  const handlePillClose = (event: any, clickedItem: { [key: string]: any }) => {
136
184
  // prevents the dropdown from closing when clicking on the pill
137
185
  event.stopPropagation();
186
+ const updatedTree = changeItem(clickedItem, false);
138
187
  //logic for removing items from returnArray or defaultReturn when pills clicked
139
188
  if (returnAllSelected) {
140
- if (returnedArray.includes(clickedItem)) {
141
- if (clickedItem.children && clickedItem.children.length > 0) {
142
- const childrenOfChecked = getChildIds(clickedItem, returnedArray);
143
- const updatedFiltered = returnedArray
144
- .filter((item) => item !== clickedItem)
145
- .filter((item) => !childrenOfChecked.includes(item.id));
146
- setReturnedArray(updatedFiltered);
147
- } else {
148
- const updatedFiltered = returnedArray.filter(
149
- (item) => item !== clickedItem
150
- );
151
- setReturnedArray(updatedFiltered);
152
- }
153
- }
189
+ onSelect(getCheckedItems(updatedTree));
154
190
  } else {
155
- if (defaultReturn.includes(clickedItem)) {
156
- getAncestorsOfUnchecked(formattedData, clickedItem);
157
- const newChecked = getCheckedItems(formattedData);
158
- const filteredReturn = updateReturnItems(newChecked).filter(
159
- (item) => item.id !== clickedItem.id
160
- );
161
- setDefaultReturn(filteredReturn);
162
- }
163
- }
164
- if (clickedItem.children && clickedItem.children.length > 0) {
165
- unCheckedRecursive(clickedItem);
191
+ onSelect(getDefaultCheckedItems(updatedTree));
166
192
  }
167
- //logic to uncheck clickedItem in formattedData
168
- unCheckIt(formattedData, clickedItem.id);
169
193
  };
170
194
 
171
195
  //handle click on input wrapper(entire div with pills, typeahead, etc) so it doesn't close when input or form pill is clicked
@@ -181,95 +205,36 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
181
205
  };
182
206
 
183
207
  //Main function to handle any click inside dropdown
184
- const handledropdownItemClick = (e: any) => {
208
+ const handledropdownItemClick = (e: any, check: boolean) => {
185
209
  const clickedItem = e.target.parentNode.id;
186
210
  //setting filterItem to "" will clear textinput and clear typeahead
187
211
  setFilterItem("");
188
212
 
189
213
  const filtered = filterFormattedDataById(formattedData, clickedItem);
190
- //check and uncheck all children of checked/unchecked parent item
191
- if (filtered[0].children && filtered[0].children.length > 0) {
192
- if (filtered[0].checked) {
193
- filtered[0].children.forEach((item: { [key: string]: any }) => {
194
- checkedRecursive(item);
195
- });
196
- } else if (!filtered[0].checked) {
197
- filtered[0].children.forEach((item: { [key: string]: any }) => {
198
- unCheckedRecursive(item);
199
- });
200
- }
201
- }
202
-
203
- const checkedItems = getCheckedItems(formattedData);
204
-
205
- //checking and unchecking items for returnAllSelected variant
206
- if (returnedArray.includes(filtered[0])) {
207
- if (!filtered[0].checked) {
208
- if (filtered[0].children && filtered[0].children.length > 0) {
209
- const childrenOfChecked = getChildIds(filtered[0], returnedArray);
210
- const updatedFiltered = returnedArray
211
- .filter((item) => item !== filtered[0])
212
- .filter((item) => !childrenOfChecked.includes(item.id));
213
-
214
- setReturnedArray(updatedFiltered);
215
- } else {
216
- const updatedFiltered = returnedArray.filter(
217
- (item) => item !== filtered[0]
218
- );
219
- setReturnedArray(updatedFiltered);
220
- }
221
- }
214
+ const updatedTree = changeItem(filtered[0], check);
215
+ console.log(updatedTree);
216
+ if (returnAllSelected) {
217
+ onSelect(getCheckedItems(updatedTree));
222
218
  } else {
223
- setReturnedArray(checkedItems);
224
- }
225
-
226
- //when item is unchecked for default variant
227
- if (!filtered[0].checked && !returnAllSelected) {
228
- //uncheck parent and grandparent if any child unchecked
229
- getAncestorsOfUnchecked(formattedData, filtered[0]);
230
-
231
- const newChecked = getCheckedItems(formattedData);
232
- //get all checked items, and filter to check if all children checked, if yes return only parent
233
- const filteredReturn = updateReturnItems(newChecked);
234
- setDefaultReturn(filteredReturn);
235
- }
236
-
237
- //when item is checked for default variant
238
- if (!returnAllSelected && filtered[0].checked) {
239
- //if checked item has children
240
- if (filtered[0].children && filtered[0].children.length > 0) {
241
- removeChildrenIfParentChecked(
242
- filtered[0],
243
- defaultReturn,
244
- setDefaultReturn
245
- );
246
- }
247
-
248
- //if clicked item has parent_id, find parent and check if all children checked or not
249
- if (filtered[0].parent_id !== null) {
250
- recursiveReturnOnlyParent(
251
- filtered[0],
252
- formattedData,
253
- defaultReturn,
254
- setDefaultReturn
255
- );
256
- } else {
257
- setDefaultReturn([filtered[0]]);
258
- }
219
+ onSelect(getDefaultCheckedItems(updatedTree));
259
220
  }
260
221
  };
261
222
 
223
+ const isExpanded = (item: any) => expanded.indexOf(item.id) > -1;
224
+
262
225
  //handle click on chevron toggles in dropdown
263
226
  const handleToggleClick = (id: string, event: React.MouseEvent) => {
264
227
  event.stopPropagation();
265
- setIsToggled((prevState: { [id: string]: boolean }) => ({
266
- ...prevState,
267
- [id]: !prevState[id],
268
- }));
269
228
  const clickedItem = filterFormattedDataById(formattedData, id);
270
-
271
229
  if (clickedItem) {
272
- clickedItem[0].expanded = !clickedItem[0].expanded;
230
+ let expandedArray = [...expanded];
231
+ const itemExpanded = isExpanded(clickedItem[0]);
232
+
233
+ if (itemExpanded)
234
+ expandedArray = expandedArray.filter((i) => i != clickedItem[0].id);
235
+ else expandedArray.push(clickedItem[0].id);
236
+
237
+ setExpanded(expandedArray);
273
238
  }
274
239
  };
275
240
 
@@ -281,23 +246,23 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
281
246
  items.map((item: { [key: string]: any }) => {
282
247
  return (
283
248
  <>
284
- <li
285
- key={item.id}
286
- className="dropdown_item"
287
- data-name={item.id}
288
- >
249
+ <li key={item.id} className="dropdown_item" data-name={item.id}>
289
250
  <div className="dropdown_item_checkbox_row">
290
251
  <div
291
- key={
292
- item.expanded ? "chevron-down" : "chevron-right"
293
- }
252
+ key={isExpanded(item) ? "chevron-down" : "chevron-right"}
294
253
  >
295
254
  <CircleIconButton
296
255
  icon={
297
- item.expanded ? "chevron-down" : "chevron-right"
256
+ isExpanded(item) ? "chevron-down" : "chevron-right"
257
+ }
258
+ className={
259
+ item.children && item.children.length > 0
260
+ ? ""
261
+ : "toggle_icon"
262
+ }
263
+ onClick={(event: any) =>
264
+ handleToggleClick(item.id, event)
298
265
  }
299
- className={item.children && item.children.length > 0 ? "" : "toggle_icon"}
300
- onClick={(event) => handleToggleClick(item.id, event)}
301
266
  variant="link"
302
267
  />
303
268
  </div>
@@ -308,13 +273,12 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
308
273
  name={item.label}
309
274
  value={item.label}
310
275
  onChange={(e) => {
311
- item.checked = !item.checked;
312
- handledropdownItemClick(e);
276
+ handledropdownItemClick(e, !item.checked);
313
277
  }}
314
278
  />
315
279
  </Checkbox>
316
280
  </div>
317
- {item.expanded &&
281
+ {isExpanded(item) &&
318
282
  item.children &&
319
283
  item.children.length > 0 &&
320
284
  !filterItem && ( // Show children if expanded is true
@@ -339,25 +303,20 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
339
303
  key={index}
340
304
  text={item.label}
341
305
  size="small"
342
- onClick={(event) => handlePillClose(event, item)}
306
+ onClick={(event: any) => handlePillClose(event, item)}
343
307
  />
344
308
  ))
345
309
  : null}
346
310
  {!returnAllSelected &&
347
311
  defaultReturn.length !== 0 &&
348
- defaultReturn
349
- .filter(
350
- (item, index, self) =>
351
- index === self.findIndex((obj) => obj.id === item.id)
352
- )
353
- .map((item, index) => (
354
- <FormPill
355
- key={index}
356
- text={item.label}
357
- size="small"
358
- onClick={(event) => handlePillClose(event, item)}
359
- />
360
- ))}
312
+ defaultReturn.map((item, index) => (
313
+ <FormPill
314
+ key={index}
315
+ text={item.label}
316
+ size="small"
317
+ onClick={(event: any) => handlePillClose(event, item)}
318
+ />
319
+ ))}
361
320
  {returnedArray.length !== 0 && returnAllSelected && <br />}
362
321
  {defaultReturn.length !== 0 && !returnAllSelected && <br />}
363
322
  <input
@@ -87,7 +87,7 @@ const NavItem = (props: NavItemProps) => {
87
87
  <span className="pb_nav_list_item_text">
88
88
  {text || children}
89
89
  </span>
90
-
90
+
91
91
  {iconRight &&
92
92
  <div
93
93
  className="pb_nav_list_item_icon_section"
@@ -44,7 +44,7 @@
44
44
  &[class*=_active] [class*=_link] {
45
45
  @include pb_title_4;
46
46
  color: $primary;
47
- letter-spacing: normal;
47
+ letter-spacing: normal;
48
48
  }
49
49
  }
50
50
  }