playbook_ui 14.9.0.pre.alpha.play1742globalheightfixes4925 → 14.9.0.pre.alpha.play17004992

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: 65c48523db92e25026da29dafc1e1d48373dd867a8d467a8a57eaaae093f26a0
4
- data.tar.gz: b6044dcebf4a60671de1db8ed4a6252426679eceb2946a6eea71b4b944710dae
3
+ metadata.gz: 585093eef9e79407e25a130a2a77f097c248bc2884116fc21bc9946c53b4cfe0
4
+ data.tar.gz: 82327cf34bd6c8b448e67549d84243c17622d2382ee07efa8730e13544c36be1
5
5
  SHA512:
6
- metadata.gz: c60478ac6a44d1ac360a61be53aace6afe588dc0f1ac47d595b0344ad08dbf1b49e3b76a8c2c7fbf6378dd90832f6244534671a389927faffac43044eac87b74
7
- data.tar.gz: a8b5110687957cd2879660f4617e1ed2b764d905d0bae7e17543c31dfb8f406d7704cd43fc18bcb466d893e89d28e408f2d35d9b6794557861fe813099138fa9
6
+ metadata.gz: e62d590332efe256a1464c1a2dfb104cb6078ddae7003114d656b016921f4252537ce52c78265b8ab27c4bca5e1dd0c93344d32080bfc2183e75cad551d1ace0
7
+ data.tar.gz: 6935b07d0cadec5f1156367e0488ce352aaf2cacbe7d9310b10b064db8428e4801e4ba7805c66a53dc68a313c5ea2be87219684465c0a4f09712c5fcc057c1a3
@@ -287,6 +287,7 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
287
287
  {...ariaProps}
288
288
  {...dataProps}
289
289
  {...htmlProps}
290
+ style={dynamicInlineProps}
290
291
  className={classnames(drawerClassNames.base, {
291
292
  [drawerClassNames.afterOpen]:
292
293
  animationState === "afterOpen",
@@ -295,7 +296,6 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
295
296
  })}
296
297
  id={id}
297
298
  onClick={(e) => e.stopPropagation()}
298
- style={dynamicInlineProps}
299
299
  >
300
300
  {children}
301
301
  </div>
@@ -306,6 +306,7 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
306
306
  {...dataProps}
307
307
  {...htmlProps}
308
308
  className={classes}
309
+ style={dynamicInlineProps}
309
310
  >
310
311
  {isModalVisible && (
311
312
  <div
@@ -326,7 +327,6 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
326
327
  animationState === "beforeClose",
327
328
  })}
328
329
  onClick={(e) => e.stopPropagation()}
329
- style={dynamicInlineProps}
330
330
  >
331
331
  {children}
332
332
  </div>
@@ -24,37 +24,34 @@ type LayoutPropTypes = {
24
24
  type LayoutSideProps = {
25
25
  children: React.ReactNode[] | React.ReactNode,
26
26
  className?: string,
27
- } & GlobalProps
27
+ }
28
28
 
29
29
  type LayoutBodyProps = {
30
30
  children: React.ReactNode[] | React.ReactNode,
31
31
  className?: string,
32
- } & GlobalProps
32
+ }
33
33
 
34
34
  type LayoutItemProps = {
35
35
  children: React.ReactNode[] | React.ReactNode,
36
36
  className?: string,
37
37
  size?: "sm" | "md" | "lg"
38
- } & GlobalProps
38
+ }
39
39
 
40
40
  type LayoutHeaderProps = {
41
41
  children: React.ReactNode[] | React.ReactNode,
42
42
  className?: string,
43
- } & GlobalProps
43
+ }
44
44
 
45
45
  type LayoutFooterProps = {
46
46
  children: React.ReactNode[] | React.ReactNode,
47
47
  className?: string,
48
- } & GlobalProps
48
+ }
49
49
 
