playbook_ui 12.26.1 → 12.27.0.pre.alpha.expandednotworking853
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/sass_partials/_flatpickr_styles.scss +11 -9
- data/app/pb_kits/playbook/pb_date_picker/sass_partials/_month_and_year_styles.scss +1 -1
- data/app/pb_kits/playbook/pb_date_picker/sass_partials/_week_styles.scss +1 -1
- data/app/pb_kits/playbook/pb_form_pill/_form_pill.scss +1 -1
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +161 -127
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.html.erb +17 -17
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx +8 -4
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.md +2 -2
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_return_all_selected.html.erb +19 -16
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_return_all_selected.jsx +2 -2
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_form.html.erb +72 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.html.erb +7 -3
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +3 -0
- data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +109 -44
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_validation.html.erb +14 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_validation.jsx +60 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +3 -1
- data/app/pb_kits/playbook/pb_phone_number_input/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.rb +6 -0
- data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.test.js +110 -62
- data/app/pb_kits/playbook/pb_rich_text_editor/_trix_styles.scss +2 -2
- data/app/pb_kits/playbook/pb_title/_title.scss +1 -1
- data/app/pb_kits/playbook/pb_title/_title.tsx +2 -3
- data/app/pb_kits/playbook/pb_title/docs/_title_light_weight.html.erb +1 -0
- data/app/pb_kits/playbook/pb_title/docs/_title_light_weight.jsx +6 -0
- data/app/pb_kits/playbook/pb_title/docs/_title_light_weight.md +1 -2
- data/app/pb_kits/playbook/pb_title/title.rb +3 -10
- data/app/pb_kits/playbook/pb_title/title.test.js +3 -3
- data/app/pb_kits/playbook/utilities/object.ts +3 -0
- data/dist/playbook-rails.js +4 -4
- data/lib/playbook/forms/builder/intl_telephone_field.rb +12 -0
- data/lib/playbook/forms/builder/multi_level_select_field.rb +12 -0
- data/lib/playbook/forms/builder.rb +2 -0
- data/lib/playbook/version.rb +2 -2
- metadata +13 -7
@@ -1,72 +1,72 @@
|
|
1
1
|
<% treeData = [{
|
2
2
|
label: "Power Home Remodeling",
|
3
3
|
value: "Power Home Remodeling",
|
4
|
-
id: "
|
4
|
+
id: "100",
|
5
5
|
expanded: true,
|
6
6
|
children: [
|
7
7
|
{
|
8
8
|
label: "People",
|
9
9
|
value: "People",
|
10
|
-
id: "
|
10
|
+
id: "101",
|
11
11
|
children: [
|
12
12
|
{
|
13
13
|
label: "Talent Acquisition",
|
14
14
|
value: "Talent Acquisition",
|
15
|
-
id: "
|
15
|
+
id: "102",
|
16
16
|
},
|
17
17
|
{
|
18
18
|
label: "Business Affairs",
|
19
19
|
value: "Business Affairs",
|
20
|
-
id: "
|
20
|
+
id: "103",
|
21
21
|
children: [
|
22
22
|
{
|
23
23
|
label: "Initiatives",
|
24
24
|
value: "Initiatives",
|
25
|
-
id: "
|
25
|
+
id: "104",
|
26
26
|
},
|
27
27
|
{
|
28
28
|
label: "Learning & Development",
|
29
29
|
value: "Learning & Development",
|
30
|
-
id: "
|
30
|
+
id: "105",
|
31
31
|
},
|
32
32
|
],
|
33
33
|
},
|
34
34
|
{
|
35
35
|
label: "People Experience",
|
36
36
|
value: "People Experience",
|
37
|
-
id: "
|
37
|
+
id: "106",
|
38
38
|
},
|
39
39
|
],
|
40
40
|
},
|
41
41
|
{
|
42
42
|
label: "Contact Center",
|
43
43
|
value: "Contact Center",
|
44
|
-
id: "
|
44
|
+
id: "107",
|
45
45
|
children: [
|
46
46
|
{
|
47
47
|
label: "Appointment Management",
|
48
48
|
value: "Appointment Management",
|
49
|
-
id: "
|
49
|
+
id: "108",
|
50
50
|
},
|
51
51
|
{
|
52
52
|
label: "Customer Service",
|
53
53
|
value: "Customer Service",
|
54
|
-
id: "
|
54
|
+
id: "109",
|
55
55
|
},
|
56
56
|
{
|
57
57
|
label: "Energy",
|
58
58
|
value: "Energy",
|
59
|
-
id: "
|
59
|
+
id: "110",
|
60
60
|
},
|
61
61
|
],
|
62
62
|
},
|
63
63
|
],
|
64
|
-
|
64
|
+
}] %>
|
65
65
|
|
66
66
|
|
67
|
-
<%= pb_rails("multi_level_select", props: {
|
68
|
-
id: "default-multi-level-select",
|
69
|
-
tree_data:treeData
|
70
|
-
}) %>
|
71
|
-
|
72
67
|
|
68
|
+
<%= pb_rails("multi_level_select", props: {
|
69
|
+
id: "multi-level-select-default-rails",
|
70
|
+
name: "my_array",
|
71
|
+
tree_data: treeData
|
72
|
+
}) %>
|
@@ -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",
|
@@ -72,15 +73,18 @@ const MultiLevelSelectDefault = (props) => {
|
|
72
73
|
return (
|
73
74
|
<div>
|
74
75
|
<MultiLevelSelect
|
75
|
-
id=
|
76
|
+
id='multiselect-default'
|
76
77
|
onSelect={(selectedNodes) =>
|
77
|
-
console.log(
|
78
|
-
|
78
|
+
console.log(
|
79
|
+
"Selected Items",
|
80
|
+
selectedNodes
|
81
|
+
)
|
82
|
+
}
|
79
83
|
treeData={treeData}
|
80
84
|
{...props}
|
81
85
|
/>
|
82
86
|
</div>
|
83
|
-
)
|
87
|
+
)
|
84
88
|
};
|
85
89
|
|
86
90
|
export default MultiLevelSelectDefault;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
The MultiLevelSelect kit renders a multi leveled select dropdown based on data from the user. `treeData` is a required prop that is expected to contain the data in the form of an array of objects. See code snippet for an example data array.
|
2
2
|
|
3
|
-
For the React version of the kit, the `onSelect` prop returns an array of all checked items
|
3
|
+
For the React version of the kit, the `onSelect` prop returns an array of objects. This array contains all checked items irrespective of whether it is a parent, child or grandchild. Open the console on this example and check and uncheck checkboxes to see this is action!
|
4
4
|
|
5
|
-
For the Rails version
|
5
|
+
For the Rails version of the kit, there is no onselect. The form submits as a array of strings, following the typical rails pattern of utilizing hidden inputs. The strings are the values of the selected items' ids. For example, ["103", "106", "107"].
|
@@ -1,72 +1,75 @@
|
|
1
1
|
<% treeData = [{
|
2
2
|
label: "Power Home Remodeling",
|
3
3
|
value: "Power Home Remodeling",
|
4
|
-
id: "
|
4
|
+
id: "100",
|
5
5
|
expanded: true,
|
6
|
-
|
7
6
|
children: [
|
8
7
|
{
|
9
8
|
label: "People",
|
10
9
|
value: "People",
|
11
|
-
id: "
|
10
|
+
id: "101",
|
12
11
|
children: [
|
13
12
|
{
|
14
13
|
label: "Talent Acquisition",
|
15
14
|
value: "Talent Acquisition",
|
16
|
-
id: "
|
15
|
+
id: "102",
|
17
16
|
},
|
18
17
|
{
|
19
18
|
label: "Business Affairs",
|
20
19
|
value: "Business Affairs",
|
21
|
-
id: "
|
20
|
+
id: "103",
|
22
21
|
children: [
|
23
22
|
{
|
24
23
|
label: "Initiatives",
|
25
24
|
value: "Initiatives",
|
26
|
-
id: "
|
25
|
+
id: "104",
|
27
26
|
},
|
28
27
|
{
|
29
28
|
label: "Learning & Development",
|
30
29
|
value: "Learning & Development",
|
31
|
-
id: "
|
30
|
+
id: "105",
|
32
31
|
},
|
33
32
|
],
|
34
33
|
},
|
35
34
|
{
|
36
35
|
label: "People Experience",
|
37
36
|
value: "People Experience",
|
38
|
-
id: "
|
37
|
+
id: "106",
|
39
38
|
},
|
40
39
|
],
|
41
40
|
},
|
42
41
|
{
|
43
42
|
label: "Contact Center",
|
44
43
|
value: "Contact Center",
|
45
|
-
id: "
|
44
|
+
id: "107",
|
46
45
|
children: [
|
47
46
|
{
|
48
47
|
label: "Appointment Management",
|
49
48
|
value: "Appointment Management",
|
50
|
-
id: "
|
49
|
+
id: "108",
|
51
50
|
},
|
52
51
|
{
|
53
52
|
label: "Customer Service",
|
54
53
|
value: "Customer Service",
|
55
|
-
id: "
|
54
|
+
id: "109",
|
56
55
|
},
|
57
56
|
{
|
58
57
|
label: "Energy",
|
59
58
|
value: "Energy",
|
60
|
-
id: "
|
59
|
+
id: "110",
|
61
60
|
},
|
62
61
|
],
|
63
62
|
},
|
64
63
|
],
|
65
|
-
|
64
|
+
}] %>
|
65
|
+
|
66
|
+
|
66
67
|
|
67
68
|
|
68
69
|
<%= pb_rails("multi_level_select", props: {
|
69
|
-
id: "
|
70
|
-
|
70
|
+
id: "multi-level-select-return-all-selected-rails",
|
71
|
+
name: "my_data_array",
|
72
|
+
tree_data: treeData,
|
71
73
|
return_all_selected: true
|
72
|
-
|
74
|
+
}) %>
|
75
|
+
|
data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_return_all_selected.jsx
CHANGED
@@ -72,9 +72,9 @@ const MultiLevelSelectReturnAllSelected = (props) => {
|
|
72
72
|
return (
|
73
73
|
<div>
|
74
74
|
<MultiLevelSelect
|
75
|
-
id="
|
75
|
+
id="multi-level-select-return-all-selected"
|
76
76
|
onSelect={(selectedNodes) =>
|
77
|
-
console.log("Selected Items", selectedNodes)
|
77
|
+
console.log("Selected Items with Return All Selected Data", selectedNodes)
|
78
78
|
}
|
79
79
|
returnAllSelected
|
80
80
|
treeData={treeData}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
<%= pb_form_with(scope: :example, url: "", method: :get) do |form| %>
|
2
|
+
|
3
|
+
<% treeData = [{
|
4
|
+
label: "Power Home Remodeling",
|
5
|
+
value: "Power Home Remodeling",
|
6
|
+
id: "powerhome1",
|
7
|
+
expanded: true,
|
8
|
+
children: [
|
9
|
+
{
|
10
|
+
label: "People",
|
11
|
+
value: "People",
|
12
|
+
id: "people1",
|
13
|
+
children: [
|
14
|
+
{
|
15
|
+
label: "Talent Acquisition",
|
16
|
+
value: "Talent Acquisition",
|
17
|
+
id: "talent1",
|
18
|
+
},
|
19
|
+
{
|
20
|
+
label: "Business Affairs",
|
21
|
+
value: "Business Affairs",
|
22
|
+
id: "business1",
|
23
|
+
children: [
|
24
|
+
{
|
25
|
+
label: "Initiatives",
|
26
|
+
value: "Initiatives",
|
27
|
+
id: "initiative1",
|
28
|
+
},
|
29
|
+
{
|
30
|
+
label: "Learning & Development",
|
31
|
+
value: "Learning & Development",
|
32
|
+
id: "development1",
|
33
|
+
},
|
34
|
+
],
|
35
|
+
},
|
36
|
+
{
|
37
|
+
label: "People Experience",
|
38
|
+
value: "People Experience",
|
39
|
+
id: "experience1",
|
40
|
+
},
|
41
|
+
],
|
42
|
+
},
|
43
|
+
{
|
44
|
+
label: "Contact Center",
|
45
|
+
value: "Contact Center",
|
46
|
+
id: "contact1",
|
47
|
+
children: [
|
48
|
+
{
|
49
|
+
label: "Appointment Management",
|
50
|
+
value: "Appointment Management",
|
51
|
+
id: "appointment1",
|
52
|
+
},
|
53
|
+
{
|
54
|
+
label: "Customer Service",
|
55
|
+
value: "Customer Service",
|
56
|
+
id: "customer1",
|
57
|
+
},
|
58
|
+
{
|
59
|
+
label: "Energy",
|
60
|
+
value: "Energy",
|
61
|
+
id: "energy1",
|
62
|
+
},
|
63
|
+
],
|
64
|
+
},
|
65
|
+
],
|
66
|
+
}] %>
|
67
|
+
|
68
|
+
<%= form.multi_level_select :example, props: {id: "with-form-multi-level-select", tree_data: treeData, return_all_selected: true, margin_bottom: "sm" } %>
|
69
|
+
<%= form.actions do |action| %>
|
70
|
+
<%= action.button props: { type: "submit", text: "Submit", variant: "primary", margin_top: "lg" } %>
|
71
|
+
<% end %>
|
72
|
+
<% end %>
|
@@ -1,3 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
<%= content_tag(:div,
|
2
|
+
aria: object.aria,
|
3
|
+
data: object.data,
|
4
|
+
id: object.id,
|
5
|
+
class: object.classname) do %>
|
6
|
+
<%= react_component("MultiLevelSelect", object.multi_level_select_options) %>
|
7
|
+
<% end %>
|
@@ -3,6 +3,8 @@
|
|
3
3
|
module Playbook
|
4
4
|
module PbMultiLevelSelect
|
5
5
|
class MultiLevelSelect < Playbook::KitBase
|
6
|
+
prop :id
|
7
|
+
prop :name
|
6
8
|
prop :tree_data, type: Playbook::Props::Array,
|
7
9
|
default: []
|
8
10
|
prop :return_all_selected, type: Playbook::Props::Boolean,
|
@@ -15,6 +17,7 @@ module Playbook
|
|
15
17
|
def multi_level_select_options
|
16
18
|
{
|
17
19
|
id: id,
|
20
|
+
name: name,
|
18
21
|
treeData: tree_data,
|
19
22
|
returnAllSelected: return_all_selected,
|
20
23
|
}
|
@@ -1,12 +1,17 @@
|
|
1
|
-
import React, { useEffect, useRef, useState } from 'react'
|
1
|
+
import React, { forwardRef, useEffect, useRef, useState } from 'react'
|
2
2
|
import classnames from 'classnames'
|
3
|
-
|
4
|
-
import { globalProps } from '../utilities/globalProps'
|
3
|
+
|
5
4
|
import intlTelInput from 'intl-tel-input'
|
6
5
|
import 'intl-tel-input/build/css/intlTelInput.css'
|
7
|
-
import TextInput from '../pb_text_input/_text_input'
|
8
6
|
import 'intl-tel-input/build/js/utils.js'
|
9
7
|
|
8
|
+
import { buildAriaProps, buildCss, buildDataProps } from '../utilities/props'
|
9
|
+
import { globalProps } from '../utilities/globalProps'
|
10
|
+
|
11
|
+
import TextInput from '../pb_text_input/_text_input'
|
12
|
+
import { Callback } from '../types'
|
13
|
+
import { isEmpty } from '../utilities/object'
|
14
|
+
|
10
15
|
declare global {
|
11
16
|
interface Window {
|
12
17
|
intlTelInputGlobals: any
|
@@ -19,20 +24,25 @@ type PhoneNumberInputProps = {
|
|
19
24
|
dark?: boolean,
|
20
25
|
data?: { [key: string]: string },
|
21
26
|
disabled?: boolean,
|
27
|
+
error?: string,
|
22
28
|
id?: string,
|
23
29
|
initialCountry?: string,
|
24
30
|
isValid?: (valid: boolean) => void,
|
25
31
|
label?: string,
|
26
32
|
name?: string,
|
27
33
|
onChange?: (e: React.FormEvent<HTMLInputElement>) => void,
|
34
|
+
onValidate?: Callback<boolean, void>,
|
28
35
|
onlyCountries: string[],
|
29
36
|
preferredCountries?: string[],
|
37
|
+
required?: boolean,
|
30
38
|
value?: string,
|
31
39
|
}
|
32
40
|
|
33
41
|
enum ValidationError {
|
34
42
|
TooShort = 2,
|
35
43
|
TooLong = 3,
|
44
|
+
MissingAreaCode = 4,
|
45
|
+
SomethingWentWrong = -99
|
36
46
|
}
|
37
47
|
|
38
48
|
const formatToGlobalCountryName = (countryName: string) => {
|
@@ -40,10 +50,10 @@ const formatToGlobalCountryName = (countryName: string) => {
|
|
40
50
|
}
|
41
51
|
|
42
52
|
const formatAllCountries = () => {
|
43
|
-
|
53
|
+
const countryData = window.intlTelInputGlobals.getCountryData()
|
44
54
|
|
45
55
|
for (let i = 0; i < countryData.length; i++) {
|
46
|
-
|
56
|
+
const country = countryData[i]
|
47
57
|
country.name = formatToGlobalCountryName(country.name)
|
48
58
|
}
|
49
59
|
}
|
@@ -71,7 +81,9 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
71
81
|
onChange = () => {
|
72
82
|
void 0
|
73
83
|
},
|
84
|
+
onValidate = () => null,
|
74
85
|
onlyCountries = [],
|
86
|
+
required = false,
|
75
87
|
preferredCountries = [],
|
76
88
|
value = "",
|
77
89
|
} = props
|
@@ -87,39 +99,83 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
87
99
|
const inputRef = useRef<HTMLInputElement>()
|
88
100
|
const [inputValue, setInputValue] = useState(value)
|
89
101
|
const [itiInit, setItiInit] = useState<any>()
|
90
|
-
const [error, setError] = useState(
|
102
|
+
const [error, setError] = useState(props.error)
|
91
103
|
const [dropDownIsOpen, setDropDownIsOpen] = useState(false)
|
92
104
|
const [selectedData, setSelectedData] = useState()
|
93
105
|
|
106
|
+
useEffect(() => {
|
107
|
+
if (error?.length > 0) {
|
108
|
+
onValidate(false)
|
109
|
+
} else {
|
110
|
+
onValidate(true)
|
111
|
+
}
|
112
|
+
}, [error, onValidate])
|
113
|
+
|
114
|
+
const showFormattedError = (reason = '') => {
|
115
|
+
const countryName = itiInit.getSelectedCountryData().name
|
116
|
+
const reasonText = reason.length > 0 ? ` (${reason})` : ''
|
117
|
+
setError(`Invalid ${countryName} phone number${reasonText}`)
|
118
|
+
return true
|
119
|
+
}
|
120
|
+
|
94
121
|
const validateTooLongNumber = (itiInit: any) => {
|
95
|
-
|
122
|
+
if (!itiInit) return
|
123
|
+
if (itiInit.getValidationError() === ValidationError.TooLong) {
|
124
|
+
return showFormattedError('too long')
|
125
|
+
} else {
|
126
|
+
setError('')
|
127
|
+
}
|
128
|
+
}
|
96
129
|
|
97
|
-
|
98
|
-
|
99
|
-
|
130
|
+
const validateTooShortNumber = (itiInit: any) => {
|
131
|
+
if (!itiInit) return
|
132
|
+
if (itiInit.getValidationError() === ValidationError.TooShort) {
|
133
|
+
return showFormattedError('too short')
|
100
134
|
} else {
|
101
|
-
|
135
|
+
if (inputValue.length === 1) {
|
136
|
+
return showFormattedError('too short')
|
137
|
+
} else {
|
138
|
+
setError('')
|
139
|
+
}
|
102
140
|
}
|
103
141
|
}
|
104
142
|
|
105
|
-
const
|
106
|
-
|
143
|
+
const validateOnlyNumbers = (itiInit: any) => {
|
144
|
+
if (!itiInit) return
|
145
|
+
if (inputValue && !containOnlyNumbers(inputValue)) {
|
146
|
+
return showFormattedError('enter numbers only')
|
147
|
+
}
|
148
|
+
}
|
107
149
|
|
108
|
-
|
109
|
-
|
110
|
-
|
150
|
+
const validateUnhandledError = (itiInit: any) => {
|
151
|
+
if (!required || !itiInit) return
|
152
|
+
if (itiInit.getValidationError() === ValidationError.SomethingWentWrong) {
|
153
|
+
if (inputValue.length === 1) {
|
154
|
+
return showFormattedError('too short')
|
155
|
+
} else if (inputValue.length === 0) {
|
156
|
+
setError('Missing phone number')
|
157
|
+
return true
|
158
|
+
} else {
|
159
|
+
return showFormattedError()
|
160
|
+
}
|
111
161
|
}
|
112
162
|
}
|
113
163
|
|
114
|
-
const
|
115
|
-
if (
|
116
|
-
|
164
|
+
const validateMissingAreaCode = (itiInit: any) => {
|
165
|
+
if (!required || !itiInit) return
|
166
|
+
if (itiInit.getValidationError() === ValidationError.MissingAreaCode) {
|
167
|
+
showFormattedError('missing area code')
|
168
|
+
return true
|
117
169
|
}
|
118
170
|
}
|
119
171
|
|
120
172
|
const validateErrors = () => {
|
121
|
-
|
122
|
-
validateOnlyNumbers()
|
173
|
+
if (itiInit) isValid(itiInit.isValidNumber())
|
174
|
+
if (validateOnlyNumbers(itiInit)) return
|
175
|
+
if (validateTooLongNumber(itiInit)) return
|
176
|
+
if (validateTooShortNumber(itiInit)) return
|
177
|
+
if (validateUnhandledError(itiInit)) return
|
178
|
+
if (validateMissingAreaCode(itiInit)) return
|
123
179
|
}
|
124
180
|
|
125
181
|
const getCurrentSelectedData = (itiInit: any, inputValue: string) => {
|
@@ -128,7 +184,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
128
184
|
|
129
185
|
const handleOnChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
130
186
|
setInputValue(evt.target.value)
|
131
|
-
validateTooLongNumber(itiInit)
|
132
187
|
const phoneNumberData = getCurrentSelectedData(itiInit, evt.target.value)
|
133
188
|
setSelectedData(phoneNumberData)
|
134
189
|
onChange(phoneNumberData)
|
@@ -137,25 +192,24 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
137
192
|
|
138
193
|
// Separating Concerns as React Docs Recommend
|
139
194
|
// This also Fixes things for our react_component rendering on the Rails Side
|
140
|
-
useEffect(
|
141
|
-
formatAllCountries()
|
142
|
-
}, [])
|
195
|
+
useEffect(formatAllCountries, [])
|
143
196
|
|
144
197
|
useEffect(() => {
|
145
|
-
const telInputInit =
|
198
|
+
const telInputInit = intlTelInput(inputRef.current, {
|
146
199
|
separateDialCode: true,
|
147
200
|
preferredCountries,
|
148
201
|
allowDropdown: !disabled,
|
202
|
+
autoInsertDialCode: false,
|
149
203
|
initialCountry,
|
150
204
|
onlyCountries,
|
151
|
-
|
152
|
-
)
|
205
|
+
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/18.1.6/js/utils.min.js"
|
206
|
+
})
|
153
207
|
|
154
208
|
inputRef.current.addEventListener("countrychange", (evt: Event) => {
|
155
|
-
validateTooLongNumber(telInputInit)
|
156
209
|
const phoneNumberData = getCurrentSelectedData(telInputInit, (evt.target as HTMLInputElement).value)
|
157
210
|
setSelectedData(phoneNumberData)
|
158
211
|
onChange(phoneNumberData)
|
212
|
+
validateErrors()
|
159
213
|
})
|
160
214
|
|
161
215
|
inputRef.current.addEventListener("open:countrydropdown", () => setDropDownIsOpen(true))
|
@@ -164,24 +218,35 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
164
218
|
setItiInit(telInputInit)
|
165
219
|
}, [])
|
166
220
|
|
221
|
+
let textInputProps: {[key: string]: any} = {
|
222
|
+
className: dropDownIsOpen ? 'dropdown_open' : '',
|
223
|
+
dark,
|
224
|
+
"data-phone-number": JSON.stringify(selectedData),
|
225
|
+
disabled,
|
226
|
+
error,
|
227
|
+
type: 'tel',
|
228
|
+
id,
|
229
|
+
label,
|
230
|
+
name,
|
231
|
+
onBlur: validateErrors,
|
232
|
+
onChange: handleOnChange,
|
233
|
+
value: inputValue
|
234
|
+
}
|
235
|
+
|
236
|
+
let wrapperProps: Record<string, unknown> = { className: classes }
|
237
|
+
|
238
|
+
if (!isEmpty(aria)) textInputProps = {...textInputProps, ...ariaProps}
|
239
|
+
if (!isEmpty(data)) wrapperProps = {...wrapperProps, ...dataProps}
|
240
|
+
if (required) textInputProps.required = true
|
241
|
+
|
167
242
|
return (
|
168
|
-
<div {...
|
243
|
+
<div {...wrapperProps}>
|
169
244
|
<TextInput
|
170
|
-
|
171
|
-
|
172
|
-
data-phone-number={JSON.stringify(selectedData)}
|
173
|
-
disabled={disabled}
|
174
|
-
error={error}
|
175
|
-
id={id}
|
176
|
-
label={label}
|
177
|
-
name={name}
|
178
|
-
onBlur={() => validateErrors()}
|
179
|
-
onChange={handleOnChange}
|
180
|
-
ref={inputRef}
|
181
|
-
value={inputValue}
|
245
|
+
ref={inputRef}
|
246
|
+
{...textInputProps}
|
182
247
|
/>
|
183
248
|
</div>
|
184
249
|
)
|
185
250
|
}
|
186
251
|
|
187
|
-
export default PhoneNumberInput
|
252
|
+
export default forwardRef(PhoneNumberInput)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<form id="example-form-validation" action="" method="get">
|
2
|
+
<%= pb_rails("phone_number_input", props: { error: "Missing phone number.", id: "validation", initial_country: "af", value: "", required: true }) %>
|
3
|
+
<%= pb_rails("button", props: {html_type: "submit", text: "Save Phone Number"}) %>
|
4
|
+
</form>
|
5
|
+
|
6
|
+
<% content_for(:pb_js) do %>
|
7
|
+
<%= javascript_tag do %>
|
8
|
+
document.addEventListener('DOMContentLoaded', function () {
|
9
|
+
document.querySelector('#example-form-validation').addEventListener('submit', function (e) {
|
10
|
+
if (e.target.querySelectorAll('[error]:not([error=""])').length > 0) e.preventDefault();
|
11
|
+
})
|
12
|
+
})
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
2
|
+
import { Button, FixedConfirmationToast, PhoneNumberInput } from "../../";
|
3
|
+
|
4
|
+
const PhoneNumberInputValidation = (props) => {
|
5
|
+
const [formErrors, setFormErrors] = useState("");
|
6
|
+
const [showFormErrors, setShowFormErrors] = useState(false);
|
7
|
+
const [phoneNumber, setPhoneNumber] = useState("");
|
8
|
+
const [countryCode, setCountryCode] = useState("af");
|
9
|
+
|
10
|
+
const handleOnValidate = (valid) => {
|
11
|
+
setFormErrors(
|
12
|
+
valid ? "" : "Please correct the fields below and try again."
|
13
|
+
);
|
14
|
+
};
|
15
|
+
|
16
|
+
const handleOnChange = ({ iso2, number }) => {
|
17
|
+
setCountryCode(iso2);
|
18
|
+
setPhoneNumber(number);
|
19
|
+
};
|
20
|
+
|
21
|
+
const handleOnSubmit = (e) => {
|
22
|
+
if (showFormErrors) e.preventDefault()
|
23
|
+
}
|
24
|
+
|
25
|
+
useEffect(() => {
|
26
|
+
setShowFormErrors(formErrors.length > 0);
|
27
|
+
}, [formErrors]);
|
28
|
+
|
29
|
+
return (
|
30
|
+
<form
|
31
|
+
action=""
|
32
|
+
method="get"
|
33
|
+
onSubmit={handleOnSubmit}
|
34
|
+
>
|
35
|
+
{showFormErrors && (
|
36
|
+
<FixedConfirmationToast
|
37
|
+
marginBottom="md"
|
38
|
+
status="error"
|
39
|
+
text={formErrors}
|
40
|
+
/>
|
41
|
+
)}
|
42
|
+
<PhoneNumberInput
|
43
|
+
error="Missing phone number."
|
44
|
+
id="validation"
|
45
|
+
initialCountry={countryCode}
|
46
|
+
onChange={handleOnChange}
|
47
|
+
onValidate={handleOnValidate}
|
48
|
+
required
|
49
|
+
value={phoneNumber}
|
50
|
+
{...props}
|
51
|
+
/>
|
52
|
+
<Button
|
53
|
+
htmlType="submit"
|
54
|
+
text="Save Phone Number"
|
55
|
+
/>
|
56
|
+
</form>
|
57
|
+
);
|
58
|
+
};
|
59
|
+
|
60
|
+
export default PhoneNumberInputValidation;
|