playbook_ui 14.8.0.pre.rc.8 → 14.9.0.pre.alpha.PBNTR686advancedtablepaginationpoc4747

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +2 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +61 -17
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta.html.erb +1 -1
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_sort.html.erb +1 -1
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_custom_cell_rails.html.erb +53 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_custom_cell_rails.md +5 -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_row.html.erb +3 -1
  16. data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +15 -0
  17. data/app/pb_kits/playbook/pb_background/_background.tsx +8 -2
  18. data/app/pb_kits/playbook/pb_button/_button.scss +6 -0
  19. data/app/pb_kits/playbook/pb_button/_button.tsx +1 -3
  20. data/app/pb_kits/playbook/pb_button/_button_mixins.scss +15 -0
  21. data/app/pb_kits/playbook/pb_button/button.rb +1 -1
  22. data/app/pb_kits/playbook/pb_button/docs/_button_default.html.erb +1 -0
  23. data/app/pb_kits/playbook/pb_button/docs/_button_default.jsx +8 -0
  24. data/app/pb_kits/playbook/pb_button/docs/_button_default.md +1 -1
  25. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +2 -2
  26. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +0 -4
  27. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_indeterminate.html.erb +84 -7
  28. data/app/pb_kits/playbook/pb_collapsible/_collapsible.tsx +3 -1
  29. data/app/pb_kits/playbook/pb_currency/_currency.tsx +7 -3
  30. data/app/pb_kits/playbook/pb_currency/currency.html.erb +2 -2
  31. data/app/pb_kits/playbook/pb_currency/currency.rb +17 -1
  32. data/app/pb_kits/playbook/pb_currency/currency.test.js +40 -3
  33. data/app/pb_kits/playbook/pb_currency/docs/_currency_negative.html.erb +4 -0
  34. data/app/pb_kits/playbook/pb_currency/docs/_currency_negative.jsx +16 -0
  35. data/app/pb_kits/playbook/pb_currency/docs/example.yml +2 -0
  36. data/app/pb_kits/playbook/pb_currency/docs/index.js +1 -0
  37. data/app/pb_kits/playbook/pb_drawer/_drawer.scss +1 -0
  38. data/app/pb_kits/playbook/pb_drawer/_drawer.tsx +159 -45
  39. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_menu.jsx +31 -0
  40. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_menu.md +6 -0
  41. data/app/pb_kits/playbook/pb_drawer/docs/example.yml +1 -1
  42. data/app/pb_kits/playbook/pb_drawer/docs/index.js +1 -0
  43. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +1 -0
  44. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.html.erb +0 -1
  45. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.rb +0 -4
  46. data/app/pb_kits/playbook/pb_dropdown/utilities/subComponentHelper.tsx +13 -2
  47. data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +2 -2
  48. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.html.erb +1 -1
  49. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +63 -12
  50. data/app/pb_kits/playbook/pb_image/_image.tsx +3 -1
  51. data/app/pb_kits/playbook/pb_layout/_layout.tsx +6 -3
  52. data/app/pb_kits/playbook/pb_overlay/_overlay.tsx +3 -1
  53. data/app/pb_kits/playbook/pb_pagination/_pagination.tsx +2 -2
  54. data/app/pb_kits/playbook/pb_progress_simple/_progress_simple.tsx +5 -3
  55. data/app/pb_kits/playbook/pb_progress_simple/progress_simple.html.erb +3 -2
  56. data/app/pb_kits/playbook/pb_progress_simple/progress_simple.rb +5 -0
  57. data/app/pb_kits/playbook/pb_section_separator/_section_separator.tsx +3 -1
  58. data/app/pb_kits/playbook/pb_selectable_card/docs/_selectable_card_default.html.erb +2 -1
  59. data/app/pb_kits/playbook/pb_table/_table.tsx +109 -25
  60. data/app/pb_kits/playbook/pb_table/docs/_table_sticky_left_columns.jsx +87 -0
  61. data/app/pb_kits/playbook/pb_table/docs/_table_sticky_left_columns_react.md +2 -0
  62. data/app/pb_kits/playbook/pb_table/docs/index.js +1 -0
  63. data/app/pb_kits/playbook/pb_table/styles/_all.scss +2 -0
  64. data/app/pb_kits/playbook/pb_table/styles/_scroll.scss +4 -0
  65. data/app/pb_kits/playbook/pb_table/styles/_sticky_columns.scss +18 -0
  66. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.html.erb +47 -0
  67. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.jsx +59 -0
  68. data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +3 -0
  69. data/app/pb_kits/playbook/pb_typeahead/index.ts +29 -3
  70. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +5 -2
  71. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +4 -0
  72. data/app/pb_kits/playbook/tokens/_height.scss +19 -0
  73. data/app/pb_kits/playbook/tokens/exports/_height.module.scss +37 -0
  74. data/app/pb_kits/playbook/utilities/_height.scss +33 -0
  75. data/app/pb_kits/playbook/utilities/_max_width.scss +29 -9
  76. data/app/pb_kits/playbook/utilities/_min_width.scss +6 -2
  77. data/app/pb_kits/playbook/utilities/_width.scss +45 -0
  78. data/app/pb_kits/playbook/utilities/globalPropNames.mjs +1 -1
  79. data/app/pb_kits/playbook/utilities/globalProps.ts +37 -4
  80. data/dist/chunks/_typeahead-B8fkIeXA.js +22 -0
  81. data/dist/chunks/_weekday_stacked-DSEuqOLN.js +45 -0
  82. data/dist/chunks/{lib-BC6ESsxG.js → lib-SyD3buPZ.js} +1 -1
  83. data/dist/chunks/{pb_form_validation-B_Z9rEbg.js → pb_form_validation-Dt8UJgrJ.js} +1 -1
  84. data/dist/chunks/vendor.js +1 -1
  85. data/dist/menu.yml +321 -0
  86. data/dist/playbook-doc.js +1 -1
  87. data/dist/playbook-rails-react-bindings.js +1 -1
  88. data/dist/playbook-rails.js +1 -1
  89. data/dist/playbook.css +1 -1
  90. data/lib/playbook/classnames.rb +4 -0
  91. data/lib/playbook/forms/builder/typeahead_field.rb +13 -0
  92. data/lib/playbook/height.rb +29 -0
  93. data/lib/playbook/kit_base.rb +16 -1
  94. data/lib/playbook/max_height.rb +29 -0
  95. data/lib/playbook/min_height.rb +29 -0
  96. data/lib/playbook/spacing.rb +21 -0
  97. data/lib/playbook/version.rb +2 -2
  98. metadata +28 -6
  99. data/dist/chunks/_typeahead-D0PihN_3.js +0 -22
  100. data/dist/chunks/_weekday_stacked-uMIX8f-A.js +0 -45
