playbook_ui 14.10.0.pre.rc.13 → 14.10.0.pre.rc.15

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: 9603c016a0966690f6834d6323bdc21cbbe20de444277b18df441c4212619cd4
4
- data.tar.gz: 6d32f54d3eab9d4f64a69c7bf0cc5c9a3df9b796dd57af61a81c9e07187eaf21
3
+ metadata.gz: 6f22de938b9147a6c17846f732787a86dd636255eccba1c109b2f130502f02ae
4
+ data.tar.gz: 4e5f2783537779a32a1d7094ae768842bdb30d9d8c1fb5a728f8d084fcb99f5a
5
5
  SHA512:
6
- metadata.gz: 2202f9e2ab3cc02fb911ae7408a1a3791487e44a1fc2585753f3ce652981b4f5a02f78ed4c786ed5b60e44978288dbb76a174ab5ac537b42ef31441562fc882d
7
- data.tar.gz: 19f085ffad6ea5ee0dede7a68abce7d3c395a616ddd0d64ca9bd3b10dbaf2cef07e23c97ddf3e1ac9ebd7b8b473e57b3fbf076230df3a8d8bcf6aa53a2fbd875
6
+ metadata.gz: 16582703fe2982b4a87d6992510e0cf6509a5dc48995cf81227f9c7e160250a2670c75d0ed5d5c51f4a68c73421ecfad0a1b143c1d3d11a98f4a35abc62a6496
7
+ data.tar.gz: ccafef9e030b7815aeb3e90c7b9a7742d7ae013e4d8b816607b9daa6e66bd0f7ddd5257da7f4eb0d993cb6ec002102880e2e3fe2eb47e0caf3a1f24d719a892b
@@ -1,66 +1,70 @@
1
1
  import React from 'react'
2
-
2
+ import ReactDOMServer from 'react-dom/server'
3
3
  import BarGraph from '../_bar_graph'
4
+ import Icon from '../../pb_icon/_icon'
5
+
6
+ const chartData = [
7
+ {
8
+ name: 'Role',
9
+ data: [0, 200, 300, 654, 656],
10
+ },
11
+ {
12
+ name: 'Company',
13
+ data: [150, 524, 320, 440, 500],
14
+ },
15
+ ]
4
16
 
5
- const chartData = [{
6
- name: 'Installation',
7
- data: [1475, 200, 3000, 654, 656],
8
- }, {
9
- name: 'Manufacturing',
10
- data: [4434, 524, 2320, 440, 500],
11
- }, {
12
- name: 'Sales & Distribution',
13
- data: [3387, 743, 1344, 434, 440],
14
- }, {
15
- name: 'Project Development',
16
- data: [3227, 878, 999, 780, 1000],
17
- }, {
18
- name: 'Other',
19
- data: [1111, 677, 3245, 500, 200],
20
- }]
17
+ const renderIcon = (iconName, color) => {
18
+ return ReactDOMServer.renderToStaticMarkup(
19
+ <Icon
20
+ color={color}
21
+ icon={iconName}
22
+ />)
23
+ };
21
24
 
22
25
  const barGraphOptions = {
23
- subtitle: {
24
- text: "Overwritten subtitle",
25
- style: {
26
- color: "red"
27
- }
26
+ yAxis: {
27
+ tickInterval: 5,
28
28
  },
29
29
  xAxis: {
30
- labels: {
31
- useHTML: true,
32
- formatter: function() {
33
- switch (this.value) {
34
- case 'Jan':
35
- return `<i class="far fa-apple-whole"></i> ${this.value}`
36
- case 'Feb':
37
- return `<i class="far fa-strawberry"></i> ${this.value}`
38
- case 'Mar':
39
- return `<i class="far fa-lemon"></i> ${this.value}`
40
- case 'Apr':
41
- return `<i class="far fa-pear"></i> ${this.value}`
42
- case 'May':
43
- return `<i class="far fa-peach"></i> ${this.value}`
44
- default:
45
- return ''
46
- }
47
- }
48
- }
49
- }
30
+ categories: ['1', '2', '3', '4', '5'],
31
+ labels: {
32
+ useHTML: true,
33
+ formatter: function () {
34
+ switch (this.value) {
35
+ case '1':
36
+ return `${renderIcon('frown', 'error')}`;
37
+ case '2':
38
+ return `${renderIcon('frown', 'warning')}`;
39
+ case '3':
40
+ return `${renderIcon('frown-open', 'neutral')}`;
41
+ case '4':
42
+ return `${renderIcon('smile', 'category_7')}`;
43
+ case '5':
44
+ return `${renderIcon('smile-beam', 'success')}`;
45
+ default:
46
+ return ''
47
+ }
48
+ },
49
+ style: {
50
+ fontSize: '1.4em',
51
+ },
52
+ y: 42,
53
+ },
54
+ },
55
+ legend: {
56
+ itemMarginTop: 62,
57
+ },
50
58
  }
