playbook_ui 14.12.0.pre.rc.8 → 14.12.0.pre.rc.10

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: dde2f316d84b4ddc9cc0377b283b36fd9c59cca4d76e16392696c5daf0b91b74
4
- data.tar.gz: f91a51386632b5cd58edc7e946d582bf49c9fe22c58a932a01d3b82b7c21a0f9
3
+ metadata.gz: 4c6777b92aed02b037115558559d120e195ef9928198b8e3748565e9afa16583
4
+ data.tar.gz: 95a487c401f7d459908d660e59a0310563183cd6f1d3ad195c15007c3ebed5b3
5
5
  SHA512:
6
- metadata.gz: 44737f2c553969cbfc368ccc5d781ce4f08eca1f39d4a61c30bb185b6ca2836d63ee389005fa44e0dde65b39d62835ec2c1d7f216f322fbe05f54b28c8206ca8
7
- data.tar.gz: 163371a4bf7d5c68446346237baf84ea638c173fe841091ec13190dac3e422dce14ea3564a08c78e69b6d4a036fa50de986a27108ffadcb3c4a2cd9f2f8465d7
6
+ metadata.gz: 8f49103a3824e0d754c720f51d8b02e5464c56a67cac077070b50c077bca03a24d72a4d479b21435afb81588c3954878b595be0beef9c0121e70a0fed95b9afa
7
+ data.tar.gz: 9045714a3aff511da3fa9feeafaf7ecfe84f6b2ff7915b67405446f35b9517cc1c6a7320812ed7b6b6636ae285f03f18c1f0aae0b252b93927e343019cadf8b3
@@ -35,6 +35,7 @@ type PhoneNumberInputProps = {
35
35
  preferredCountries?: string[],
36
36
  required?: boolean,
37
37
  value?: string,
38
+ formatAsYouType?: boolean,
38
39
  }
39
40
 
40
41
  enum ValidationError {
@@ -87,6 +88,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
87
88
  required = false,
88
89
  preferredCountries = [],
89
90
  value = "",
91
+ formatAsYouType = false,
90
92
  } = props
91
93
 
92
94
  const ariaProps = buildAriaProps(aria)
@@ -99,8 +101,8 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
99
101
  )
100
102
 
101
103
  const inputRef = useRef<HTMLInputElement>()
104
+ const itiRef = useRef<any>(null);
102
105
  const [inputValue, setInputValue] = useState(value)
103
- const [itiInit, setItiInit] = useState<any>()
104
106
  const [error, setError] = useState(props.error)
105
107
  const [dropDownIsOpen, setDropDownIsOpen] = useState(false)
106
108
  const [selectedData, setSelectedData] = useState()
@@ -130,8 +132,12 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
130
132
  }
131
133
  })
132
134
 
135
+ const unformatNumber = (formattedNumber: any) => {
136
+ return formattedNumber.replace(/\D/g, "")
137
+ }
138
+
133
139
  const showFormattedError = (reason = '') => {
134
- const countryName = itiInit.getSelectedCountryData().name
140
+ const countryName = itiRef.current.getSelectedCountryData().name
135
141
  const reasonText = reason.length > 0 ? ` (${reason})` : ''
136
142
  setError(`Invalid ${countryName} phone number${reasonText}`)
137
143
  return true
@@ -189,12 +195,12 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
189
195
  }
190
196
 
191
197
  const validateErrors = () => {
192
- if (itiInit) isValid(itiInit.isValidNumber())
193
- if (validateOnlyNumbers(itiInit)) return
194
- if (validateTooLongNumber(itiInit)) return
195
- if (validateTooShortNumber(itiInit)) return
196
- if (validateUnhandledError(itiInit)) return
197
- if (validateMissingAreaCode(itiInit)) return
198
+ if (itiRef.current) isValid(itiRef.current.isValidNumber())
199
+ if (validateOnlyNumbers(itiRef.current)) return
200
+ if (validateTooLongNumber(itiRef.current)) return
201
+ if (validateTooShortNumber(itiRef.current)) return
202
+ if (validateUnhandledError(itiRef.current)) return
203
+ if (validateMissingAreaCode(itiRef.current)) return
198
204
  }
199
205
 
