playbook_ui 14.9.0.pre.alpha.PBNTR746datepickerdefaultbug4903 → 14.9.0.pre.alpha.PLAY1660reactdropzone5020

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_custom.jsx +53 -49
  3. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_custom_rails.html.erb +29 -36
  4. data/app/pb_kits/playbook/pb_collapsible/_collapsible.tsx +9 -4
  5. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleContent.tsx +2 -2
  6. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleMain.tsx +2 -2
  7. data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +2 -4
  8. data/app/pb_kits/playbook/pb_drawer/_drawer.tsx +2 -2
  9. data/app/pb_kits/playbook/pb_file_upload/_file_upload.tsx +24 -15
  10. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_accept.jsx +3 -1
  11. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_description.jsx +4 -1
  12. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_max_size.jsx +1 -1
  13. data/app/pb_kits/playbook/pb_layout/_layout.tsx +30 -11
  14. data/app/pb_kits/playbook/pb_link/_link.scss +3 -3
  15. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_filter.jsx +166 -0
  16. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.jsx +2 -0
  17. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_user.jsx +89 -0
  18. data/app/pb_kits/playbook/pb_skeleton_loading/docs/example.yml +2 -1
  19. data/app/pb_kits/playbook/pb_skeleton_loading/docs/index.js +2 -0
  20. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible.jsx +75 -0
  21. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible.md +1 -0
  22. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_click.jsx +108 -0
  23. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_click.md +2 -0
  24. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_content.jsx +94 -0
  25. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_content.md +0 -0
  26. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_rows.jsx +83 -0
  27. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_rows.md +3 -0
  28. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_table.jsx +120 -0
  29. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_table.md +1 -0
  30. data/app/pb_kits/playbook/pb_table/docs/example.yml +5 -0
  31. data/app/pb_kits/playbook/pb_table/docs/index.js +5 -0
  32. data/app/pb_kits/playbook/pb_table/styles/_all.scss +2 -1
  33. data/app/pb_kits/playbook/pb_table/styles/_collapsible.scss +35 -0
  34. data/app/pb_kits/playbook/pb_table/subcomponents/_table_row.tsx +106 -1
  35. data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +35 -3
  36. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_mask.jsx +88 -0
  37. data/app/pb_kits/playbook/pb_text_input/docs/example.yml +1 -0
  38. data/app/pb_kits/playbook/pb_text_input/docs/index.js +1 -0
  39. data/app/pb_kits/playbook/pb_text_input/inputMask.ts +64 -0
  40. data/app/pb_kits/playbook/pb_text_input/text_input.test.js +139 -2
  41. data/dist/chunks/_typeahead-CZ_5SxHq.js +36 -0
  42. data/dist/chunks/_weekday_stacked-D345GDEZ.js +45 -0
  43. data/dist/chunks/lib-CuCy3_xO.js +29 -0
  44. data/dist/chunks/{pb_form_validation-DXJs12Hd.js → pb_form_validation-D37k10a0.js} +1 -1
  45. data/dist/chunks/vendor.js +1 -1
  46. data/dist/menu.yml +1 -1
  47. data/dist/playbook-doc.js +1 -1
  48. data/dist/playbook-rails-react-bindings.js +1 -1
  49. data/dist/playbook-rails.js +1 -1
  50. data/dist/playbook.css +1 -1
  51. data/lib/playbook/version.rb +1 -1
  52. metadata +21 -6
  53. data/dist/chunks/_typeahead-8iXlv4ii.js +0 -22
  54. data/dist/chunks/_weekday_stacked-QiMNKnzf.js +0 -45
  55. data/dist/chunks/lib-orI4wF5u.js +0 -29
