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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +1 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +11 -16
  4. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +1 -1
  5. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +8 -1
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_sort.html.erb +1 -3
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_subrow_headers.html.erb +1 -1
  8. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_subrow_headers.md +1 -1
  9. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers.html.erb +43 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_multiple.html.erb +58 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +3 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/index.js +185 -92
  13. data/app/pb_kits/playbook/pb_advanced_table/table_body.html.erb +1 -1
  14. data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +55 -8
  15. data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +17 -14
  16. data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +78 -0
  17. data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +4 -3
  18. data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +13 -2
  19. data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.html.erb +2 -6
  20. data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.rb +6 -4
  21. data/app/pb_kits/playbook/pb_button/button.html.erb +2 -3
  22. data/app/pb_kits/playbook/pb_card/_card.scss +21 -3
  23. data/app/pb_kits/playbook/pb_card/docs/_card_header.html.erb +18 -0
  24. data/app/pb_kits/playbook/pb_card/docs/_card_header.jsx +40 -0
  25. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +1 -6
  26. data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +1 -6
  27. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +7 -12
  28. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.html.erb +9 -14
  29. data/app/pb_kits/playbook/pb_dropdown/dropdown_option.html.erb +6 -11
  30. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +8 -14
  31. data/app/pb_kits/playbook/pb_file_upload/_file_upload.tsx +24 -15
  32. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_accept.jsx +3 -1
  33. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_description.jsx +4 -1
  34. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_max_size.jsx +1 -1
  35. data/app/pb_kits/playbook/pb_icon_circle/_icon_circle.scss +1 -13
  36. data/app/pb_kits/playbook/pb_section_separator/_section_separator.scss +64 -1
  37. data/app/pb_kits/playbook/pb_section_separator/_section_separator.tsx +3 -1
  38. data/app/pb_kits/playbook/pb_section_separator/docs/_section_separator_color.html.erb +10 -0
  39. data/app/pb_kits/playbook/pb_section_separator/docs/_section_separator_color.jsx +42 -0
  40. data/app/pb_kits/playbook/pb_section_separator/docs/_section_separator_color.md +3 -0
  41. data/app/pb_kits/playbook/pb_section_separator/docs/example.yml +2 -0
  42. data/app/pb_kits/playbook/pb_section_separator/docs/index.js +1 -0
  43. data/app/pb_kits/playbook/pb_section_separator/section_separator.rb +4 -1
  44. data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading.scss +2 -2
  45. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_description.md +3 -0
  46. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_border_radius.html.erb +9 -0
  47. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_border_radius_rails.md +1 -0
  48. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_color.html.erb +7 -0
  49. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_default.html.erb +1 -1
  50. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_filter.html.erb +119 -0
  51. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_filter.jsx +10 -2
  52. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.html.erb +15 -0
  53. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.jsx +6 -2
  54. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width_rails.md +3 -0
  55. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_layout.html.erb +3 -0
  56. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_user.html.erb +63 -0
  57. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_user.jsx +11 -3
  58. data/app/pb_kits/playbook/pb_skeleton_loading/docs/example.yml +7 -2
  59. data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.html.erb +8 -12
  60. data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.rb +48 -1
  61. data/app/pb_kits/playbook/pb_table/_table.tsx +8 -1
  62. data/app/pb_kits/playbook/pb_table/docs/example.yml +2 -0
  63. data/app/pb_kits/playbook/pb_table/docs/index.js +2 -0
  64. data/app/pb_kits/playbook/pb_table/styles/_scroll.scss +20 -0
  65. data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +8 -1
  66. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_mask.jsx +18 -0
  67. data/app/pb_kits/playbook/pb_text_input/inputMask.ts +45 -1
  68. data/app/pb_kits/playbook/pb_text_input/text_input.test.js +80 -0
  69. data/app/pb_kits/playbook/pb_timeline/_timeline.tsx +5 -5
  70. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_gap.html.erb +4 -5
  71. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_gap.jsx +4 -4
  72. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_gap.md +1 -1
  73. data/app/pb_kits/playbook/pb_timeline/timeline.rb +6 -6
  74. data/app/pb_kits/playbook/utilities/_gap.scss +29 -0
  75. data/app/pb_kits/playbook/utilities/globalPropNames.mjs +1 -0
  76. data/app/pb_kits/playbook/utilities/globalProps.ts +18 -9
  77. data/dist/chunks/_typeahead-C2iCBqxQ.js +36 -0
  78. data/dist/chunks/_weekday_stacked-CWnbnW7m.js +45 -0
  79. data/dist/chunks/vendor.js +1 -1
  80. data/dist/menu.yml +1 -1
  81. data/dist/playbook-doc.js +1 -1
  82. data/dist/playbook-rails-react-bindings.js +1 -1
  83. data/dist/playbook-rails.js +1 -1
  84. data/dist/playbook.css +1 -1
  85. data/lib/playbook/classnames.rb +1 -0
  86. data/lib/playbook/spacing.rb +21 -0
  87. data/lib/playbook/version.rb +1 -1
  88. metadata +21 -6
  89. data/dist/chunks/_typeahead-aym7Ky_O.js +0 -22
  90. data/dist/chunks/_weekday_stacked-BZj1pop-.js +0 -45
  91. /data/app/pb_kits/playbook/pb_skeleton_loading/docs/{_skeleton_loading_border_radius.md → _skeleton_loading_border_radius_react.md} +0 -0
  92. /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
- # - skeleton_loading_default: Default
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
- <!-- Go to pb_content_tag definition in kit_base.rb for usage information. Commented out options are default (showing the default shape), and each can be deleted if not customizing that param. -->
2
- <!-- If using nonstandard params please un-comment out and replace with your custom params. -->
3
- <%= pb_content_tag(
4
- # :div,
5
- # aria: object.aria,
6
- # class: object.classname,
7
- # data: object.data,
8
- # id: object.id,
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 < ::Playbook::KitBase
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
- stickyRightColumn.reverse().forEach((colId, index) => {
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={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
- gap?: 'xs' | 'sm' | 'md' | 'lg' | 'none',
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
- gap = 'none',
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 gapStyle = gap === 'none' ? '' : `gap_${gap}`
43
- const timelineCss = buildCss('pb_timeline_kit', `${orientation}`, dateStyle, gapStyle)
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", gap: "xs"}) do %>
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", gap: "sm"}) do %>
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", gap: "md"}) do %>
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", gap: "lg"}) do %>
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
- gap="xs"
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
- gap="sm"
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
- gap="md"
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
- gap="lg"
137
+ itemGap="lg"
138
138
  orientation="vertical"
139
139
  >
140
140
  <Timeline.Item
@@ -1 +1 @@
1
- Use the optional `gap` prop to render the timeline kit with adjusted spacing between nodes. The `gap` prop will only work when utilized with a vertical timeline. Horizontal timelines will show no difference in spacing.
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 :gap, type: Playbook::Props::Enum,
12
- values: %w[xs sm md lg none],
13
- default: "none"
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
- gap_class)
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 gap_class
29
- gap == "none" ? nil : "gap_#{gap}"
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
+ }
@@ -27,6 +27,7 @@ export default [
27
27
  "overflow",
28
28
  "order",
29
29
  "numberSpacing",
30
+ "gap",
30
31
  "maxWidth",
31
32
  "minWidth",
32
33
  "width",