playbook_ui 14.9.0.pre.alpha.play1703errorstatealignment4889 → 14.9.0.pre.alpha.play1703errorstatealignment4991

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.
Files changed (44) 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_form_group/_error_state_mixin.scss +49 -0
  8. data/app/pb_kits/playbook/pb_form_group/_form_group.scss +46 -12
  9. data/app/pb_kits/playbook/pb_link/_link.scss +3 -3
  10. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible.jsx +75 -0
  11. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible.md +1 -0
  12. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_click.jsx +108 -0
  13. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_click.md +2 -0
  14. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_content.jsx +94 -0
  15. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_content.md +0 -0
  16. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_rows.jsx +83 -0
  17. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_rows.md +3 -0
  18. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_table.jsx +120 -0
  19. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_table.md +1 -0
  20. data/app/pb_kits/playbook/pb_table/docs/example.yml +5 -0
  21. data/app/pb_kits/playbook/pb_table/docs/index.js +5 -0
  22. data/app/pb_kits/playbook/pb_table/styles/_all.scss +2 -1
  23. data/app/pb_kits/playbook/pb_table/styles/_collapsible.scss +35 -0
  24. data/app/pb_kits/playbook/pb_table/subcomponents/_table_row.tsx +106 -1
  25. data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +35 -3
  26. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_mask.jsx +88 -0
  27. data/app/pb_kits/playbook/pb_text_input/docs/example.yml +1 -0
  28. data/app/pb_kits/playbook/pb_text_input/docs/index.js +1 -0
  29. data/app/pb_kits/playbook/pb_text_input/inputMask.ts +64 -0
  30. data/app/pb_kits/playbook/pb_text_input/text_input.test.js +139 -2
  31. data/dist/chunks/_typeahead-l1kq1p9m.js +22 -0
  32. data/dist/chunks/_weekday_stacked-B28kYXl9.js +45 -0
  33. data/dist/chunks/{lib-CVPInSs5.js → lib-CuCy3_xO.js} +3 -3
  34. data/dist/chunks/{pb_form_validation-CDLJ5eAG.js → pb_form_validation-D37k10a0.js} +1 -1
  35. data/dist/chunks/vendor.js +1 -1
  36. data/dist/menu.yml +1 -1
  37. data/dist/playbook-doc.js +1 -1
  38. data/dist/playbook-rails-react-bindings.js +1 -1
  39. data/dist/playbook-rails.js +1 -1
  40. data/dist/playbook.css +1 -1
  41. data/lib/playbook/version.rb +1 -1
  42. metadata +20 -6
  43. data/dist/chunks/_typeahead-4sdDeM4X.js +0 -22
  44. data/dist/chunks/_weekday_stacked-CblTZ9cd.js +0 -45
