playbook_ui 12.27.0 → 12.28.0.pre.alpha.PLAY603datepickerquickpickinputpresetdropdown869
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 +4 -4
- data/app/pb_kits/playbook/pb_date_picker/_date_picker.scss +26 -0
- data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +102 -95
- data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +18 -2
- data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +15 -4
- data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +84 -1
- data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +38 -4
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_rails.html.erb +12 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_rails.md +3 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_range_limit.html.erb +12 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_range_limit.jsx +18 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_range_limit.md +1 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_react.jsx +17 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_react.md +1 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/example.yml +4 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/index.js +3 -1
- data/app/pb_kits/playbook/pb_date_picker/plugins/quickPick.tsx +185 -0
- data/app/pb_kits/playbook/pb_date_picker/sass_partials/_calendar_input_icon.scss +3 -2
- data/app/pb_kits/playbook/pb_date_picker/sass_partials/_quick_pick_styles.scss +75 -0
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +28 -3
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx +1 -0
- data/app/pb_kits/playbook/pb_nav/_item.tsx +1 -1
- data/app/pb_kits/playbook/pb_nav/_subtle_mixin.scss +1 -1
- data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +26 -5
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_access_input_element.jsx +26 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_access_input_element.md +3 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_clear_field.jsx +30 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_clear_field.md +3 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_custom.html.erb +11 -0
- data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_custom.jsx +36 -0
- data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_custom.md +19 -0
- data/app/pb_kits/playbook/pb_selectable_card_icon/docs/example.yml +2 -2
- data/app/pb_kits/playbook/pb_selectable_card_icon/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.html.erb +2 -0
- data/app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.rb +2 -0
- data/app/pb_kits/playbook/pb_selectable_icon/selectable_icon.html.erb +1 -1
- data/app/pb_kits/playbook/pb_selectable_icon/selectable_icon.rb +2 -0
- data/dist/playbook-rails.js +279 -7
- data/lib/playbook/version.rb +2 -2
- metadata +23 -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
|
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,185 @@
|
|
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<Date>) => {
|
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
|
+
|
107
|
+
if (selectedDates.length > 0 && activeLabel) {
|
108
|
+
// const selected = pluginData.rangesNav.querySelectorAll(".nav-item-link")
|
109
|
+
// selected.forEach(el => {
|
110
|
+
// if (el.innerHTML === activeLabel)
|
111
|
+
// el.classList.add('active')
|
112
|
+
// return
|
113
|
+
// })
|
114
|
+
|
115
|
+
pluginData.rangesButtons[activeLabel].classList.add('active');
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
const isLabelMatchingSelectedDates = (selectedDates: Array<Date>) => {
|
120
|
+
return activeLabel && selectedDates[0].toDateString() === pluginData.ranges[activeLabel][0].toDateString() &&
|
121
|
+
selectedDates[1].toDateString() === pluginData.ranges[activeLabel][1].toDateString()
|
122
|
+
}
|
123
|
+
|
124
|
+
|
125
|
+
return {
|
126
|
+
// onReady is a hook from flatpickr that runs when calender is in a ready state
|
127
|
+
onReady(selectedDates: Array<Date>) {
|
128
|
+
// 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
|
129
|
+
for (const [label, range] of Object.entries(pluginData.ranges)) {
|
130
|
+
addRangeButton(label).addEventListener('click', function () {
|
131
|
+
|
132
|
+
const start = moment(range[0]).toDate();
|
133
|
+
const end = moment(range[1]).toDate();
|
134
|
+
|
135
|
+
if (!start) {
|
136
|
+
fp.clear();
|
137
|
+
}
|
138
|
+
else {
|
139
|
+
activeLabel = label
|
140
|
+
fp.setDate([start, end], true);
|
141
|
+
fp.close();
|
142
|
+
}
|
143
|
+
});
|
144
|
+
}
|
145
|
+
// conditional to check if there is a dropdown to add it to the calendar container and get it the classes it needs
|
146
|
+
if (pluginData.rangesNav.children.length > 0) {
|
147
|
+
|
148
|
+
fp.calendarContainer.prepend(pluginData.rangesNav);
|
149
|
+
pluginData.rangesNav.classList.add('quick-pick-ul')
|
150
|
+
fp.calendarContainer.classList.add('quick-pick-drop-down');
|
151
|
+
|
152
|
+
/**
|
153
|
+
*
|
154
|
+
* @param {Array} selectedDates
|
155
|
+
*/
|
156
|
+
// function to give the active button the active class
|
157
|
+
selectActiveRangeButton(selectedDates);
|
158
|
+
}
|
159
|
+
},
|
160
|
+
onValueUpdate(selectedDates: Array<Date>) {
|
161
|
+
selectActiveRangeButton(selectedDates)
|
162
|
+
},
|
163
|
+
|
164
|
+
onClose(selectedDates: Array<Date>) {
|
165
|
+
// remove the active class from the button if the selected dates don't match the label
|
166
|
+
if (!isLabelMatchingSelectedDates(selectedDates)) {
|
167
|
+
pluginData.rangesButtons[activeLabel]?.classList.remove('active');
|
168
|
+
activeLabel = ""
|
169
|
+
}
|
170
|
+
|
171
|
+
// set the date to the first date in the array if the user types only one date
|
172
|
+
if (selectedDates.length === 1) {
|
173
|
+
fp.setDate([selectedDates[0], selectedDates[0]], true);
|
174
|
+
}
|
175
|
+
|
176
|
+
// set the input value to the selected dates when the dropdown is closed
|
177
|
+
if (selectedDates.length < 2 && selectedDates.length > 0) {
|
178
|
+
fp.input.placeholder = fp.formatDate(this.selectedDates[0], fp.config.dateFormat);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
};
|
182
|
+
};
|
183
|
+
}
|
184
|
+
|
185
|
+
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
|
-
|
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
|
+
}
|
@@ -51,8 +51,25 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
51
51
|
const dropdownRef = useRef(null)
|
52
52
|
|
53
53
|
|
54
|
-
|
55
|
-
|
54
|
+
const getExpandedItems = (treeData: { [key: string]: string }[]) => {
|
55
|
+
let expandedItems: any[] = [];
|
56
|
+
|
57
|
+
const traverse = (items: string | any[]) => {
|
58
|
+
for (let i = 0; i < items.length; i++) {
|
59
|
+
const item = items[i];
|
60
|
+
if (item.expanded) {
|
61
|
+
expandedItems.push(item.id);
|
62
|
+
}
|
63
|
+
if (Array.isArray(item.children)) {
|
64
|
+
traverse(item.children);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
traverse(treeData);
|
70
|
+
return expandedItems;
|
71
|
+
}
|
72
|
+
|
56
73
|
//state for whether dropdown is open or closed
|
57
74
|
const [isClosed, setIsClosed] = useState(true)
|
58
75
|
//state from onchange for textinput, to use for filtering to create typeahead
|
@@ -63,6 +80,10 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
63
80
|
const [formattedData, setFormattedData] = useState([])
|
64
81
|
//state for return for default
|
65
82
|
const [defaultReturn, setDefaultReturn] = useState([])
|
83
|
+
// Get expanded items from treeData.
|
84
|
+
const initialExpandedItems = getExpandedItems(treeData);
|
85
|
+
// Initialize state with expanded items.
|
86
|
+
const [expanded, setExpanded] = useState(initialExpandedItems);
|
66
87
|
|
67
88
|
|
68
89
|
useEffect(() => {
|
@@ -153,11 +174,13 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
153
174
|
return tree
|
154
175
|
}
|
155
176
|
|
177
|
+
|
178
|
+
|
156
179
|
//function to map over data and add parent_id + depth property to each item
|
157
180
|
const addCheckedAndParentProperty = (
|
158
181
|
treeData: { [key: string]: any }[],
|
159
182
|
parent_id: string = null,
|
160
|
-
depth: number = 0
|
183
|
+
depth: number = 0,
|
161
184
|
) => {
|
162
185
|
if (!Array.isArray(treeData)) {
|
163
186
|
return
|
@@ -295,6 +318,8 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
|
295
318
|
)
|
296
319
|
}
|
297
320
|
|
321
|
+
|
322
|
+
|
298
323
|
return (
|
299
324
|
<div {...ariaProps} {...dataProps} className={classes} id={id}>
|
300
325
|
<div ref={dropdownRef} className='wrapper'>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { forwardRef, useEffect, useRef, useState } from 'react'
|
1
|
+
import React, { forwardRef, useEffect, useRef, useState, useImperativeHandle } from 'react'
|
2
2
|
import classnames from 'classnames'
|
3
3
|
|
4
4
|
import intlTelInput from 'intl-tel-input'
|
@@ -64,7 +64,7 @@ const containOnlyNumbers = (value: string) => {
|
|
64
64
|
return /^[()+\-\ .\d]*$/g.test(value)
|
65
65
|
}
|
66
66
|
|
67
|
-
const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
67
|
+
const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefObject<unknown>) => {
|
68
68
|
const {
|
69
69
|
aria = {},
|
70
70
|
className,
|
@@ -111,6 +111,23 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
111
111
|
}
|
112
112
|
}, [error, onValidate])
|
113
113
|
|
114
|
+
/*
|
115
|
+
useImperativeHandle exposes the kit's input element to a parent component via a ref.
|
116
|
+
See the Playbook docs for use cases.
|
117
|
+
Read: https://react.dev/reference/react/useImperativeHandle
|
118
|
+
*/
|
119
|
+
useImperativeHandle(ref, () => {
|
120
|
+
return {
|
121
|
+
clearField() {
|
122
|
+
setInputValue("")
|
123
|
+
setError("")
|
124
|
+
},
|
125
|
+
inputNode() {
|
126
|
+
return inputRef.current
|
127
|
+
}
|
128
|
+
}
|
129
|
+
})
|
130
|
+
|
114
131
|
const showFormattedError = (reason = '') => {
|
115
132
|
const countryName = itiInit.getSelectedCountryData().name
|
116
133
|
const reasonText = reason.length > 0 ? ` (${reason})` : ''
|
@@ -201,8 +218,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
201
218
|
allowDropdown: !disabled,
|
202
219
|
autoInsertDialCode: false,
|
203
220
|
initialCountry,
|
204
|
-
onlyCountries
|
205
|
-
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/18.1.6/js/utils.min.js"
|
221
|
+
onlyCountries
|
206
222
|
})
|
207
223
|
|
208
224
|
inputRef.current.addEventListener("countrychange", (evt: Event) => {
|
@@ -242,7 +258,12 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
242
258
|
return (
|
243
259
|
<div {...wrapperProps}>
|
244
260
|
<TextInput
|
245
|
-
ref={
|
261
|
+
ref={
|
262
|
+
inputNode => {
|
263
|
+
ref ? ref.current = inputNode : null
|
264
|
+
inputRef.current = inputNode
|
265
|
+
}
|
266
|
+
}
|
246
267
|
{...textInputProps}
|
247
268
|
/>
|
248
269
|
</div>
|
data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_access_input_element.jsx
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
import React, { useEffect, useRef } from 'react'
|
2
|
+
import { Body, PhoneNumberInput } from '../..'
|
3
|
+
|
4
|
+
const PhoneNumberInputAccessInputElement = (props) => {
|
5
|
+
// 1. Create a ref - this accesses the kit's input element.
|
6
|
+
const ref = useRef()
|
7
|
+
|
8
|
+
// 2. Add any event listener to ref.current.inputNode() inside a useEffect hook and trigger it once.
|
9
|
+
useEffect(() => {
|
10
|
+
ref.current.inputNode().addEventListener("click", () => alert("Clicked!"))
|
11
|
+
}, [])
|
12
|
+
|
13
|
+
// 3. Pass the ref to the ref prop.
|
14
|
+
return (
|
15
|
+
<>
|
16
|
+
<Body text="Click the input field below:" />
|
17
|
+
<PhoneNumberInput
|
18
|
+
id="access-input-element"
|
19
|
+
ref={ref}
|
20
|
+
{...props}
|
21
|
+
/>
|
22
|
+
</>
|
23
|
+
)
|
24
|
+
}
|
25
|
+
|
26
|
+
export default PhoneNumberInputAccessInputElement
|
data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_access_input_element.md
ADDED
@@ -0,0 +1,3 @@
|
|
1
|
+
To access the kit's input element attributes or add event listeners, create a `ref` inside your parent component, pass it to the kit's `ref` prop, and use `ref.current.inputNode()` with your desired attribute or event listener inside a `useEffect` hook. `useEffect` is necessary because the `ref` will be initially `undefined`. Calling `useEffect` with an empty dependency array ensures your event listeners won't be added twice.
|
2
|
+
|
3
|
+
`inputNode()` is a custom function inside the kit that returns the input DOM element and its attributes. For example, to get the `name` attribute, use `ref.current.inputNode().name`
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import React, { useRef } from 'react'
|
2
|
+
import { Button, PhoneNumberInput } from '../..'
|
3
|
+
|
4
|
+
const PhoneNumberInputClearField = (props) => {
|
5
|
+
// 1. Create a ref - this accesses the kit's input element.
|
6
|
+
const ref = useRef()
|
7
|
+
|
8
|
+
// 2. Use clearField() to clear the field.
|
9
|
+
const handleClick = () => {
|
10
|
+
ref.current.clearField()
|
11
|
+
}
|
12
|
+
|
13
|
+
// 3. Pass the ref to the ref prop.
|
14
|
+
return (
|
15
|
+
<>
|
16
|
+
<PhoneNumberInput
|
17
|
+
id="clear-field"
|
18
|
+
ref={ref}
|
19
|
+
{...props}
|
20
|
+
/>
|
21
|
+
|
22
|
+
<Button
|
23
|
+
onClick={handleClick}
|
24
|
+
text="Clear the Input Field"
|
25
|
+
/>
|
26
|
+
</>
|
27
|
+
)
|
28
|
+
}
|
29
|
+
|
30
|
+
export default PhoneNumberInputClearField
|
@@ -0,0 +1,3 @@
|
|
1
|
+
To clear a number inside the input element, create a `ref` inside your parent component, pass it to the kit's `ref` prop, and use `ref.current.clearField()`.
|
2
|
+
|
3
|
+
`clearField()` is a custom function inside the kit to clear numbers and the error message while still providing validation.
|
@@ -6,6 +6,8 @@ examples:
|
|
6
6
|
- phone_number_input_initial_country: Initial Country
|
7
7
|
- phone_number_input_only_countries: Limited Countries
|
8
8
|
- phone_number_input_validation: Form Validation
|
9
|
+
- phone_number_input_clear_field: Clearing the Input Field
|
10
|
+
- phone_number_input_access_input_element: Accessing the Input Element
|
9
11
|
|
10
12
|
rails:
|
11
13
|
- phone_number_input_default: Default
|
@@ -3,3 +3,5 @@ export { default as PhoneNumberInputPreferredCountries } from './_phone_number_i
|
|
3
3
|
export { default as PhoneNumberInputInitialCountry } from './_phone_number_input_initial_country'
|
4
4
|
export { default as PhoneNumberInputOnlyCountries } from './_phone_number_input_only_countries'
|
5
5
|
export { default as PhoneNumberInputValidation } from './_phone_number_input_validation'
|
6
|
+
export { default as PhoneNumberInputClearField } from './_phone_number_input_clear_field'
|
7
|
+
export { default as PhoneNumberInputAccessInputElement } from './_phone_number_input_access_input_element'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<div class="pb--doc-demo-row">
|
2
|
+
<% svg_url = "https://upload.wikimedia.org/wikipedia/commons/3/3b/Wrench_font_awesome.svg" %>
|
3
|
+
|
4
|
+
<%= pb_rails("selectable_card_icon", props: {
|
5
|
+
custom_icon: svg_url,
|
6
|
+
title_text: "Customization",
|
7
|
+
body_text: "Personalize everything",
|
8
|
+
input_id: 1,
|
9
|
+
checked: true,
|
10
|
+
}) %>
|
11
|
+
</div>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import SelectableCardIcon from '../_selectable_card_icon'
|
3
|
+
|
4
|
+
const svg = {
|
5
|
+
newChat: (
|
6
|
+
<svg
|
7
|
+
ariaHidden="true"
|
8
|
+
focusable="false"
|
9
|
+
role="img"
|
10
|
+
viewBox="0 0 512 512"
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
12
|
+
>
|
13
|
+
<path
|
14
|
+
d="M448 0H64C28.7 0 0 28.7 0 64v288c0 35.3 28.7 64 64 64h96v84c0 7.1 5.8 12 12 12 2.4 0 4.9-.7 7.1-2.4L304 416h144c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64zm16 352c0 8.8-7.2 16-16 16H288l-12.8 9.6L208 428v-60H64c-8.8 0-16-7.2-16-16V64c0-8.8 7.2-16 16-16h384c8.8 0 16 7.2 16 16v288zM336 184h-56v-56c0-8.8-7.2-16-16-16h-16c-8.8 0-16 7.2-16 16v56h-56c-8.8 0-16 7.2-16 16v16c0 8.8 7.2 16 16 16h56v56c0 8.8 7.2 16 16 16h16c8.8 0 16-7.2 16-16v-56h56c8.8 0 16-7.2 16-16v-16c0-8.8-7.2-16-16-16z"
|
15
|
+
fill="currentColor"
|
16
|
+
/>
|
17
|
+
</svg>
|
18
|
+
),
|
19
|
+
}
|
20
|
+
|
21
|
+
const SelectableCardIconCustom = (props) => {
|
22
|
+
return (
|
23
|
+
<div className="pb--doc-demo-row">
|
24
|
+
<SelectableCardIcon
|
25
|
+
bodyText="Talk to someone you love"
|
26
|
+
checked
|
27
|
+
customIcon={svg.newChat}
|
28
|
+
inputId={1}
|
29
|
+
titleText="New Chat"
|
30
|
+
{...props}
|
31
|
+
/>
|
32
|
+
</div>
|
33
|
+
)
|
34
|
+
}
|
35
|
+
|
36
|
+
export default SelectableCardIconCustom
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Tips for Custom Icons
|
2
|
+
|
3
|
+
When using custom icons it is important to introduce a "clean" SVG. In order to ensure these custom icons perform as intended within your kit(s), ensure these things have been modified from the original SVG markup:
|
4
|
+
|
5
|
+
Attributes must be React compatible e.g. <code>xmlns:xlink</code> should be <code>xmlnsXlink</code> and so on. <strong>There should be no hyphenated attributes and no semi-colons!.</strong>
|
6
|
+
|
7
|
+
Fill colors with regards to <code>g</code> or <code>path</code> nodes, e.g. <code>fill="black"</code>, should be replaced with <code>currentColor</code> ala <code>fill="currentColor"</code>. Your mileage may vary depending on the complexity of your SVG.
|
8
|
+
|
9
|
+
Pay attention to your custom icon's dimensions and `viewBox` attribute. It is best to use a `viewBox="0 0 512 512"` starting point __when designing instead of trying to retrofit the viewbox afterwards__!
|
10
|
+
|
11
|
+
You must source *your own SVG into component/view* you are working on. This can easily be done in programmatic and maintainable ways.
|
12
|
+
|
13
|
+
### React
|
14
|
+
|
15
|
+
So long as you have a valid React `<SVG>` node, you can send it as the `customIcon` prop and the kit will take care of the rest.
|
16
|
+
|
17
|
+
### Rails
|
18
|
+
|
19
|
+
Some Rails applications use only webpack(er) which means using `image_url` will be successful over `image_path` in most cases especially development where Webpack Dev Server is serving assets over HTTP. Rails applications still using Asset Pipeline may use `image_path` or `image_url`. Of course, YMMV depending on any custom configurations in your Rails application.
|
@@ -5,10 +5,10 @@ examples:
|
|
5
5
|
- selectable_card_icon_checkmark: Checkmark
|
6
6
|
- selectable_card_icon_single_select: Single Select
|
7
7
|
- selectable_card_icon_options: With Options
|
8
|
-
|
9
|
-
|
8
|
+
- selectable_card_icon_custom: Custom Icon
|
10
9
|
|
11
10
|
react:
|
12
11
|
- selectable_card_icon_default: Default
|
13
12
|
- selectable_card_icon_checkmark: Checkmark
|
14
13
|
- selectable_card_icon_single_select: Single Select
|
14
|
+
- selectable_card_icon_custom: Custom Icon
|
@@ -1,3 +1,4 @@
|
|
1
1
|
export { default as SelectableCardIconDefault } from './_selectable_card_icon_default.jsx'
|
2
2
|
export { default as SelectableCardIconCheckmark } from './_selectable_card_icon_checkmark.jsx'
|
3
3
|
export { default as SelectableCardIconSingleSelect } from './_selectable_card_icon_single_select.jsx'
|
4
|
+
export { default as SelectableCardIconCustom } from './_selectable_card_icon_custom.jsx'
|
@@ -8,12 +8,14 @@
|
|
8
8
|
value: object.value,
|
9
9
|
checked: object.checked,
|
10
10
|
disabled: object.disabled,
|
11
|
+
custom_icon: object.custom_icon,
|
11
12
|
icon: object.checkmark,
|
12
13
|
multi: object.multi,
|
13
14
|
dark: object.dark,
|
14
15
|
input_options: object.input_options
|
15
16
|
}) do %>
|
16
17
|
<%= pb_rails("selectable_icon", props: {
|
18
|
+
custom_icon: object.custom_icon,
|
17
19
|
icon: object.icon,
|
18
20
|
inputs: "disabled",
|
19
21
|
text: object.title_text,
|
@@ -4,6 +4,8 @@ module Playbook
|
|
4
4
|
module PbSelectableCardIcon
|
5
5
|
class SelectableCardIcon < Playbook::KitBase
|
6
6
|
# Icon and text props
|
7
|
+
prop :custom_icon, type: Playbook::Props::String,
|
8
|
+
default: nil
|
7
9
|
prop :icon, type: Playbook::Props::String
|
8
10
|
prop :title_text, type: Playbook::Props::String
|
9
11
|
prop :body_text, type: Playbook::Props::String
|
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
<% if object.inputs == "disabled" %>
|
7
7
|
|
8
|
-
<%= pb_rails("icon", props: { icon: object.icon, size: "2x" }) %>
|
8
|
+
<%= pb_rails("icon", props: { custom_icon: object.custom_icon, icon: object.icon, size: "2x" }) %>
|
9
9
|
<%= pb_rails("title", props: { text: object.text, tag: "h4", size: 4 }) %>
|
10
10
|
|
11
11
|
<% else %>
|