@@ -0,0 +1,120 @@
1
+ import React from 'react'
2
+ import {Pill, Background, Table, Icon} from "playbook-ui"
3
+
4
+ const TableWithCollapsibleWithNestedTable = (props) => {
5
+
6
+ const Content = () => {
7
+ return (
8
+ <Table
9
+ borderRadius="none"
10
+ container={false}
11
+ size="sm"
12
+ {...props}
13
+ >
14
+ <Table.Head>
15
+ <Background
16
+ tag="tr"
17
+ {...props}
18
+ >
19
+ <Table.Header>{"Alt Header"}</Table.Header>
20
+ <Table.Header>{"Alt Header"}</Table.Header>
21
+ <Table.Header>{"Alt Header"}</Table.Header>
22
+ <Table.Header>{"Alt Header"}</Table.Header>
23
+ </Background>
24
+ </Table.Head>
25
+ <Table.Body>
26
+ <Table.Row>
27
+ <Table.Cell>{"Expanded"}</Table.Cell>
28
+ <Table.Cell>{"Expanded"}</Table.Cell>
29
+ <Table.Cell>{"Expanded"}</Table.Cell>
30
+ <Table.Cell>
31
+ <Pill text="Pill"
32
+ variant="primary"
33
+ {...props}
34
+ />
35
+ </Table.Cell>
36
+ </Table.Row>
37
+ <Table.Row>
38
+ <Table.Cell>{"Expanded"}</Table.Cell>
39
+ <Table.Cell>{"Expanded"}</Table.Cell>
40
+ <Table.Cell>{"Expanded"}</Table.Cell>
41
+ <Table.Cell>
42
+ <Pill text="Pill"
43
+ variant="primary"
44
+ {...props}
45
+ />
46
+ </Table.Cell>
47
+ </Table.Row>
48
+ <Table.Row>
49
+ <Table.Cell>{"Expanded"}</Table.Cell>
50
+ <Table.Cell>{"Expanded"}</Table.Cell>
51
+ <Table.Cell>{"Expanded"}</Table.Cell>
52
+ <Table.Cell>
53
+ <Pill text="Pill"
54
+ variant="primary"
55
+ {...props}
56
+ />
57
+ </Table.Cell>
58
+ </Table.Row>
59
+ </Table.Body>
60
+ </Table>
61
+ );
62
+ };
63
+
64
+ return (
65
+ <Table
66
+ size="sm"
67
+ {...props}
68
+ >
69
+ <Table.Head>
70
+ <Table.Row>
71
+ <Table.Header>{'Column 1'}</Table.Header>
72
+ <Table.Header>{'Column 2'}</Table.Header>
73
+ <Table.Header>{'Column 3'}</Table.Header>
74
+ <Table.Header>{'Column 4'}</Table.Header>
75
+ <Table.Header>{'Column 5'}</Table.Header>
76
+ <Table.Header>{''}</Table.Header>
77
+ </Table.Row>
78
+
79
+ </Table.Head>
80
+ <Table.Body>
81
+ <Table.Row collapsible
82
+ collapsibleContent={<Content/>}
83
+ collapsibleSideHighlight={false}
84
+ >
85
+ <Table.Cell>{'Value 1'}</Table.Cell>
86
+ <Table.Cell>{'Value 2'}</Table.Cell>
87
+ <Table.Cell>{'Value 3'}</Table.Cell>
88
+ <Table.Cell>{'Value 4'}</Table.Cell>
89
+ <Table.Cell>{'Value 5'}</Table.Cell>
90
+ <Table.Cell textAlign="right">{
91
+ <Icon
92
+ color="primary"
93
+ fixedWidth
94
+ icon="chevron-down"
95
+ />}
96
+ </Table.Cell>
97
+
98
+ </Table.Row>
99
+ <Table.Row>
100
+ <Table.Cell>{'Value 1'}</Table.Cell>
101
+ <Table.Cell>{'Value 2'}</Table.Cell>
102
+ <Table.Cell>{'Value 3'}</Table.Cell>
103
+ <Table.Cell>{'Value 4'}</Table.Cell>
104
+ <Table.Cell>{'Value 5'}</Table.Cell>
105
+ <Table.Cell>{''}</Table.Cell>
106
+ </Table.Row>
107
+ <Table.Row>
108
+ <Table.Cell>{'Value 1'}</Table.Cell>
109
+ <Table.Cell>{'Value 2'}</Table.Cell>
110
+ <Table.Cell>{'Value 3'}</Table.Cell>
111
+ <Table.Cell>{'Value 4'}</Table.Cell>
112
+ <Table.Cell>{'Value 5'}</Table.Cell>
113
+ <Table.Cell>{''}</Table.Cell>
114
+ </Table.Row>
115
+ </Table.Body>
116
+ </Table>
117
+ )
118
+ }
119
+
120
+ export default TableWithCollapsibleWithNestedTable
@@ -0,0 +1 @@
1
+ The `collapsibleContent` can also be used to display nested Tables within each Row.
@@ -55,3 +55,8 @@ 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,3 +26,8 @@ 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,4 +21,5 @@
21
21
  @import "striped";
