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
@@ -8,6 +8,8 @@ examples:
8
8
  - date_picker_input: Input Field
9
9
  - date_picker_label: Label
10
10
  - date_picker_range: Range
11
+ - date_picker_quick_pick_rails: Range (Quick Pick)
12
+ - date_picker_quick_pick_range_limit: Range (Quick Pick w/ “This” Range limit)
11
13
  - date_picker_format: Format
12
14
  - date_picker_disabled: Disabled Dates
13
15
  - date_picker_min_max: Min Max
@@ -33,6 +35,8 @@ examples:
33
35
  - date_picker_on_change: onChange
34
36
  - date_picker_on_close: onClose
35
37
  - date_picker_range: Range
38
+ - date_picker_quick_pick_react: Range (Quick Pick)
39
+ - date_picker_quick_pick_range_limit: Range (Quick Pick w/ “This” Range limit)
36
40
  - date_picker_format: Format
37
41
  - date_picker_disabled: Disabled Dates
38
42
  - date_picker_min_max: Min Max
@@ -19,4 +19,6 @@ export { default as DatePickerWeek } from './_date_picker_week.jsx'
19
19
  export { default as DatePickerPositions } from './_date_picker_positions.jsx'
20
20
  export { default as DatePickerPositionsElement } from './_date_picker_positions_element.jsx'
21
21
  export { default as DatePickerAllowInput } from './_date_picker_allow_input'
