playbook_ui 16.1.0.pre.alpha.PLAY277014019 → 16.1.0.pre.alpha.ghostcrewfileuploadtext14338

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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +12 -2
  3. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +33 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.jsx +71 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.md +4 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort.md +1 -1
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
  9. data/app/pb_kits/playbook/pb_button/_button.scss +41 -0
  10. data/app/pb_kits/playbook/pb_button/_button.tsx +5 -0
  11. data/app/pb_kits/playbook/pb_button/button.html.erb +12 -8
  12. data/app/pb_kits/playbook/pb_button/button.rb +5 -0
  13. data/app/pb_kits/playbook/pb_button/button.test.js +105 -0
  14. data/app/pb_kits/playbook/pb_button/docs/_button_icon_variant.html.erb +21 -0
  15. data/app/pb_kits/playbook/pb_button/docs/_button_icon_variant.jsx +180 -0
  16. data/app/pb_kits/playbook/pb_button/docs/_button_icon_variant_rails.md +1 -0
  17. data/app/pb_kits/playbook/pb_button/docs/_button_icon_variant_react.md +1 -0
  18. data/app/pb_kits/playbook/pb_button/docs/example.yml +2 -0
  19. data/app/pb_kits/playbook/pb_button/docs/index.js +1 -0
  20. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +1 -1
  21. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +17 -0
  22. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +10 -1
  23. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -0
  24. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.html.erb +6 -0
  25. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.jsx +17 -0
  26. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.md +3 -0
  27. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
  28. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
  29. data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +12 -4
  30. data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +25 -16
  31. data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +3 -0
  32. data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +36 -1
  33. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_required_indicator.html.erb +5 -0
  34. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_required_indicator.jsx +13 -0
  35. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_required_indicator.md +1 -0
  36. data/app/pb_kits/playbook/pb_date_picker/docs/example.yml +2 -0
  37. data/app/pb_kits/playbook/pb_date_picker/docs/index.js +2 -1
  38. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +60 -12
  39. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_required_indicator.html.erb +14 -0
  40. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_required_indicator.jsx +42 -0
  41. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_required_indicator.md +3 -0
  42. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display_rails.html.erb +1 -1
  43. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +6 -3
  44. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +1 -0
  45. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +3 -1
  46. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +2 -2
  47. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +2 -1
  48. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +17 -5
  49. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +11 -0
  50. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +24 -1
  51. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +15 -10
  52. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +4 -0
  53. data/app/pb_kits/playbook/pb_dropdown/index.js +132 -79
  54. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +16 -0
  55. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +6 -0
  56. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +157 -3
  57. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_button.html.erb +1 -1
  58. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_button.jsx +1 -0
  59. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_date_picker.html.erb +1 -1
  60. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_date_picker.jsx +1 -0
  61. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_default.html.erb +3 -3
  62. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_default.jsx +4 -1
  63. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_full_width.html.erb +4 -4
  64. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_full_width.jsx +4 -1
  65. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_typeahead.jsx +1 -0
  66. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
  67. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +648 -549
  68. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
  69. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +4 -7
  70. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +3 -0
  71. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_required_indicator.html.erb +72 -0
  72. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_required_indicator.jsx +87 -0
  73. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_required_indicator.md +3 -0
  74. data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +2 -0
  75. data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +18 -17
  76. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +3 -0
  77. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +55 -25
  78. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select_options.tsx +4 -4
  79. data/app/pb_kits/playbook/pb_multiple_users/_multiple_users.scss +8 -3
  80. data/app/pb_kits/playbook/pb_multiple_users/_multiple_users.tsx +71 -67
  81. data/app/pb_kits/playbook/pb_multiple_users/docs/_multiple_users_max_displayed_users.jsx +71 -0
  82. data/app/pb_kits/playbook/pb_multiple_users/docs/example.yml +2 -1
  83. data/app/pb_kits/playbook/pb_multiple_users/docs/index.js +2 -1
  84. data/app/pb_kits/playbook/pb_nav/_item.tsx +5 -3
  85. data/app/pb_kits/playbook/pb_nav/_nav.scss +82 -3
  86. data/app/pb_kits/playbook/pb_nav/docs/_collapsible_nav_disabled_item.html.erb +24 -0
  87. data/app/pb_kits/playbook/pb_nav/docs/_collapsible_nav_disabled_item.jsx +87 -0
  88. data/app/pb_kits/playbook/pb_nav/docs/example.yml +2 -6
  89. data/app/pb_kits/playbook/pb_nav/docs/index.js +2 -1
  90. data/app/pb_kits/playbook/pb_nav/item.html.erb +1 -1
  91. data/app/pb_kits/playbook/pb_nav/item.rb +1 -1
  92. data/app/pb_kits/playbook/pb_nav/navTypes.ts +1 -0
  93. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +20 -2
  94. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +9 -0
  95. data/app/pb_kits/playbook/pb_select/_select.tsx +26 -18
  96. data/app/pb_kits/playbook/pb_select/docs/_select_required_indicator.html.erb +24 -0
  97. data/app/pb_kits/playbook/pb_select/docs/_select_required_indicator.jsx +33 -0
  98. data/app/pb_kits/playbook/pb_select/docs/_select_required_indicator.md +3 -0
  99. data/app/pb_kits/playbook/pb_select/docs/example.yml +3 -2
  100. data/app/pb_kits/playbook/pb_select/docs/index.js +1 -0
  101. data/app/pb_kits/playbook/pb_select/select.html.erb +7 -1
  102. data/app/pb_kits/playbook/pb_select/select.rb +3 -0
  103. data/app/pb_kits/playbook/pb_star_rating/_star_rating.tsx +3 -0
  104. data/app/pb_kits/playbook/pb_star_rating/docs/_star_rating_interactive.html.erb +1 -1
  105. data/app/pb_kits/playbook/pb_star_rating/docs/_star_rating_interactive.jsx +1 -0
  106. data/app/pb_kits/playbook/pb_star_rating/star_rating.html.erb +2 -2
  107. data/app/pb_kits/playbook/pb_star_rating/subcomponents/_star_rating_interactive.tsx +55 -39
  108. data/app/pb_kits/playbook/pb_table/index.ts +29 -27
  109. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
  110. data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +29 -1
  111. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +411 -323
  112. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
  113. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +16 -0
  114. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +23 -0
  115. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +3 -0
  116. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  117. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +22 -21
  118. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
  119. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -1
  120. data/app/pb_kits/playbook/tokens/_colors.scss +3 -0
  121. data/app/pb_kits/playbook/tokens/_colors_accessible.scss +250 -0
  122. data/app/pb_kits/playbook/tokens/exports/_colors.module.scss +10 -0
  123. data/dist/chunks/{_pb_line_graph-BgKF_zz1.js → _pb_line_graph-CC2Ywwix.js} +1 -1
  124. data/dist/chunks/_typeahead-DtzfGPUT.js +1 -0
  125. data/dist/chunks/{globalProps-BhVYCqRf.js → globalProps-DYr2qrIf.js} +1 -1
  126. data/dist/chunks/lib-DgqmX9CF.js +29 -0
  127. data/dist/chunks/vendor.js +3 -3
  128. data/dist/menu.yml +2 -2
  129. data/dist/playbook-rails-react-bindings.js +1 -1
  130. data/dist/playbook-rails.js +1 -1
  131. data/dist/playbook.css +2 -2
  132. data/dist/reset.css +1 -1
  133. data/lib/playbook/forms/builder/collection_select_field.rb +7 -1
  134. data/lib/playbook/forms/builder/date_picker_field.rb +8 -2
  135. data/lib/playbook/forms/builder/dropdown_field.rb +7 -1
  136. data/lib/playbook/forms/builder/form_field_builder.rb +1 -1
  137. data/lib/playbook/forms/builder/multi_level_select_field.rb +8 -0
  138. data/lib/playbook/forms/builder/select_field.rb +7 -1
  139. data/lib/playbook/forms/builder/time_zone_select_field.rb +7 -1
  140. data/lib/playbook/forms/builder/typeahead_field.rb +15 -1
  141. data/lib/playbook/forms/builder.rb +2 -2
  142. data/lib/playbook/tokens/colors.json +99 -0
  143. data/lib/playbook/tokens.rb +99 -0
  144. data/lib/playbook/version.rb +1 -1
  145. data/lib/playbook.rb +1 -0
  146. metadata +37 -6
  147. data/dist/chunks/_typeahead-Do4yr_4Z.js +0 -1
  148. data/dist/chunks/lib-DD34ZrWL.js +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c80372f38c690f8a892d1558660133f123bd5fdab59ee755fb09517e3ea094c6