22
22
  @import "outer_padding";
23
23
  @import "sticky_columns";
24
- @import "scroll";
24
+ @import "scroll";
25
+ @import "collapsible";
@@ -0,0 +1,35 @@
1
+ @import "../../tokens/colors";
2
+
3
+ .table_collapsible_side_highlight {
4
+ border-left: 4px solid $primary;
5
+ &.dark {
6
+ border-left: 4px solid $active_dark;
7
+ }
8
+ }
9
+
10
+ [class^="pb_table"] {
11
+ &.table-sm,
12
+ &.table-md,
13
+ &.table-lg {
14
+ &.table-card {
15
+ tbody,
16
+ .pb_table_tbody {
17
+ tr,
18
+ .pb_table_tr {
19
+ &.collapsible_table_row {
20
+ td,
21
+ .pb_table_td {
22
+ border-bottom-color: transparent;
23
+ }
24
+ &:hover {
25
+ td,
26
+ .pb_table_td {
27
+ border-bottom-color: darken($border_light, 10%);
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
@@ -7,14 +7,21 @@ 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";
10
12
 
11
13
  type TableRowPropTypes = {
12
14
  aria?: { [key: string]: string };
13
15
  children: React.ReactNode[] | React.ReactNode;
14
16
  className: string;
17
+ collapsible?: boolean;
18
+ collapsibleContent?: React.ReactNode[] | React.ReactNode;
19
+ collapsibleSideHighlight?: boolean;
15
20
  data?: { [key: string]: string };
21
+ dark?: boolean;
16
22
  htmlOptions?: { [key: string]: string | number | boolean | (() => void) };
17
23
  id?: string;
24
+ toggleCellId?: string;
18
25
  sideHighlightColor: string;
19
26
  tag?: "table" | "div";
20
27
  };
@@ -23,10 +30,15 @@ const TableRow = (props: TableRowPropTypes): React.ReactElement => {
23
30
  const {
24
31
  aria = {},
25
32
  children,
33
+ collapsible,
34
+ collapsibleContent,
35
+ collapsibleSideHighlight = true,
26
36
  className,
27
37
  data = {},
38
+ dark = false,
28
39
  htmlOptions = {},
29
40
  id,
41
+ toggleCellId,
30
42
  sideHighlightColor = "none",
31
43
  tag = "table",
32
44
  } = props;
@@ -36,17 +48,110 @@ const TableRow = (props: TableRowPropTypes): React.ReactElement => {
36
48
  const htmlProps = buildHtmlProps(htmlOptions);
37
49
  const sideHighlightClass =
38
50
  sideHighlightColor != "" ? `side_highlight_${sideHighlightColor}` : null;
51
+
52
+ const [isCollapsed, setIsCollapsed] = useCollapsible(true);
53
+
54
+ const collapsibleRow = collapsible && isCollapsed === true ? "collapsible_table_row" : null;
39
55
  const classes = classnames(
40
56
  buildCss("pb_table_row_kit", sideHighlightClass),
41
57
  "pb_table_tr",
58
+ collapsibleRow,
42
59
  globalProps(props),
43
60
  className
44
61
  );
45
62
  const isTableTag = tag === "table";
46
63
 
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
+
47
84
  return (
48
85
  <>
49
- {isTableTag ? (
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 ? (
50
155
  <tr
51
156
  {...ariaProps}
52
157
  {...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
+ }