22
- export { default as DatePickerOnClose } from './_date_picker_on_close.jsx'
22
+ export { default as DatePickerQuickPickReact } from './_date_picker_quick_pick_react'
23
+ export { default as DatePickerQuickPickRangeLimit } from './_date_picker_quick_pick_range_limit'
24
+ export { default as DatePickerOnClose } from './_date_picker_on_close.jsx'
@@ -0,0 +1,168 @@
1
+ import moment from 'moment'
2
+
3
+ type FpTypes = {
4
+ setDate: (arg0: any, arg1: boolean) => void,
5
+ config: { [key: string]: string },
6
+ clear: (arg0: boolean, arg1: boolean) => void,
7
+ close: () => void,
8
+ calendarContainer?: {
9
+ classList: { add: (arg0: string) => void };
10
+ prepend: (arg0: HTMLDivElement) => void;
11
+ append: (arg0: HTMLDivElement) => void;
12
+ },
13
+ loadedPlugins: string[],
14
+ };
15
+
16
+ type pluginDataType = {
17
+ ranges: { [key: string]: Date[] },
18
+ rangesNav: HTMLUListElement,
19
+ rangesButtons: [] | any,
20
+ }
21
+
22
+ let activeLabel = ""
23
+
24
+ const quickPickPlugin = (thisRangesEndToday: boolean) => {
25
+ return function (fp: FpTypes & any): any {
26
+ const thisWeekEndDate = thisRangesEndToday ? new Date() : moment().endOf('isoWeek').toDate()
27
+ const thisMonthEndDate = thisRangesEndToday ? new Date() : moment().endOf('month').toDate()
28
+ const thisQuarterEndDate = thisRangesEndToday ? new Date() : moment().endOf('quarter').toDate()
29
+ const thisYearEndDate = thisRangesEndToday ? new Date() : moment().endOf('year').toDate()
30
+
31
+ // variable that holds the ranges available
32
+ const ranges = {
33
+ 'Today': [new Date(), new Date()],
34
+ 'Yesterday': [moment().subtract(1, 'days').toDate(), moment().subtract(1, 'days').toDate()],
35
+ 'This week': [moment().startOf('isoWeek').toDate(), thisWeekEndDate],
36
+ 'This month': [moment().startOf('month').toDate(), thisMonthEndDate],
37
+ 'This quarter': [moment().startOf('quarter').toDate(), thisQuarterEndDate],
38
+ 'This year': [moment().startOf('year').toDate(), thisYearEndDate],
39
+ 'Last week': [
40
+ moment().subtract(1, 'week').startOf('isoWeek').toDate(),
41
+ moment().subtract(1, 'week').endOf('isoWeek').toDate()
42
+ ],
43
+ 'Last month': [
44
+ moment().subtract(1, 'month').startOf('month').toDate(),
45
+ moment().subtract(1, 'month').endOf('month').toDate()
46
+ ],
47
+ 'Last quarter': [
48
+ moment().subtract(1, 'quarter').startOf('quarter').toDate(),
49
+ moment().subtract(1, 'quarter').endOf('quarter').toDate()
50
+ ],
51
+ 'Last year': [
52
+ moment().subtract(1, 'year').startOf('year').toDate(),
53
+ moment().subtract(1, 'year').endOf('year').toDate()
54
+ ]
55
+ }
56
+ //creating the ul element for the nav dropdown and giving it classnames
57
+ const rangesNav = document.createElement('ul');
58
+
59
+ // creating the pluginData object that will hold the properties of this plugin
60
+ const pluginData: pluginDataType = {
61
+ ranges: ranges,
62
+ rangesNav: rangesNav,
63
+ rangesButtons: [],
64
+ };
65
+
66
+ /**
67
+ * @param {string} label
68
+ * @returns HTML Element
69
+ */
70
+
71
+ //function for creating the range buttons in the nav
72
+ const addRangeButton = (label: string) => {
73
+
74
+ // creating new elements to mimick selectable card component
75
+ const div2 = document.createElement('div');
76
+ div2.className = "nav-item-link"
77
+ div2.innerHTML = label;
78
+
79
+ pluginData.rangesButtons[label] = div2;
80
+
81
+ // create li elements inside the dropdown
82
+ const item = document.createElement('li');
83
+ item.className = "nav-item";
84
+
85
+ // append those nav items to the li items
86
+ item.appendChild(pluginData.rangesButtons[label]);
87
+
88
+ // append the li item to the ul rangeNav prop
89
+ pluginData.rangesNav.appendChild(item);
90
+
91
+ // return the ranges buton prop
92
+ return pluginData.rangesButtons[label];
93
+ };
94
+
95
+ const selectActiveRangeButton = (selectedDates: Array<string>) => {
96
+ const current = pluginData.rangesNav.querySelector('.active');
97
+
98
+ if (current) {
99
+ current.classList.remove('active');
100
+ }
101
+ /** conditional statment to extract start and end dates from selectedDates,
102
+ * then loop through ranges prop in pluginData
103
+ * and check if chosen dates equal to a date in the ranges prop
104
+ * if they are equal, add the active class
105
+ */
106
+ if (selectedDates.length > 0 && activeLabel) {
107
+ // const selected = pluginData.rangesNav.querySelectorAll(".nav-item-link")
108
+ // selected.forEach(el => {
109
+ // if (el.innerHTML === activeLabel)
110
+ // el.classList.add('active')
111
+ // return
112
+ // })
113
+
114
+ pluginData.rangesButtons[activeLabel].classList.add('active');
115
+ }
116
+ }
117
+
118
+
119
+ return {
120
+ // onReady is a hook from flatpickr that runs when calender is in a ready state
121
+ onReady(selectedDates: Array<string>) {
122
+ // loop through the ranges and create an anchor tag for each range and add an event listener to set the date when user clicks on a date range
123
+ for (const [label, range] of Object.entries(pluginData.ranges)) {
124
+ addRangeButton(label).addEventListener('click', function () {
125
+
126
+ const start = moment(range[0]).toDate();
127
+ const end = moment(range[1]).toDate();
128
+
129
+ if (!start) {
130
+ fp.clear();
131
+ }
132
+ else {
133
+ activeLabel = label
134
+ fp.setDate([start, end], true);
135
+ fp.close();
136
+ }
137
+ });
138
+ }
139
+ // conditional to check if there is a dropdown to add it to the calendar container and get it the classes it needs
140
+ if (pluginData.rangesNav.children.length > 0) {
141
+
142
+ fp.calendarContainer.prepend(pluginData.rangesNav);
143
+ pluginData.rangesNav.classList.add('quick-pick-ul')
144
+ fp.calendarContainer.classList.add('quick-pick-drop-down');
145
+
146
+ /**
147
+ *
148
+ * @param {Array} selectedDates
149
+ */
150
+ // function to give the active button the active class
151
+ selectActiveRangeButton(selectedDates);
152
+ }
153
+ },
154
+ onValueUpdate(selectedDates: Array<string>) {
155
+ selectActiveRangeButton(selectedDates);
156
+ },
157
+
158
+ onClose(selectedDates: Array<string>) {
159
+ // set the input value to the selected dates when the dropdown is closed
160
+ if (selectedDates.length < 2 && selectedDates.length > 0) {
161
+ fp.input.placeholder = fp.formatDate(this.selectedDates[0], fp.config.dateFormat);
162
+ }
163
+ }
164
+ };
165
+ };
166
+ }
167
+
168
+ export default quickPickPlugin;
@@ -1,3 +1,4 @@
1
+ @import "../../tokens/colors";
1
2
  // Calendar Icon Styles