@@ -3,6 +3,7 @@ examples:
3
3
  - advanced_table_beta: Default (Required Props)
4
4
  - advanced_table_beta_subrow_headers: SubRow Headers
5
5
  - advanced_table_beta_sort: Enable Sorting
6
+ - advanced_table_custom_cell_rails: Custom Components for Cells
6
7
 
7
8
  react:
8
9
  - advanced_table_default: Default (Required Props)
@@ -17,3 +18,5 @@ examples:
17
18
  - advanced_table_inline_row_loading: Inline Row Loading
18
19
  - advanced_table_responsive: Responsive Tables
19
20
  - advanced_table_custom_cell: Custom Components for Cells
21
+ - advanced_table_pagination: Pagination
22
+ - advanced_table_pagination_with_props: Pagination Props
@@ -10,3 +10,5 @@ export { default as AdvancedTableTableProps } from './_advanced_table_table_prop
10
10
  export { default as AdvancedTableInlineRowLoading } from './_advanced_table_inline_row_loading.jsx'
11
11
  export { default as AdvancedTableResponsive } from './_advanced_table_responsive.jsx'
12
12
  export { default as AdvancedTableCustomCell } from './_advanced_table_custom_cell.jsx'
13
+ export { default as AdvancedTablePagination } from './_advanced_table_pagination.jsx'
14
+ export { default as AdvancedTablePaginationWithProps } from './_advanced_table_pagination_with_props.jsx'
@@ -11,7 +11,9 @@
11
11
  </button>
12
12
  <% end %>
13
13
  <%= pb_rails("flex/flex_item", props:{padding_left: index.zero? && object.row[:children].present? ? "none" : "xs"}) do %>
14
- <% if index.zero? %>
14
+ <% if column[:custom_renderer].present? %>
15
+ <%= raw(column[:custom_renderer].call(object.row, custom_renderer_value(column, index))) %>
16
+ <% elsif index.zero? %>
15
17
  <% if object.depth.zero? %>
