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

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 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