playbook_ui 14.10.0.pre.alpha.PLAY1774timelinelabelstepspacing5274 → 14.10.0.pre.alpha.PLAY1774timelinelabelstepspacing5314
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/_playbook.scss +1 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_subrow_headers.html.erb +1 -1
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_subrow_headers.md +1 -1
- data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -1
- data/app/pb_kits/playbook/pb_advanced_table/index.js +53 -8
- data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +23 -18
- data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +6 -0
- data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.html.erb +2 -2
- data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.rb +6 -4
- data/app/pb_kits/playbook/pb_file_upload/_file_upload.tsx +24 -15
- data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_accept.jsx +3 -1
- data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_description.jsx +4 -1
- data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_max_size.jsx +1 -1
- data/app/pb_kits/playbook/pb_table/_table.tsx +8 -1
- data/app/pb_kits/playbook/pb_table/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_table/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_mask.jsx +18 -0
- data/app/pb_kits/playbook/pb_text_input/inputMask.ts +22 -1
- data/app/pb_kits/playbook/pb_text_input/text_input.test.js +80 -0
- data/app/pb_kits/playbook/pb_timeline/_timeline.scss +14 -13
- data/app/pb_kits/playbook/pb_timeline/_timeline.tsx +5 -5
- data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_gap.html.erb +4 -5
- data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_gap.jsx +4 -4
- data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_gap.md +1 -1
- data/app/pb_kits/playbook/pb_timeline/timeline.rb +6 -6
- data/app/pb_kits/playbook/utilities/_gap.scss +29 -0
- data/app/pb_kits/playbook/utilities/globalPropNames.mjs +1 -0
- data/app/pb_kits/playbook/utilities/globalProps.ts +18 -9
- data/dist/chunks/_typeahead-C2iCBqxQ.js +36 -0
- data/dist/chunks/_weekday_stacked-CWnbnW7m.js +45 -0
- data/dist/chunks/lazysizes-B7xYodB-.js +1 -0
- data/dist/chunks/vendor.js +1 -1
- data/dist/playbook-doc.js +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/classnames.rb +1 -0
- data/lib/playbook/spacing.rb +21 -0
- data/lib/playbook/version.rb +1 -1
- metadata +6 -5
- data/dist/chunks/_typeahead-gJLWiR0r.js +0 -22
- data/dist/chunks/_weekday_stacked-BT8jIMPK.js +0 -45
- data/dist/chunks/lazysizes-DHz07jlL.js +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01c306061158ee8ed9da26a1bbe5f2422340b912127fcb42c7a1e7bf7731b608
|
4
|
+
data.tar.gz: 46fb0a4066cb64275f8b07681cc45fcbe86e3cfd1b42377e8fc47247942c5137
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d26b0f52c83776834ca565658284f9aa5fa1f06eb4a5290023a4fcccb6e47704c6689b8ec3d70516ffae1bad3e407d26bb1e3d2b8cda38f3568d5767f7d3d0b9
|
7
|
+
data.tar.gz: e79862c8e779f667381aeb9035d80ea7e0227a174b3e40bd5e1b630b5fa6f5776927906333b3f5a87881605bde7618ac9f1d00a964450c0f747b2b7132549848
|
data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_subrow_headers.html.erb
CHANGED
@@ -36,5 +36,5 @@
|
|
36
36
|
|
37
37
|
<%= pb_rails("advanced_table", props: { table_data: @table_data, column_definitions: column_definitions }) do %>
|
38
38
|
<%= pb_rails("advanced_table/table_header", props: { column_definitions: column_definitions }) %>
|
39
|
-
<%= pb_rails("advanced_table/table_body", props: { id: "
|
39
|
+
<%= pb_rails("advanced_table/table_body", props: { id: "test_table", table_data: @table_data, column_definitions: column_definitions, subrow_headers: subrow_headers, enable_toggle_expansion: "all" }) %>
|
40
40
|
<% end %>
|
@@ -1,3 +1,3 @@
|
|
1
1
|
`subrow_headers` is an optional prop that if present will add header rows at each level of the nested data. The prop takes an array of strings, each string being the text for each header row. The array of strings must be in the order in which they need to be rendered in the UI according to depth.
|
2
2
|
|
3
|
-
`enable_toggle_expansion` is an additional optional prop that can be used in conjunction with the subRowHeaders prop. `enable_toggle_expansion` is a string that can be "all", "header" or "none". If set to "all", the toggle
|
3
|
+
`enable_toggle_expansion` is an additional optional prop that can be used in conjunction with the subRowHeaders prop. `enable_toggle_expansion` is a string that can be "all", "header" or "none". If set to "all", the toggle expansion button will appear in the table header as well as in the subRow headers. If set to "header", the button will only appear in header and NOT in subRow headers. This prop is set to "header" by default.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
examples:
|
2
2
|
rails:
|
3
3
|
- advanced_table_beta: Default (Required Props)
|
4
|
-
|
4
|
+
- advanced_table_beta_subrow_headers: SubRow Headers
|
5
5
|
- advanced_table_collapsible_trail_rails: Collapsible Trail
|
6
6
|
- advanced_table_beta_sort: Enable Sorting
|
7
7
|
- advanced_table_custom_cell_rails: Custom Components for Cells
|
@@ -65,8 +65,7 @@ export default class PbAdvancedTable extends PbEnhancedElement {
|
|
65
65
|
if (!dataContent) {
|
66
66
|
return;
|
67
67
|
}
|
68
|
-
|
69
|
-
// Split the dataContent to get all ancestor IDs, check against simpleExpandedRows
|
68
|
+
// Split the dataContent to get all ancestor IDs, check against ExpandedRows
|
70
69
|
const ancestorIds = dataContent.split("-").slice(0, -1);
|
71
70
|
|
72
71
|
const prefixedAncestorIds = ancestorIds.map(
|
@@ -76,7 +75,22 @@ export default class PbAdvancedTable extends PbEnhancedElement {
|
|
76
75
|
PbAdvancedTable.expandedRows.has(id)
|
77
76
|
);
|
78
77
|
|
79
|
-
|
78
|
+
const checkIfParentIsExpanded = () => {
|
79
|
+
if (dataContent.endsWith("sr")) {
|
80
|
+
const parentRowId = childRow.dataset.rowParent;
|
81
|
+
const isParentVisible =
|
82
|
+
childRow.previousElementSibling.classList.contains("is-visible");
|
83
|
+
if (parentRowId) {
|
84
|
+
const isInSet = PbAdvancedTable.expandedRows.has(parentRowId);
|
85
|
+
if (isInSet && isParentVisible) {
|
86
|
+
return true;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
return false;
|
91
|
+
};
|
92
|
+
|
93
|
+
if (allAncestorsExpanded || checkIfParentIsExpanded()) {
|
80
94
|
childRow.style.display = "table-row";
|
81
95
|
childRow.classList.add("is-visible");
|
82
96
|
} else {
|
@@ -143,7 +157,7 @@ export default class PbAdvancedTable extends PbEnhancedElement {
|
|
143
157
|
static handleToggleAllHeaders(element) {
|
144
158
|
const table = element.closest(".pb_table");
|
145
159
|
const firstLevelButtons = table.querySelectorAll(
|
146
|
-
".pb_advanced_table_body > .pb_table_tr [data-advanced-table]"
|
160
|
+
".pb_advanced_table_body > .pb_table_tr[data-row-depth='0'] [data-advanced-table]"
|
147
161
|
);
|
148
162
|
|
149
163
|
const allExpanded = Array.from(firstLevelButtons).every(
|
@@ -175,12 +189,43 @@ export default class PbAdvancedTable extends PbEnhancedElement {
|
|
175
189
|
}
|
176
190
|
}
|
177
191
|
|
178
|
-
|
192
|
+
static handleToggleAllSubRows(element, rowDepth) {
|
193
|
+
const table = element.closest(".pb_table");
|
194
|
+
const parentRow = element.closest("tr");
|
195
|
+
if (!parentRow) {
|
196
|
+
return;
|
197
|
+
}
|
198
|
+
const rowParentId = parentRow.dataset.rowParent;
|
199
|
+
// Select all buttons that for subrows at that depth and with same rowParent
|
200
|
+
const subRowButtons = table.querySelectorAll(
|
201
|
+
`.pb_advanced_table_body > .pb_table_tr[data-row-depth='${rowDepth}'].pb_table_tr[data-row-parent='${rowParentId}'] [data-advanced-table]`
|
202
|
+
);
|
203
|
+
|
204
|
+
const allExpanded = Array.from(subRowButtons).every(
|
205
|
+
(button) =>
|
206
|
+
button.querySelector(UP_ARROW_SELECTOR).style.display === "inline-block"
|
207
|
+
);
|
208
|
+
|
209
|
+
if (allExpanded) {
|
210
|
+
subRowButtons.forEach((button) => {
|
211
|
+
button.click();
|
212
|
+
PbAdvancedTable.expandedRows.delete(button.id);
|
213
|
+
});
|
214
|
+
} else {
|
215
|
+
subRowButtons.forEach((button) => {
|
216
|
+
if (!PbAdvancedTable.expandedRows.has(button.id)) {
|
217
|
+
button.click();
|
218
|
+
PbAdvancedTable.expandedRows.add(button.id);
|
219
|
+
}
|
220
|
+
});
|
221
|
+
}
|
222
|
+
}
|
179
223
|
}
|
180
224
|
|
181
225
|
window.expandAllRows = (element) => {
|
182
226
|
PbAdvancedTable.handleToggleAllHeaders(element);
|
183
227
|
};
|
184
|
-
|
185
|
-
|
186
|
-
|
228
|
+
|
229
|
+
window.expandAllSubRows = (element, rowDepth) => {
|
230
|
+
PbAdvancedTable.handleToggleAllSubRows(element, rowDepth);
|
231
|
+
};
|
@@ -31,7 +31,7 @@ module Playbook
|
|
31
31
|
end.compact
|
32
32
|
end
|
33
33
|
|
34
|
-
def render_row_and_children(row, column_definitions, current_depth, first_parent_child, ancestor_ids = [], top_parent_id = nil)
|
34
|
+
def render_row_and_children(row, column_definitions, current_depth, first_parent_child, ancestor_ids = [], top_parent_id = nil, additional_classes: "", table_data_attributes: {})
|
35
35
|
top_parent_id ||= row.object_id
|
36
36
|
new_ancestor_ids = ancestor_ids + [row.object_id]
|
37
37
|
leaf_columns = flatten_columns(column_definitions)
|
@@ -39,30 +39,35 @@ module Playbook
|
|
39
39
|
output = ActiveSupport::SafeBuffer.new
|
40
40
|
is_first_child_of_subrow = current_depth.positive? && first_parent_child && subrow_headers[current_depth - 1].present?
|
41
41
|
|
42
|
-
|
42
|
+
subrow_ancestor_ids = ancestor_ids + ["#{row.object_id}sr"]
|
43
|
+
subrow_data_attributes = {
|
44
|
+
advanced_table_content: subrow_ancestor_ids.join("-"),
|
45
|
+
row_depth: current_depth,
|
46
|
+
row_parent: "#{id}_#{ancestor_ids.last}",
|
47
|
+
}
|
48
|
+
# Subrow header if applicable
|
49
|
+
output << pb_rails("advanced_table/table_subrow_header", props: { row: row, column_definitions: leaf_columns, depth: current_depth, subrow_header: subrow_headers[current_depth - 1], collapsible_trail: collapsible_trail, classname: "toggle-content", subrow_data_attributes: subrow_data_attributes }) if is_first_child_of_subrow && enable_toggle_expansion == "all"
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
column_definitions: leaf_columns,
|
49
|
-
depth: current_depth,
|
50
|
-
collapsible_trail: collapsible_trail,
|
51
|
-
})
|
51
|
+
current_data_attributes = current_depth.zero? ? { row_depth: 0 } : table_data_attributes
|
52
|
+
|
53
|
+
# Additional class and data attributes needed for toggle logic
|
54
|
+
output << pb_rails("advanced_table/table_row", props: { id: id, row: row, column_definitions: leaf_columns, depth: current_depth, collapsible_trail: collapsible_trail, classname: additional_classes, table_data_attributes: current_data_attributes })
|
52
55
|
|
53
56
|
if row[:children].present?
|
54
|
-
|
57
|
+
row[:children].each do |child_row|
|
55
58
|
is_first_child = row[:children].first == child_row
|
56
|
-
|
57
|
-
child_output = render_row_and_children(child_row, column_definitions, current_depth + 1, is_first_child, new_ancestor_ids, top_parent_id)
|
58
|
-
|
59
59
|
immediate_parent_id = row.object_id
|
60
|
-
top_parent = top_parent_id
|
61
|
-
# Combine ancestor_ids to build the content id
|
62
60
|
data_content = new_ancestor_ids.join("-") + "-#{child_row.object_id}"
|
63
61
|
|
64
|
-
|
65
|
-
|
62
|
+
child_data_attributes = {
|
63
|
+
top_parent: "#{id}_#{top_parent_id}",
|
64
|
+
row_depth: current_depth + 1,
|
65
|
+
row_parent: "#{id}_#{immediate_parent_id}",
|
66
|
+
advanced_table_content: data_content,
|
67
|
+
}
|
68
|
+
|
69
|
+
output << render_row_and_children(child_row, column_definitions, current_depth + 1, is_first_child, new_ancestor_ids, top_parent_id, additional_classes: "toggle-content", table_data_attributes: child_data_attributes)
|
70
|
+
end
|
66
71
|
end
|
67
72
|
|
68
73
|
output
|
@@ -11,6 +11,12 @@ module Playbook
|
|
11
11
|
prop :depth
|
12
12
|
prop :collapsible_trail, type: Playbook::Props::Boolean,
|
13
13
|
default: true
|
14
|
+
prop :table_data_attributes, type: Playbook::Props::HashProp,
|
15
|
+
default: {}
|
16
|
+
|
17
|
+
def data
|
18
|
+
Hash(prop(:data)).merge(table_data_attributes)
|
19
|
+
end
|
14
20
|
|
15
21
|
def classname
|
16
22
|
generate_classname("pb_table_tr", "bg-white", subrow_depth_classname, separator: " ")
|
@@ -1,6 +1,6 @@
|
|
1
|
-
<%= pb_content_tag(:
|
1
|
+
<%= pb_content_tag(:tr) do %>
|
2
2
|
<% object.column_definitions.each_with_index do |column, index| %>
|
3
|
-
<%= pb_rails("table/table_cell", props: { classname:
|
3
|
+
<%= pb_rails("table/table_cell", props: { classname: "id-cell chrome-styles"}) do %>
|
4
4
|
<%= pb_rails("flex", props:{ align: "center", justify: "start" }) do %>
|
5
5
|
<% if collapsible_trail && index.zero? %>
|
6
6
|
<% (1..depth).each do |i| %>
|
@@ -14,13 +14,15 @@ module Playbook
|
|
14
14
|
default: ""
|
15
15
|
prop :collapsible_trail, type: Playbook::Props::Boolean,
|
16
16
|
default: true
|
17
|
+
prop :subrow_data_attributes, type: Playbook::Props::HashProp,
|
18
|
+
default: {}
|
17
19
|
|
18
|
-
def
|
19
|
-
|
20
|
+
def data
|
21
|
+
Hash(prop(:data)).merge(subrow_data_attributes)
|
20
22
|
end
|
21
23
|
|
22
|
-
def
|
23
|
-
generate_classname("
|
24
|
+
def classname
|
25
|
+
generate_classname("pb_table_tr", "bg-silver", "pb_subrow_header", subrow_depth_classname, separator: " ")
|
24
26
|
end
|
25
27
|
|
26
28
|
private
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import React, { useEffect, useCallback, useRef } from 'react'
|
2
|
-
import { useDropzone, DropzoneInputProps, DropzoneRootProps } from 'react-dropzone'
|
2
|
+
import { useDropzone, DropzoneInputProps, DropzoneRootProps, FileRejection } from 'react-dropzone'
|
3
3
|
import classnames from 'classnames'
|
4
4
|
|
5
5
|
import { buildCss, buildDataProps, noop, buildHtmlProps } from '../utilities/props'
|
@@ -9,8 +9,10 @@ import type { Callback } from '../types'
|
|
9
9
|
import Body from '../pb_body/_body'
|
10
10
|
import Card from '../pb_card/_card'
|
11
11
|
|
12
|
+
import { isEmpty } from '../utilities/object'
|
13
|
+
|
12
14
|
type FileUploadProps = {
|
13
|
-
accept?: string[]
|
15
|
+
accept?: Record<string, string[]>,
|
14
16
|
className?: string,
|
15
17
|
customMessage?: string,
|
16
18
|
dark?: boolean,
|
@@ -19,7 +21,7 @@ type FileUploadProps = {
|
|
19
21
|
acceptedFilesDescription?: string,
|
20
22
|
maxSize?: number,
|
21
23
|
onFilesAccepted: Callback<File, File>,
|
22
|
-
onFilesRejected: (error: string, files:
|
24
|
+
onFilesRejected: (error: string, files: readonly FileRejection[]) => void,
|
23
25
|
}
|
24
26
|
|
25
27
|
const getFormattedFileSize = (fileSize: number): string => {
|
@@ -28,7 +30,7 @@ const getFormattedFileSize = (fileSize: number): string => {
|
|
28
30
|
|
29
31
|
const FileUpload = (props: FileUploadProps): React.ReactElement => {
|
30
32
|
const {
|
31
|
-
accept =
|
33
|
+
accept = {},
|
32
34
|
acceptedFilesDescription = '',
|
33
35
|
className,
|
34
36
|
customMessage,
|
@@ -48,30 +50,37 @@ const FileUpload = (props: FileUploadProps): React.ReactElement => {
|
|
48
50
|
getRootProps: () => DropzoneRootProps & any;
|
49
51
|
getInputProps: () => DropzoneInputProps & any;
|
50
52
|
isDragActive: boolean;
|
51
|
-
|
53
|
+
fileRejections: readonly FileRejection[];
|
52
54
|
}
|
53
55
|
|
54
|
-
const { getRootProps, getInputProps, isDragActive,
|
56
|
+
const { getRootProps, getInputProps, isDragActive, fileRejections }: DropZoneProps = useDropzone({
|
55
57
|
accept,
|
56
58
|
maxSize,
|
57
59
|
onDrop,
|
58
60
|
})
|
59
61
|
|
60
|
-
const prevRejected = useRef<
|
62
|
+
const prevRejected = useRef<readonly FileRejection[] | null>(null);
|
61
63
|
|
62
|
-
|
64
|
+
let maxFileSizeText = ''
|
65
|
+
if (maxSize !== undefined) {
|
66
|
+
maxFileSizeText = `Max file size is ${getFormattedFileSize(maxSize)}.`
|
67
|
+
}
|
63
68
|
|
64
69
|
useEffect(() => {
|
65
|
-
if (
|
66
|
-
const isFileTooLarge = maxSize &&
|
70
|
+
if (fileRejections === prevRejected.current) return
|
71
|
+
const isFileTooLarge = maxSize && fileRejections.length > 0 && fileRejections[0].file.size > maxSize;
|
67
72
|
if (isFileTooLarge) {
|
68
|
-
onFilesRejected(`File size is too large! ${maxFileSizeText}`,
|
73
|
+
onFilesRejected(`File size is too large! ${maxFileSizeText}`, fileRejections)
|
69
74
|
}
|
70
|
-
prevRejected.current =
|
71
|
-
}, [maxFileSizeText, maxSize, onFilesRejected,
|
75
|
+
prevRejected.current = fileRejections
|
76
|
+
}, [maxFileSizeText, maxSize, onFilesRejected, fileRejections])
|
72
77
|
|
73
78
|
const acceptedFileTypes = () => {
|
74
|
-
|
79
|
+
if (!accept) {
|
80
|
+
return []
|
81
|
+
}
|
82
|
+
|
83
|
+
return Object.keys(accept).map((fileType) => {
|
75
84
|
if (fileType.startsWith('image/')) {
|
76
85
|
return fileType.replace('image/', ' ')
|
77
86
|
} else {
|
@@ -86,7 +95,7 @@ const FileUpload = (props: FileUploadProps): React.ReactElement => {
|
|
86
95
|
const getDescription = () => {
|
87
96
|
return customMessage
|
88
97
|
? customMessage
|
89
|
-
: `Choose a file or drag it here.${accept
|
98
|
+
: `Choose a file or drag it here.${isEmpty(accept) ? '' : ` The accepted file types are: ${acceptedFilesDescription || acceptedFileTypes()}.`}${maxSize ? ` ${maxFileSizeText}` : ''}`;
|
90
99
|
}
|
91
100
|
|
92
101
|
return (
|
@@ -25,7 +25,10 @@ const FileUploadCustomDescription = (props) => {
|
|
25
25
|
{...props}
|
26
26
|
/>
|
27
27
|
<FileUpload
|
28
|
-
accept={
|
28
|
+
accept={{
|
29
|
+
"application/pdf": [".pdf"],
|
30
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"],
|
31
|
+
}}
|
29
32
|
acceptedFilesDescription="Adobe (.pdf) and Microsoft (.xslx)"
|
30
33
|
onFilesAccepted={handleOnFilesAccepted}
|
31
34
|
{...props}
|
@@ -18,7 +18,7 @@ const AcceptedFilesList = ({ files }) => (
|
|
18
18
|
const RejectedFilesList = ({ files }) => (
|
19
19
|
<List>
|
20
20
|
{files.map((file) => (
|
21
|
-
<ListItem key={file.name}><Body color="error">{`${file.name} (file too large)`}</Body></ListItem>
|
21
|
+
<ListItem key={file.file.name}><Body color="error">{`${file.file.name} (file too large)`}</Body></ListItem>
|
22
22
|
))}
|
23
23
|
</List>
|
24
24
|
)
|
@@ -69,6 +69,7 @@ const Table = (props: TableProps): React.ReactElement => {
|
|
69
69
|
const outerPaddingCss = outerPadding ? `outer_padding_${spaceCssName}${outerPadding}` : ''
|
70
70
|
const isTableTag = tag === 'table'
|
71
71
|
const dynamicInlineProps = globalInlineProps(props)
|
72
|
+
const stickyRightColumnReversed = stickyRightColumn.reverse()
|
72
73
|
|
73
74
|
const classNames = classnames(
|
74
75
|
'pb_table',
|
@@ -148,7 +149,7 @@ const Table = (props: TableProps): React.ReactElement => {
|
|
148
149
|
if (!stickyRightColumn.length) return;
|
149
150
|
let accumulatedWidth = 0;
|
150
151
|
|
151
|
-
|
152
|
+
stickyRightColumnReversed.forEach((colId, index) => {
|
152
153
|
const isLastColumn = index === stickyRightColumn.length - 1;
|
153
154
|
const header = document.querySelector(`th[id="${colId}"]`);
|
154
155
|
const cells = document.querySelectorAll(`td[id="${colId}"]`);
|
@@ -186,6 +187,12 @@ const Table = (props: TableProps): React.ReactElement => {
|
|
186
187
|
setTimeout(() => {
|
187
188
|
handleStickyRightColumns();
|
188
189
|
}, 10);
|
190
|
+
|
191
|
+
window.addEventListener('resize', handleStickyRightColumns);
|
192
|
+
|
193
|
+
return () => {
|
194
|
+
window.removeEventListener('resize', handleStickyRightColumns);
|
195
|
+
};
|
189
196
|
}, [stickyRightColumn]);
|
190
197
|
|
191
198
|
useEffect(() => {
|
@@ -35,6 +35,8 @@ examples:
|
|
35
35
|
- table_lg: Large
|
36
36
|
- table_sticky: Sticky Header
|
37
37
|
- table_sticky_left_columns: Sticky Left Column
|
38
|
+
- table_sticky_right_columns: Sticky Right Column
|
39
|
+
- table_sticky_columns: Sticky Left and Right Columns
|
38
40
|
- table_alignment_row: Row Alignment
|
39
41
|
- table_alignment_column: Cell Alignment
|
40
42
|
- table_alignment_shift_row: Row Shift
|
@@ -26,6 +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 TableStickyRightColumns } from './_table_sticky_right_columns.jsx'
|
30
|
+
export { default as TableStickyColumns } from './_table_sticky_columns.jsx'
|
29
31
|
export { default as TableWithCollapsible } from './_table_with_collapsible.jsx'
|
30
32
|
export { default as TableWithCollapsibleWithCustomContent } from './_table_with_collapsible_with_custom_content.jsx'
|
31
33
|
export { default as TableWithCollapsibleWithNestedTable } from './_table_with_collapsible_with_nested_table.jsx'
|
@@ -16,6 +16,8 @@ const TextInputMask = (props) => {
|
|
16
16
|
zipCode: '',
|
17
17
|
postalCode: '',
|
18
18
|
ssn: '',
|
19
|
+
creditCard: '',
|
20
|
+
cvv: ''
|
19
21
|
})
|
20
22
|
|
21
23
|
const handleOnChangeFormField = ({ target }) => {
|
@@ -57,6 +59,22 @@ const TextInputMask = (props) => {
|
|
57
59
|
value={formFields.ssn}
|
58
60
|
{...props}
|
59
61
|
/>
|
62
|
+
<TextInput
|
63
|
+
label="Credit Card"
|
64
|
+
mask="creditCard"
|
65
|
+
name="creditCard"
|
66
|
+
onChange={handleOnChangeFormField}
|
67
|
+
value={formFields.creditCard}
|
68
|
+
{...props}
|
69
|
+
/>
|
70
|
+
<TextInput
|
71
|
+
label="CVV"
|
72
|
+
mask="cvv"
|
73
|
+
name="cvv"
|
74
|
+
onChange={handleOnChangeFormField}
|
75
|
+
value={formFields.cvv}
|
76
|
+
{...props}
|
77
|
+
/>
|
60
78
|
|
61
79
|
<br />
|
62
80
|
<br />
|
@@ -6,7 +6,7 @@ type InputMask = {
|
|
6
6
|
}
|
7
7
|
|
8
8
|
type InputMaskDictionary = {
|
9
|
-
[key in 'currency' | 'zipCode' | 'postalCode' | 'ssn']: InputMask
|
9
|
+
[key in 'currency' | 'zipCode' | 'postalCode' | 'ssn' | 'creditCard' | 'cvv']: InputMask
|
10
10
|
}
|
11
11
|
|
12
12
|
const formatCurrencyDefaultValue = (value: string): string => {
|
@@ -58,6 +58,15 @@ const formatSSN = (value: string): string => {
|
|
58
58
|
.replace(/(\d{3})(?=\d)/, '$1-')
|
59
59
|
}
|
60
60
|
|
61
|
+
const formatCreditCard = (value: string): string => {
|
62
|
+
const cleaned = value.replace(/\D/g, '').slice(0, 16)
|
63
|
+
return cleaned.replace(/(\d{4})(?=\d)/g, '$1 ')
|
64
|
+
}
|
65
|
+
|
66
|
+
const formatCVV = (value: string): string => {
|
67
|
+
return value.replace(/\D/g, '').slice(0, 4)
|
68
|
+
}
|
69
|
+
|
61
70
|
export const INPUTMASKS: InputMaskDictionary = {
|
62
71
|
currency: {
|
63
72
|
format: formatCurrency,
|
@@ -84,4 +93,16 @@ export const INPUTMASKS: InputMaskDictionary = {
|
|
84
93
|
pattern: '\\d{3}-\\d{2}-\\d{4}',
|
85
94
|
placeholder: '123-45-6789',
|
86
95
|
},
|
96
|
+
creditCard: {
|
97
|
+
format: formatCreditCard,
|
98
|
+
formatDefaultValue: formatCreditCard,
|
99
|
+
pattern: '\\d{4} \\d{4} \\d{4} \\d{4}',
|
100
|
+
placeholder: '1234 5678 9012 3456',
|
101
|
+
},
|
102
|
+
cvv: {
|
103
|
+
format: formatCVV,
|
104
|
+
formatDefaultValue: formatCVV,
|
105
|
+
pattern: '\\d{3,4}',
|
106
|
+
placeholder: '123',
|
107
|
+
},
|
87
108
|
}
|
@@ -226,3 +226,83 @@ test('returns masked ssn value', () => {
|
|
226
226
|
|
227
227
|
expect(input.value).toBe('123-45-6789')
|
228
228
|
})
|
229
|
+
|
230
|
+
const TextInputCreditCardMask = (props) => {
|
231
|
+
const [creditCard, setValue] = useState('')
|
232
|
+
const handleOnChange = ({ target }) => {
|
233
|
+
setValue(target.value)
|
234
|
+
}
|
235
|
+
|
236
|
+
return (
|
237
|
+
<TextInput
|
238
|
+
mask="creditCard"
|
239
|
+
onChange={handleOnChange}
|
240
|
+
value={creditCard}
|
241
|
+
{...props}
|
242
|
+
/>
|
243
|
+
)
|
244
|
+
}
|
245
|
+
|
246
|
+
test('returns masked credit card value', () => {
|
247
|
+
render(
|
248
|
+
<TextInputCreditCardMask
|
249
|
+
data={{ testid: testId }}
|
250
|
+
/>
|
251
|
+
)
|
252
|
+
|
253
|
+
const kit = screen.getByTestId(testId)
|
254
|
+
|
255
|
+
const input = within(kit).getByRole('textbox')
|
256
|
+
|
257
|
+
fireEvent.change(input, { target: { value: '1234567890123456' } })
|
258
|
+
|
259
|
+
expect(input.value).toBe('1234 5678 9012 3456')
|
260
|
+
|
261
|
+
fireEvent.change(input, { target: { value: '1234' } })
|
262
|
+
|
263
|
+
expect(input.value).toBe('1234')
|
264
|
+
|
265
|
+
fireEvent.change(input, { target: { value: '' } })
|
266
|
+
|
267
|
+
expect(input.value).toBe('')
|
268
|
+
})
|
269
|
+
|
270
|
+
const TextInputCVVMask = (props) => {
|
271
|
+
const [cvv, setValue] = useState('')
|
272
|
+
const handleOnChange = ({ target }) => {
|
273
|
+
setValue(target.value)
|
274
|
+
}
|
275
|
+
|
276
|
+
return (
|
277
|
+
<TextInput
|
278
|
+
mask="cvv"
|
279
|
+
onChange={handleOnChange}
|
280
|
+
value={cvv}
|
281
|
+
{...props}
|
282
|
+
/>
|
283
|
+
)
|
284
|
+
}
|
285
|
+
|
286
|
+
test('returns masked CVV value', () => {
|
287
|
+
render(
|
288
|
+
<TextInputCVVMask
|
289
|
+
data={{ testid: testId }}
|
290
|
+
/>
|
291
|
+
)
|
292
|
+
|
293
|
+
const kit = screen.getByTestId(testId)
|
294
|
+
|
295
|
+
const input = within(kit).getByRole('textbox')
|
296
|
+
|
297
|
+
fireEvent.change(input, { target: { value: '1234' } })
|
298
|
+
|
299
|
+
expect(input.value).toBe('1234')
|
300
|
+
|
301
|
+
fireEvent.change(input, { target: { value: '123' } })
|
302
|
+
|
303
|
+
expect(input.value).toBe('123')
|
304
|
+
|
305
|
+
fireEvent.change(input, { target: { value: '' } })
|
306
|
+
|
307
|
+
expect(input.value).toBe('')
|
308
|
+
})
|