16
18
  <%= object.row[column[:accessor].to_sym] %>
17
19
  <% else %>
@@ -26,6 +26,21 @@ module Playbook
26
26
 
27
27
  private
28
28
 
29
+ def custom_renderer_value(column, index)
30
+ if index.zero?
31
+ if depth.zero?
32
+ row[column[:accessor].to_sym]
33
+ else
34
+ depth_accessors.each_with_index do |item, accessor_index|
35
+ key = item.to_sym
36
+ return row[key] if depth - 1 == accessor_index
37
+ end
38
+ end
39
+ else
40
+ row[column[:accessor].to_sym]
41
+ end
42
+ end
43
+
29
44
  def subrow_depth_classname
30
45
  depth.positive? ? "depth-sub-row-#{depth}" : ""
31
46
  end
@@ -1,7 +1,7 @@
1
1
  import React, { useEffect, useState } from 'react'
2
2
  import classnames from 'classnames'
3
3
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
4
- import { GlobalProps, globalProps } from '../utilities/globalProps'
4
+ import { GlobalProps, globalProps, globalInlineProps } from '../utilities/globalProps'
5
5
 
6
6
  type BackgroundColors = 'gradient' |
7
7
  'dark' |'light' | 'white' | 'success' | 'warning' | 'error' | 'info' | 'neutral' | 'primary' | 'shadow' |
@@ -145,6 +145,12 @@ const Background = (props: BackgroundProps): React.ReactElement => {
145
145
  } : {})
146
146
  };
147
147
 
148
+ const dynamicInlineProps = globalInlineProps(props);
149
+ const combinedStyles = {
150
+ ...backgroundStyle,
151
+ ...dynamicInlineProps
152
+ };
153
+
148
154
  const Tag: React.ReactElement | any = `${tag}`;
149
155
  const ariaProps = buildAriaProps(aria);
150
156
  const dataProps = buildDataProps(data);
@@ -158,7 +164,7 @@ const Background = (props: BackgroundProps): React.ReactElement => {
158
164
  alt={alt}
159
165
  className={classes}
160
166
  id={id}
161
- style={backgroundStyle}
167
+ style={combinedStyles}
162
168
  >
163
169
  {children}
164
170
  </Tag>
@@ -77,6 +77,12 @@ $pb_button_sizes: (
77
77
  &[class*=_loading] {
78
78
  @include pb_button_loading(true);
79
79
  }
80
+
81
+ // Danger ===================
82
+ &[class*=_danger] {
83
+ @include pb_button_danger;
84
+ }
85
+
80
86
  // Dark Variants =============
81
87
  &.dark {
82
88
  &[class*=_primary] {
@@ -37,7 +37,7 @@ type ButtonPropTypes = {
37
37
  type?: 'inline' | null,
38
38
  htmlType?: 'submit' | 'reset' | 'button' | undefined,
39
39
  value?: string | null,
40
- variant?: 'primary' | 'secondary' | 'link'| 'reaction',
40
+ variant?: 'primary' | 'secondary' | 'link'| 'danger' | 'reaction',
41
41
  wrapperClass?: string,
42
42
  } & GlobalProps
43
43
 
@@ -206,8 +206,6 @@ const Button = (props: ButtonPropTypes): React.ReactElement => {
206
206
  <Icon icon={icon ? icon : "face-smile-plus"} />
207
207
  )
208
208
  }
209
-
210
-
211
209
  </button>
212
210
  );
213
211
  } else {
@@ -167,6 +167,21 @@ $pb_button_border_width: 0px;
167
167
  }
168
168
  }
169
169
 
170
+ // Danger ======================
171
+ @mixin pb_button_danger {
172
+ @include pb_button_variant($error, $text_dk_default, transparent);
173
+
174
+ @media (hover:hover) {
175
+ &:hover {
176
+ @include pb_button_hover($bg: darken($error, $pb_button_hover_darken));
177
+ }
178
+ &:active {
179
+ transition: none;
180
+ @include pb_button_variant($error);
181
+ }
182
+ }
183
+ }
184
+
170
185
  // Dark Primary =================
