playbook_ui 12.24.0.pre.alpha.play824753 → 12.24.0.pre.alpha.railsmultilevelimprovements739
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_phone_number_input/_phone_number_input.tsx +45 -110
- data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +1 -3
- data/app/pb_kits/playbook/pb_phone_number_input/docs/index.js +0 -1
- data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.rb +0 -6
- data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.test.js +62 -110
- data/dist/playbook-rails.js +2 -2
- data/lib/playbook/forms/builder/multi_level_select_field.rb +12 -0
- data/lib/playbook/forms/builder.rb +2 -1
- data/lib/playbook/version.rb +1 -1
- metadata +3 -6
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_validation.html.erb +0 -14
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_validation.jsx +0 -60
- data/app/pb_kits/playbook/utilities/object.ts +0 -3
- data/lib/playbook/forms/builder/intl_telephone_field.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca13d82593114ee6fa9797c521e2e7da4b675f48923d7ad41c74fc14d5d224d6
|
4
|
+
data.tar.gz: 8f1309ad083e6e90c2a6278b7f3afb4cdcea79e06dcfbb7481ae9273d4f9f6ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f1e03060911871628a3c79d92bb7c01fbedf754b705a2feefe065082b03dca4bde92bec88d390a508639a1e5ac1f157d0130775cf6056d55260beae9b149c18
|
7
|
+
data.tar.gz: 0ce1d542f2d0a41150fb52757501f9423392495c0554b5757330c178db347168973bb55f41fd86e017dee8475d27545f58332d60609667ec727d17da63241583
|
@@ -1,16 +1,11 @@
|
|
1
|
-
import React, {
|
1
|
+
import React, { useEffect, useRef, useState } from 'react'
|
2
2
|
import classnames from 'classnames'
|
3
|
-
|
4
|
-
import intlTelInput from 'intl-tel-input'
|
5
|
-
import 'intl-tel-input/build/css/intlTelInput.css'
|
6
|
-
import 'intl-tel-input/build/js/utils.js'
|
7
|
-
|
8
3
|
import { buildAriaProps, buildCss, buildDataProps } from '../utilities/props'
|
9
4
|
import { globalProps } from '../utilities/globalProps'
|
10
|
-
|
5
|
+
import intlTelInput from 'intl-tel-input'
|
6
|
+
import 'intl-tel-input/build/css/intlTelInput.css'
|
11
7
|
import TextInput from '../pb_text_input/_text_input'
|
12
|
-
import
|
13
|
-
import { isEmpty } from '../utilities/object'
|
8
|
+
import 'intl-tel-input/build/js/utils.js'
|
14
9
|
|
15
10
|
declare global {
|
16
11
|
interface Window {
|
@@ -24,25 +19,20 @@ type PhoneNumberInputProps = {
|
|
24
19
|
dark?: boolean,
|
25
20
|
data?: { [key: string]: string },
|
26
21
|
disabled?: boolean,
|
27
|
-
error?: string,
|
28
22
|
id?: string,
|
29
23
|
initialCountry?: string,
|
30
24
|
isValid?: (valid: boolean) => void,
|
31
25
|
label?: string,
|
32
26
|
name?: string,
|
33
27
|
onChange?: (e: React.FormEvent<HTMLInputElement>) => void,
|
34
|
-
onValidate?: Callback<boolean, void>,
|
35
28
|
onlyCountries: string[],
|
36
29
|
preferredCountries?: string[],
|
37
|
-
required?: boolean,
|
38
30
|
value?: string,
|
39
31
|
}
|
40
32
|
|
41
33
|
enum ValidationError {
|
42
34
|
TooShort = 2,
|
43
35
|
TooLong = 3,
|
44
|
-
MissingAreaCode = 4,
|
45
|
-
SomethingWentWrong = -99
|
46
36
|
}
|
47
37
|
|
48
38
|
const formatToGlobalCountryName = (countryName: string) => {
|
@@ -50,10 +40,10 @@ const formatToGlobalCountryName = (countryName: string) => {
|
|
50
40
|
}
|
51
41
|
|
52
42
|
const formatAllCountries = () => {
|
53
|
-
|
43
|
+
let countryData = window.intlTelInputGlobals.getCountryData()
|
54
44
|
|
55
45
|
for (let i = 0; i < countryData.length; i++) {
|
56
|
-
|
46
|
+
let country = countryData[i]
|
57
47
|
country.name = formatToGlobalCountryName(country.name)
|
58
48
|
}
|
59
49
|
}
|
@@ -81,9 +71,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
81
71
|
onChange = () => {
|
82
72
|
void 0
|
83
73
|
},
|
84
|
-
onValidate = () => null,
|
85
74
|
onlyCountries = [],
|
86
|
-
required = false,
|
87
75
|
preferredCountries = [],
|
88
76
|
value = "",
|
89
77
|
} = props
|
@@ -99,84 +87,39 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
99
87
|
const inputRef = useRef<HTMLInputElement>()
|
100
88
|
const [inputValue, setInputValue] = useState(value)
|
101
89
|
const [itiInit, setItiInit] = useState<any>()
|
102
|
-
const [error, setError] = useState(
|
90
|
+
const [error, setError] = useState('')
|
103
91
|
const [dropDownIsOpen, setDropDownIsOpen] = useState(false)
|
104
92
|
const [selectedData, setSelectedData] = useState()
|
105
93
|
|
106
|
-
useEffect(() => {
|
107
|
-
if (error?.length > 0) {
|
108
|
-
onValidate(false)
|
109
|
-
} else {
|
110
|
-
onValidate(true)
|
111
|
-
}
|
112
|
-
}, [error, onValidate])
|
113
|
-
|
114
|
-
if (itiInit) isValid(itiInit.isValidNumber())
|
115
|
-
|
116
|
-
const showFormattedError = (reason = '') => {
|
117
|
-
const countryName = itiInit.getSelectedCountryData().name
|
118
|
-
const reasonText = reason.length > 0 ? ` (${reason})` : ''
|
119
|
-
setError(`Invalid ${countryName} phone number${reasonText}`)
|
120
|
-
return true
|
121
|
-
}
|
122
|
-
|
123
94
|
const validateTooLongNumber = (itiInit: any) => {
|
124
|
-
|
125
|
-
if (itiInit.getValidationError() === ValidationError.TooLong) {
|
126
|
-
return showFormattedError('too long')
|
127
|
-
} else {
|
128
|
-
setError('')
|
129
|
-
}
|
130
|
-
}
|
95
|
+
const error = itiInit.getValidationError()
|
131
96
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
return showFormattedError('too short')
|
97
|
+
if (error === ValidationError.TooLong) {
|
98
|
+
const countryName = itiInit.getSelectedCountryData().name
|
99
|
+
setError(`Invalid ${countryName} phone number (too long)`)
|
136
100
|
} else {
|
137
|
-
|
138
|
-
return showFormattedError('too short')
|
139
|
-
} else {
|
140
|
-
setError('')
|
141
|
-
}
|
101
|
+
setError("")
|
142
102
|
}
|
143
103
|
}
|
144
104
|
|
145
|
-
const
|
146
|
-
|
147
|
-
if (inputValue && !containOnlyNumbers(inputValue)) {
|
148
|
-
return showFormattedError('enter numbers only')
|
149
|
-
}
|
150
|
-
}
|
105
|
+
const validateTooShortNumber = () => {
|
106
|
+
const error = itiInit.getValidationError()
|
151
107
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
if (inputValue.length === 1) {
|
156
|
-
return showFormattedError('too short')
|
157
|
-
} else if (inputValue.length === 0) {
|
158
|
-
setError('Missing phone number')
|
159
|
-
return true
|
160
|
-
} else {
|
161
|
-
return showFormattedError()
|
162
|
-
}
|
108
|
+
if (error === ValidationError.TooShort) {
|
109
|
+
const countryName = itiInit.getSelectedCountryData().name
|
110
|
+
setError(`Invalid ${countryName} phone number (too short)`)
|
163
111
|
}
|
164
112
|
}
|
165
113
|
|
166
|
-
const
|
167
|
-
if (
|
168
|
-
|
169
|
-
showFormattedError('missing area code')
|
170
|
-
return true
|
114
|
+
const validateOnlyNumbers = () => {
|
115
|
+
if (inputValue && !containOnlyNumbers(inputValue)) {
|
116
|
+
setError("Invalid phone number. Enter numbers only.")
|
171
117
|
}
|
172
118
|
}
|
173
119
|
|
174
120
|
const validateErrors = () => {
|
175
|
-
|
176
|
-
|
177
|
-
if (validateTooShortNumber(itiInit)) return
|
178
|
-
if (validateUnhandledError(itiInit)) return
|
179
|
-
if (validateMissingAreaCode(itiInit)) return
|
121
|
+
validateTooShortNumber()
|
122
|
+
validateOnlyNumbers()
|
180
123
|
}
|
181
124
|
|
182
125
|
const getCurrentSelectedData = (itiInit: any, inputValue: string) => {
|
@@ -185,31 +128,34 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
185
128
|
|
186
129
|
const handleOnChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
187
130
|
setInputValue(evt.target.value)
|
131
|
+
validateTooLongNumber(itiInit)
|
188
132
|
const phoneNumberData = getCurrentSelectedData(itiInit, evt.target.value)
|
189
133
|
setSelectedData(phoneNumberData)
|
190
134
|
onChange(phoneNumberData)
|
135
|
+
isValid(itiInit.isValidNumber())
|
191
136
|
}
|
192
137
|
|
193
138
|
// Separating Concerns as React Docs Recommend
|
194
139
|
// This also Fixes things for our react_component rendering on the Rails Side
|
195
|
-
useEffect(
|
140
|
+
useEffect(() => {
|
141
|
+
formatAllCountries()
|
142
|
+
}, [])
|
196
143
|
|
197
144
|
useEffect(() => {
|
198
|
-
const telInputInit = intlTelInput(inputRef.current, {
|
145
|
+
const telInputInit = new intlTelInput(inputRef.current, {
|
199
146
|
separateDialCode: true,
|
200
147
|
preferredCountries,
|
201
148
|
allowDropdown: !disabled,
|
202
|
-
autoInsertDialCode: false,
|
203
149
|
initialCountry,
|
204
150
|
onlyCountries,
|
205
|
-
|
206
|
-
|
151
|
+
}
|
152
|
+
)
|
207
153
|
|
208
154
|
inputRef.current.addEventListener("countrychange", (evt: Event) => {
|
155
|
+
validateTooLongNumber(telInputInit)
|
209
156
|
const phoneNumberData = getCurrentSelectedData(telInputInit, (evt.target as HTMLInputElement).value)
|
210
157
|
setSelectedData(phoneNumberData)
|
211
158
|
onChange(phoneNumberData)
|
212
|
-
validateErrors()
|
213
159
|
})
|
214
160
|
|
215
161
|
inputRef.current.addEventListener("open:countrydropdown", () => setDropDownIsOpen(true))
|
@@ -218,35 +164,24 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
218
164
|
setItiInit(telInputInit)
|
219
165
|
}, [])
|
220
166
|
|
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
|
-
|
242
167
|
return (
|
243
|
-
<div {...
|
168
|
+
<div {...ariaProps} {...dataProps} className={classes}>
|
244
169
|
<TextInput
|
245
|
-
|
246
|
-
|
170
|
+
className={dropDownIsOpen ? 'dropdown_open' : ''}
|
171
|
+
dark={dark}
|
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}
|
247
182
|
/>
|
248
183
|
</div>
|
249
184
|
)
|
250
185
|
}
|
251
186
|
|
252
|
-
export default
|
187
|
+
export default PhoneNumberInput
|
@@ -5,11 +5,9 @@ examples:
|
|
5
5
|
- phone_number_input_preferred_countries: Preferred Countries
|
6
6
|
- phone_number_input_initial_country: Initial Country
|
7
7
|
- phone_number_input_only_countries: Limited Countries
|
8
|
-
- phone_number_input_validation: Form Validation
|
9
8
|
|
10
9
|
rails:
|
11
10
|
- phone_number_input_default: Default
|
12
11
|
- phone_number_input_preferred_countries: Preferred Countries
|
13
12
|
- phone_number_input_initial_country: Initial Country
|
14
|
-
- phone_number_input_only_countries: Limited Countries
|
15
|
-
- phone_number_input_validation: Form Validation
|
13
|
+
- phone_number_input_only_countries: Limited Countries
|
@@ -2,4 +2,3 @@ export { default as PhoneNumberInputDefault } from './_phone_number_input_defaul
|
|
2
2
|
export { default as PhoneNumberInputPreferredCountries } from './_phone_number_input_preferred_countries'
|
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
|
-
export { default as PhoneNumberInputValidation } from './_phone_number_input_validation'
|
@@ -5,8 +5,6 @@ module Playbook
|
|
5
5
|
class PhoneNumberInput < Playbook::KitBase
|
6
6
|
prop :disabled, type: Playbook::Props::Boolean,
|
7
7
|
default: false
|
8
|
-
prop :required, type: Playbook::Props::Boolean,
|
9
|
-
default: false
|
10
8
|
prop :initial_country, type: Playbook::Props::String,
|
11
9
|
default: ""
|
12
10
|
prop :label, type: Playbook::Props::String,
|
@@ -17,8 +15,6 @@ module Playbook
|
|
17
15
|
default: []
|
18
16
|
prop :preferred_countries, type: Playbook::Props::Array,
|
19
17
|
default: []
|
20
|
-
prop :error, type: Playbook::Props::String,
|
21
|
-
default: ""
|
22
18
|
prop :value, type: Playbook::Props::String,
|
23
19
|
default: ""
|
24
20
|
|
@@ -31,13 +27,11 @@ module Playbook
|
|
31
27
|
id: id,
|
32
28
|
dark: dark,
|
33
29
|
disabled: disabled,
|
34
|
-
error: error,
|
35
30
|
initialCountry: initial_country,
|
36
31
|
label: label,
|
37
32
|
name: name,
|
38
33
|
onlyCountries: only_countries,
|
39
34
|
preferredCountries: preferred_countries,
|
40
|
-
required: required,
|
41
35
|
value: value,
|
42
36
|
}
|
43
37
|
end
|
@@ -1,122 +1,74 @@
|
|
1
|
-
import React from
|
2
|
-
import { render, screen } from
|
3
|
-
import PhoneNumberInput from
|
1
|
+
import React from 'react'
|
2
|
+
import { render, screen } from '../utilities/test-utils'
|
3
|
+
import PhoneNumberInput from './_phone_number_input'
|
4
4
|
|
5
|
-
const testId = "phoneNumberInput"
|
5
|
+
const testId = "phoneNumberInput"
|
6
6
|
|
7
|
-
test(
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
render(<PhoneNumberInput {...props} />);
|
14
|
-
const kit = screen.getByRole("textbox");
|
15
|
-
expect(kit).toBeDisabled();
|
16
|
-
});
|
17
|
-
|
18
|
-
test("should be enabled by default", () => {
|
19
|
-
const props = {
|
20
|
-
id: testId,
|
21
|
-
};
|
22
|
-
|
23
|
-
render(<PhoneNumberInput {...props} />);
|
24
|
-
const kit = screen.getByRole("textbox");
|
25
|
-
expect(kit).not.toBeDisabled();
|
26
|
-
});
|
27
|
-
|
28
|
-
test("should have label", () => {
|
29
|
-
const label = "Phone Number";
|
30
|
-
const props = {
|
31
|
-
id: testId,
|
32
|
-
label,
|
33
|
-
};
|
34
|
-
|
35
|
-
render(<PhoneNumberInput {...props} />);
|
36
|
-
const kit = screen.getByText(label);
|
37
|
-
expect(kit).toBeInTheDocument();
|
38
|
-
});
|
39
|
-
|
40
|
-
test("should pass data prop", () => {
|
41
|
-
const props = {
|
42
|
-
data: { testid: testId },
|
43
|
-
id: testId,
|
44
|
-
};
|
45
|
-
|
46
|
-
render(<PhoneNumberInput {...props} />);
|
47
|
-
const kit = screen.getByTestId(testId);
|
48
|
-
expect(kit).toBeInTheDocument();
|
49
|
-
});
|
50
|
-
|
51
|
-
test("should pass className prop", () => {
|
52
|
-
const className = "custom-class-name";
|
53
|
-
const props = {
|
54
|
-
className,
|
55
|
-
data: { testid: testId },
|
56
|
-
id: testId,
|
57
|
-
};
|
58
|
-
|
59
|
-
render(<PhoneNumberInput {...props} />);
|
60
|
-
const kit = screen.getByTestId(testId);
|
61
|
-
expect(kit).toHaveClass(className);
|
62
|
-
});
|
63
|
-
|
64
|
-
test("should pass value prop", () => {
|
65
|
-
const value = "1234567890";
|
66
|
-
const props = {
|
67
|
-
id: testId,
|
68
|
-
value,
|
69
|
-
};
|
7
|
+
test('should be disabled', () => {
|
8
|
+
const props = {
|
9
|
+
disabled: true,
|
10
|
+
id: testId,
|
11
|
+
}
|
70
12
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
})
|
13
|
+
render(<PhoneNumberInput {...props} />)
|
14
|
+
const kit = screen.getByRole("textbox")
|
15
|
+
expect(kit).toBeDisabled()
|
16
|
+
})
|
75
17
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
};
|
18
|
+
test('should be enabled by default', () => {
|
19
|
+
const props = {
|
20
|
+
id: testId,
|
21
|
+
}
|
81
22
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
/>
|
87
|
-
);
|
88
|
-
const kit = screen.getByRole("textbox");
|
89
|
-
expect(kit).toHaveAttribute("required");
|
90
|
-
});
|
23
|
+
render(<PhoneNumberInput {...props} />)
|
24
|
+
const kit = screen.getByRole("textbox")
|
25
|
+
expect(kit).not.toBeDisabled()
|
26
|
+
})
|
91
27
|
|
92
|
-
test(
|
28
|
+
test('should have label', () => {
|
29
|
+
const label = 'Phone Number'
|
93
30
|
const props = {
|
94
|
-
|
95
|
-
|
96
|
-
}
|
31
|
+
id: testId,
|
32
|
+
label,
|
33
|
+
}
|
97
34
|
|
98
|
-
render(
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
);
|
103
|
-
const kit = screen.getByRole("textbox");
|
104
|
-
expect(kit).toHaveAttribute("error");
|
105
|
-
});
|
106
|
-
|
107
|
-
test("should trigger callback", () => {
|
108
|
-
const handleOnValidate = jest.fn((valid) => valid)
|
35
|
+
render(<PhoneNumberInput {...props} />)
|
36
|
+
const kit = screen.getByText(label)
|
37
|
+
expect(kit).toBeInTheDocument()
|
38
|
+
})
|
109
39
|
|
40
|
+
test('should pass data prop', () => {
|
110
41
|
const props = {
|
111
|
-
|
112
|
-
|
113
|
-
}
|
42
|
+
data: { testid: testId },
|
43
|
+
id: testId,
|
44
|
+
}
|
114
45
|
|
115
|
-
render(
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
);
|
46
|
+
render(<PhoneNumberInput {...props} />)
|
47
|
+
const kit = screen.getByTestId(testId)
|
48
|
+
expect(kit).toBeInTheDocument()
|
49
|
+
})
|
120
50
|
|
121
|
-
|
122
|
-
|
51
|
+
test('should pass className prop', () => {
|
52
|
+
const className = 'custom-class-name'
|
53
|
+
const props = {
|
54
|
+
className,
|
55
|
+
data: { testid: testId },
|
56
|
+
id: testId,
|
57
|
+
}
|
58
|
+
|
59
|
+
render(<PhoneNumberInput {...props} />)
|
60
|
+
const kit = screen.getByTestId(testId)
|
61
|
+
expect(kit).toHaveClass(className)
|
62
|
+
})
|
63
|
+
|
64
|
+
test('should pass value prop', () => {
|
65
|
+
const value = '1234567890'
|
66
|
+
const props = {
|
67
|
+
id: testId,
|
68
|
+
value,
|
69
|
+
}
|
70
|
+
|
71
|
+
render(<PhoneNumberInput {...props} />)
|
72
|
+
const kit = screen.getByRole("textbox")
|
73
|
+
expect(kit).toHaveDisplayValue(value)
|
74
|
+
})
|