playbook_ui 12.27.0 → 12.28.0.pre.alpha.PLAY603datepickerquickpickinputpresetdropdown869

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) 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 +185 -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/_multi_level_select.tsx +28 -3
  21. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx +1 -0
  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/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +26 -5
  25. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_access_input_element.jsx +26 -0
  26. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_access_input_element.md +3 -0
  27. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_clear_field.jsx +30 -0
  28. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_clear_field.md +3 -0
  29. data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +2 -0
  30. data/app/pb_kits/playbook/pb_phone_number_input/docs/index.js +2 -0
  31. data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_custom.html.erb +11 -0
  32. data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_custom.jsx +36 -0
  33. data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_custom.md +19 -0
  34. data/app/pb_kits/playbook/pb_selectable_card_icon/docs/example.yml +2 -2
  35. data/app/pb_kits/playbook/pb_selectable_card_icon/docs/index.js +1 -0
  36. data/app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.html.erb +2 -0
  37. data/app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.rb +2 -0
  38. data/app/pb_kits/playbook/pb_selectable_icon/selectable_icon.html.erb +1 -1
  39. data/app/pb_kits/playbook/pb_selectable_icon/selectable_icon.rb +2 -0
  40. data/dist/playbook-rails.js +279 -7
  41. data/lib/playbook/version.rb +2 -2
  42. 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 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,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
- 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
+ }
@@ -51,8 +51,25 @@ const MultiLevelSelect = (props: MultiLevelSelectProps) => {
51
51
  const dropdownRef = useRef(null)
52
52
 
53
53
 
54
- //state for expanded property
55
- const [expanded, setExpanded] = useState([])
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'>
@@ -12,6 +12,7 @@ const treeData = [
12
12
  label: "People",
13
13
  value: "People",
14
14
  id: "people1",
15
+ expanded: true,
15
16
  children: [
16
17
  {
17
18
  label: "Talent Acquisition",
@@ -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
  }
@@ -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={inputRef}
261
+ ref={
262
+ inputNode => {
263
+ ref ? ref.current = inputNode : null
264
+ inputRef.current = inputNode
265
+ }
266
+ }
246
267
  {...textInputProps}
247
268
  />
248
269
  </div>
@@ -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
@@ -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 %>