171
186
  @mixin pb_button_primary_dark{
172
187
  @include pb_button_variant($primary_action);
@@ -16,7 +16,7 @@ module Playbook
16
16
  prop :new_window, type: Playbook::Props::Boolean,
17
17
  default: false
18
18
  prop :variant, type: Playbook::Props::Enum,
19
- values: %w[primary secondary link reaction],
19
+ values: %w[primary secondary link danger reaction],
20
20
  default: "primary"
21
21
  prop :count, type: Playbook::Props::Number
22
22
  prop :highlight, type: Playbook::Props::Boolean,
@@ -2,4 +2,5 @@
2
2
  <%= pb_rails("button", props: { text: "Button Secondary", variant: "secondary", margin_right: "lg" }) %>
3
3
  <%= pb_rails("button", props: { text: "Button Link", variant: "link", margin_right: "lg" }) %>
4
4
  <%= pb_rails("button", props: { text: "Button Disabled", disabled: true, margin_right: "lg" }) %>
5
+ <%= pb_rails("button", props: { text: "Button Danger", variant: "danger", margin_right: "lg" }) %>
5
6
 
@@ -34,6 +34,14 @@ const ButtonDefault = (props) => (
34
34
  text='Button Disabled'
35
35
  {...props}
36
36
  />
37
+ <Button
38
+ marginRight='lg'
39
+ onClick={() => alert("button clicked!")}
40
+ tabIndex={0}
41
+ text='Button Danger'
42
+ variant='danger'
43
+ {...props}
44
+ />
37
45
  </div>
38
46
  )
39
47
 
@@ -1 +1 @@
1
- The primary button is used for the most important button on the page. Secondary buttons can be used for actions that are less important. Button links can be helpful for buttons that do not need a lot of attention drawn to them. Disabled buttons are used when you don't want the user to click the button.
1
+ The primary button is used for the most important button on the page. Secondary buttons can be used for actions that are less important. Button links can be helpful for buttons that do not need a lot of attention drawn to them. Disabled buttons are used when you don't want the user to click the button. Danger buttons are used to indicate destructive actions such as deleting.
@@ -6,12 +6,12 @@
6
6
  ) do %>
7
7
  <%= content.presence || object.input %>
8
8
  <% if object.indeterminate %>
9
- <span class="pb_checkbox_indeterminate">
9
+ <span data-pb-checkbox-icon-span="true" class="pb_checkbox_indeterminate">
10
10
  <%= pb_rails("icon", props: { icon: "minus", classname: "indeterminate_icon", fixed_width: true}) %>
11
11
  <%= pb_rails("icon", props: { icon: "check", classname: "check_icon hidden", fixed_width: true}) %>
12
12
  </span>
13
13
  <% else %>
14
- <span class="pb_checkbox_checkmark">
14
+ <span data-pb-checkbox-icon-span="true" class="pb_checkbox_checkmark">
15
15
  <%= pb_rails("icon", props: { icon: "check", classname: "check_icon", fixed_width: true}) %>
16
16
  <%= pb_rails("icon", props: { icon: "minus", classname: "indeterminate_icon hidden", fixed_width: true}) %>
17
17
  </span>
@@ -18,10 +18,6 @@ module Playbook
18
18
  prop :form_spacing, type: Playbook::Props::Boolean,
19
19
  default: false
20
20
 
21
- def checked_html
22
- checked ? "checked='true'" : nil
23
- end
24
-
25
21
  def classname
26
22
  generate_classname("pb_checkbox_kit", checked_class) + indeterminate_class + error_class
27
23
  end
