playbook_ui 12.26.1.pre.alpha.PLAY860PhoneNumInputOptions836 → 12.26.1.pre.alpha.play716popoverkitcloseonclickissue833

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d29efc1387739988efa50765a736b0388d28d4127c2e757512a060491b1ed873
4
- data.tar.gz: f444ac00a01b6ad4c04d4fb1173989c537757b3460336250fb81c501f763ec85
3
+ metadata.gz: bc05d54c9a3beeca0cdfd89f672b2aa6a3b5ed7cd3eb0ce9fe49aad64c2c5cf4
4
+ data.tar.gz: 40811095a508f62f3b0be30ba0910c8f7bf1e0a058b3c3cef71418c2db782382
5
5
  SHA512:
6
- metadata.gz: 1bafc61037c2a1464d26592a4d341aae0d9897318be213a6cc5e55e1cb8757e063c0ca7b0e220a10148699e9a72a7c41de0390f78c567a5ec0dd5cbfcba45ec2
7
- data.tar.gz: 71899073dbfd3874b8e9ac40787bc21529c961561474482599c71b59deb92a78e93985dd52ebd3b63f7b891877f364b073d4763bb84e9c12e22ceb2fa7e25ed3
6
+ metadata.gz: 21ae38d7936b12cafb50c6fb7a6bd7a1c250359e0c375eb34e713c9127876c583f22e7b983dea3fb8c54207997ea55e15221af2dc78cc87765b99868bd22b1ba
7
+ data.tar.gz: 1253d8ba46fb06dab662b03797e95f5b82ed240d613e4d8277d1b65c185823933ac54985828fc782c05de3cd72a889902393ffb9d0445d5ca9bbb35ebf011f11
@@ -1,16 +1,11 @@
1
- import React, { forwardRef, useEffect, useRef, useState, useImperativeHandle } from '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 { Callback } from '../types'
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
- const countryData = window.intlTelInputGlobals.getCountryData()
43
+ let countryData = window.intlTelInputGlobals.getCountryData()
54
44
 
55
45
  for (let i = 0; i < countryData.length; i++) {
56
- const country = countryData[i]
46
+ let country = countryData[i]
57
47
  country.name = formatToGlobalCountryName(country.name)
58
48
  }
59
49
  }
@@ -64,7 +54,7 @@ const containOnlyNumbers = (value: string) => {
64
54
  return /^[()+\-\ .\d]*$/g.test(value)
65
55
  }
66
56
 