51
59
 
52
- const BarGraphCustom = (props) => (
60
+ const BarGraphCustom = () => (
53
61
  <div>
54
62
  <BarGraph
55
- axisTitle="Number of Employees"
56
63
  chartData={chartData}
57
64
  customOptions={barGraphOptions}
58
- id="bar-custom"
59
- subTitle="Subtitle to replace"
65
+ id="happiness-dashboard"
66
+ legend
60
67
  title="Bar Graph with Custom Overrides"
61
- xAxisCategories={['Jan', 'Feb', 'Mar', 'Apr', 'May']}
62
- yAxisMin={0}
63
- {...props}
64
68
  />
65
69
  </div>
66
70
  )
@@ -1,49 +1,42 @@
1
1
  <% data = [{
2
- name: 'Installation',
3
- data: [1475,200,3000,654,656]
2
+ name: 'Role',
3
+ data: [0, 200, 300, 654, 656],
4
4
  }, {
5
- name: 'Manufacturing',
6
- data: [4434,524,2320,440,500]
7
- }, {
8
- name: 'Sales & Distribution',
9
- data: [3387,743,1344,434,440,]
10
- }, {
11
- name: 'Project Development',
12
- data: [3227,878,999,780,1000]
13
- }, {
14
- name: 'Other',
15
- data: [1111,677,3245,500,200]
5
+ name: 'Company',
6
+ data: [150, 524, 320, 440, 500],
16
7
  }] %>
17
8
 
18
9
  <% bar_graph_options = {
19
- customOptions: {
20
- subtitle: {
21
- text: "Overwritten subtitle",
22
- style: {
23
- color: "red"
24
- }
10
+ customOptions: {
11
+ yAxis: {
12
+ tickInterval: 5,
13
+ },
14
+ xAxis: {
15
+ categories: [
16
+ raw(pb_rails("icon", props: { icon: "frown", color: "error", size: "2x" })),
17
+ raw(pb_rails("icon", props: { icon: "frown", color: "warning", size: "2x" })),
18
+ raw(pb_rails("icon", props: { icon: "frown-open", color: "neutral", size: "2x" })),
19
+ raw(pb_rails("icon", props: { icon: "smile", color: "category_7", size: "2x" })),
20
+ raw(pb_rails("icon", props: { icon: "smile-beam", color: "success", size: "2x" }))
21
+ ],
22
+ labels: {
23
+ useHTML: true,
24
+ sytle: {
25
+ fontSize: '1.4em',
25
26
  },
26
- xAxis: {
27
- categories: [
28
- '<i class="far fa-apple-whole"></i> Jan',
29
- '<i class="far fa-strawberry"></i> Feb',
30
- '<i class="far fa-lemon"></i> Mar',
31
- '<i class="far fa-pear"></i> Apr',
32
- '<i class="far fa-peach"></i> May'
33
- ],
34
- labels: {
35
- useHTML: true,
36
- }
37
- }
38
- }
27
+ y: 42,
28
+ },
29
+ },
30
+ legend: {
31
+ itemMarginTop: 62,
32
+ },
33
+ }
39
34
  } %>
40
35
 
41
36
  <%= pb_rails("bar_graph", props: {
42
- axis_title: 'Number of Employees',
43
37
  chart_data: data,
44
38
  id: "bar-default",
45
- y_axis_min: 0,
46
- subtitle: 'Subtitle to replace',
39
+ legend: true,
47
40
  title: 'Bar Graph with Custom Overrides',
48
41
  custom_options: bar_graph_options
49
- }) %>
42
+ }) %>
@@ -17,7 +17,7 @@
17
17
  outline-offset: 2px;
18
18
  }
19
19
  &:visited {
20
- color: $data_3;
20
+ color: darken($primary_action, 10%);
21
21
  }
22
22
  &.dark {
23
23
  @include pb_link($active_dark);
@@ -34,7 +34,7 @@
34
34
  }
35
35
 
36
36
  &:visited {
37
- color: $data_3;
37
+ color: darken($primary_action, 10%);
38
38
  }
39
39
  }
40
40
  }
@@ -48,7 +48,7 @@
48
48
  }
49
49
 
50
50
  &:visited {
51
- color: $data_3;
51
+ color: darken($primary_action, 10%);
52
52
  }
53
53
  }
54
54
  }
@@ -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
+ })