200
206
  const getCurrentSelectedData = (itiInit: any, inputValue: string) => {
@@ -203,10 +209,16 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
203
209
 
204
210
  const handleOnChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
205
211
  setInputValue(evt.target.value)
206
- const phoneNumberData = getCurrentSelectedData(itiInit, evt.target.value)
212
+ let phoneNumberData
213
+ if (formatAsYouType) {
214
+ const formattedPhoneNumberData = getCurrentSelectedData(itiRef.current, evt.target.value)
215
+ phoneNumberData = {...formattedPhoneNumberData, number: unformatNumber(formattedPhoneNumberData.number)}
216
+ } else {
217
+ phoneNumberData = getCurrentSelectedData(itiRef.current, evt.target.value)
218
+ }
207
219
  setSelectedData(phoneNumberData)
208
220
  onChange(phoneNumberData)
209
- isValid(itiInit.isValidNumber())
221
+ isValid(itiRef.current.isValidNumber())
210
222
  }
211
223
 
212
224
  // Separating Concerns as React Docs Recommend
@@ -230,9 +242,11 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
230
242
  onlyCountries,
231
243
  countrySearch: false,
232
244
  fixDropdownWidth: false,
233
- formatAsYouType: false,
245
+ formatAsYouType: formatAsYouType,
234
246
  })
235
247
 
248
+ itiRef.current = telInputInit;
249
+
236
250
  inputRef.current.addEventListener("countrychange", (evt: Event) => {
237
251
  const phoneNumberData = getCurrentSelectedData(telInputInit, (evt.target as HTMLInputElement).value)
238
252
  setSelectedData(phoneNumberData)
@@ -243,7 +257,11 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
243
257
  inputRef.current.addEventListener("open:countrydropdown", () => setDropDownIsOpen(true))
244
258
  inputRef.current.addEventListener("close:countrydropdown", () => setDropDownIsOpen(false))
245
259
 
246
- setItiInit(telInputInit)
260
+ if (formatAsYouType) {
261
+ inputRef.current?.addEventListener("input", (evt) => {
262
+ handleOnChange(evt as unknown as React.ChangeEvent<HTMLInputElement>);
263
+ });
264
+ }
247
265
  }, [])
248
266
 