4
- data.tar.gz: 03c79eade34ad7bf5f7df371fe01754f406ff0e6542f4091917a3de83970d3f9
3
+ metadata.gz: e77463bcd2917e10f224e72546b98f389c106c928b9377cf0ab0bc2e9c6e1226
4
+ data.tar.gz: 5d44603ac547c6944a0b47e7614ac49b2ea79d5c17bf684962ce481b5a5209e6
5
5
  SHA512:
6
- metadata.gz: d943d2f57817921271bccba33530e373199c84cbecfc3d895a8189ecba601ae9ec319d04dd140f897d72a9a61d6e1e9c56460a4298abe077a17ae1247a748e5f
7
- data.tar.gz: cec79e75ecb7caca2ff6a9286596236d72dc4dab14b5b3f51169fb05ca11960e8e7482c53a1a0fbdd8698c337bed0001a0c1827b2772a4ab4752606219b82490
6
+ metadata.gz: eefd223b13bfbe1e0d0338c8e18230865956ee33b37afe29bbc93681fd88759f480df9ffe9e1cc2dd197669ec60b3fe9f9c2d70e50964b58e87432bdd1ec15e6
7
+ data.tar.gz: 23cf3b5b1a6000d46dc2666eb99f4e682d1d09e8fb2ddfbd9a649c26a7fc9eaf2d5e75e1128401c9ce12e609fba0e7f42e270a0314e86f8f3330a440bf3a91ec
@@ -66,8 +66,18 @@ const TableCellRenderer = ({
66
66
  // Find the "owning" colDefinition by accessor. Needed for multi column logic
67
67
  const colDef = findColumnDefByAccessor(columnDefinitions ?? [], column.id)
68
68
  const cellAlignment = colDef?.columnStyling?.cellAlignment ?? "right"
69
- const cellFontColor = colDef?.columnStyling?.fontColor
70
- const cellBackgroundColor = colDef?.columnStyling?.cellBackgroundColor
69
+
70
+ // Support function-based styling for conditional rendering
71
+ const cellFontColorValue = colDef?.columnStyling?.fontColor
72
+ const cellFontColor = typeof cellFontColorValue === 'function'
73
+ ? cellFontColorValue(row)
74
+ : cellFontColorValue
75
+
76
+ const cellBackgroundColorValue = colDef?.columnStyling?.cellBackgroundColor
77
+ const cellBackgroundColor = typeof cellBackgroundColorValue === 'function'
78
+ ? cellBackgroundColorValue(row)
79
+ : cellBackgroundColorValue
80
+
71
81
  const paddingValue = colDef?.columnStyling?.cellPadding ?? customRowStyle?.cellPadding
72
82
  const paddingClass = paddingValue ? `p_${paddingValue}` : undefined
73
83
 
@@ -920,6 +920,39 @@ test("columnStyling.backgroundColor works as excpected", () => {
920
920
  expect(firstEnrollmentCell).toHaveStyle({ backgroundColor: colors.error_subtle });
921
921
  });
922
922
 
923
+ test("columnStyling.cellBackgroundColor works as expected with function", () => {
924
+ const styledColumnDefs = [
925
+ {
926
+ accessor: "year",
927
+ label: "Year",
928
+ cellAccessors: ["quarter", "month", "day"],
929
+ },
930
+ {
931
+ accessor: "newEnrollments",
932
+ label: "New Enrollments",
933
+ columnStyling:{
934
+ cellBackgroundColor: (row) => row.original.newEnrollments > 15 ? colors.success_subtle : colors.error_subtle,
935
+ fontColor: (row) => row.original.newEnrollments > 15 ? colors.success : colors.error,
936
+ },
937
+ },
938
+ {
939
+ accessor: "scheduledMeetings",
940
+ label: "Scheduled Meetings",
941
+ },
942
+ ];
943
+
944
+ render(
945
+ <AdvancedTable
946
+ columnDefinitions={styledColumnDefs}
947
+ data={{ testid: testId }}
948
+ tableData={MOCK_DATA}
949
+ />
950
+ );
951
+
952
+ const firstEnrollmentCell = screen.getAllByText("20")[0].closest("td");
953
+ expect(firstEnrollmentCell).toHaveStyle({ backgroundColor: colors.success_subtle, color: colors.success });
954
+ });
955
+
923
956
  test("columnStyling.headerBackgroundColor works as excpected", () => {
924
957
  const styledColumnDefs = [
925
958
  {
@@ -0,0 +1,71 @@
1
+ import React from "react"
2
+ import AdvancedTable from '../_advanced_table'
3
+ import colors from '../../tokens/exports/_colors.module.scss'
4
+ import MOCK_DATA from "./advanced_table_mock_data.json"
5
+ import Flex from '../../pb_flex/_flex'
6
+ import Title from '../../pb_title/_title'
7
+ import Body from '../../pb_body/_body'
8
+
9
+
10
+ const AdvancedTableColumnStylingBackgroundCustom = (props) => {
11
+ const columnDefinitions = [
12
+ {
13
+ accessor: "year",
14
+ label: "Year",
15
+ cellAccessors: ["quarter", "month", "day"],
16
+ customRenderer: (row, value) => (
17
+ <Flex flexDirection="column">
18
+ <Title size={4}
19
+ text={value}
20
+ />
21
+ <Body text="lorem ipsum" />
22
+ <Body text="lorem ipsum" />
23
+ </Flex>
24
+ ),
25
+ },
26
+ {
27
+ accessor: "newEnrollments",
28
+ label: "New Enrollments",
29
+ columnStyling:{
30
+ cellBackgroundColor: (row) => row.original.newEnrollments > 15 ? colors.success_subtle : colors.error_subtle,
31
+ fontColor: (row) => row.original.newEnrollments > 15 ? colors.success : colors.error,
32
+ },
33
+ },
34
+ {
35
+ accessor: "scheduledMeetings",
36
+ label: "Scheduled Meetings",
37
+ columnStyling:{
38
+ cellBackgroundColor: (row) => row.original.scheduledMeetings >= 15 ? colors.info_subtle : colors.warning_subtle
39
+ },
40
+ },
41
+ {
42
+ accessor: "attendanceRate",
43
+ label: "Attendance Rate",
44
+ columnStyling:{cellBackgroundColor: colors.info, headerBackgroundColor: colors.info, fontColor: colors.white, headerFontColor: colors.white},
45
+ },
46
+ {
47
+ accessor: "completedClasses",
48
+ label: "Completed Classes",
49
+ },
50
+ {
51
+ accessor: "classCompletionRate",
52
+ label: "Class Completion Rate",
53
+ },
54
+ {
55
+ accessor: "graduatedStudents",
56
+ label: "Graduated Students",
57
+ },
58
+ ]
59
+
60
+ return (
61
+ <div>
62
+ <AdvancedTable
63
+ columnDefinitions={columnDefinitions}
64
+ tableData={MOCK_DATA}
65
+ {...props}
66
+ />
67
+ </div>
68
+ )
69
+ }
70
+
71
+ export default AdvancedTableColumnStylingBackgroundCustom
@@ -0,0 +1,4 @@
1
+ `cellBackgroundColor` and `fontColor` can also accept functions for conditional styling based on row data. The function receives the row object and returns a color value.
2
+
3
+ See the code snippet below for more details.
4
+
@@ -2,4 +2,4 @@ the `enableSorting` prop is a boolean prop set to false by default. If true, the
2
2
 
3
3
  ### sortIcon
4
4
 
5
- An optional prop, `sortIcon` allows you to customize your icon sets by passing it an array of any comma-separated pair of icon values. The first icon value will replace the kit's default icon when sort direction is desc, and the second value will replace the default icon when sort direction is asc. `sortIcon` also allows you to pass it a single icon as a string, in which case the icon will not toggle with the sort state. Default for this prop is `["arrow-up-short-wide", "arrow-down-short-wide"]`. All strings must be valid FA icons.
5
+ An optional prop, `sortIcon` allows you to customize your icon sets by passing it an array of any comma-separated pair of icon values. The first icon value will replace the kit's default icon when sort direction is desc, and the second value will replace the default icon when sort direction is asc. `sortIcon` also allows you to pass it a single icon as a string, in which case the icon will not toggle with the sort state. Default for this prop is `["arrow-up-wide-short", "arrow-down-short-wide"]`. All strings must be valid FA icons.
@@ -78,6 +78,7 @@ examples:
78
78
  - advanced_table_column_styling: Column Styling
79
79
  - advanced_table_column_styling_column_headers: Column Styling with Multiple Headers
80
80
  - advanced_table_column_styling_background: Column Styling Background Color
81
+ - advanced_table_column_styling_background_custom: Column Styling Background Color (Custom)
81
82
  - advanced_table_column_styling_background_multi: Column Styling Background Color with Multiple Headers
82
83
  - advanced_table_padding_control: Padding Control using Column Styling
83
84
  - advanced_table_column_border_color: Column Group Border Color
@@ -47,4 +47,5 @@ export { default as AdvancedTableSortPerColumnForMultiColumn } from './_advanced
47
47
  export { default as AdvancedTablePaddingControl } from './_advanced_table_padding_control.jsx'
48
48
  export { default as AdvancedTablePaddingControlPerRow } from './_advanced_table_padding_control_per_row.jsx'
49
49
  export { default as AdvancedTableColumnStylingBackground } from './_advanced_table_column_styling_background.jsx'
50
- export { default as AdvancedTableColumnStylingBackgroundMulti } from './_advanced_table_column_styling_background_multi.jsx'
50
+ export { default as AdvancedTableColumnStylingBackgroundMulti } from './_advanced_table_column_styling_background_multi.jsx'
51
+ export { default as AdvancedTableColumnStylingBackgroundCustom } from './_advanced_table_column_styling_background_custom.jsx'
@@ -112,4 +112,45 @@ $pb_button_sizes: (
112
112
  @include pb_button_disabled_dark;
113
113
  }
114
114
  }
115
+
116
+ // Icon-only button (icon prop set, no text) - square with equal padding
117
+ // Rails: uses .pb_button_icon_only class
118
+ // React: detects when pb_button_content has an empty text span
119
+ &.pb_button_icon_only,
120
+ &:has(.pb_button_content > span:empty) {
121
+ aspect-ratio: 1;
122
+ min-width: auto;
123
+ width: auto;
124
+ height: auto;
125
+ padding: $pb_button_v_padding !important;
126
+ min-height: ($pb_button_v_padding * 2) + $font_small;
127
+
128
+ &.pb_button_size_sm {
129
+ padding: $font_smaller !important;
130
+ min-height: ($font_smaller * 2) + $font_smaller;
131
+ }
132
+
133
+ &.pb_button_size_md {
134
+ padding: $font_small !important;
135
+ min-height: ($font_small * 2) + $font_small;
136
+ }
137
+
138
+ &.pb_button_size_lg {
139
+ padding: ($font_large - 2px) !important;
140
+ min-height: (($font_large - 2px) * 2) + ($font_large - 2px);
141
+ }
142
+
143
+ // Remove margins from icons
144
+ .button_with_icon,
145
+ .button_with_icon_right {
146
+ margin-right: 0;
147
+ margin-left: 0;
148
+ }
149
+
150
+ // Remove margins from Rails icon wrapper spans
151
+ > span {
152
+ margin-right: 0 !important;
153
+ margin-left: 0 !important;
154
+ }
155
+ }
115
156
  }
@@ -51,9 +51,13 @@ const buttonClassName = (props: ButtonPropTypes) => {
51
51
  type = 'inline',
52
52
  variant = 'primary',
53
53
  size = null,
54
+ text,
55
+ children,
54
56
  } = props
55
57
 
56
58
  const classNames = ['pb_button_kit']
59
+ // Icon-only: has icon, no text/children, and not a reaction button (reaction buttons have count)
60
+ const isIconOnly = icon && !text && !children && variant !== 'reaction'
57
61
 
58
62
  if (variant) classNames.push(`pb_button_${variant}`)
59
63
  if (type) classNames.push(`pb_button_${type}`)
@@ -63,6 +67,7 @@ const buttonClassName = (props: ButtonPropTypes) => {
63
67
  if (size) classNames.push(`pb_button_size_${size}`)
64
68
  if (variant === 'reaction' && icon && !isValidEmoji(icon)) classNames.push('pb_button_reaction_default')
65
69
  if (variant === 'reaction' && highlight) classNames.push('pb_button_active')
70
+ if (isIconOnly) classNames.push('pb_button_icon_only')
66
71
 
67
72
  return classNames.join(' ')
68
73
  }
@@ -16,17 +16,21 @@
16
16
  <% end %>
17
17
  <% end %>
18
18
  <% else %>
19
- <% if object.icon && !object.icon_right %>
20
- <span>
21
- <%= pb_rails("icon", props: { icon: "#{icon}", fixed_width: true, margin_right: "xs", font_style: object.icon_font_family }) %>
22
- </span>
19
+ <% if !object.loading %>
20
+ <% if object.icon && !object.icon_right %>
21
+ <span>
22
+ <%= pb_rails("icon", props: { icon: "#{icon}", fixed_width: true, font_style: object.icon_font_family }.merge((object.icon_only? && content.blank?) ? {} : { margin_right: "xs" })) %>
23
+ </span>
24
+ <% end %>
23
25
  <% end %>
24
26
  <%= pb_rails("icon", props: { custom_icon: Playbook::Engine::root.join(spinner_path), pulse: true, fixed_width: true, classname: "loading-icon" }) %>
25
27
  <span class="pb_button_content"><%= content.presence || object.text %></span>
26
- <% if object.icon && object.icon_right %>
27
- <span>
28
- <%= pb_rails("icon", props: { icon: "#{icon}", fixed_width: true, margin_left: "xs", font_style: object.icon_font_family }) %>
29
- </span>
28
+ <% if !object.loading %>
29
+ <% if object.icon && object.icon_right %>
30
+ <span>
31
+ <%= pb_rails("icon", props: { icon: "#{icon}", fixed_width: true, font_style: object.icon_font_family }.merge((object.icon_only? && content.blank?) ? {} : { margin_left: "xs" })) %>
32
+ </span>
33
+ <% end %>
30
34
  <% end %>
31
35
  <% end %>
32
36
 
@@ -73,6 +73,10 @@ module Playbook
73
73
  emoji_regex.match?(icon)
74
74
  end
75
75
 
76
+ def icon_only?
77
+ icon.present? && text.blank? && variant != "reaction"
78
+ end
79
+
76
80
  def classname
77
81
  class_names = ["pb_button_kit"]
78
82
  class_names << "pb_button_#{variant}" if variant
@@ -82,6 +86,7 @@ module Playbook
82
86
  class_names << "pb_button_size_#{size}" if size
83
87
  class_names << "pb_button_reaction_default" if variant === "reaction" && icon && !valid_emoji(icon)
84
88
  class_names << "pb_button_active" if variant === "reaction" && highlight
89
+ class_names << "pb_button_icon_only" if icon_only?
85
90
 
86
91
  class_names.join(" ")
87
92
  generate_classname(class_names.compact.join(" "), separator: " ")
@@ -131,3 +131,108 @@ test('should render child target prop', () => {
131
131
  expect(kit).toHaveAttribute('target', 'child')
132
132
  expect(kit).not.toHaveAttribute('rel')
133
133
  })
134
+
135
+ describe('icon-only button', () => {
136
+ test('adds pb_button_icon_only class when icon is provided without text', () => {
137
+ render(
138
+ <Button
139
+ data={{ testid: 'icon-only-test' }}
140
+ icon="plus"
141
+ />
142
+ )
143
+
144
+ const kit = screen.getByTestId('icon-only-test')
145
+ expect(kit).toHaveClass('pb_button_icon_only')
146
+ })
147
+
148
+ test('does not add pb_button_icon_only class when text is provided', () => {
149
+ render(
150
+ <Button
151
+ data={{ testid: 'icon-with-text-test' }}
152
+ icon="plus"
153
+ text="Click me"
154
+ />
155
+ )
156
+
157
+ const kit = screen.getByTestId('icon-with-text-test')
158
+ expect(kit).not.toHaveClass('pb_button_icon_only')
159
+ })
160
+
161
+ test('does not add pb_button_icon_only class when children are provided', () => {
162
+ render(
163
+ <Button
164
+ data={{ testid: 'icon-with-children-test' }}
165
+ icon="plus"
166
+ >
167
+ Click me
168
+ </Button>
169
+ )
170
+
171
+ const kit = screen.getByTestId('icon-with-children-test')
172
+ expect(kit).not.toHaveClass('pb_button_icon_only')
173
+ })
174
+
175
+ test('does not add pb_button_icon_only class when variant is reaction', () => {
176
+ render(
177
+ <Button
178
+ data={{ testid: 'reaction-icon-test' }}
179
+ icon="plus"
180
+ variant="reaction"
181
+ />
182
+ )
183
+
184
+ const kit = screen.getByTestId('reaction-icon-test')
185
+ expect(kit).not.toHaveClass('pb_button_icon_only')
186
+ })
187
+
188
+ test('adds pb_button_icon_only class with different variants', () => {
189
+ const variants = ['primary', 'secondary', 'link', 'danger']
190
+
191
+ variants.forEach(variant => {
192
+ const { unmount } = render(
193
+ <Button
194
+ data={{ testid: `icon-only-${variant}-test` }}
195
+ icon="plus"
196
+ variant={variant}
197
+ />
198
+ )
199
+
200
+ const kit = screen.getByTestId(`icon-only-${variant}-test`)
201
+ expect(kit).toHaveClass('pb_button_icon_only')
202
+ unmount()
203
+ })
204
+ })
205
+
206
+ test('adds pb_button_icon_only class with loading state', () => {
207
+ render(
208
+ <Button
209
+ data={{ testid: 'icon-only-loading-test' }}
210
+ icon="plus"
211
+ loading
212
+ />
213
+ )
214
+
215
+ const kit = screen.getByTestId('icon-only-loading-test')
216
+ expect(kit).toHaveClass('pb_button_icon_only')
217
+ expect(kit).toHaveClass('pb_button_loading')
218
+ })
219
+
220
+ test('adds pb_button_icon_only class with size variants', () => {
221
+ const sizes = ['sm', 'md', 'lg']
222
+
223
+ sizes.forEach(size => {
224
+ const { unmount } = render(
225
+ <Button
226
+ data={{ testid: `icon-only-${size}-test` }}
227
+ icon="plus"
228
+ size={size}
229
+ />
230
+ )
231
+
232
+ const kit = screen.getByTestId(`icon-only-${size}-test`)
233
+ expect(kit).toHaveClass('pb_button_icon_only')
234
+ expect(kit).toHaveClass(`pb_button_size_${size}`)
235
+ unmount()
236
+ })
237
+ })
238
+ })
@@ -0,0 +1,21 @@
1
+ <%= pb_rails("caption", props: { margin_y: "md", text: "Small Size (sm)" }) %>
2
+ <%= pb_rails("button", props: { icon: "plus", size: "sm", margin_right: "lg" }) %>
3
+ <%= pb_rails("button", props: { icon: "plus", size: "sm", variant: "secondary", margin_right: "lg" }) %>
4
+ <%= pb_rails("button", props: { icon: "plus", size: "sm", variant: "link", margin_right: "lg" }) %>
5
+ <%= pb_rails("button", props: { icon: "plus", size: "sm", variant: "danger", margin_right: "lg" }) %>
6
+ <%= pb_rails("button", props: { icon: "plus", size: "sm", disabled: true, margin_right: "lg" }) %>
7
+ <%= pb_rails("button", props: { icon: "plus", size: "sm", loading: true, margin_right: "lg" }) %>
8
+ <%= pb_rails("caption", props: { margin_y: "md", text: "Medium Size (md)" }) %>
9
+ <%= pb_rails("button", props: { icon: "plus", size: "md", margin_right: "lg" }) %>
10
+ <%= pb_rails("button", props: { icon: "plus", size: "md", variant: "secondary", margin_right: "lg" }) %>
11
+ <%= pb_rails("button", props: { icon: "plus", size: "md", variant: "link", margin_right: "lg" }) %>
12
+ <%= pb_rails("button", props: { icon: "plus", size: "md", variant: "danger", margin_right: "lg" }) %>
13
+ <%= pb_rails("button", props: { icon: "plus", size: "md", disabled: true, margin_right: "lg" }) %>
14
+ <%= pb_rails("button", props: { icon: "plus", size: "md", loading: true, margin_right: "lg" }) %>
15
+ <%= pb_rails("caption", props: { margin_y: "md", text: "Large Size (lg)" }) %>
16
+ <%= pb_rails("button", props: { icon: "plus", size: "lg", margin_right: "lg" }) %>
17
+ <%= pb_rails("button", props: { icon: "plus", size: "lg", variant: "secondary", margin_right: "lg" }) %>
18
+ <%= pb_rails("button", props: { icon: "plus", size: "lg", variant: "link", margin_right: "lg" }) %>
19
+ <%= pb_rails("button", props: { icon: "plus", size: "lg", variant: "danger", margin_right: "lg" }) %>
20
+ <%= pb_rails("button", props: { icon: "plus", size: "lg", disabled: true, margin_right: "lg" }) %>
21
+ <%= pb_rails("button", props: { icon: "plus", size: "lg", loading: true, margin_right: "lg" }) %>
@@ -0,0 +1,180 @@
1
+ import React from 'react'
2
+ import Button from "../../pb_button/_button"
3
+ import Caption from "../../pb_caption/_caption"
4
+
5
+ const ButtonIconVariant = (props) => (
6
+ <div>
7
+ <Caption
8
+ marginY="md"
9
+ text="Small Size (sm)"
10
+ />
11
+ <Button
12
+ icon="plus"
13
+ marginRight='lg'
14
+ size="sm"
15
+ tabIndex={0}
16
+ {...props}
17
+ />
18
+ {' '}
19
+ <Button
20
+ icon="plus"
21
+ marginRight='lg'
22
+ size="sm"
23
+ tabIndex={0}
24
+ variant="secondary"
25
+ {...props}
26
+ />
27
+ {' '}
28
+ <Button
29
+ icon="plus"
30
+ marginRight='lg'
31
+ size="sm"
32
+ tabIndex={0}
33
+ variant="link"
34
+ {...props}
35
+ />
36
+ {' '}
37
+ <Button
38
+ icon="plus"
39
+ marginRight='lg'
40
+ size="sm"
41
+ tabIndex={0}
42
+ variant="danger"
43
+ {...props}
44
+ />
45
+ {' '}
46
+ <Button
47
+ disabled
48
+ icon="plus"
49
+ marginRight='lg'
50
+ size="sm"
51
+ tabIndex={0}
52
+ {...props}
53
+ />
54
+ {' '}
55
+ <Button
56
+ icon="plus"
57
+ loading
58
+ marginRight='lg'
59
+ size="sm"
60
+ tabIndex={0}
61
+ {...props}
62
+ />
63
+ <br/>
64
+ <Caption
65
+ marginY="md"
66
+ text="Medium Size (md)"
67
+ />
68
+ <Button
69
+ icon="plus"
70
+ marginRight='lg'
71
+ size="md"
72
+ tabIndex={0}
73
+ {...props}
74
+ />
75
+ {' '}
76
+ <Button
77
+ icon="plus"
78
+ marginRight='lg'
79
+ size="md"
80
+ tabIndex={0}
81
+ variant="secondary"
82
+ {...props}
83
+ />
84
+ {' '}
85
+ <Button
86
+ icon="plus"
87
+ marginRight='lg'
88
+ size="md"
89
+ tabIndex={0}
90
+ variant="link"
91
+ {...props}
92
+ />
93
+ {' '}
94
+ <Button
95
+ icon="plus"
96
+ marginRight='lg'
97
+ size="md"
98
+ tabIndex={0}
99
+ variant="danger"
100
+ {...props}
101
+ />
102
+ {' '}
103
+ <Button
104
+ disabled
105
+ icon="plus"
106
+ marginRight='lg'
107
+ size="md"
108
+ tabIndex={0}
109
+ {...props}
110
+ />
111
+ {' '}
112
+ <Button
113
+ icon="plus"
114
+ loading
115
+ marginRight='lg'
116
+ size="md"
117
+ tabIndex={0}
118
+ {...props}
119
+ />
120
+ <br/>
121
+ <Caption
122
+ marginY="md"
123
+ text="Large Size (lg)"
124
+ />
125
+ <Button
126
+ icon="plus"
127
+ marginRight='lg'
128
+ size="lg"
129
+ tabIndex={0}
130
+ {...props}
131
+ />
132
+ {' '}
133
+ <Button
134
+ icon="plus"
135
+ marginRight='lg'
136
+ size="lg"
137
+ tabIndex={0}
138
+ variant="secondary"
139
+ {...props}
140
+ />
141
+ {' '}
142
+ <Button
143
+ icon="plus"
144
+ marginRight='lg'
145
+ size="lg"
146
+ tabIndex={0}
147
+ variant="link"
148
+ {...props}
149
+ />
150
+ {' '}
151
+ <Button
152
+ icon="plus"
153
+ marginRight='lg'
154
+ size="lg"
155
+ tabIndex={0}
156
+ variant="danger"
157
+ {...props}
158
+ />
159
+ {' '}
160
+ <Button
161
+ disabled
162
+ icon="plus"
163
+ marginRight='lg'
164
+ size="lg"
165
+ tabIndex={0}
166
+ {...props}
167
+ />
168
+ {' '}
169
+ <Button
170
+ icon="plus"
171
+ loading
172
+ marginRight='lg'
173
+ size="lg"
174
+ tabIndex={0}
175
+ {...props}
176
+ />
177
+ </div>
178
+ )
179
+
180
+ export default ButtonIconVariant
@@ -0,0 +1 @@
1
+ The icon button variant automatically renders when you provide an `icon` prop without a corresponding `text` prop. The button will only display an icon (no text) and will be wrapped with the icon button styling. This works with all button variants including "link", "primary", "secondary", etc. Simply use `<%= pb_rails("button", props: { icon: "plus", variant: "secondary" }) %>` to get an icon button.
@@ -0,0 +1 @@
1
+ The icon button variant automatically renders when you provide an `icon` prop without a corresponding `text` prop. The button will only display an icon (no text) and will be wrapped with the icon button styling. This works with all button variants including "link", "primary", "secondary", etc. Simply use `<Button icon="rocket" variant="primary" />` to get an icon button.
@@ -13,6 +13,7 @@ examples:
13
13
  - button_form: Button Form Attribute
14
14
  - button_managed_disabled: Button Toggle Disabled State
15
15
  - button_managed_disabled_helper: Button Toggle Disabled State Helper
16
+ - button_icon_variant: Icon Button Variant
16
17
 
17
18
  react:
18
19
  - button_default: Button Variants
@@ -27,6 +28,7 @@ examples:
27
28
  - button_size: Button Size
28
29
  - button_form: Button Form Attribute
29
30
  - button_hover: Button Hover
31
+ - button_icon_variant: Icon Button Variant
30
32
 
31
33
  swift:
32
34
  - button_default_swift: Button Variants
@@ -4,6 +4,7 @@ export { default as ButtonLink } from './_button_link.jsx'
4
4
  export { default as ButtonLoading } from './_button_loading.jsx'
5
5
  export { default as ButtonBlockContent } from './_button_block_content.jsx'
6
6
  export { default as ButtonIconOptions } from './_button_icon_options.jsx'
7
+ export { default as ButtonIconVariant } from './_button_icon_variant.jsx'
7
8
  export { default as ButtonAccessibility } from './_button_accessibility.jsx'
8
9
  export { default as ButtonOptions } from './_button_options.jsx'
9
10
  export { default as ButtonSize } from './_button_size.jsx'
@@ -168,4 +168,4 @@ $transition: $transition_cubic;
168
168
  border-color: $error;
169
169
  }
170
170
  }
171
- }
171
+ }