2
3
  .cal_icon_wrapper {
3
4
  pointer-events: none;
@@ -13,8 +14,8 @@
13
14
  padding-left: $space_sm - 1;
14
15
  color: $text_lt_light;
15
16
  @media (hover: hover) {
16
- &:hover {
17
- cursor: pointer;
17
+ &:hover{
18
+ background-color: rgba($focus_input_light,$opacity_5);
18
19
  }
19
20
  }
20
21
  &.dark {
@@ -0,0 +1,75 @@
1
+ @import "../../tokens/animation-curves";
2
+ @import "../../tokens/colors";
3
+ @import "../../tokens/typography";
4
+ @import "../../tokens/titles";
5
+ @import "../../tokens/spacing";
6
+
7
+ $pb_card_border_width: 1px;
8
+ $pb_card_border_radius: $border_rad_heavier;
9
+
10
+ // used to display dropdown on the left of the calender
11
+ .quick-pick-drop-down {
12
+ width: auto;
13
+ display: grid;
14
+ }
15
+
16
+ .quick-pick-ul {
17
+ padding: $space_xs 0px;
18
+ margin: 0;
19
+ list-style: none;
20
+ }
21
+
22
+ .nav-item {
23
+ list-style: none;
24
+ border-radius: 6px;
25
+ border-bottom: 0;
26
+ margin: $space_xs $space_sm;
27
+ }
28
+
29
+ .nav-item-link {
30
+ text-decoration: none;
31
+ border-width: $pb_card_border_width;
32
+ border-style: solid;
33
+ border-color: $border_light;
34
+ border-radius: $pb_card_border_radius;
35
+ padding: $space_xs 14px;
36
+ transition-property: color, background-color;
37
+ transition-duration: 0.15s;
38
+ transition-timing-function: $bezier;
39
+ line-height: 1.4;
40
+ color: $charcoal;
41
+ font-size: $font_default;
42
+ font-weight: $regular;
43
+ &.active {
44
+ border-width: 2px;
45
+ border-color: $primary;
46
+ }
47
+ @media (hover:hover) {
48
+ &:hover {
49
+ cursor: pointer;
50
+ box-shadow: $shadow-deep;
51
+ border-color: $slate;
52
+ }
53
+ }
54
+ }
55
+
56
+ // Hide the calendar
57
+ .quick-pick-drop-down > .flatpickr-months, .quick-pick-drop-down > .flatpickr-innerContainer {
58
+ display: none;
59
+ }
60
+
61
+ @media only screen and (max-width: 767px) {
62
+ .quick-pick-ul {
63
+ padding: $space_xs $space_xs;
64
+ display: grid;
65
+ grid-template-columns: 1fr 1fr;
66
+ }
67
+
68
+ .nav-item {
69
+ margin: $space_xxs $space_xs;
70
+ }
71
+
72
+ .nav-item-link {
73
+ padding: $space_xs $space_xxs;
74
+ }
75
+ }
@@ -1,58 +1,16 @@
1
- //function for unchecking items in formattedData
2
- export const unCheckIt = (
3
- formattedData: { [key: string]: any }[],
4
- id: string
5
- ) => {
6
- formattedData.map((item: { [key: string]: any }) => {
7
- if (item.id === id && item.checked) {
8
- item.checked = false;
9
- }
10
- if (item.children && item.children.length > 0) {
11
- unCheckIt(item.children, id);
12
- }
13
- return item;
14
- });
15
- };
16
-
17
1
  //function to retrieve all ancestors of unchecked item and set checked to false
18
2
  export const getAncestorsOfUnchecked = (
19
- formattedData: { [key: string]: any }[],
3
+ data: { [key: string]: any }[],
20
4
  item: { [key: string]: any }
21
5
  ) => {
22
6
  if (item.parent_id) {
23
- const ancestors = filterFormattedDataById(formattedData, item.parent_id);
24
- ancestors[0].checked = false;
25
-
26
- if (ancestors[0].parent_id) {
27
- getAncestorsOfUnchecked(formattedData, ancestors[0]);
28
- }
7
+ const ancestor = filterFormattedDataById(data, item.parent_id);
8
+ ancestor[0].checked = false;
9
+ ancestor[0].parent_id && getAncestorsOfUnchecked(data, ancestor[0])
29
10
  }
11
+ return data;
30
12
  };
31
-
32
- //recursively check all child and grandchild items if parent checked
33
- export const checkedRecursive = (item: { [key: string]: any }) => {
34
- if (!item.checked) {
35
- item.checked = true;
36
- }
37
- if (item.children && item.children.length > 0) {
38
- item.children.forEach((childItem: { [key: string]: any }) => {
39
- checkedRecursive(childItem);
40
- });
41
- }
42
- };
43
-
44
- //recursively uncheck all child and grandchild items if parent unchecked
45
- export const unCheckedRecursive = (item: { [key: string]: any }) => {
46
- if (item.checked) {
47
- item.checked = false;
48
- }
49
- if (item.children && item.children.length > 0) {
50
- item.children.forEach((childItem: { [key: string]: any }) => {
51
- unCheckedRecursive(childItem);
52
- });
53
- }
54
- };
55
-
13
+
56
14
  //function is going over formattedData and returning all objects that match the
57
15
  //id of the clicked item from the dropdown
58
16
  export const filterFormattedDataById = (
@@ -64,6 +22,7 @@ export const filterFormattedDataById = (
64
22
  for (const item of data) {
65
23
  if (item.id.toLowerCase() === (term.toLowerCase())) {
66
24
  matched.push(item);
25
+ return
67
26
  }
68
27
 
69
28
  if (item.children && item.children.length > 0) {
@@ -116,97 +75,60 @@ export const getCheckedItems = (
116
75
  });
117
76
  return checkedItems;
118
77
  };
78
+
79
+ export const getDefaultCheckedItems = (treeData:{ [key: string]: any }[]) => {
80
+ const checkedDefault: { [key: string]: any }[] = [];
119
81
 
120
- export const getChildIds = (
121
- item: { [key: string]: any },
122
- defaultArray: { [key: string]: any }[]
123
- ) => {
124
- let childIds: string[] = [];
125
- item.children.forEach((child: { [key: string]: any }) => {
126
- childIds.push(child.id);
127
- if (child.children && child.children.length > 0) {
128
- const childChildIds = getChildIds(child, defaultArray);
129
- childIds.push(...childChildIds);
82
+ const traverseTree = (items:{ [key: string]: any }[]) => {
83
+ if (!Array.isArray(items)) {
84
+ return;
130
85
  }
131
- });
132
- return childIds;
133
- };
86
+ items.forEach((item:{ [key: string]: any }) => {
87
+ if (item.checked) {
88
+ if (item.children && item.children.length > 0) {
89
+ const uncheckedChildren = item.children.filter((child:{ [key: string]: any }) => !child.checked);
90
+ if (uncheckedChildren.length === 0) {
91
+ checkedDefault.push(item);
92
+ return;
93
+ }
94
+ } else {
95
+ const parent = items.find((parentItem:{ [key: string]: any }) => parentItem.id === item.parentId);
96
+ if (!parent || !parent.checked) {
97
+ checkedDefault.push(item);
98
+ }
99
+ }
100
+ }
134
101
 
135
- export const updateReturnItems = (newChecked: { [key: string]: any }[]) => {
136
- const updatedCheckedItems: { [key: string]: any }[] = [];
137
- for (const item of newChecked) {
138
- if (item.children && item.children.length > 0) {
139
- const allChildrenChecked = item.children.every(
140
- (child: { [key: string]: any }) => child.checked
141
- );
142
- if (allChildrenChecked) {
143
- updatedCheckedItems.push(item);
102
+ if (item.children && item.children.length > 0) {
103
+ traverseTree(item.children);
144
104
  }
145
- }
146
- const childItem = updatedCheckedItems.some((x) => x.id === item?.parent_id);
147
- if (!childItem) {
148
- updatedCheckedItems.push(item);
149
- }
150
- }
151
- const filteredReturn = updatedCheckedItems.filter((item) => {
152
- return !updatedCheckedItems.find(
153
- (otherItem) => otherItem.id === item.parent_id
154
- );
155
- });
156
- return filteredReturn;
105
+ });
106
+ };
107
+
108
+ traverseTree(treeData);
109
+
110
+ return checkedDefault;
157
111
  };
158
112
 
159
- export const recursiveReturnOnlyParent = (
160
- items: { [key: string]: any },
161
- formattedData: { [key: string]: any }[],
162
- defaultReturn: { [key: string]: any }[],
163
- setDefaultReturn: any
113
+ export const recursiveCheckParent = (
114
+ item: { [key: string]: any },
115
+ data:any
164
116
  ) => {
165
- const parent = filterFormattedDataById(formattedData, items.parent_id);
117
+ if (item.parent_id !== null) {
118
+ const parent = filterFormattedDataById(data, item.parent_id);
166
119
  const allChildrenChecked = parent[0].children.every(
167
120
  (child: { [key: string]: any }) => child.checked
168
121
  );
169
122
  if (allChildrenChecked) {
170
- // Only return the parent and remove its children from defaultReturn
171
123
  parent[0].checked = true;
172
- const filteredDefaultReturn = defaultReturn.filter((item) => {
173
- // Remove children of the specific parent
174
- if (
175
- parent[0].children.find(
176
- (child: { [key: string]: any }) => child.id === item.id
177
- )
178
- ) {
179
- return false;
180
- }
181
- });
182
- setDefaultReturn([...filteredDefaultReturn, parent[0]]);
183
- // Check if the parent has a parent and its children are all checked
184
124
  const parentHasParent = parent[0].parent_id !== null;
185
125
  if (parentHasParent) {
186
- recursiveReturnOnlyParent(
126
+ recursiveCheckParent(
187
127
  parent[0],
188
- formattedData,
189
- filteredDefaultReturn,
190
- setDefaultReturn
128
+ data
191
129
  );
192
130
  }
193
- } else {
194
- const checkedChildren = parent[0].children.filter(
195
- (child: { [key: string]: any }) => child.checked
196
- );
197
- const updatedDefaultReturn = [...defaultReturn, ...checkedChildren];
198
- setDefaultReturn(updatedDefaultReturn);
199
- }
200
- };
201
-
202
- export const removeChildrenIfParentChecked = (
203
- items: { [key: string]: any },
204
- defaultReturn: { [key: string]: any }[],
205
- setDefaultReturn: any
206
- ) => {
207
- const childIds = getChildIds(items, defaultReturn);
208
- const filteredDefaultArray = defaultReturn.filter(
209
- (item: { [key: string]: any }) => childIds !== item.id
210
- );
211
- setDefaultReturn([...filteredDefaultArray, items]);
212
- };
131
+ }
132
+ }
133
+ return data;
134
+ }