playbook_ui 12.26.0 → 12.26.1.pre.alpha.PLAY603datepickerquickpickinputpresetdropdown831

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