playbook_ui 14.10.0.pre.alpha.PBNTR662stickyrightcolumnreact5160 → 14.10.0.pre.alpha.PLAY1750pbcontenttagkitbutton5308
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/_playbook.scss +1 -0
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +11 -16
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +1 -1
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +8 -1
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_sort.html.erb +1 -3
- 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/_advanced_table_column_headers.html.erb +43 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_multiple.html.erb +58 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +3 -0
- data/app/pb_kits/playbook/pb_advanced_table/index.js +185 -92
- data/app/pb_kits/playbook/pb_advanced_table/table_body.html.erb +1 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +55 -8
- data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +17 -14
- data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +78 -0
- data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +4 -3
- data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +13 -2
- data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.html.erb +2 -6
- data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.rb +6 -4
- data/app/pb_kits/playbook/pb_button/button.html.erb +2 -3
- data/app/pb_kits/playbook/pb_card/_card.scss +21 -3
- data/app/pb_kits/playbook/pb_card/docs/_card_header.html.erb +18 -0
- data/app/pb_kits/playbook/pb_card/docs/_card_header.jsx +40 -0
- data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +1 -6
- data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +1 -6
- data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +7 -12
- data/app/pb_kits/playbook/pb_dropdown/dropdown_container.html.erb +9 -14
- data/app/pb_kits/playbook/pb_dropdown/dropdown_option.html.erb +6 -11
- data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +8 -14
- 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_icon_circle/_icon_circle.scss +1 -13
- data/app/pb_kits/playbook/pb_section_separator/_section_separator.scss +64 -1
- data/app/pb_kits/playbook/pb_section_separator/_section_separator.tsx +3 -1
- data/app/pb_kits/playbook/pb_section_separator/docs/_section_separator_color.html.erb +10 -0
- data/app/pb_kits/playbook/pb_section_separator/docs/_section_separator_color.jsx +42 -0
- data/app/pb_kits/playbook/pb_section_separator/docs/_section_separator_color.md +3 -0
- data/app/pb_kits/playbook/pb_section_separator/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_section_separator/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_section_separator/section_separator.rb +4 -1
- data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading.scss +2 -2
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_description.md +3 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_border_radius.html.erb +9 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_border_radius_rails.md +1 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_color.html.erb +7 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_default.html.erb +1 -1
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_filter.html.erb +119 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_filter.jsx +10 -2
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.html.erb +15 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.jsx +6 -2
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width_rails.md +3 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_layout.html.erb +3 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_user.html.erb +63 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_user.jsx +11 -3
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/example.yml +7 -2
- data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.html.erb +8 -12
- data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.rb +48 -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_table/styles/_scroll.scss +20 -0
- data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +8 -1
- 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 +45 -1
- data/app/pb_kits/playbook/pb_text_input/text_input.test.js +80 -0
- 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/vendor.js +1 -1
- data/dist/menu.yml +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 +21 -6
- data/dist/chunks/_typeahead-aym7Ky_O.js +0 -22
- data/dist/chunks/_weekday_stacked-BZj1pop-.js +0 -45
- /data/app/pb_kits/playbook/pb_skeleton_loading/docs/{_skeleton_loading_border_radius.md → _skeleton_loading_border_radius_react.md} +0 -0
- /data/app/pb_kits/playbook/pb_skeleton_loading/docs/{_skeleton_loading_height_width.md → _skeleton_loading_height_width_react.md} +0 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
<%= pb_rails("button", props: { id: "toggle-user-button", margin_bottom: "md", text: "Show User", variant: "secondary" }) %>
|
2
|
+
|
3
|
+
<div id="skeleton-loading-user-content">
|
4
|
+
<%= pb_rails("flex", props: { align_items: "center" }) do %>
|
5
|
+
<%= pb_rails("skeleton_loading", props: { border_radius: "rounded", height: "38px", padding_right: "sm", width: "38px"}) %>
|
6
|
+
<%= pb_rails("skeleton_loading", props: { gap: "xxs", height: "18px", stack: 2, width: "161px"}) %>
|
7
|
+
<% end %>
|
8
|
+
<%= pb_rails("flex", props: { align_items: "start", padding_top: "md" }) do %>
|
9
|
+
<%= pb_rails("flex", props: { align_items: "center", flex_direction: "column" }) do %>
|
10
|
+
<%= pb_rails("skeleton_loading", props: { border_radius: "rounded", height: "100px", padding_bottom: "xs", width: "100px"}) %>
|
11
|
+
<%= pb_rails("skeleton_loading", props: { height: "32px", padding_bottom: "xxs", width: "144px"}) %>
|
12
|
+
<%= pb_rails("skeleton_loading", props: { height: "21px", width: "164px"}) %>
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div id="user-content" style="display: none;">
|
18
|
+
<div>
|
19
|
+
<%= pb_rails("user", props: {
|
20
|
+
name: "Anna Black",
|
21
|
+
title: "Remodeling Consultant",
|
22
|
+
orientation: "horizontal",
|
23
|
+
align: "left",
|
24
|
+
avatar_url: "https://randomuser.me/api/portraits/women/44.jpg"
|
25
|
+
}) %>
|
26
|
+
</div>
|
27
|
+
<div>
|
28
|
+
<%= pb_rails("flex", props: { align_items: "start", padding_top: "md" }) do %>
|
29
|
+
<%= pb_rails("user", props: {
|
30
|
+
name: "Anna Black",
|
31
|
+
title: "Remodeling Consultant",
|
32
|
+
orientation: "vertical",
|
33
|
+
align: "center",
|
34
|
+
size: "lg",
|
35
|
+
avatar_url: "https://randomuser.me/api/portraits/women/44.jpg"
|
36
|
+
}) %>
|
37
|
+
<% end %>
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<script>
|
42
|
+
document.addEventListener("DOMContentLoaded", function() {
|
43
|
+
const toggleButton = document.getElementById('toggle-user-button')
|
44
|
+
const skeletonUserContentDiv = document.getElementById('skeleton-loading-user-content')
|
45
|
+
const userContentDiv = document.getElementById('user-content')
|
46
|
+
|
47
|
+
let isLoading = true
|
48
|
+
|
49
|
+
toggleButton.addEventListener('click', function() {
|
50
|
+
isLoading = !isLoading
|
51
|
+
|
52
|
+
if (isLoading) {
|
53
|
+
skeletonUserContentDiv.style.display = 'block'
|
54
|
+
userContentDiv.style.display = 'none'
|
55
|
+
toggleButton.textContent = 'Show User'
|
56
|
+
} else {
|
57
|
+
skeletonUserContentDiv.style.display = 'none'
|
58
|
+
userContentDiv.style.display = 'block'
|
59
|
+
toggleButton.textContent = 'Show Skeleton Loading'
|
60
|
+
}
|
61
|
+
})
|
62
|
+
})
|
63
|
+
</script>
|
@@ -1,11 +1,19 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { Flex, SkeletonLoading, User } from "playbook-ui";
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { Button, Flex, SkeletonLoading, User } from "playbook-ui";
|
3
3
|
|
4
4
|
const SkeletonLoadingUser = (props) => {
|
5
|
-
const isLoading = true
|
5
|
+
const [isLoading, setIsLoading] = useState(true)
|
6
|
+
const toggleLoading = () => setIsLoading((prev) => !prev)
|
6
7
|
|
7
8
|
return (
|
8
9
|
<div>
|
10
|
+
<Button
|
11
|
+
marginBottom="md"
|
12
|
+
onClick={toggleLoading}
|
13
|
+
variant="secondary"
|
14
|
+
>
|
15
|
+
{isLoading ? "Show User" : "Show Skeleton Loading"}
|
16
|
+
</Button>
|
9
17
|
<div>
|
10
18
|
{isLoading ? (
|
11
19
|
<Flex alignItems="center">
|
@@ -1,8 +1,13 @@
|
|
1
1
|
examples:
|
2
2
|
|
3
3
|
rails:
|
4
|
-
|
5
|
-
|
4
|
+
- skeleton_loading_default: Default
|
5
|
+
- skeleton_loading_color: Color
|
6
|
+
- skeleton_loading_layout: Layout
|
7
|
+
- skeleton_loading_border_radius: Border Radius
|
8
|
+
- skeleton_loading_height_width: Height & Width
|
9
|
+
- skeleton_loading_user: User Component Example
|
10
|
+
- skeleton_loading_filter: Filter Component Example
|
6
11
|
|
7
12
|
react:
|
8
13
|
- skeleton_loading_default: Default
|
@@ -1,12 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# **combined_html_options
|
10
|
-
) do %>
|
11
|
-
<span>SKELETON_LOADING CONTENT</span>
|
12
|
-
<% end %>
|
1
|
+
<%= pb_content_tag do %>
|
2
|
+
<% stack.times do |index| %>
|
3
|
+
<div
|
4
|
+
class="<%= item_classname(index) %>"
|
5
|
+
style="<%= item_inline_styles %>"
|
6
|
+
></div>
|
7
|
+
<% end %>
|
8
|
+
<% end %>
|
@@ -2,7 +2,54 @@
|
|
2
2
|
|
3
3
|
module Playbook
|
4
4
|
module PbSkeletonLoading
|
5
|
-
class SkeletonLoading <
|
5
|
+
class SkeletonLoading < Playbook::KitBase
|
6
|
+
prop :height, type: Playbook::Props::String,
|
7
|
+
default: "16px"
|
8
|
+
prop :width, type: Playbook::Props::String,
|
9
|
+
default: "100%"
|
10
|
+
prop :border_radius, type: Playbook::Props::Enum,
|
11
|
+
values: %w[none xs sm md lg xl rounded],
|
12
|
+
default: "sm"
|
13
|
+
prop :gap, type: Playbook::Props::Enum,
|
14
|
+
values: %w[none xxs xs sm md lg xl xxl],
|
15
|
+
default: "xxs"
|
16
|
+
prop :stack, type: Playbook::Props::Number,
|
17
|
+
default: 1
|
18
|
+
prop :color, type: Playbook::Props::Enum,
|
19
|
+
values: %w[default white],
|
20
|
+
default: "default"
|
21
|
+
prop :dark, type: Playbook::Props::Boolean,
|
22
|
+
default: false
|
23
|
+
|
24
|
+
def classname
|
25
|
+
generate_classname("pb_skeleton_loading")
|
26
|
+
end
|
27
|
+
|
28
|
+
def global_inline_props
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
|
32
|
+
def item_classname(index = nil)
|
33
|
+
classes = [
|
34
|
+
"pb_skeleton_loading_item",
|
35
|
+
"border_radius_#{border_radius}",
|
36
|
+
"color_#{color}",
|
37
|
+
("dark" if dark),
|
38
|
+
gap_class(index),
|
39
|
+
]
|
40
|
+
classes.compact.join(" ")
|
41
|
+
end
|
42
|
+
|
43
|
+
def item_inline_styles
|
44
|
+
styles = []
|
45
|
+
styles << "height: #{height}"
|
46
|
+
styles << "width: #{width}"
|
47
|
+
styles.join("; ")
|
48
|
+
end
|
49
|
+
|
50
|
+
def gap_class(index = nil)
|
51
|
+
stack > 1 && index.to_i.positive? && gap != "none" ? "gap_#{gap}" : nil
|
52
|
+
end
|
6
53
|
end
|
7
54
|
end
|
8
55
|
end
|
@@ -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'
|
@@ -4,6 +4,26 @@
|
|
4
4
|
display: block;
|
5
5
|
overflow-x: scroll;
|
6
6
|
|
7
|
+
// hides duplicate scroll bar for those that see two (byproduct of repeated table-responsive-scroll class
|
8
|
+
// hides outer scroll bar in chrome and safari
|
9
|
+
&:not(.table-responsive-scroll .table-responsive-scroll) {
|
10
|
+
&::-webkit-scrollbar {
|
11
|
+
height: 0px;
|
12
|
+
}
|
13
|
+
}
|
14
|
+
// hides outer scroll bar in firefox
|
15
|
+
@supports (scrollbar-width: none) {
|
16
|
+
scrollbar-width: none;
|
17
|
+
.pb_table {
|
18
|
+
overflow-x: auto;
|
19
|
+
scrollbar-width: auto;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
// ensures visible inner scroll bar can scroll
|
23
|
+
.pb_table {
|
24
|
+
overflow-x: auto;
|
25
|
+
}
|
26
|
+
|
7
27
|
// Responsive Styles
|
8
28
|
@media (max-width: 1600px) {
|
9
29
|
&[class*="table-responsive-scroll"] {
|
@@ -123,6 +123,13 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
123
123
|
|
124
124
|
const childInput = children ? children.type === "input" : undefined
|
125
125
|
|
126
|
+
let formattedValue;
|
127
|
+
if (isMaskedInput && value) {
|
128
|
+
formattedValue = INPUTMASKS[mask].formatDefaultValue(value.toString())
|
129
|
+
} else {
|
130
|
+
formattedValue = value
|
131
|
+
}
|
132
|
+
|
126
133
|
const textInput = (
|
127
134
|
childInput ? React.cloneElement(children, { className: "text_input" }) :
|
128
135
|
(<input
|
@@ -138,7 +145,7 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
138
145
|
ref={ref}
|
139
146
|
required={required}
|
140
147
|
type={type}
|
141
|
-
value={
|
148
|
+
value={formattedValue}
|
142
149
|
/>)
|
143
150
|
)
|
144
151
|
|
@@ -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 />
|
@@ -1,11 +1,30 @@
|
|
1
1
|
type InputMask = {
|
2
2
|
format: (value: string) => string
|
3
|
+
formatDefaultValue: (value: string) => string
|
3
4
|
pattern: string
|
4
5
|
placeholder: string
|
5
6
|
}
|
6
7
|
|
7
8
|
type InputMaskDictionary = {
|
8
|
-
[key in 'currency' | 'zipCode' | 'postalCode' | 'ssn']: InputMask
|
9
|
+
[key in 'currency' | 'zipCode' | 'postalCode' | 'ssn' | 'creditCard' | 'cvv']: InputMask
|
10
|
+
}
|
11
|
+
|
12
|
+
const formatCurrencyDefaultValue = (value: string): string => {
|
13
|
+
// Remove non-numeric characters except for the decimal point
|
14
|
+
const numericValue = value.replace(/[^0-9.]/g, '')
|
15
|
+
|
16
|
+
if (!numericValue) return ''
|
17
|
+
|
18
|
+
// Parse the numeric value as a float to handle decimals
|
19
|
+
const dollars = parseFloat(numericValue)
|
20
|
+
if (isNaN(dollars) || dollars === 0) return ''
|
21
|
+
|
22
|
+
// Format as currency
|
23
|
+
return new Intl.NumberFormat('en-US', {
|
24
|
+
style: 'currency',
|
25
|
+
currency: 'USD',
|
26
|
+
maximumFractionDigits: 2,
|
27
|
+
}).format(dollars)
|
9
28
|
}
|
10
29
|
|
11
30
|
const formatCurrency = (value: string): string => {
|
@@ -39,26 +58,51 @@ const formatSSN = (value: string): string => {
|
|
39
58
|
.replace(/(\d{3})(?=\d)/, '$1-')
|
40
59
|
}
|
41
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
|
+
|
42
70
|
export const INPUTMASKS: InputMaskDictionary = {
|
43
71
|
currency: {
|
44
72
|
format: formatCurrency,
|
73
|
+
formatDefaultValue: formatCurrencyDefaultValue,
|
45
74
|
// eslint-disable-next-line no-useless-escape
|
46
75
|
pattern: '^\\$\\d{1,3}(?:,\\d{3})*(?:\\.\\d{2})?$',
|
47
76
|
placeholder: '$0.00',
|
48
77
|
},
|
49
78
|
zipCode: {
|
50
79
|
format: formatBasicPostal,
|
80
|
+
formatDefaultValue: formatBasicPostal,
|
51
81
|
pattern: '\\d{5}',
|
52
82
|
placeholder: '12345',
|
53
83
|
},
|
54
84
|
postalCode: {
|
55
85
|
format: formatExtendedPostal,
|
86
|
+
formatDefaultValue: formatExtendedPostal,
|
56
87
|
pattern: '\\d{5}-\\d{4}',
|
57
88
|
placeholder: '12345-6789',
|
58
89
|
},
|
59
90
|
ssn: {
|
60
91
|
format: formatSSN,
|
92
|
+
formatDefaultValue: formatSSN,
|
61
93
|
pattern: '\\d{3}-\\d{2}-\\d{4}',
|
62
94
|
placeholder: '123-45-6789',
|
63
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
|
+
},
|
64
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
|
+
})
|
@@ -20,7 +20,7 @@ type TimelineProps = {
|
|
20
20
|
id?: string,
|
21
21
|
orientation?: string,
|
22
22
|
showDate?: boolean,
|
23
|
-
|
23
|
+
itemGap?: 'xs' | 'sm' | 'md' | 'lg' | 'none',
|
24
24
|
} & GlobalProps
|
25
25
|
|
26
26
|
const Timeline = ({
|
@@ -32,15 +32,15 @@ const Timeline = ({
|
|
32
32
|
id,
|
33
33
|
orientation = 'horizontal',
|
34
34
|
showDate = false,
|
35
|
-
|
35
|
+
itemGap = 'none',
|
36
36
|
...props
|
37
37
|
}: TimelineProps): React.ReactElement => {
|
38
38
|
const ariaProps = buildAriaProps(aria)
|
39
39
|
const dataProps = buildDataProps(data)
|
40
40
|
const htmlProps = buildHtmlProps(htmlOptions)
|
41
41
|
const dateStyle = showDate === true ? '_with_date' : ''
|
42
|
-
const
|
43
|
-
const timelineCss = buildCss('pb_timeline_kit', `${orientation}`, dateStyle,
|
42
|
+
const itemGapStyle = itemGap === 'none' ? '' : `gap_${itemGap}`
|
43
|
+
const timelineCss = buildCss('pb_timeline_kit', `${orientation}`, dateStyle, itemGapStyle)
|
44
44
|
|
45
45
|
return (
|
46
46
|
<div
|
@@ -60,4 +60,4 @@ Timeline.Step = TimelineStep
|
|
60
60
|
Timeline.Label = TimelineLabel
|
61
61
|
Timeline.Detail = TimelineDetail
|
62
62
|
|
63
|
-
export default Timeline
|
63
|
+
export default Timeline
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<%= pb_rails("flex", props: { justify: "evenly" }) do %>
|
2
2
|
<%= pb_rails("flex/flex_item") do %>
|
3
|
-
<%= pb_rails("timeline", props: {orientation: "vertical",
|
3
|
+
<%= pb_rails("timeline", props: {orientation: "vertical", item_gap: "xs"}) do %>
|
4
4
|
<%= pb_rails("timeline/item", props: {icon: "user", icon_color: "royal"}) do %>
|
5
5
|
<%= pb_rails("body", props: {
|
6
6
|
text: "Conversation started",
|
@@ -23,7 +23,7 @@
|
|
23
23
|
<% end %>
|
24
24
|
|
25
25
|
<%= pb_rails("flex/flex_item") do %>
|
26
|
-
<%= pb_rails("timeline", props: {orientation: "vertical",
|
26
|
+
<%= pb_rails("timeline", props: {orientation: "vertical", item_gap: "sm"}) do %>
|
27
27
|
<%= pb_rails("timeline/item", props: {icon: "user", icon_color: "royal"}) do %>
|
28
28
|
<%= pb_rails("body", props: {
|
29
29
|
text: "Conversation started",
|
@@ -46,7 +46,7 @@
|
|
46
46
|
<% end %>
|
47
47
|
|
48
48
|
<%= pb_rails("flex/flex_item") do %>
|
49
|
-
<%= pb_rails("timeline", props: {orientation: "vertical",
|
49
|
+
<%= pb_rails("timeline", props: {orientation: "vertical", item_gap: "md"}) do %>
|
50
50
|
<%= pb_rails("timeline/item", props: {icon: "user", icon_color: "royal"}) do %>
|
51
51
|
<%= pb_rails("body", props: {
|
52
52
|
text: "Conversation started",
|
@@ -69,7 +69,7 @@
|
|
69
69
|
<% end %>
|
70
70
|
|
71
71
|
<%= pb_rails("flex/flex_item") do %>
|
72
|
-
<%= pb_rails("timeline", props: {orientation: "vertical",
|
72
|
+
<%= pb_rails("timeline", props: {orientation: "vertical", item_gap: "lg"}) do %>
|
73
73
|
<%= pb_rails("timeline/item", props: {icon: "user", icon_color: "royal"}) do %>
|
74
74
|
<%= pb_rails("body", props: {
|
75
75
|
text: "Conversation started",
|
@@ -91,4 +91,3 @@
|
|
91
91
|
<% end %>
|
92
92
|
<% end %>
|
93
93
|
<% end %>
|
94
|
-
|
@@ -10,7 +10,7 @@ const TimelineWithGap = (props) => (
|
|
10
10
|
<Flex justify="evenly">
|
11
11
|
<FlexItem>
|
12
12
|
<Timeline
|
13
|
-
|
13
|
+
itemGap="xs"
|
14
14
|
orientation="vertical"
|
15
15
|
>
|
16
16
|
<Timeline.Item
|
@@ -51,7 +51,7 @@ const TimelineWithGap = (props) => (
|
|
51
51
|
</FlexItem>
|
52
52
|
<FlexItem>
|
53
53
|
<Timeline
|
54
|
-
|
54
|
+
itemGap="sm"
|
55
55
|
orientation="vertical"
|
56
56
|
>
|
57
57
|
<Timeline.Item
|
@@ -92,7 +92,7 @@ const TimelineWithGap = (props) => (
|
|
92
92
|
</FlexItem>
|
93
93
|
<FlexItem>
|
94
94
|
<Timeline
|
95
|
-
|
95
|
+
itemGap="md"
|
96
96
|
orientation="vertical"
|
97
97
|
>
|
98
98
|
<Timeline.Item
|
@@ -134,7 +134,7 @@ const TimelineWithGap = (props) => (
|
|
134
134
|
</FlexItem>
|
135
135
|
<FlexItem>
|
136
136
|
<Timeline
|
137
|
-
|
137
|
+
itemGap="lg"
|
138
138
|
orientation="vertical"
|
139
139
|
>
|
140
140
|
<Timeline.Item
|
@@ -1 +1 @@
|
|
1
|
-
Use the optional `
|
1
|
+
Use the optional `itemGap` prop to render the timeline kit with adjusted spacing between nodes. The `itemGap` prop will only work when utilized with a vertical timeline. Horizontal timelines will show no difference in spacing.
|
@@ -8,15 +8,15 @@ module Playbook
|
|
8
8
|
default: "horizontal"
|
9
9
|
prop :show_date, type: Playbook::Props::Boolean,
|
10
10
|
default: false
|
11
|
-
prop :
|
12
|
-
|
13
|
-
|
11
|
+
prop :item_gap, type: Playbook::Props::Enum,
|
12
|
+
values: %w[xs sm md lg none],
|
13
|
+
default: "none"
|
14
14
|
|
15
15
|
def classname
|
16
16
|
generate_classname("pb_timeline_kit",
|
17
17
|
orientation,
|
18
18
|
date_class,
|
19
|
-
|
19
|
+
item_gap_class)
|
20
20
|
end
|
21
21
|
|
22
22
|
private
|
@@ -25,8 +25,8 @@ module Playbook
|
|
25
25
|
show_date ? "with_date" : nil
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
29
|
-
|
28
|
+
def item_gap_class
|
29
|
+
item_gap == "none" ? nil : "gap_#{item_gap}"
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
@import "spacing";
|
2
|
+
|
3
|
+
.gap_none {
|
4
|
+
gap: 0;
|
5
|
+
}
|
6
|
+
|
7
|
+
.gap_xxs {
|
8
|
+
gap: $space_xxs;
|
9
|
+
}
|
10
|
+
|
11
|
+
.gap_xs {
|
12
|
+
gap: $space_xs;
|
13
|
+
}
|
14
|
+
|
15
|
+
.gap_sm {
|
16
|
+
gap: $space_sm;
|
17
|
+
}
|
18
|
+
|
19
|
+
.gap_md {
|
20
|
+
gap: $space_md;
|
21
|
+
}
|
22
|
+
|
23
|
+
.gap_lg {
|
24
|
+
gap: $space_lg;
|
25
|
+
}
|
26
|
+
|
27
|
+
.gap_xl {
|
28
|
+
gap: $space_xl;
|
29
|
+
}
|