50
+ // Side component
50
51
  const Side = (props: LayoutSideProps) => {
51
52
  const { children, className } = props
52
- const dynamicInlineProps = globalInlineProps(props)
53
53
  return (
54
- <div
55
- className={classnames('layout_sidebar', globalProps(props), className)}
56
- style={dynamicInlineProps}
57
- >
54
+ <div className={classnames('layout_sidebar', globalProps(props), className)}>
58
55
  {children}
59
56
  </div>
60
57
  )
@@ -63,12 +60,8 @@ const Side = (props: LayoutSideProps) => {
63
60
  // Body component
64
61
  const Body = (props: LayoutBodyProps) => {
65
62
  const { children, className } = props
66
- const dynamicInlineProps = globalInlineProps(props)
67
63
  return (
68
- <div
69
- className={classnames('layout_body', globalProps(props), className)}
70
- style={dynamicInlineProps}
71
- >
64
+ <div className={classnames('layout_body', globalProps(props), className)}>
72
65
  {children}
73
66
  </div>
74
67
  )
@@ -78,12 +71,8 @@ const Body = (props: LayoutBodyProps) => {
78
71
  const Item = (props: LayoutItemProps) => {
79
72
  const { children, className, size = 'sm' } = props
80
73
  const sizeClass = `size_${size}`
81
- const dynamicInlineProps = globalInlineProps(props)
82
74
  return (
83
- <div
84
- className={classnames('layout_item', sizeClass, globalProps(props), className)}
85
- style={dynamicInlineProps}
86
- >
75
+ <div className={classnames('layout_item', sizeClass, globalProps(props), className)}>
87
76
  {children}
88
77
  </div>
89
78
  )
@@ -92,12 +81,8 @@ const Item = (props: LayoutItemProps) => {
92
81
  // Header component
93
82
  const Header = (props: LayoutHeaderProps) => {
94
83
  const { children, className } = props
95
- const dynamicInlineProps = globalInlineProps(props)
96
84
  return (
97
- <div
98
- className={classnames('layout_header', globalProps(props), className)}
99
- style={dynamicInlineProps}
100
- >
85
+ <div className={classnames('layout_header', globalProps(props), className)}>
101
86
  {children}
102
87
  </div>
103
88
  )
@@ -106,12 +91,8 @@ const Header = (props: LayoutHeaderProps) => {
106
91
  // Footer component
107
92
  const Footer = (props: LayoutFooterProps) => {
108
93
  const { children, className } = props
109
- const dynamicInlineProps = globalInlineProps(props)
110
94
  return (
111
- <div
112
- className={classnames('layout_footer', globalProps(props), className)}
113
- style={dynamicInlineProps}
114
- >
95
+ <div className={classnames('layout_footer', globalProps(props), className)}>
115
96
  {children}
116
97
  </div>
117
98
  )
@@ -19,8 +19,11 @@ $section_colors_dark: (
19
19
  align-items: center;
20
20
  position: relative;
21
21
  span {
22
- padding: 0 $space_xs;
22
+ padding: 0;
23
23
  display: flex;
24
+ [class*="pb_caption_kit"] {
25
+ padding: 0 $space_xs;
26
+ }
24
27
  }
25
28
  &::before {
26
29
  content: "";
@@ -1,4 +1,4 @@
1
- import React, { forwardRef } from 'react'
1
+ import React, { forwardRef, ChangeEvent } from 'react'
2
2
  import classnames from 'classnames'
3
3
 
4
4
  import { globalProps, GlobalProps, domSafeProps } from '../utilities/globalProps'
@@ -10,6 +10,8 @@ import Caption from '../pb_caption/_caption'
10
10
  import Body from '../pb_body/_body'
11
11
  import Icon from '../pb_icon/_icon'
12
12
 
13
+ import { INPUTMASKS } from './inputMask'
14
+
13
15
  type TextInputProps = {
14
16
  aria?: { [key: string]: string },
15
17
  className?: string,
@@ -22,6 +24,7 @@ type TextInputProps = {
22
24
  inline?: boolean,
23
25
  name: string,
24
26
  label: string,
27
+ mask?: 'currency' | 'zipCode' | 'postalCode' | 'ssn',
25
28
  onChange: (e: React.FormEvent<HTMLInputElement>) => void,
26
29
  placeholder: string,
27
30
  required?: boolean,
@@ -47,6 +50,7 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
47
50
  htmlOptions = {},
48
51
  id,
49
52
  inline = false,
53
+ mask = null,
50
54
  name,
51
55
  label,
52
56
  onChange = () => { void 0 },
@@ -90,6 +94,33 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
90
94
  />
91
95
  )
92
96
 
97
+ const isMaskedInput = mask && mask in INPUTMASKS
98
+
99
+ const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
100
+ if (isMaskedInput) {
101
+ const inputValue = e.target.value
102
+
103
+ let cursorPosition = e.target.selectionStart;
104
+ const isAtEnd = cursorPosition === inputValue.length;
105
+
106
+ const formattedValue = INPUTMASKS[mask].format(inputValue)
107
+ e.target.value = formattedValue
108
+
109
+ // Keep cursor position
110
+ if (!isAtEnd) {
111
+ // Account for extra characters (e.g., commas added/removed in currency)
112
+ if (formattedValue.length - inputValue.length === 1) {
113
+ cursorPosition = cursorPosition + 1
114
+ } else if (mask === "currency" && formattedValue.length - inputValue.length === -1) {
115
+ cursorPosition = cursorPosition - 1
116
+ }
117
+ e.target.selectionStart = e.target.selectionEnd = cursorPosition
118
+ }
119
+ }
120
+
121
+ onChange(e)
122
+ }
123
+
93
124
  const childInput = children ? children.type === "input" : undefined
94
125
 
95
126
  const textInput = (
@@ -101,8 +132,9 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
101
132
  id={id}
102
133
  key={id}
103
134
  name={name}
104
- onChange={onChange}
105
- placeholder={placeholder}
135
+ onChange={isMaskedInput ? handleChange : onChange}
136
+ pattern={isMaskedInput ? INPUTMASKS[mask]?.pattern : undefined}
137
+ placeholder={placeholder || (isMaskedInput ? INPUTMASKS[mask]?.placeholder : undefined)}
106
138
  ref={ref}
107
139
  required={required}
108
140
  type={type}
@@ -0,0 +1,88 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import Caption from '../../pb_caption/_caption'
4
+ import TextInput from '../../pb_text_input/_text_input'
5
+ import Title from '../../pb_title/_title'
6
+
7
+ const TextInputMask = (props) => {
8
+ const [ssn, setSSN] = useState('')
9
+ const handleOnChangeSSN = ({ target }) => {
10
+ setSSN(target.value)
11
+ }
12
+ const ref = React.createRef()
13
+
14
+ const [formFields, setFormFields] = useState({
15
+ currency: '',
16
+ zipCode: '',
17
+ postalCode: '',
18
+ ssn: '',
19
+ })
20
+
21
+ const handleOnChangeFormField = ({ target }) => {
22
+ const { name, value } = target
23
+ setFormFields({ ...formFields, [name]: value })
24
+ }
25
+
26
+ return (
27
+ <div>
28
+ <TextInput
29
+ label="Currency"
30
+ mask="currency"
31
+ name="currency"
32
+ onChange={handleOnChangeFormField}
33
+ value={formFields.currency}
34
+ {...props}
35
+ />
36
+ <TextInput
37
+ label="Zip Code"
38
+ mask="zipCode"
39
+ name="zipCode"
40
+ onChange={handleOnChangeFormField}
41
+ value={formFields.zipCode}
42
+ {...props}
43
+ />
44
+ <TextInput
45
+ label="Postal Code"
46
+ mask="postalCode"
47
+ name="postalCode"
48
+ onChange={handleOnChangeFormField}
49
+ value={formFields.postalCode}
50
+ {...props}
51
+ />
52
+ <TextInput
53
+ label="SSN"
54
+ mask="ssn"
55
+ name="ssn"
56
+ onChange={handleOnChangeFormField}
57
+ value={formFields.ssn}
58
+ {...props}
59
+ />
60
+
61
+ <br />
62
+ <br />
63
+
64
+ <Title>{'Event Handler Props'}</Title>
65
+
66
+ <br />
67
+ <Caption>{'onChange'}</Caption>
68
+
69
+ <br />
70
+
71
+ <TextInput
72
+ label="SSN"
73
+ mask="ssn"
74
+ onChange={handleOnChangeSSN}
75
+ placeholder="Enter SSN"
76
+ ref={ref}
77
+ value={ssn}
78
+ {...props}
79
+ />
80
+
81
+ {ssn !== '' && (
82
+ <React.Fragment>{`SSN is: ${ssn}`}</React.Fragment>
83
+ )}
84
+ </div>
85
+ )
86
+ }
87
+
88
+ export default TextInputMask
@@ -16,6 +16,7 @@ examples:
16
16
  - text_input_add_on: Add On
17
17
  - text_input_inline: Inline
18
18
  - text_input_no_label: No Label
19
+ - text_input_mask: Mask
19
20
 
20
21
  swift:
21
22
  - text_input_default_swift: Default
@@ -5,3 +5,4 @@ export { default as TextInputDisabled } from './_text_input_disabled.jsx'
5
5
  export { default as TextInputAddOn } from './_text_input_add_on.jsx'
6
6
  export { default as TextInputInline } from './_text_input_inline.jsx'
7
7
  export { default as TextInputNoLabel } from './_text_input_no_label.jsx'
8
+ export { default as TextInputMask } from './_text_input_mask.jsx'
@@ -0,0 +1,64 @@
1
+ type InputMask = {
2
+ format: (value: string) => string
3
+ pattern: string
4
+ placeholder: string
5
+ }
6
+
7
+ type InputMaskDictionary = {
8
+ [key in 'currency' | 'zipCode' | 'postalCode' | 'ssn']: InputMask
9
+ }
10
+
11
+ const formatCurrency = (value: string): string => {
12
+ const numericValue = value.replace(/[^0-9]/g, '').slice(0, 15)
13
+
14
+ if (!numericValue) return ''
15
+
16
+ const dollars = parseFloat((parseInt(numericValue) / 100).toFixed(2))
17
+ if (dollars === 0) return ''
18
+
19
+ return new Intl.NumberFormat('en-US', {
20
+ style: 'currency',
21
+ currency: 'USD',
22
+ maximumFractionDigits: 2,
23
+ }).format(dollars)
24
+ }
25
+
26
+ const formatBasicPostal = (value: string): string => {
27
+ return value.replace(/\D/g, '').slice(0, 5)
28
+ }
29
+
30
+ const formatExtendedPostal = (value: string): string => {
31
+ const cleaned = value.replace(/\D/g, '').slice(0, 9)
32
+ return cleaned.replace(/(\d{5})(?=\d)/, '$1-')
33
+ }
34
+
35
+ const formatSSN = (value: string): string => {
36
+ const cleaned = value.replace(/\D/g, '').slice(0, 9)
37
+ return cleaned
38
+ .replace(/(\d{5})(?=\d)/, '$1-')
39
+ .replace(/(\d{3})(?=\d)/, '$1-')
40
+ }
41
+
42
+ export const INPUTMASKS: InputMaskDictionary = {
43
+ currency: {
44
+ format: formatCurrency,
45
+ // eslint-disable-next-line no-useless-escape
46
+ pattern: '^\\$\\d{1,3}(?:,\\d{3})*(?:\\.\\d{2})?$',
47
+ placeholder: '$0.00',
48
+ },
49
+ zipCode: {
50
+ format: formatBasicPostal,
51
+ pattern: '\\d{5}',
52
+ placeholder: '12345',
53
+ },
54
+ postalCode: {
55
+ format: formatExtendedPostal,
56
+ pattern: '\\d{5}-\\d{4}',
57
+ placeholder: '12345-6789',
58
+ },
59
+ ssn: {
60
+ format: formatSSN,
61
+ pattern: '\\d{3}-\\d{2}-\\d{4}',
62
+ placeholder: '123-45-6789',
63
+ },
64
+ }
@@ -1,5 +1,5 @@
1
- import React from 'react'
2
- import { render, screen } from '../utilities/test-utils'
1
+ import React, { useState } from 'react'
2
+ import { render, screen, fireEvent, within } from '../utilities/test-utils'
3
3
 
4
4
  import TextInput from './_text_input'
5
5
 
@@ -89,3 +89,140 @@ test('returns additional class name', () => {
89
89
  const kit = screen.getByTestId(testId)
90
90
  expect(kit).toHaveClass(`${kitClass} mb_lg`)
91
91
  })
92
+
93
+
94
+ const TextInputCurrencyMask = (props) => {
95
+ const [currency, setValue] = useState('')
96
+ const handleOnChange = ({ target }) => {
97
+ setValue(target.value)
98
+ }
99
+
100
+ return (
101
+ <TextInput
102
+ mask="currency"
103
+ onChange={handleOnChange}
104
+ value={currency}
105
+ {...props}
106
+ />
107
+ )
108
+ }
109
+
110
+ test('returns masked currency value', () => {
111
+ render(
112
+ <TextInputCurrencyMask
113
+ data={{ testid: testId }}
114
+ />
115
+ )
116
+
117
+ const kit = screen.getByTestId(testId)
118
+
119
+ const input = within(kit).getByRole('textbox');
120
+
121
+ fireEvent.change(input, { target: { value: '123456' } });
122
+
123
+ expect(input.value).toBe('$1,234.56')
124
+
125
+ fireEvent.change(input, { target: { value: '1' } });
126
+
127
+ expect(input.value).toBe('$0.01')
128
+
129
+ fireEvent.change(input, { target: { value: '' } });
130
+
131
+ expect(input.value).toBe('')
132
+ })
133
+
134
+ const TextInputZipCodeMask = (props) => {
135
+ const [zipCode, setValue] = useState('')
136
+ const handleOnChange = ({ target }) => {
137
+ setValue(target.value)
138
+ }
139
+
140
+ return (
141
+ <TextInput
142
+ mask="zipCode"
143
+ onChange={handleOnChange}
144
+ value={zipCode}
145
+ {...props}
146
+ />
147
+ )
148
+ }
149
+
150
+ test('returns masked zip code value', () => {
151
+ render(
152
+ <TextInputZipCodeMask
153
+ data={{ testid: testId }}
154
+ />
155
+ )
156
+
157
+ const kit = screen.getByTestId(testId)
158
+
159
+ const input = within(kit).getByRole('textbox');
160
+
161
+ fireEvent.change(input, { target: { value: '123456' } });
162
+
163
+ expect(input.value).toBe('12345')
164
+ })
165
+
166
+ const TextInputPostalCodeMask = (props) => {
167
+ const [postalCode, setValue] = useState('')
168
+ const handleOnChange = ({ target }) => {
169
+ setValue(target.value)
170
+ }
171
+
172
+ return (
173
+ <TextInput
174
+ mask="postalCode"
175
+ onChange={handleOnChange}
176
+ value={postalCode}
177
+ {...props}
178
+ />
179
+ )
180
+ }
181
+
182
+ test('returns masked postal code value', () => {
183
+ render(
184
+ <TextInputPostalCodeMask
185
+ data={{ testid: testId }}
186
+ />
187
+ )
188
+
189
+ const kit = screen.getByTestId(testId)
190
+
191
+ const input = within(kit).getByRole('textbox');
192
+
193
+ fireEvent.change(input, { target: { value: '123456789' } });
194
+
195
+ expect(input.value).toBe('12345-6789')
196
+ })
197
+
198
+ const TextInputSSNMask = (props) => {
199
+ const [ssn, setValue] = useState('')
200
+ const handleOnChange = ({ target }) => {
201
+ setValue(target.value)
202
+ }
203
+
204
+ return (
205
+ <TextInput
206
+ mask="ssn"
207
+ onChange={handleOnChange}
208
+ value={ssn}
209
+ {...props}
210
+ />
211
+ )
212
+ }
213
+
214
+ test('returns masked ssn value', () => {
215
+ render(
216
+ <TextInputSSNMask
217
+ data={{ testid: testId }}
218
+ />
219
+ )
220
+
221
+ const kit = screen.getByTestId(testId)
222
+
223
+ const input = within(kit).getByRole('textbox');
224
+
225
+ fireEvent.change(input, { target: { value: '123456789' } });
226
+
227
+ expect(input.value).toBe('123-45-6789')
228
+ })