@@ -1,7 +1,84 @@
1
- <%= pb_rails("checkbox" , props: {
2
- text: "Select ",
3
- value: "checkbox-value",
4
- name: "main",
5
- indeterminate: true,
6
- id: "test-indeterminate-js"
7
- }) %>
1
+ <% checkboxes = [
2
+ { name: 'Coffee', id: 'coffee', checked: false },
3
+ { name: 'Ice Cream', id: 'ice-cream', checked: false },
4
+ { name: 'Chocolate', id: 'chocolate', checked: true }
5
+ ] %>
6
+
7
+ <%= pb_rails("table", props: { container: false, size: "md" }) do %>
8
+ <thead>
9
+ <tr>
10
+ <th>
11
+ <%= pb_rails("checkbox", props: {
12
+ checked: true,
13
+ text: "Uncheck All",
14
+ value: "checkbox-value",
15
+ name: "main-checkbox",
16
+ indeterminate: true,
17
+ id: "indeterminate-checkbox"
18
+ }) %>
19
+ </th>
20
+ </tr>
21
+ </thead>
22
+
23
+ <tbody>
24
+ <% checkboxes.each do |checkbox| %>
25
+ <tr>
26
+ <td>
27
+ <%= pb_rails("checkbox", props: {
28
+ checked: checkbox[:checked],
29
+ text: checkbox[:name],
30
+ value: checkbox[:id],
31
+ name: "#{checkbox[:id]}-indeterminate-checkbox",
32
+ id: "#{checkbox[:id]}-indeterminate-checkbox",
33
+ }) %>
34
+ </td>
35
+ </tr>
36
+ <% end %>
37
+ </tbody>
38
+ <% end %>
39
+
40
+ <script>
41
+ document.addEventListener('DOMContentLoaded', function() {
42
+ const mainCheckboxWrapper = document.getElementById('indeterminate-checkbox');
43
+ const mainCheckbox = document.getElementsByName("main-checkbox")[0];
44
+ const childCheckboxes = document.querySelectorAll('input[type="checkbox"][id$="indeterminate-checkbox"]');
45
+
46
+ const updateMainCheckbox = () => {
47
+ // Count the number of checked child checkboxes
48
+ const checkedCount = Array.from(childCheckboxes).filter(cb => cb.checked).length;
49
+ // Determine if the main checkbox should be in an indeterminate state
50
+ const indeterminate = checkedCount > 0 && checkedCount < childCheckboxes.length;
51
+
52
+ // Set the main checkbox states
53
+ mainCheckbox.indeterminate = indeterminate;
54
+ mainCheckbox.checked = checkedCount > 0;
55
+
56
+ // Determine the main checkbox label based on the number of checked checkboxes
57
+ const text = checkedCount === 0 ? 'Check All' : 'Uncheck All';
58
+
59
+ // Determine the icon class to add and remove based on the number of checked checkboxes
60
+ const iconClassToAdd = checkedCount === 0 ? 'pb_checkbox_checkmark' : 'pb_checkbox_indeterminate';
61
+ const iconClassToRemove = checkedCount === 0 ? 'pb_checkbox_indeterminate' : 'pb_checkbox_checkmark';
62
+
63
+ // Update main checkbox label
64
+ mainCheckboxWrapper.getElementsByClassName('pb_body_kit')[0].textContent = text;
65
+
66
+ // Add and remove the icon class to the main checkbox wrapper
67
+ mainCheckboxWrapper.querySelector('[data-pb-checkbox-icon-span]').classList.add(iconClassToAdd);
68
+ mainCheckboxWrapper.querySelector('[data-pb-checkbox-icon-span]').classList.remove(iconClassToRemove);
69
+
70
+ // Toggle the visibility of the checkbox icon based on the indeterminate state
71
+ mainCheckboxWrapper.getElementsByClassName("indeterminate_icon")[0].classList.toggle('hidden', !indeterminate);
72
+ mainCheckboxWrapper.getElementsByClassName("check_icon")[0].classList.toggle('hidden', indeterminate);
73
+ };
74
+
75
+ mainCheckbox.addEventListener('change', function() {
76
+ childCheckboxes.forEach(cb => cb.checked = this.checked);
77
+ updateMainCheckbox();
78
+ });
79
+
80
+ childCheckboxes.forEach(cb => {
81
+ cb.addEventListener('change', updateMainCheckbox);
82
+ });
83
+ });
84
+ </script>
@@ -2,7 +2,7 @@ import React, { useEffect, ReactElement } from 'react'
2
2
  import classnames from 'classnames'
3
3
  import useCollapsible from './useCollapsible'
4
4
 
5
- import { globalProps } from '../utilities/globalProps'
5
+ import { globalProps, globalInlineProps } from '../utilities/globalProps'
6
6
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
7
7
 
8
8
  import CollapsibleContent from './child_kits/CollapsibleContent'