249
267
  let textInputProps: {[key: string]: any} = {
@@ -0,0 +1,15 @@
1
+ <%= pb_rails("phone_number_input", props: {
2
+ id: "phone_number_input",
3
+ format_as_you_type: true
4
+ }) %>
5
+
6
+ <%= pb_rails("button", props: {id: "clickable", text: "Save Phone Number"}) %>
7
+
8
+ <%= javascript_tag do %>
9
+ document.querySelector('#clickable').addEventListener('click', () => {
10
+ const formattedPhoneNumber = document.querySelector('#phone_number_input').value
11
+ const unformattedPhoneNumber = formattedPhoneNumber.replace(/\D/g, "")
12
+
13
+ alert(`Formatted: ${formattedPhoneNumber}. Unformatted: ${unformattedPhoneNumber}`)
14
+ })
15
+ <% end %>
@@ -0,0 +1,24 @@
1
+ import React, { useState } from "react";
2
+ import { PhoneNumberInput, Body } from "playbook-ui";
3
+
4
+ const PhoneNumberInputFormat = (props) => {
5
+ const [phoneNumber, setPhoneNumber] = useState("");
6
+
7
+ const handleOnChange = ({ number }) => {
8
+ setPhoneNumber(number);
9
+ };
10
+
11
+ return (
12
+ <>
13
+ <PhoneNumberInput
14
+ formatAsYouType
15
+ id="format"
16
+ onChange={handleOnChange}
17
+ {...props}
18
+ />
19
+ {phoneNumber && <Body>Unformatted number: {phoneNumber}</Body>}
20
+ </>
21
+ );
22
+ };
23
+
24
+ export default PhoneNumberInputFormat;
@@ -0,0 +1 @@
1
+ NOTE: the `number` in the React `onChange` event will not include formatting (no spaces, dashes, and parentheses). For Rails, the `value` will include formatting and its value must be sanitized manually.
@@ -8,10 +8,12 @@ examples:
8
8
  - phone_number_input_validation: Form Validation
9
9
  - phone_number_input_clear_field: Clearing the Input Field
10
10
  - phone_number_input_access_input_element: Accessing the Input Element
11
+ - phone_number_input_format: Format as You Type
11
12
 
12
13
  rails:
13
14
  - phone_number_input_default: Default
14
15
  - phone_number_input_preferred_countries: Preferred Countries
15
16
  - phone_number_input_initial_country: Initial Country
16
17
  - phone_number_input_only_countries: Limited Countries
17
- - phone_number_input_validation: Form Validation
18
+ - phone_number_input_validation: Form Validation
19
+ - phone_number_input_format: Format as You Type
@@ -5,3 +5,4 @@ export { default as PhoneNumberInputOnlyCountries } from './_phone_number_input_
5
5
  export { default as PhoneNumberInputValidation } from './_phone_number_input_validation'
6
6
  export { default as PhoneNumberInputClearField } from './_phone_number_input_clear_field'
7
7
  export { default as PhoneNumberInputAccessInputElement } from './_phone_number_input_access_input_element'
8
+ export { default as PhoneNumberInputFormat } from './_phone_number_input_format'
@@ -21,6 +21,8 @@ module Playbook
21
21
  default: ""
22
22
  prop :value, type: Playbook::Props::String,
23
23
  default: ""
24
+ prop :format_as_you_type, type: Playbook::Props::Boolean,
25
+ default: false
24
26
 
25
27
  def classname
26
28
  generate_classname("pb_phone_number_input")
@@ -32,6 +34,7 @@ module Playbook
32
34
  dark: dark,
33
35
  disabled: disabled,
34
36
  error: error,
37
+ formatAsYouType: format_as_you_type,
35
38
  initialCountry: initial_country,
36
39
  label: label,
37
40
  name: name,
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { render, screen } from "../utilities/test-utils";
2
+ import { render, screen, act } from "../utilities/test-utils";
3
3
  import PhoneNumberInput from "./_phone_number_input";
4
4
 
5
5
  const testId = "phoneNumberInput";
@@ -120,3 +120,22 @@ test("should trigger callback", () => {
120
120
 
121
121
  expect(handleOnValidate).toBeCalledWith(true)
122
122
  });
123
+
124
+ test("should format phone number as '555-555-5555' with formatAsYouType and 'us' country", () => {
125
+ const props = {
126
+ initialCountry: 'us',
127
+ formatAsYouType: true,
128
+ id: testId,
129
+ };
130
+
131
+ render(<PhoneNumberInput {...props} />);
132
+
133
+ const input = screen.getByRole("textbox");
134
+
135
+ act(() => {
136
+ input.value = "5555555555";
137
+ input.dispatchEvent(new Event('input', { bubbles: true }));
138
+ });
139
+
140
+ expect(input.value).toBe("555-555-5555");
141
+ });
@@ -50,7 +50,7 @@ $gap_lg: $height_from_top + $space_lg;
50
50
  >div {
51
51
  &:last-child {
52
52
  flex-basis: auto !important;
53
- [class=pb_timeline_item_step] {
53
+ [class^=pb_timeline_item_step] {
54
54
  [class=pb_timeline_item_connector] {
55
55
  opacity: 0;
56
56
  }
@@ -66,29 +66,25 @@ $gap_lg: $height_from_top + $space_lg;
66
66
  [class*=pb_timeline_item_kit] {
67
67
  &[class*=_solid] {
68
68
  flex-basis: 100%;
69
- [class=pb_timeline_item_left_block] {
70
- height: 0px;
71
- }
72
- [class=pb_timeline_item_step] {
69
+ [class^=pb_timeline_item_step] {
73
70
  @include flex_wrapper(row);
71
+ align-items: center;
74
72
  margin-top: $space_xs;
75
73
  margin-bottom: $space_xs;
76
74
  [class=pb_timeline_item_connector] {
77
- @include pb_timeline_line_solid($connector_width, $connector_width, $height_from_top $icon_margin 0 $icon_margin );
75
+ @include pb_timeline_line_solid($connector_width, $connector_width, 0 $icon_margin 0 $icon_margin );
78
76
  }
79
77
  }
80
78
  }
81
79
  &[class*=_dotted] {
82
80
  flex-basis: 100%;
83
- [class=pb_timeline_item_left_block] {
84
- height: 0;
85
- }
86
- [class=pb_timeline_item_step] {
81
+ [class^=pb_timeline_item_step] {
87
82
  @include flex_wrapper(row);
83
+ align-items: center;
88
84
  margin-top: $space_xs;
89
85
  margin-bottom: $space_xs;
90
86
  [class=pb_timeline_item_connector] {
91
- @include pb_timeline_line_dotted_horizontal($connector_width, $connector_width, $height_from_top $icon_margin 0 $icon_margin );
87
+ @include pb_timeline_line_dotted_horizontal($connector_width, $connector_width, 0 $icon_margin 0 $icon_margin );
92
88
  }
93
89
  }
94
90
  }
@@ -99,7 +95,7 @@ $gap_lg: $height_from_top + $space_lg;
99
95
  >div {
100
96
  &:last-child {
101
97
  flex-basis: auto !important;
102
- [class=pb_timeline_item_step] {
98
+ [class^=pb_timeline_item_step] {
103
99
  [class=pb_timeline_item_connector] {
104
100
  opacity: 0;
105
101
  }
@@ -129,12 +125,12 @@ $gap_lg: $height_from_top + $space_lg;
129
125
  }
130
126
  }
131
127
  }
132
- [class=pb_timeline_item_step] {
128
+ [class^=pb_timeline_item_step] {
133
129
  @include flex_wrapper(row);
134
130
  margin-top: $space_xs;
135
131
  margin-bottom: $space_xs;
136
132
  [class=pb_timeline_item_connector] {
137
- @include pb_timeline_line_solid($connector_width, $connector_width, $height_from_top $icon_margin 0 $icon_margin );
133
+ @include pb_timeline_line_solid($connector_width, $connector_width, 0 $icon_margin 0 $icon_margin );
138
134
  }
139
135
  }
140
136
  }
@@ -153,12 +149,12 @@ $gap_lg: $height_from_top + $space_lg;
153
149
  }
154
150
  }
155
151
  }
156
- [class=pb_timeline_item_step] {
152
+ [class^=pb_timeline_item_step] {
157
153
  @include flex_wrapper(row);
158
154
  margin-top: $space_xs;
159
155
  margin-bottom: $space_xs;
160
156
  [class=pb_timeline_item_connector] {
161
- @include pb_timeline_line_dotted_horizontal($connector_width, $connector_width, $height_from_top $icon_margin 0 $icon_margin );
157
+ @include pb_timeline_line_dotted_horizontal($connector_width, $connector_width, 0 $icon_margin 0 $icon_margin );
162
158
  }
163
159
  }
164
160
  }
@@ -170,7 +166,7 @@ $gap_lg: $height_from_top + $space_lg;
170
166
  align-items: flex-start;
171
167
  align-self: auto;
172
168
  >div:last-child {
173
- [class=pb_timeline_item_step] {
169
+ [class^=pb_timeline_item_step] {
174
170
  [class=pb_timeline_item_connector] {
175
171
  opacity: 0;
176
172
  }
@@ -180,13 +176,14 @@ $gap_lg: $height_from_top + $space_lg;
180
176
  @include flex_wrapper(row);
181
177
  &[class*=_solid] {
182
178
  flex-basis: 100%;
183
- [class=pb_timeline_item_step] {
179
+ [class^=pb_timeline_item_step] {
184
180
  @include flex_wrapper(column);
181
+ align-items: center;
185
182
  align-content: flex-start;
186
183
  margin-right: $space_sm;
187
184
  margin-left: $space_sm;
188
185
  [class=pb_timeline_item_connector] {
189
- @include pb_timeline_line_solid($connector_width, $connector_width, $icon_margin 0 $icon_margin $height_from_top);
186
+ @include pb_timeline_line_solid($connector_width, $connector_width, $icon_margin 0 $icon_margin 0);
190
187
  }
191
188
  }
192
189
  [class=pb_timeline_item_left_block] {
@@ -200,12 +197,13 @@ $gap_lg: $height_from_top + $space_lg;
200
197
  }
201
198
  &[class*=_dotted] {
202
199
  flex-basis: 100%;
203
- [class=pb_timeline_item_step] {
200
+ [class^=pb_timeline_item_step] {
204
201
  @include flex_wrapper(column);
202
+ align-items: center;
205
203
  margin-right: $space_sm;
206
204
  margin-left: $space_sm;
207
205
  [class=pb_timeline_item_connector] {
208
- @include pb_timeline_line_dotted_vertical($connector_width, $connector_width, $icon_margin 0 $icon_margin $height_from_top);
206
+ @include pb_timeline_line_dotted_vertical($connector_width, $connector_width, $icon_margin 0 $icon_margin 0);
209
207
  }
210
208
  }
211
209
  [class=pb_timeline_item_left_block] {
@@ -223,7 +221,7 @@ $gap_lg: $height_from_top + $space_lg;
223
221
  align-items: flex-start;
224
222
  align-self: auto;
225
223
  >div:last-child {
226
- [class=pb_timeline_item_step] {
224
+ [class^=pb_timeline_item_step] {
227
225
  [class=pb_timeline_item_connector] {
228
226
  opacity: 0;
229
227
  }
@@ -233,13 +231,14 @@ $gap_lg: $height_from_top + $space_lg;
233
231
  @include flex_wrapper(row);
234
232
  &[class*=_solid] {
235
233
  flex-basis: 100%;
236
- [class=pb_timeline_item_step] {
234
+ [class^=pb_timeline_item_step] {
237
235
  @include flex_wrapper(column);
236
+ align-items: center;
238
237
  align-content: flex-start;
239
238
  margin-right: $space_sm;
240
239
  margin-left: $space_sm;
241
240
  [class=pb_timeline_item_connector] {
242
- @include pb_timeline_line_solid($connector_width, $connector_width, $icon_margin 0 $icon_margin $height_from_top);
241
+ @include pb_timeline_line_solid($connector_width, $connector_width, $icon_margin 0 $icon_margin 0);
243
242
  }
244
243
  }
245
244
  [class=pb_timeline_item_left_block] {
@@ -253,12 +252,13 @@ $gap_lg: $height_from_top + $space_lg;
253
252
  }
254
253
  &[class*=_dotted] {
255
254
  flex-basis: 100%;
256
- [class=pb_timeline_item_step] {
255
+ [class^=pb_timeline_item_step] {
257
256
  @include flex_wrapper(column);
257
+ align-items: center;
258
258
  margin-right: $space_sm;
259
259
  margin-left: $space_sm;
260
260
  [class=pb_timeline_item_connector] {
261
- @include pb_timeline_line_dotted_vertical($connector_width, $connector_width, $icon_margin 0 $icon_margin $height_from_top);
261
+ @include pb_timeline_line_dotted_vertical($connector_width, $connector_width, $icon_margin 0 $icon_margin 0);
262
262
  }
263
263
  }
264
264
  [class=pb_timeline_item_left_block] {
@@ -274,7 +274,7 @@ $gap_lg: $height_from_top + $space_lg;
274
274
  }
275
275
  &[class*=_gap_xs] {
276
276
  [class*=pb_timeline_item_kit] {
277
- [class=pb_timeline_item_step] {
277
+ [class^=pb_timeline_item_step] {
278
278
  [class=pb_timeline_item_connector] {
279
279
  height: $gap_xs !important;
280
280
  }
@@ -283,7 +283,7 @@ $gap_lg: $height_from_top + $space_lg;
283
283
  }
284
284
  &[class*=_gap_sm] {
285
285
  [class*=pb_timeline_item_kit] {
286
- [class=pb_timeline_item_step] {
286
+ [class^=pb_timeline_item_step] {
287
287
  [class=pb_timeline_item_connector] {
288
288
  height: $gap_sm !important;
289
289
  }
@@ -292,7 +292,7 @@ $gap_lg: $height_from_top + $space_lg;
292
292
  }
293
293
  &[class*=_gap_md] {
294
294
  [class*=pb_timeline_item_kit] {
295
- [class=pb_timeline_item_step] {
295
+ [class^=pb_timeline_item_step] {
296
296
  [class=pb_timeline_item_connector] {
297
297
  height: $gap_md !important;
298
298
  }
@@ -301,7 +301,7 @@ $gap_lg: $height_from_top + $space_lg;
301
301
  }
302
302
  &[class*=_gap_lg] {
303
303
  [class*=pb_timeline_item_kit] {
304
- [class=pb_timeline_item_step] {
304
+ [class^=pb_timeline_item_step] {
305
305
  [class=pb_timeline_item_connector] {
306
306
  height: $gap_lg !important;
307
307
  }