playbook_ui 14.9.0.pre.alpha.PBNTR738collapsiblewithintablekit4855 → 14.9.0.pre.alpha.PLAY1731inputmasking4868

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_collapsible/_collapsible.tsx +4 -9
  3. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleContent.tsx +2 -2
  4. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleMain.tsx +2 -2
  5. data/app/pb_kits/playbook/pb_table/docs/example.yml +0 -5
  6. data/app/pb_kits/playbook/pb_table/docs/index.js +0 -5
  7. data/app/pb_kits/playbook/pb_table/styles/_all.scss +1 -2
  8. data/app/pb_kits/playbook/pb_table/subcomponents/_table_row.tsx +1 -106
  9. data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +35 -3
  10. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_mask.jsx +88 -0
  11. data/app/pb_kits/playbook/pb_text_input/docs/example.yml +1 -0
  12. data/app/pb_kits/playbook/pb_text_input/docs/index.js +1 -0
  13. data/app/pb_kits/playbook/pb_text_input/inputMask.ts +64 -0
  14. data/app/pb_kits/playbook/pb_text_input/text_input.test.js +139 -2
  15. data/dist/chunks/_typeahead-Cx7J1O_I.js +22 -0
  16. data/dist/chunks/_weekday_stacked-Z6CSak-K.js +45 -0
  17. data/dist/chunks/{lib-CtS0DLMo.js → lib-SyD3buPZ.js} +3 -3
  18. data/dist/chunks/{pb_form_validation-C3vQKj7-.js → pb_form_validation-Dt8UJgrJ.js} +1 -1
  19. data/dist/chunks/vendor.js +1 -1
  20. data/dist/playbook-doc.js +1 -1
  21. data/dist/playbook-rails-react-bindings.js +1 -1
  22. data/dist/playbook-rails.js +1 -1
  23. data/dist/playbook.css +1 -1
  24. data/lib/playbook/version.rb +1 -1
  25. metadata +7 -16
  26. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible.jsx +0 -75
  27. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible.md +0 -1
  28. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_click.jsx +0 -108
  29. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_click.md +0 -2
  30. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_content.jsx +0 -94
  31. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_content.md +0 -0
  32. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_rows.jsx +0 -83
  33. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_rows.md +0 -3
  34. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_table.jsx +0 -120
  35. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_table.md +0 -1
  36. data/app/pb_kits/playbook/pb_table/styles/_collapsible.scss +0 -35
  37. data/dist/chunks/_typeahead-ClJHKLj6.js +0 -22
  38. data/dist/chunks/_weekday_stacked-DaVTrOfA.js +0 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7836406579285d2cbea8a738bb95e477a2f513f6243b4a59cfaef6985ee22a3a
4
- data.tar.gz: 0af1be0d7a9f685d063c68d09d7d441317bb99b6bd591eab66d738300ab49dd9
3
+ metadata.gz: b40a6d80e2e00552963c2986d6ae0e1bad26cd43320b0959b534e37f0e7d71fa
4
+ data.tar.gz: 52bb830750b5edc87fe3a4c670c944e1fcdf78764649ebcc708db45257694c0d
5
5
  SHA512:
6
- metadata.gz: cdb9e010f6a61b4b6a52ce0f317127c1f097607aa846b14887b0163625d1718eac6a93eff8e5bc1f7aab34f06eb3d69c56fea4667e513751c697f58ca221fe6a
7
- data.tar.gz: 99fb58c0a203ddc69ebf6fa643274582d5b96e32284d1817fbe6d9d9ebfd5fbe358816e674bbf02cdbb394490283db22e077c44a2116e23935a37852ba628354
6
+ metadata.gz: c4f3b88e16a1efe2875d297f45e030854943cc62a8a8ddaf8629c6e6f674e54583e72bfe24d1185ef67de57b3e497597f0c328c427f362f6868b5b136e720541
7
+ data.tar.gz: 8d309708dbc3912906a4f2b73829fcff692c195b641bd352dee6e313409f74c6ba2c8ea782bbb4c8784a7ca5e5ee3ed5084e4cc147130e37bc9f5203e4cbdee2
@@ -2,7 +2,7 @@ import React, { useEffect, ReactElement } from 'react'
2
2
  import classnames from 'classnames'