@@ -75,6 +75,7 @@ const Collapsible = ({
75
75
  globalProps(props),
76
76
  className
77
77
  )
78
+ const dynamicInlineProps = globalInlineProps(props)
78
79
  return (
79
80
  <CollapsibleContext.Provider value={{ collapsed: isCollapsed, toggle, icon, iconSize, iconColor, onIconClick, onClick }}>
80
81
  <div
@@ -83,6 +84,7 @@ const Collapsible = ({
83
84
  {...htmlProps}
84
85
  className={classes}
85
86
  id={id}
87
+ style={dynamicInlineProps}
86
88
  >
87
89
  {Main ? (
88
90
  <CollapsibleMain {...mainProps}>
@@ -101,7 +101,11 @@ const Currency = (props: CurrencyProps): React.ReactElement => {
101
101
  return decimalPart ? `${formattedWhole}.${decimalPart}` : formattedWhole;
102
102
  }
103
103
 
104
- const getAmount = abbreviate ? getAbbreviatedValue('amount') : formatAmount(getMatchingDecimalAmount)
104
+ const swapNegative = size === "sm" && symbol !== ""
105
+ const handleNegative = amount.startsWith("-") && swapNegative ? "-" : ""
106
+ const getAbsoluteAmount = (amountString: string) => amountString.replace(/^-/,'')
107
+ const getAbbrOrFormatAmount = abbreviate ? getAbbreviatedValue('amount') : formatAmount(getMatchingDecimalAmount)
108
+ const getAmount = swapNegative ? getAbsoluteAmount(getAbbrOrFormatAmount) : getAbbrOrFormatAmount
105
109
  const getAbbreviation = abbreviate ? getAbbreviatedValue('unit') : null
106
110
  const getDecimalValue = abbreviate ? '' : getMatchingDecimalValue
107
111
 
@@ -118,7 +122,7 @@ const Currency = (props: CurrencyProps): React.ReactElement => {
118
122
  <div className={`pb_currency_wrapper${variantClass || emphasizedClass}`}>
119
123
  {unstyled ? (
120
124
  <>
121
- <div>{symbol}</div>
125
+ <div>{handleNegative}{symbol}</div>
122
126
  <div>{getAmount}</div>
123
127
  <div>
124
128
  {getAbbreviation}
@@ -132,7 +136,7 @@ const Currency = (props: CurrencyProps): React.ReactElement => {
132
136
  color="light"
133
137
  dark={dark}
134
138
  >
135
- {symbol}
139
+ {handleNegative}{symbol}
136
140
  </Body>
137
141
 
138
142
  <Title
@@ -3,12 +3,12 @@
3
3
 
4
4
  <div class=<%= "pb_currency_wrapper#{object.variant_class || object.emphasized_class}" %>>
5
5
  <% if object.unstyled %>
6
- <div><%= object.symbol %></div>
6
+ <div><%= object.negative_sign %><%= object.symbol %></div>
7
7
  <div><%= object.title_props[:text] %></div>
8
8
  <div><%= object.body_props[:text] %></div>
9
9
  <% else %>
10
10
  <%= pb_rails("body", props: object.currency_wrapper_props) do %>
11
- <%= object.symbol %>
11
+ <%= object.negative_sign %><%= object.symbol %>
12
12
  <% end %>
13
13
  <%= pb_rails("title", props: object.title_props) %>
14
14
  <%= pb_rails("body", props: object.body_props) %>
@@ -68,12 +68,20 @@ module Playbook
68
68
  def title_props
69
69
  {
70
70
  size: size_value,
71
- text: abbreviate ? abbreviated_value : formatted_amount,
71
+ text: swap_negative ? absolute_amount(abbr_or_format_amount) : abbr_or_format_amount,
72
72
  classname: "pb_currency_value",
73
73
  dark: dark,
74
74
  }
75
75
  end
76
76
 
77
+ def abbr_or_format_amount
78
+ abbreviate ? abbreviated_value : formatted_amount
79
+ end
80
+
81
+ def negative_sign
82
+ amount.starts_with?("-") && swap_negative ? "-" : ""
83
+ end
84
+
77
85
  def body_props
78
86
  {
79
87
  text: units_element,
@@ -159,6 +167,14 @@ module Playbook
159
167
  whole_value
160
168
  end
161
169
  end
170
+
171
+ def absolute_amount(amount_string)
172
+ amount_string.sub(/^-/, "")
173
+ end
174
+
175
+ def swap_negative
176
+ size == "sm" && symbol != ""
177
+ end
162
178
  end
163
179
  end
164
180
  end
@@ -65,7 +65,7 @@ test('decimals default prop returns decimals as body text', () => {
65
65
 
66
66
  test('commaSeparator prop returns comma separated amount', () => {
67
67
  render(
68
- <Currency
68
+ <Currency
69
69
  amount="1234567890"
70
70
  commaSeparator
71
71
  data={{ testid: 'comma-test' }}
@@ -76,7 +76,7 @@ test('commaSeparator prop returns comma separated amount', () => {
76
76
 
77
77
  test('commaSeparator prop returns comma separated amount with decimals', () => {
78
78
  render(
79
- <Currency
79
+ <Currency
80
80
  amount="1234567890.12"
81
81
  commaSeparator
82
82
  data={{ testid: 'comma-test-decimals' }}
@@ -87,7 +87,7 @@ test('commaSeparator prop returns comma separated amount with decimals', () => {
87
87
 
88
88
  test('commaSeparator prop returns comma separated amount with decimals="matching"', () => {
89
89
  render(
90
- <Currency
90
+ <Currency
91
91
  amount="1234567890.12"
92
92
  commaSeparator
93
93
  data={{ testid: 'comma-test-decimals-matching' }}
@@ -96,3 +96,40 @@ test('commaSeparator prop returns comma separated amount with decimals="matching
96
96
  )
97
97
  expect(screen.getByTestId('comma-test-decimals-matching')).toHaveTextContent('1,234,567,890.12')
98
98
  })
99
+
100
+ test('handles negative amounts correctly', () => {
101
+ render(
102
+ <>
103
+ <Currency
104
+ amount="-320"
105
+ data={{ testid: 'test-negative-default' }}
106
+ />
107
+ <Currency
108
+ abbreviate
109
+ amount="-3200000"
110
+ data={{ testid: 'test-negative-millions' }}
111
+ />
112
+ <Currency
113
+ amount="-123456.78"
114
+ commaSeparator
115
+ data={{ testid: 'test-negative-comma-decimals' }}
116
+ />
117
+ <Currency
118
+ amount="-400.50"
119
+ data={{ testid: 'test-negative-no-symbol' }}
120
+ symbol=""
121
+ />
122
+ <Currency
123
+ amount="-500.55"
124
+ data={{ testid: 'test-negative-medium-size' }}
125
+ size="md"
126
+ />
127
+ </>
128
+ )
129
+
130
+ expect(screen.getByTestId('test-negative-default')).toHaveTextContent('-$320')
131
+ expect(screen.getByTestId('test-negative-millions')).toHaveTextContent('-$3.2M')
132
+ expect(screen.getByTestId('test-negative-comma-decimals')).toHaveTextContent('-$123,456.78')
133
+ expect(screen.getByTestId('test-negative-no-symbol')).toHaveTextContent('-400.50')
134
+ expect(screen.getByTestId('test-negative-medium-size')).toHaveTextContent('$-500.55')
135
+ })
@@ -0,0 +1,4 @@
1
+ <%= pb_rails("currency", props: {
2
+ amount: "-2,000.50",
3
+ size: "sm",
4
+ }) %>
@@ -0,0 +1,16 @@
1
+ import React from 'react'
2
+
3
+ import Currency from '../_currency'
4
+
5
+ const CurrencyNegative = (props) => {
6
+ return (
7
+ <>
8
+ <Currency
9
+ amount="-2,000.50"
10
+ {...props}
11
+ />
12
+ </>
13
+ )
14
+ }
15
+
16
+ export default CurrencyNegative
@@ -9,6 +9,7 @@ examples:
9
9
  - currency_matching_decimals: Matching Decimals
10
10
  - currency_unstyled: Unstyled
11
11
  - currency_comma_separator: Comma Separator
12
+ - currency_negative: Negative
12
13
 
13
14
  react:
14
15
  - currency_variants: Variants
@@ -19,6 +20,7 @@ examples:
19
20
  - currency_matching_decimals: Matching Decimals
20
21
  - currency_unstyled: Unstyled
21
22
  - currency_comma_separator: Comma Separator
23
+ - currency_negative: Negative
22
24
 
23
25
  swift:
24
26
  - currency_size_swift: Size
@@ -6,3 +6,4 @@ export { default as CurrencyAbbreviated } from './_currency_abbreviated.jsx'
6
6
  export { default as CurrencyMatchingDecimals } from './_currency_matching_decimals.jsx'
7
7
  export { default as CurrencyUnstyled } from './_currency_unstyled.jsx'
8
8
  export { default as CurrencyCommaSeparator } from './_currency_comma_separator.jsx'
9
+ export { default as CurrencyNegative } from './_currency_negative.jsx'
@@ -88,6 +88,7 @@ body.PBDrawer__Body--close {
88
88
  }
89
89
 
90
90
  .pb_drawer.pb_drawer_after_open {
91
+ pointer-events: auto;
91
92
  transform: translate3d(0, 0, 0);
92
93
  }
93
94