playbook_ui 14.9.0 → 14.10.0.pre.alpha.PBNTR775formmatingmaskdefaultvalue5137

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/CollapsibleTrail.tsx +1 -6
  3. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +11 -3
  4. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +62 -18
  5. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +2 -2
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_collapsible_trail_rails.html.erb +36 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_collapsible_trail_rails.md +1 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pagination.jsx +50 -0
  9. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pagination.md +1 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pagination_with_props.jsx +57 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pagination_with_props.md +5 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/advanced_table_pagination_mock_data.json +5600 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +3 -0
  14. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -0
  15. data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +4 -2
  16. data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +5 -3
  17. data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +8 -0
  18. data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +2 -0
  19. data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.html.erb +8 -0
  20. data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.rb +2 -0
  21. data/app/pb_kits/playbook/pb_avatar/_avatar.scss +0 -2
  22. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_custom.jsx +53 -49
  23. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_custom_rails.html.erb +29 -36
  24. data/app/pb_kits/playbook/pb_body/_body.scss +14 -13
  25. data/app/pb_kits/playbook/pb_body/_body_mixins.scss +22 -16
  26. data/app/pb_kits/playbook/pb_bread_crumbs/docs/_bread_crumbs_default.jsx +6 -0
  27. data/app/pb_kits/playbook/pb_caption/_caption_mixin.scss +2 -1
  28. data/app/pb_kits/playbook/pb_card/_card.tsx +7 -7
  29. data/app/pb_kits/playbook/pb_card/_card_mixin.scss +2 -2
  30. data/app/pb_kits/playbook/pb_card/docs/_card_header.html.erb +21 -1
  31. data/app/pb_kits/playbook/pb_card/docs/_card_header.jsx +50 -0
  32. data/app/pb_kits/playbook/pb_card/docs/_card_header.md +1 -1
  33. data/app/pb_kits/playbook/pb_collapsible/_collapsible.tsx +9 -4
  34. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleContent.tsx +2 -2
  35. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleMain.tsx +2 -2
  36. data/app/pb_kits/playbook/pb_drawer/_drawer.tsx +2 -2
  37. data/app/pb_kits/playbook/pb_file_upload/_file_upload.scss +5 -0
  38. data/app/pb_kits/playbook/pb_file_upload/_file_upload.tsx +7 -2
  39. data/app/pb_kits/playbook/pb_file_upload/file_upload.html.erb +8 -2
  40. data/app/pb_kits/playbook/pb_form_group/_error_state_mixin.scss +57 -0
  41. data/app/pb_kits/playbook/pb_form_group/_form_group.scss +11 -2
  42. data/app/pb_kits/playbook/pb_gauge/_gauge.scss +31 -1
  43. data/app/pb_kits/playbook/pb_gauge/_gauge.tsx +3 -3
  44. data/app/pb_kits/playbook/pb_gauge/docs/_gauge_complex.jsx +25 -5
  45. data/app/pb_kits/playbook/pb_home_address_street/_home_address_street.tsx +17 -1
  46. data/app/pb_kits/playbook/pb_home_address_street/docs/_home_address_street_emphasis.html.erb +17 -1
  47. data/app/pb_kits/playbook/pb_home_address_street/docs/_home_address_street_emphasis.jsx +15 -0
  48. data/app/pb_kits/playbook/pb_home_address_street/docs/_home_address_street_emphasis.md +2 -1
  49. data/app/pb_kits/playbook/pb_home_address_street/home_address_street.rb +15 -1
  50. data/app/pb_kits/playbook/pb_home_address_street/none_emphasis.html.erb +32 -0
  51. data/app/pb_kits/playbook/pb_home_address_street/none_emphasis.rb +29 -0
  52. data/app/pb_kits/playbook/pb_layout/_layout.tsx +30 -11
  53. data/app/pb_kits/playbook/pb_link/_link.scss +3 -3
  54. data/app/pb_kits/playbook/pb_nav/_bold_mixin.scss +11 -1
  55. data/app/pb_kits/playbook/pb_nav/_collapsible_nav.scss +16 -2
  56. data/app/pb_kits/playbook/pb_nav/_vertical_nav.scss +1 -1
  57. data/app/pb_kits/playbook/pb_pagination/_pagination.tsx +2 -2
  58. data/app/pb_kits/playbook/pb_selectable_card/_selectable_card.scss +4 -4
  59. data/app/pb_kits/playbook/pb_selectable_card/selectable_card.html.erb +1 -1
  60. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_filter.jsx +166 -0
  61. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.jsx +2 -0
  62. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_user.jsx +89 -0
  63. data/app/pb_kits/playbook/pb_skeleton_loading/docs/example.yml +2 -1
  64. data/app/pb_kits/playbook/pb_skeleton_loading/docs/index.js +2 -0
  65. data/app/pb_kits/playbook/pb_stat_change/_stat_change.tsx +44 -36
  66. data/app/pb_kits/playbook/pb_stat_change/stat_change.html.erb +4 -4
  67. data/app/pb_kits/playbook/pb_table/_table.tsx +6 -6
  68. data/app/pb_kits/playbook/pb_table/docs/_table_sticky_left_columns.html.erb +77 -0
  69. data/app/pb_kits/playbook/pb_table/docs/_table_sticky_left_columns_rails.md +1 -0
  70. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible.jsx +75 -0
  71. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible.md +1 -0
  72. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_click.jsx +108 -0
  73. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_click.md +2 -0
  74. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_content.jsx +94 -0
  75. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_custom_content.md +0 -0
  76. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_rows.jsx +83 -0
  77. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_rows.md +3 -0
  78. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_table.jsx +120 -0
  79. data/app/pb_kits/playbook/pb_table/docs/_table_with_collapsible_with_nested_table.md +1 -0
  80. data/app/pb_kits/playbook/pb_table/docs/example.yml +7 -0
  81. data/app/pb_kits/playbook/pb_table/docs/index.js +5 -0
  82. data/app/pb_kits/playbook/pb_table/index.ts +102 -26
  83. data/app/pb_kits/playbook/pb_table/styles/_all.scss +2 -1
  84. data/app/pb_kits/playbook/pb_table/styles/_collapsible.scss +35 -0
  85. data/app/pb_kits/playbook/pb_table/styles/_scroll.scss +28 -3
  86. data/app/pb_kits/playbook/pb_table/styles/_sticky_columns.scss +0 -1
  87. data/app/pb_kits/playbook/pb_table/subcomponents/_table_row.tsx +106 -1
  88. data/app/pb_kits/playbook/pb_table/table.html.erb +8 -2
  89. data/app/pb_kits/playbook/pb_table/table.rb +21 -2
  90. data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +43 -4
  91. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_mask.jsx +88 -0
  92. data/app/pb_kits/playbook/pb_text_input/docs/example.yml +1 -0
  93. data/app/pb_kits/playbook/pb_text_input/docs/index.js +1 -0
  94. data/app/pb_kits/playbook/pb_text_input/inputMask.ts +87 -0
  95. data/app/pb_kits/playbook/pb_text_input/text_input.test.js +139 -2
  96. data/app/pb_kits/playbook/pb_title/_title.scss +6 -5
  97. data/app/pb_kits/playbook/pb_title/_title_mixin.scss +13 -0
  98. data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +115 -46
  99. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_highlight.jsx +9 -2
  100. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
  101. data/app/pb_kits/playbook/tokens/_titles.scss +0 -8
  102. data/app/pb_kits/playbook/utilities/_hover.scss +11 -2
  103. data/app/pb_kits/playbook/utilities/globalProps.ts +2 -0
  104. data/app/pb_kits/playbook/utilities/test/globalProps/hover.test.js +15 -0
  105. data/dist/chunks/{_typeahead-B8fkIeXA.js → _typeahead-CoIYBETL.js} +3 -3
  106. data/dist/chunks/_weekday_stacked-Qj3GFYzA.js +45 -0
  107. data/dist/chunks/lazysizes-B7xYodB-.js +1 -0
  108. data/dist/chunks/{lib-SyD3buPZ.js → lib-sMFo2JZy.js} +3 -3
  109. data/dist/chunks/{pb_form_validation-Dt8UJgrJ.js → pb_form_validation-CgvjWbOK.js} +1 -1
  110. data/dist/chunks/vendor.js +1 -1
  111. data/dist/menu.yml +1 -1
  112. data/dist/playbook-doc.js +1 -1
  113. data/dist/playbook-rails-react-bindings.js +1 -1
  114. data/dist/playbook-rails.js +1 -1
  115. data/dist/playbook.css +1 -1
  116. data/lib/playbook/hover.rb +7 -1
  117. data/lib/playbook/version.rb +2 -2
  118. metadata +37 -10
  119. data/dist/chunks/_weekday_stacked-CVYxBYHe.js +0 -45
  120. data/dist/chunks/lazysizes-DHz07jlL.js +0 -1