@@ -0,0 +1,94 @@
1
+ import React from 'react'
2
+ import { Table, Icon, Card, Body, Image, Flex } from 'playbook-ui'
3
+
4
+ const TableWithCollapsibleWithCustomContent = (props) => {
5
+
6
+ const Content = () => {
7
+ return (
8
+ <Card
9
+ borderNone
10
+ borderRadius="none"
11
+ color="light"
12
+ paddingX="xl"
13
+ paddingY="md"
14
+ {...props}
15
+ >
16
+ <Body paddingBottom="sm"
17
+ text="Expanded Custom Layout"
18
+ {...props}
19
+ />
20
+ <Flex justify="between">
21
+ <Image
22
+ url="https://via.placeholder.com/150"
23
+ />
24
+ <Image
25
+ url="https://via.placeholder.com/150"
26
+ />
27
+ <Image
28
+ url="https://via.placeholder.com/150"
29
+ />
30
+ <Image
31
+ url="https://via.placeholder.com/150"
32
+ />
33
+ </Flex>
34
+ </Card>
35
+ );
36
+ };
37
+
38
+ return (
39
+ <Table
40
+ size="sm"
41
+ {...props}
42
+ >
43
+ <Table.Head>
44
+ <Table.Row>
45
+ <Table.Header>{'Column 1'}</Table.Header>
46
+ <Table.Header>{'Column 2'}</Table.Header>
47
+ <Table.Header>{'Column 3'}</Table.Header>
48
+ <Table.Header>{'Column 4'}</Table.Header>
49
+ <Table.Header>{'Column 5'}</Table.Header>
50
+ <Table.Header>{''}</Table.Header>
51
+ </Table.Row>
52
+
53
+ </Table.Head>
54
+ <Table.Body>
55
+ <Table.Row collapsible
56
+ collapsibleContent={<Content/>}
57
+ {...props}
58
+ >
59
+ <Table.Cell>{'Value 1'}</Table.Cell>
60
+ <Table.Cell>{'Value 2'}</Table.Cell>
61
+ <Table.Cell>{'Value 3'}</Table.Cell>
62
+ <Table.Cell>{'Value 4'}</Table.Cell>
63
+ <Table.Cell>{'Value 5'}</Table.Cell>
64
+ <Table.Cell textAlign="right">{
65
+ <Icon
66
+ color="primary"
67
+ fixedWidth
68
+ icon="chevron-down"
69
+ />}
70
+ </Table.Cell>
71
+
72
+ </Table.Row>
73
+ <Table.Row>
74
+ <Table.Cell>{'Value 1'}</Table.Cell>
75
+ <Table.Cell>{'Value 2'}</Table.Cell>
76
+ <Table.Cell>{'Value 3'}</Table.Cell>
77
+ <Table.Cell>{'Value 4'}</Table.Cell>
78
+ <Table.Cell>{'Value 5'}</Table.Cell>
79
+ <Table.Cell>{''}</Table.Cell>
80
+ </Table.Row>
81
+ <Table.Row>
82
+ <Table.Cell>{'Value 1'}</Table.Cell>
83
+ <Table.Cell>{'Value 2'}</Table.Cell>
84
+ <Table.Cell>{'Value 3'}</Table.Cell>
85
+ <Table.Cell>{'Value 4'}</Table.Cell>
86
+ <Table.Cell>{'Value 5'}</Table.Cell>
87
+ <Table.Cell>{''}</Table.Cell>
88
+ </Table.Row>
89
+ </Table.Body>
90
+ </Table>
91
+ )
92
+ }
93
+
94
+ export default TableWithCollapsibleWithCustomContent
@@ -0,0 +1,83 @@
1
+ import React from 'react'
2
+ import { Table, Background, Icon } from 'playbook-ui'
3
+
4
+ const TableWithCollapsibleWithNestedRows = (props) => {
5
+
6
+ const Content = () => {
7
+ return (
8
+ <Table
9
+ borderRadius="none"
10
+ container={false}
11
+ size="sm"
12
+ {...props}
13
+ >
14
+ <Background tag="tr"
15
+ {...props}
16
+ >
17
+ <Table.Cell>Expanded</Table.Cell>
18
+ <Table.Cell>Expanded</Table.Cell>
19
+ <Table.Cell>Expanded</Table.Cell>
20
+ <Table.Cell>Expanded</Table.Cell>
21
+ <Table.Cell>Expanded</Table.Cell>
22
+ </Background>
23
+ </Table>
24
+ );
25
+ };
26
+
27
+ return (
28
+ <Table
29
+ size="sm"
30
+ {...props}
31
+ >
32
+ <Table.Head>
33
+ <Table.Row>
34
+ <Table.Header>{'Column 1'}</Table.Header>
35
+ <Table.Header>{'Column 2'}</Table.Header>
36
+ <Table.Header>{'Column 3'}</Table.Header>
37
+ <Table.Header>{'Column 4'}</Table.Header>
38
+ <Table.Header>{'Column 5'}</Table.Header>
39
+ <Table.Header>{''}</Table.Header>
40
+ </Table.Row>
41
+
42
+ </Table.Head>
43
+ <Table.Body>
44
+ <Table.Row collapsible
45
+ collapsibleContent={<Content/>}
46
+ collapsibleSideHighlight={false}
47
+ >
48
+ <Table.Cell>{'Value 1'}</Table.Cell>
49
+ <Table.Cell>{'Value 2'}</Table.Cell>
50
+ <Table.Cell>{'Value 3'}</Table.Cell>
51
+ <Table.Cell>{'Value 4'}</Table.Cell>
52
+ <Table.Cell>{'Value 5'}</Table.Cell>
53
+ <Table.Cell textAlign="right">{
54
+ <Icon
55
+ color="primary"
56
+ fixedWidth
57
+ icon="chevron-down"
58
+ />}
59
+ </Table.Cell>
60
+
61
+ </Table.Row>
62
+ <Table.Row>
63
+ <Table.Cell>{'Value 1'}</Table.Cell>
64
+ <Table.Cell>{'Value 2'}</Table.Cell>
65
+ <Table.Cell>{'Value 3'}</Table.Cell>
66
+ <Table.Cell>{'Value 4'}</Table.Cell>
67
+ <Table.Cell>{'Value 5'}</Table.Cell>
68
+ <Table.Cell>{''}</Table.Cell>
69
+ </Table.Row>
70
+ <Table.Row>
71
+ <Table.Cell>{'Value 1'}</Table.Cell>
72
+ <Table.Cell>{'Value 2'}</Table.Cell>
73
+ <Table.Cell>{'Value 3'}</Table.Cell>
74
+ <Table.Cell>{'Value 4'}</Table.Cell>
75
+ <Table.Cell>{'Value 5'}</Table.Cell>
76
+ <Table.Cell>{''}</Table.Cell>
77
+ </Table.Row>
78
+ </Table.Body>
79
+ </Table>
80
+ )
81
+ }
82
+
83
+ export default TableWithCollapsibleWithNestedRows
@@ -0,0 +1,3 @@
1
+ The `collapsibleContent` can display any content, including nested Table Rows.
2
+
3
+ Additionally, the `collapsibleSideHighlight` can also be removed by setting it to false if needed. This prop is set to true by default.
@@ -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