3
3
  import useCollapsible from './useCollapsible'
4
4
 
5
- import { globalProps, globalInlineProps, GlobalProps } from '../utilities/globalProps'
5
+ import { globalProps, globalInlineProps } from '../utilities/globalProps'
6
6
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
7
7
 
8
8
  import CollapsibleContent from './child_kits/CollapsibleContent'
@@ -32,7 +32,6 @@ type CollapsibleProps = {
32
32
  onClick?: ()=> void,
33
33
  htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
34
34
  id?: string,
35
- tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div' | 'tr' | 'th' | 'td' | 'thead' | 'col',
36
35
  }
37
36
 
38
37
  const Collapsible = ({
@@ -48,9 +47,8 @@ const Collapsible = ({
48
47
  onIconClick,
49
48
  onClick,
50
49
  id,
51
- tag = 'div',
52
50
  ...props
53
- }: CollapsibleProps & GlobalProps): React.ReactElement => {
51
+ }: CollapsibleProps): React.ReactElement => {
54
52
  const [isCollapsed, toggle, setIsCollapsed] = useCollapsible(collapsed)
55
53
 
56
54
  useEffect(()=> {
@@ -78,12 +76,9 @@ const Collapsible = ({
78
76
  className
79
77
  )
80
78
  const dynamicInlineProps = globalInlineProps(props)
81
-
82
- const Tag: React.ReactElement | any = `${tag}`;
83
-
84
79
  return (
85
80
  <CollapsibleContext.Provider value={{ collapsed: isCollapsed, toggle, icon, iconSize, iconColor, onIconClick, onClick }}>
86
- <Tag
81
+ <div
87
82
  {...ariaProps}
88
83
  {...dataProps}
89
84
  {...htmlProps}
@@ -101,7 +96,7 @@ const Collapsible = ({
101
96
  <CollapsibleContent {...contentProps}>
102
97
  {contentChildren}
103
98
  </CollapsibleContent>
104
- </Tag>
99
+ </div>
105
100
  </CollapsibleContext.Provider>
106
101
  )
107
102
  }
@@ -1,7 +1,7 @@
1
1
  import classnames from 'classnames'
2
2
  import React, { useContext, useRef, useEffect } from 'react'
3
3
  import { buildCss } from '../../utilities/props'
4
- import { globalProps, GlobalProps } from '../../utilities/globalProps'
4
+ import { globalProps } from '../../utilities/globalProps'
5
5
  import { hideElement, showElement } from '../_helper_functions'
6
6
 
7
7
  import CollapsibleContext from '../context'
@@ -15,7 +15,7 @@ const CollapsibleContent = ({
15
15
  children,
16
16
  className,
17
17
  ...props
18
- }: CollapsibleContentProps & GlobalProps): React.ReactElement => {
18
+ }: CollapsibleContentProps): React.ReactElement => {
19
19
  const context: {[key: string]: boolean | string} = useContext(CollapsibleContext)
20
20
  const contentCSS = buildCss('pb_collapsible_content_kit')
21
21
  const contentSpacing = globalProps(props)
@@ -3,7 +3,7 @@
3
3
  import classnames from 'classnames'
4
4
  import React, { useContext } from 'react'
5
5
  import { buildCss } from '../../utilities/props'
6
- import { globalProps, GlobalProps } from '../../utilities/globalProps'
6
+ import { globalProps } from '../../utilities/globalProps'
7
7
 
8
8
  import Flex from '../../pb_flex/_flex'
9
9
  import FlexItem from '../../pb_flex/_flex_item'
@@ -25,7 +25,7 @@ const CollapsibleMain = ({
25
25
  className,
26
26
  cursor = 'pointer',
27
27
  ...props
28
- }: CollapsibleMainProps & GlobalProps): React.ReactElement=> {
28
+ }: CollapsibleMainProps): React.ReactElement=> {
29
29
  const {collapsed, toggle, icon, iconSize, iconColor, onIconClick, onClick}: any = useContext(CollapsibleContext)
30
30
  const mainCSS = buildCss('pb_collapsible_main_kit')
31
31
  const mainSpacing = globalProps(props, { cursor })
@@ -55,8 +55,3 @@ examples:
55
55
  - table_with_subcomponents: Table with Sub Components (Table Elements)
56
56
  - table_with_subcomponents_as_divs: Table with Sub Components (Divs)
57
57
  - table_outer_padding: Outer Padding
58
- - table_with_collapsible: Table with Collapsible
59
- - table_with_collapsible_with_custom_click: Table with Collapsible with Custom Click
60
- - table_with_collapsible_with_custom_content: Table with Collapsible with Custom Content
61
- - table_with_collapsible_with_nested_rows: Table with Collapsible with Nested Rows
62
- - table_with_collapsible_with_nested_table: Table with Collapsible with Nested Table
@@ -26,8 +26,3 @@ export { default as TableWithSubcomponents } from './_table_with_subcomponents.j
26
26
  export { default as TableWithSubcomponentsAsDivs } from './_table_with_subcomponents_as_divs.jsx'
27
27
  export { default as TableOuterPadding } from './_table_outer_padding.jsx'
28
28
  export { default as TableStickyLeftColumns } from './_table_sticky_left_columns.jsx'
29
- export { default as TableWithCollapsible } from './_table_with_collapsible.jsx'
30
- export { default as TableWithCollapsibleWithCustomContent } from './_table_with_collapsible_with_custom_content.jsx'
31
- export { default as TableWithCollapsibleWithNestedTable } from './_table_with_collapsible_with_nested_table.jsx'
32
- export { default as TableWithCollapsibleWithNestedRows } from './_table_with_collapsible_with_nested_rows.jsx'
33
- export { default as TableWithCollapsibleWithCustomClick } from './_table_with_collapsible_with_custom_click.jsx'
@@ -21,5 +21,4 @@
21
21
  @import "striped";
22
22
  @import "outer_padding";
23
23
  @import "sticky_columns";
24
- @import "scroll";
25
- @import "collapsible";
24
+ @import "scroll";
@@ -7,21 +7,14 @@ import {
7
7
  buildHtmlProps,
8
8
  } from "../../utilities/props";
9
9
  import { globalProps } from "../../utilities/globalProps";
10
- import Collapsible from "../../pb_collapsible/_collapsible";
11
- import useCollapsible from "../../pb_collapsible/useCollapsible";
12
10
 
13
11
  type TableRowPropTypes = {
14
12
  aria?: { [key: string]: string };
15
13
  children: React.ReactNode[] | React.ReactNode;
16
14
  className: string;
17
- collapsible?: boolean;
18
- collapsibleContent?: React.ReactNode[] | React.ReactNode;
19
- collapsibleSideHighlight?: boolean;
20
15
  data?: { [key: string]: string };
21
- dark?: boolean;
22
16
  htmlOptions?: { [key: string]: string | number | boolean | (() => void) };
23
17
  id?: string;
24
- toggleCellId?: string;
25
18
  sideHighlightColor: string;
26
19
  tag?: "table" | "div";
27
20
  };
@@ -30,15 +23,10 @@ const TableRow = (props: TableRowPropTypes): React.ReactElement => {
30
23
  const {
31
24
  aria = {},
32
25
  children,
33
- collapsible,
34
- collapsibleContent,
35
- collapsibleSideHighlight = true,
36
26
  className,
37
27
  data = {},
38
- dark = false,
39
28
  htmlOptions = {},
40
29
  id,
41
- toggleCellId,
42
30
  sideHighlightColor = "none",
43
31
  tag = "table",
44
32
  } = props;
@@ -48,110 +36,17 @@ const TableRow = (props: TableRowPropTypes): React.ReactElement => {
48
36
  const htmlProps = buildHtmlProps(htmlOptions);
49
37
  const sideHighlightClass =
50
38
  sideHighlightColor != "" ? `side_highlight_${sideHighlightColor}` : null;
51
-
52
- const [isCollapsed, setIsCollapsed] = useCollapsible(true);
53
-
54
- const collapsibleRow = collapsible && isCollapsed === true ? "collapsible_table_row" : null;
55
39
  const classes = classnames(
56
40
  buildCss("pb_table_row_kit", sideHighlightClass),
57
41
  "pb_table_tr",
58
- collapsibleRow,
59
42
  globalProps(props),
60
43
  className
61
44
  );
62
45
  const isTableTag = tag === "table";
63
46
 
64
- // const [isCollapsed, setIsCollapsed] = useCollapsible(true);
65
-
66
- const colSpan = React.Children.count(children);
67
-
68
- const handleRowClick = (event: React.MouseEvent) => {
69
- if (toggleCellId) {
70
- const target = event.target as HTMLElement;
71
- const clickedCell = target.closest(`#${toggleCellId}`);
72
- const isIconClick =
73
- target instanceof SVGElement &&
74
- (target.matches("svg.pb_custom_icon") || target.closest("svg.pb_custom_icon"));
75
-
76
- if (clickedCell || isIconClick) {
77
- setIsCollapsed(!isCollapsed);
78
- }
79
- } else {
80
- setIsCollapsed(!isCollapsed);
81
- }
82
- };
83
-
84
47
  return (
85
48
  <>
86
- {collapsible ? (
87
- isTableTag ? (
88
- <>
89
- <tr
90
- {...ariaProps}
91
- {...dataProps}
92
- {...htmlProps}
93
- className={classes}
94
- id={id}
95
- onClick={(e)=>handleRowClick(e)}
96
- style={{ cursor: toggleCellId ? "default" : "pointer" }}
97
- >
98
- {children}
99
- </tr>
100
- <tr>
101
- <Collapsible
102
- collapsed={isCollapsed}
103
- dark={dark}
104
- htmlOptions={{ colSpan: colSpan }}
105
- padding="none"
106
- tag="td"
107
- >
108
- <tr/>
109
- <Collapsible.Content
110
- className={collapsibleSideHighlight ? `table_collapsible_side_highlight` : ''}
111
- dark={dark}
112
- margin="none"
113
- padding="none"
114
- >
115
- {collapsibleContent}
116
- </Collapsible.Content>
117
- </Collapsible>
118
- </tr>
119
- </>
120
- ) : (
121
- <>
122
- <div
123
- {...ariaProps}
124
- {...dataProps}
125
- {...htmlProps}
126
- className={classes}
127
- id={id}
128
- onClick={handleRowClick}
129
- style={{ cursor: "pointer" }}
130
- >
131
- {children}
132
- </div>
133
- <tr>
134
- <Collapsible
135
- collapsed={isCollapsed}
136
- dark={dark}
137
- htmlOptions={{ colSpan: colSpan }}
138
- padding="none"
139
- tag="td"
140
- >
141
- <tr/>
142
- <Collapsible.Content
143
- className={collapsibleSideHighlight ? `table_collapsible_side_highlight` : ''}
144
- dark={dark}
145
- margin="none"
146
- padding="none"
147
- >
148
- {collapsibleContent}
149
- </Collapsible.Content>
150
- </Collapsible>
151
- </tr>
152
- </>
153
- )
154
- ) : isTableTag ? (
49
+ {isTableTag ? (
155
50
  <tr
156
51
  {...ariaProps}
157
52
  {...dataProps}
@@ -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
+ })