@@ -1,32 +1,108 @@
1
1
  import PbEnhancedElement from '../pb_enhanced_element'
2
2
 
3
3
  export default class PbTable extends PbEnhancedElement {
4
- static get selector(): string {
5
- return '.table-responsive-collapse'
6
- }
7
-
8
- connect(): void {
9
- const tables = document.querySelectorAll('.table-responsive-collapse');
10
-
11
- // Each Table
12
- [].forEach.call(tables, (table: HTMLTableElement) => {
13
- // Header Titles
14
- const headers: string[] = [];
15
- [].forEach.call(table.querySelectorAll('th'), (header: HTMLTableCellElement) => {
16
- const colSpan = header.colSpan
17
- for (let i = 0; i < colSpan; i++) {
18
- headers.push(header.textContent.replace(/\r?\n|\r/, ''));
4
+ private stickyLeftColumns: string[] = [];
5
+ private handleStickyColumnsRef: () => void;
6
+
7
+ static get selector(): string {
8
+ return '.table-responsive-collapse'
9
+ }
10
+
11
+ connect(): void {
12
+ const tables = document.querySelectorAll('.table-responsive-collapse');
13
+ // Each Table
14
+ [].forEach.call(tables, (table: HTMLTableElement) => {
15
+ // Header Titles
16
+ const headers: string[] = [];
17
+ [].forEach.call(table.querySelectorAll('th'), (header: HTMLTableCellElement) => {
18
+ const colSpan = header.colSpan
19
+ for (let i = 0; i < colSpan; i++) {
20
+ headers.push(header.textContent.replace(/\r?\n|\r/, ''));
21
+ }
22
+ });
23
+ // for each row in tbody
24
+ [].forEach.call(table.querySelectorAll('tbody tr'), (row: HTMLTableRowElement) => {
25
+ // for each cell
26
+ [].forEach.call(row.cells, (cell: HTMLTableCellElement, headerIndex: number) => {
27
+ // apply the attribute
28
+ cell.setAttribute('data-title', headers[headerIndex])
29
+ })
30
+ })
31
+ });
32
+
33
+ // New sticky columns logic
34
+ this.initStickyColumns();
35
+ }
36
+
37
+ private initStickyColumns(): void {
38
+ // Find tables with sticky-left-column class
39
+ const tables = document.querySelectorAll('.sticky-left-column');
40
+
41
+ tables.forEach((table) => {
42
+ // Extract sticky left column IDs by looking at the component's class
43
+ const classList = Array.from(table.classList);
44
+
45
+ // Look for classes in the format sticky-left-column-{ids}
46
+ const stickyColumnClass = classList.find(cls => cls.startsWith('sticky-columns-'));
47
+ if (stickyColumnClass) {
48
+ // Extract the IDs from the class name
49
+ this.stickyLeftColumns = stickyColumnClass
50
+ .replace('sticky-columns-', '')
51
+ .split('-');
52
+
53
+ if (this.stickyLeftColumns.length > 0) {
54
+ setTimeout(() => {
55
+ this.handleStickyColumnsRef = this.handleStickyColumns.bind(this);
56
+ this.handleStickyColumns();
57
+ window.addEventListener('resize', this.handleStickyColumnsRef);
58
+ }, 10);
59
+ }
19
60
  }
20
61
  });
62
+ }
21
63
 
22
- // for each row in tbody
23
- [].forEach.call(table.querySelectorAll('tbody tr'), (row: HTMLTableRowElement) => {
24
- // for each cell
25
- [].forEach.call(row.cells, (cell: HTMLTableCellElement, headerIndex: number) => {
26
- // apply the attribute
27
- cell.setAttribute('data-title', headers[headerIndex])
28
- })
29
- })
30
- })
31
- }
32
- }
64
+ private handleStickyColumns(): void {
65
+ let accumulatedWidth = 0;
66
+
67
+ this.stickyLeftColumns.forEach((colId, index) => {
68
+ const isLastColumn = index === this.stickyLeftColumns.length - 1;
69
+ const header = document.querySelector(`th[id="${colId}"]`);
70
+ const cells = document.querySelectorAll(`td[id="${colId}"]`);
71
+
72
+ if (header) {
73
+ header.classList.add('sticky');
74
+ (header as HTMLElement).style.left = `${accumulatedWidth}px`;
75
+
76
+ if (!isLastColumn) {
77
+ header.classList.add('with-border');
78
+ header.classList.remove('sticky-shadow');
79
+ } else {
80
+ header.classList.remove('with-border');
81
+ header.classList.add('sticky-shadow');
82
+ }
83
+
84
+ accumulatedWidth += (header as HTMLElement).offsetWidth;
85
+ }
86
+
87
+ cells.forEach((cell) => {
88
+ cell.classList.add('sticky');
89
+ (cell as HTMLElement).style.left = `${accumulatedWidth - (header as HTMLElement).offsetWidth}px`;
90
+
91
+ if (!isLastColumn) {
92
+ cell.classList.add('with-border');
93
+ cell.classList.remove('sticky-shadow');
94
+ } else {
95
+ cell.classList.remove('with-border');
96
+ cell.classList.add('sticky-shadow');
97
+ }
98
+ });
99
+ });
100
+ }
101
+
102
+ // Cleanup method to remove event listener
103
+ disconnect(): void {
104
+ if (this.handleStickyColumnsRef) {
105
+ window.removeEventListener('resize', this.handleStickyColumnsRef);
106
+ }
107
+ }
108
+ }
@@ -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
+ }
@@ -1,4 +1,29 @@
1
- .table-responsive-scroll {
2
- display: block;
3
- overflow-x: auto;
1
+ @import "../../tokens/screen_sizes";
2
+
3
+ .table-responsive-scroll {
4
+ display: block;
5
+ overflow-x: scroll;
6
+
7
+ // Responsive Styles
8
+ @media (max-width: 1600px) {
9
+ &[class*="table-responsive-scroll"] {
10
+ border-radius: 4px;
11
+ box-shadow: 1px 0 0 0px $border_light
12
+ }
13
+
14
+ &[class^=pb_table].table-sm.table-card thead tr th:last-child,
15
+ &[class^=pb_table].table-sm:not(.no-hover).table-card tbody tr td:last-child {
16
+ border-right-width: 0px;
17
+ }
18
+
19
+ &[class^=pb_table].table-md.table-card thead tr th:last-child,
20
+ &[class^=pb_table].table-md:not(.no-hover).table-card tbody tr td:last-child {
21
+ border-right-width: 0px;
22
+ }
23
+
24
+ &[class^=pb_table].table-lg.table-card thead tr th:last-child,
25
+ &[class^=pb_table].table-lg:not(.no-hover).table-card tbody tr td:last-child {
26
+ border-right-width: 0px;
27
+ }
4
28
  }
29
+ }
@@ -3,7 +3,6 @@
3
3
  [class^="pb_table"] {
4
4
  .sticky {
5
5
  position: sticky !important;
6
- left: 0;
7
6
  z-index: 1;
8
7
  background-color: white;
9
8
  }
@@ -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,10 @@
1
- <%= content_tag(:div) do %>
1
+ <% if object.responsive_classname %>
2
+ <% responsive_class = object.responsive_classname %>
3
+ <% else %>
4
+ <% responsive_class = nil %>
5
+ <% end %>
6
+
7
+ <%= content_tag(:div, class: responsive_class) do %>
2
8
  <% if object.tag == "table" %>
3
9
  <%= content_tag(:table,
4
10
  aria: object.aria,
@@ -18,4 +24,4 @@
18
24
  <%= content.presence %>
19
25
  <% end %>
20
26
  <% end %>
21
- <% end %>
27
+ <% end %>
@@ -23,6 +23,8 @@ module Playbook
23
23
  prop :text
24
24
  prop :sticky, type: Playbook::Props::Boolean,
25
25
  default: false
26
+ prop :sticky_left_column, type: Playbook::Props::Array,
27
+ default: []
26
28
  prop :vertical_border, type: Playbook::Props::Boolean,
27
29
  default: false
28
30
  prop :striped, type: Playbook::Props::Boolean,
@@ -37,12 +39,16 @@ module Playbook
37
39
  def classname
38
40
  generate_classname(
39
41
  "pb_table", "table-#{size}", single_line_class, dark_class,
40
- disable_hover_class, container_class, data_table_class, sticky_class, collapse_class,
41
- vertical_border_class, striped_class, outer_padding_class,
42
+ disable_hover_class, container_class, data_table_class, sticky_class, sticky_left_column_class,
43
+ collapse_class, vertical_border_class, striped_class, outer_padding_class,
42
44
  "table-responsive-#{responsive}", separator: " "
43
45
  )
44
46
  end
45
47
 
48
+ def responsive_classname
49
+ responsive ? "table-responsive-#{responsive}" : nil
50
+ end
51
+
46
52
  private
47
53
 
48
54
  def dark_class
@@ -73,6 +79,19 @@ module Playbook
73
79
  sticky ? "sticky-header" : nil
74
80
  end
75
81
 
82
+ def sticky_left_column_class
83
+ if sticky_left_column.empty?
84
+ nil
85
+ else
86
+ sticky_col_classname = "sticky-left-column sticky-columns"
87
+ sticky_left_column.each do |id|
88
+ sticky_col_classname += "-#{id}"
89
+ end
90
+
91
+ sticky_col_classname
92
+ end
93
+ end
94
+
76
95
  def striped_class
77
96
  striped ? "striped" : nil
78
97
  end
@@ -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,8 +94,42 @@ 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
 
126
+ let formattedValue;
127
+ if (isMaskedInput && value) {
128
+ formattedValue = INPUTMASKS[mask].formatDefaultValue(value.toString())
129
+ } else {
130
+ formattedValue = value
131
+ }
132
+
95
133
  const textInput = (
96
134
  childInput ? React.cloneElement(children, { className: "text_input" }) :
97
135
  (<input
@@ -101,12 +139,13 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
101
139
  id={id}
102
140
  key={id}
103
141
  name={name}
104
- onChange={onChange}
105
- placeholder={placeholder}
142
+ onChange={isMaskedInput ? handleChange : onChange}
143
+ pattern={isMaskedInput ? INPUTMASKS[mask]?.pattern : undefined}
144
+ placeholder={placeholder || (isMaskedInput ? INPUTMASKS[mask]?.placeholder : undefined)}
106
145
  ref={ref}
107
146
  required={required}
108
147
  type={type}
109
- value={value}
148
+ value={formattedValue}
110
149
  />)
111
150
  )
112
151
 
@@ -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'