67
- const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefObject<unknown>) => {
57
+ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
68
58
  const {
69
59
  aria = {},
70
60
  className,
@@ -81,9 +71,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
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,100 +87,39 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
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(props.error)
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
- /*
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
-
131
- const showFormattedError = (reason = '') => {
132
- const countryName = itiInit.getSelectedCountryData().name
133
- const reasonText = reason.length > 0 ? ` (${reason})` : ''
134
- setError(`Invalid ${countryName} phone number${reasonText}`)
135
- return true
136
- }
137
-
138
94
  const validateTooLongNumber = (itiInit: any) => {
139
- if (!itiInit) return
140
- if (itiInit.getValidationError() === ValidationError.TooLong) {
141
- return showFormattedError('too long')
142
- } else {
143
- setError('')
144
- }
145
- }
95
+ const error = itiInit.getValidationError()
146
96
 
147
- const validateTooShortNumber = (itiInit: any) => {
148
- if (!itiInit) return
149
- if (itiInit.getValidationError() === ValidationError.TooShort) {
150
- return showFormattedError('too short')
97
+ if (error === ValidationError.TooLong) {
98
+ const countryName = itiInit.getSelectedCountryData().name
99
+ setError(`Invalid ${countryName} phone number (too long)`)
151
100
  } else {
152
- if (inputValue.length === 1) {
153
- return showFormattedError('too short')
154
- } else {
155
- setError('')
156
- }
101
+ setError("")
157
102
  }
158
103
  }
159
104
 
160
- const validateOnlyNumbers = (itiInit: any) => {
161
- if (!itiInit) return
162
- if (inputValue && !containOnlyNumbers(inputValue)) {
163
- return showFormattedError('enter numbers only')
164
- }
165
- }
105
+ const validateTooShortNumber = () => {
106
+ const error = itiInit.getValidationError()
166
107
 
167
- const validateUnhandledError = (itiInit: any) => {
168
- if (!required || !itiInit) return
169
- if (itiInit.getValidationError() === ValidationError.SomethingWentWrong) {
170
- if (inputValue.length === 1) {
171
- return showFormattedError('too short')
172
- } else if (inputValue.length === 0) {
173
- setError('Missing phone number')
174
- return true
175
- } else {
176
- return showFormattedError()
177
- }
108
+ if (error === ValidationError.TooShort) {
109
+ const countryName = itiInit.getSelectedCountryData().name
110
+ setError(`Invalid ${countryName} phone number (too short)`)
178
111
  }
179
112
  }
180
113
 
181
- const validateMissingAreaCode = (itiInit: any) => {
182
- if (!required || !itiInit) return
183
- if (itiInit.getValidationError() === ValidationError.MissingAreaCode) {
184
- showFormattedError('missing area code')
185
- return true
114
+ const validateOnlyNumbers = () => {
115
+ if (inputValue && !containOnlyNumbers(inputValue)) {
116
+ setError("Invalid phone number. Enter numbers only.")
186
117
  }
187
118
  }
188
119
 
189
120
  const validateErrors = () => {
190
- if (itiInit) isValid(itiInit.isValidNumber())
191
- if (validateOnlyNumbers(itiInit)) return
192
- if (validateTooLongNumber(itiInit)) return
193
- if (validateTooShortNumber(itiInit)) return
194
- if (validateUnhandledError(itiInit)) return
195
- if (validateMissingAreaCode(itiInit)) return
121
+ validateTooShortNumber()
122
+ validateOnlyNumbers()
196
123
  }
197
124
 
198
125
  const getCurrentSelectedData = (itiInit: any, inputValue: string) => {
@@ -201,6 +128,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
201
128
 
202
129
  const handleOnChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
203
130
  setInputValue(evt.target.value)
131
+ validateTooLongNumber(itiInit)
204
132
  const phoneNumberData = getCurrentSelectedData(itiInit, evt.target.value)
205
133
  setSelectedData(phoneNumberData)
206
134
  onChange(phoneNumberData)
@@ -209,24 +137,25 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
209
137
 
210
138
  // Separating Concerns as React Docs Recommend
211
139
  // This also Fixes things for our react_component rendering on the Rails Side
212
- useEffect(formatAllCountries, [])
140
+ useEffect(() => {
141
+ formatAllCountries()
142
+ }, [])
213
143
 
214
144
  useEffect(() => {
215
- const telInputInit = intlTelInput(inputRef.current, {
145
+ const telInputInit = new intlTelInput(inputRef.current, {
216
146
  separateDialCode: true,
217
147
  preferredCountries,
218
148
  allowDropdown: !disabled,
219
- autoInsertDialCode: false,
220
149
  initialCountry,
221
150
  onlyCountries,
222
- utilsScript: "intl-tel-input/build/js/utils.js"
223
- })
151
+ }
152
+ )
224
153
 
225
154
  inputRef.current.addEventListener("countrychange", (evt: Event) => {
155
+ validateTooLongNumber(telInputInit)
226
156
  const phoneNumberData = getCurrentSelectedData(telInputInit, (evt.target as HTMLInputElement).value)
227
157
  setSelectedData(phoneNumberData)
228
158
  onChange(phoneNumberData)
229
- validateErrors()
230
159
  })
231
160
 
232
161
  inputRef.current.addEventListener("open:countrydropdown", () => setDropDownIsOpen(true))
@@ -235,40 +164,24 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
235
164
  setItiInit(telInputInit)
236
165
  }, [])
237
166
 
238
- let textInputProps: {[key: string]: any} = {
239
- className: dropDownIsOpen ? 'dropdown_open' : '',
240
- dark,
241
- "data-phone-number": JSON.stringify(selectedData),
242
- disabled,
243
- error,
244
- type: 'tel',
245
- id,
246
- label,
247
- name,
248
- onBlur: validateErrors,
249
- onChange: handleOnChange,
250
- value: inputValue
251
- }
252
-
253
- let wrapperProps: Record<string, unknown> = { className: classes }
254
-
255
- if (!isEmpty(aria)) textInputProps = {...textInputProps, ...ariaProps}
256
- if (!isEmpty(data)) wrapperProps = {...wrapperProps, ...dataProps}
257
- if (required) textInputProps.required = true
258
-
259
167
  return (
260
- <div {...wrapperProps}>
168
+ <div {...ariaProps} {...dataProps} className={classes}>
261
169
  <TextInput
262
- ref={
263
- inputNode => {
264
- ref ? ref.current = inputNode : null
265
- inputRef.current = inputNode
266
- }
267
- }
268
- {...textInputProps}
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}
269
182
  />
270
183
  </div>
271
184
  )
272
185
  }
273
186
 
274
- export default forwardRef(PhoneNumberInput)
187
+ export default PhoneNumberInput
@@ -5,13 +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
- - phone_number_input_clear_field: Clearing the Input Field
10
- - phone_number_input_access_input_element: Accessing the Input Element
11
8
 
12
9
  rails:
13
10
  - phone_number_input_default: Default
14
11
  - phone_number_input_preferred_countries: Preferred Countries
15
12
  - phone_number_input_initial_country: Initial Country
16
- - phone_number_input_only_countries: Limited Countries
17
- - phone_number_input_validation: Form Validation
13
+ - phone_number_input_only_countries: Limited Countries
@@ -2,6 +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'
6
- export { default as PhoneNumberInputClearField } from './_phone_number_input_clear_field'
7
- export { default as PhoneNumberInputAccessInputElement } from './_phone_number_input_access_input_element'
@@ -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 "react";
2
- import { render, screen } from "../utilities/test-utils";
3
- import PhoneNumberInput from "./_phone_number_input";
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("should be disabled", () => {
8
- const props = {
9
- disabled: true,
10
- id: testId,
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
- render(<PhoneNumberInput {...props} />);
72
- const kit = screen.getByRole("textbox");
73
- expect(kit).toHaveDisplayValue(value);
74
- });
13
+ render(<PhoneNumberInput {...props} />)
14
+ const kit = screen.getByRole("textbox")
15
+ expect(kit).toBeDisabled()
16
+ })
75
17
 
76
- //TODO: test required field presence
77
- test("should pass required prop", () => {
78
- const props = {
79
- id: testId,
80
- };
18
+ test('should be enabled by default', () => {
19
+ const props = {
20
+ id: testId,
21
+ }
81
22
 
82
- render(
83
- <PhoneNumberInput
84
- required
85
- {...props}
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("should have error attribute when invalid", () => {
28
+ test('should have label', () => {
29
+ const label = 'Phone Number'
93
30
  const props = {
94
- id: testId,
95
- error: "Something isn't right here."
96
- };
31
+ id: testId,
32
+ label,
33
+ }
97
34
 
98
- render(
99
- <PhoneNumberInput
100
- {...props}
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
- id: testId,
112
- onValidate: handleOnValidate
113
- };
42
+ data: { testid: testId },
43
+ id: testId,
44
+ }
114
45
 
115
- render(
116
- <PhoneNumberInput
117
- {...props}
118
- />
119
- );
46
+ render(<PhoneNumberInput {...props} />)
47
+ const kit = screen.getByTestId(testId)
48
+ expect(kit).toBeInTheDocument()
49
+ })
120
50
 
121
- expect(handleOnValidate).toBeCalledWith(true)
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
+ })
@@ -171,17 +171,19 @@ const PbReactPopover = (props: PbPopoverProps) => {
171
171
 
172
172
  switch (closeOnClick) {
173
173
  case "outside":
174
- if (!targetIsPopover || targetIsReference) {
174
+ if (!targetIsPopover && !targetIsReference) {
175
175
  shouldClosePopover(true);
176
176
  }
177
177
  break;
178
178
  case "inside":
179
- if (targetIsPopover || targetIsReference) {
179
+ if (targetIsPopover) {
180
180
  shouldClosePopover(true);
181
181
  }
182
182
  break;
183
183
  case "any":
184
- shouldClosePopover(true);
184
+ if (targetIsPopover || !targetIsPopover && !targetIsReference) {
185
+ shouldClosePopover(true);
186
+ }
185
187
  break;
186
188
  }
187
189
  },
@@ -49,13 +49,16 @@ export default class PbPopover extends PbEnhancedElement {
49
49
  checkCloseTooltip() {
50
50
  document.querySelector('body').addEventListener('click', ({ target } ) => {
51
51
  const isTooltipElement = (target as HTMLElement).closest(`#${this.tooltipId}`) !== null
52
+ const isTriggerElement = (target as HTMLElement).closest(`#${this.triggerElementId}`) !== null
52
53
 
53
54
  switch (this.closeOnClick) {
54
55
  case 'any':
55
- this.hideTooltip()
56
+ if (isTooltipElement || !isTooltipElement && !isTriggerElement) {
57
+ this.hideTooltip()
58
+ }
56
59
  break
57
60
  case 'outside':
58
- if (!isTooltipElement) {
61
+ if (!isTooltipElement && !isTriggerElement) {
59
62
  this.hideTooltip()
60
63
  }
61
64
  break