playbook_ui 16.1.0.pre.rc.1 → 16.1.0.pre.rc.2

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +2 -2
  3. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +4 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_inline_row_loading.md +2 -2
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_inline_row_loading_rails.html.erb +64 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_inline_row_loading_rails.md +18 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +51 -1
  9. data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +1 -1
  10. data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +34 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +1 -1
  12. data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +19 -0
  13. data/app/pb_kits/playbook/pb_table/docs/_sections.yml +68 -0
  14. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_input_options.html.erb +39 -0
  15. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_input_options.md +3 -0
  16. data/app/pb_kits/playbook/pb_textarea/docs/example.yml +1 -0
  17. data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +4 -10
  18. data/app/pb_kits/playbook/pb_textarea/textarea.rb +28 -0
  19. data/app/pb_kits/playbook/utilities/test/globalProps/borderRadius.test.js +33 -0
  20. data/app/pb_kits/playbook/utilities/test/globalProps/bottom.test.js +60 -0
  21. data/app/pb_kits/playbook/utilities/test/globalProps/cursor.test.js +42 -0
  22. data/app/pb_kits/playbook/utilities/test/globalProps/dark.test.js +33 -0
  23. data/app/pb_kits/playbook/utilities/test/globalProps/gap.test.js +87 -0
  24. data/app/pb_kits/playbook/utilities/test/globalProps/height.test.js +68 -0
  25. data/app/pb_kits/playbook/utilities/test/globalProps/htmlOptions.test.js +510 -0
  26. data/app/pb_kits/playbook/utilities/test/globalProps/left.test.js +60 -0
  27. data/app/pb_kits/playbook/utilities/test/globalProps/lineHeight.test.js +33 -0
  28. data/app/pb_kits/playbook/utilities/test/globalProps/margin.test.js +95 -0
  29. data/app/pb_kits/playbook/utilities/test/globalProps/numberSpacing.test.js +33 -0
  30. data/app/pb_kits/playbook/utilities/test/globalProps/overflow.test.js +68 -0
  31. data/app/pb_kits/playbook/utilities/test/globalProps/padding.test.js +95 -0
  32. data/app/pb_kits/playbook/utilities/test/globalProps/position.test.js +33 -0
  33. data/app/pb_kits/playbook/utilities/test/globalProps/right.test.js +60 -0
  34. data/app/pb_kits/playbook/utilities/test/globalProps/shadow.test.js +33 -0
  35. data/app/pb_kits/playbook/utilities/test/globalProps/textAlign.test.js +41 -0
  36. data/app/pb_kits/playbook/utilities/test/globalProps/top.test.js +60 -0
  37. data/app/pb_kits/playbook/utilities/test/globalProps/verticalAlign.test.js +40 -0
  38. data/app/pb_kits/playbook/utilities/test/globalProps/width.test.js +66 -0
  39. data/app/pb_kits/playbook/utilities/test/globalProps/zIndex.test.js +50 -0
  40. data/lib/playbook/version.rb +1 -1
  41. metadata +28 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 417316b6bfeb02f46d29a9daa9fc6bccd490cffd020f4569d84a3d4ea52b8c34
4
- data.tar.gz: 3b0839900fc4f979e353a7f7efe1b5d5c2d3f7d2a67aa46eabd8d4bcb9d2d6d0
3
+ metadata.gz: b33580083b52f611802569a00b2e0cb6d8ae6aaa31f5a91ffc329649a4857834
4
+ data.tar.gz: 26448175846617514793f365ac4518dd47836608e0dc05ae295461cfb1778517
5
5
  SHA512:
6
- metadata.gz: b2cd2d914be8f665656c2992f46523369558857bc21fccb957ef5177d3585353f3b82e86ab6a626bba0f2db88d7de71e4d21dcc48d9c324cafc3d3cf19bdae1b
7
- data.tar.gz: 82d3240c1359f6a8ecf636d7e1dba9fc8a73e5a5ec244027f93e1a14bd67c8c619302609adf530dfddf25888f4995ab1e3e476314a9300b33963301d0f206c26
6
+ metadata.gz: '04091c2ae2661ba6f1361b3e611e85f86b607efdfec658cbc48c57a7128565803535ea9cff192d7d3c1ca670cc69ce207d636b49b30d531815b310852985328d'
7
+ data.tar.gz: 553b79a320a6e6f2de774bd7ac22de0285dd27be4686c442de0560b1f495ddd7c9a28d4211c8a765f4b9b6040ed70a3ac49069278da2a631870f632cefa3ad43
@@ -11,8 +11,8 @@
11
11
  <% if content.present? %>
12
12
  <% content.presence %>
13
13
  <% else %>
14
- <%= pb_rails("advanced_table/table_header", props: { table_id: object.id, column_definitions: object.column_definitions, enable_toggle_expansion: object.enable_toggle_expansion, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, show_actions_bar: object.show_actions_bar }) %>
15
- <%= pb_rails("advanced_table/table_body", props: { table_id: object.id, table_data: object.table_data, column_definitions: object.column_definitions, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, enable_toggle_expansion: object.enable_toggle_expansion, row_styling: object.row_styling }) %>
14
+ <%= pb_rails("advanced_table/table_header", props: { table_id: object.id, column_definitions: object.column_definitions, enable_toggle_expansion: object.enable_toggle_expansion, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, show_actions_bar: object.show_actions_bar, inline_row_loading: object.inline_row_loading, persist_toggle_expansion_button: object.persist_toggle_expansion_button, table_data: object.table_data }) %>
15
+ <%= pb_rails("advanced_table/table_body", props: { table_id: object.id, table_data: object.table_data, column_definitions: object.column_definitions, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, enable_toggle_expansion: object.enable_toggle_expansion, row_styling: object.row_styling, inline_row_loading: object.inline_row_loading }) %>
16
16
  <% end %>
17
17
  <% end %>
18
18
  <% end %>
@@ -33,6 +33,10 @@ module Playbook
33
33
  default: false
34
34
  prop :row_styling, type: Playbook::Props::Array,
35
35
  default: []
36
+ prop :inline_row_loading, type: Playbook::Props::Boolean,
37
+ default: false
38
+ prop :persist_toggle_expansion_button, type: Playbook::Props::Boolean,
39
+ default: false
36
40
 
37
41
  def classname
38
42
  additional_classes = [
@@ -6,9 +6,9 @@ In the first Advanced Table in this code example, 2021 has an empty children arr
6
6
  This prop is set to `false` by default.
7
7
 
8
8
 
9
- ### persistToggleExpansion
9
+ ### persistToggleExpansionButton
10
10
  The `persistToggleExpansionButton` is a boolean prop that renders the toggle-all icon in the top left header cell for complex datasets with empty `children` arrays and advanced querying logic explained in the preceeding doc example. Your logic may require an additional query helper file to update data specifically from requerying via toggle all buttons.
11
11
 
12
12
  In the second and third Advanced Tables in this code example, all 3 rows have empty children arrays. The second Advanced Table demonstrates that the toggle all button does not render (prior to an initial row expansion) without `persistToggleExpansionButton` in place. The third Advanced Table shows the toggle all button due to `persistToggleExpansionButton`.
13
13
 
14
- This prop is set to false by default and should only be used in conjunction with `inlineRowLoading`.
14
+ This prop is set to `false` by default and should only be used in conjunction with `inlineRowLoading`.
@@ -0,0 +1,64 @@
1
+ <% column_definitions = [
2
+ {
3
+ accessor: "year",
4
+ label: "Year",
5
+ cellAccessors: ["quarter", "month", "day"],
6
+ },
7
+ {
8
+ accessor: "newEnrollments",
9
+ label: "New Enrollments",
10
+ },
11
+ {
12
+ accessor: "scheduledMeetings",
13
+ label: "Scheduled Meetings",
14
+ },
15
+ {
16
+ accessor: "attendanceRate",
17
+ label: "Attendance Rate",
18
+ },
19
+ {
20
+ accessor: "completedClasses",
21
+ label: "Completed Classes",
22
+ },
23
+ {
24
+ accessor: "classCompletionRate",
25
+ label: "Class Completion Rate",
26
+ },
27
+ {
28
+ accessor: "graduatedStudents",
29
+ label: "Graduated Students",
30
+ }
31
+ ] %>
32
+
33
+ <%= pb_rails("caption", props: { text: "Inline Row Loading - Demonstrated in Row 1 (Rows 2 and 3 have data)" }) %>
34
+
35
+ <%= pb_rails("advanced_table", props: {
36
+ id: "inline-loading-table-1",
37
+ table_data: @table_data_inline_loading,
38
+ column_definitions: column_definitions,
39
+ enable_toggle_expansion: "all",
40
+ inline_row_loading: true,
41
+ margin_bottom: "md"
42
+ }) %>
43
+
44
+ <%= pb_rails("caption", props: { text: "Inline Row Loading with No Subrow Data - All Rows Display Inline Row Loading and the Toggle All Button is not rendered" }) %>
45
+
46
+ <%= pb_rails("advanced_table", props: {
47
+ id: "inline-loading-table-2",
48
+ table_data: @table_data_inline_loading_empty_children,
49
+ column_definitions: column_definitions,
50
+ enable_toggle_expansion: "all",
51
+ inline_row_loading: true,
52
+ margin_bottom: "md"
53
+ }) %>
54
+
55
+ <%= pb_rails("caption", props: { text: "Inline Row Loading and Persist Toggle Expansion Button with No Subrow Data - All Rows Display Inline Row Loading and the Toggle All Button is rendered" }) %>
56
+
57
+ <%= pb_rails("advanced_table", props: {
58
+ id: "inline-loading-table-3",
59
+ table_data: @table_data_inline_loading_empty_children,
60
+ column_definitions: column_definitions,
61
+ enable_toggle_expansion: "all",
62
+ inline_row_loading: true,
63
+ persist_toggle_expansion_button: true
64
+ }) %>
@@ -0,0 +1,18 @@
1
+ ### inline_row_loading
2
+ By default, the kit assumes that the initial dataset is complete, rendering expansion controls only when children are present. If, however, you want to implement lazy-loading patterns where children are fetched only when a parent is expanded, use the `inline_row_loading` prop.
3
+
4
+ When `inline_row_loading` is set to `true`:
5
+ - Expansion controls are rendered for rows with empty `children` arrays (you must pass `children: []` to any row that will have children loaded later)
6
+ - When such a row is expanded, an inline loading indicator appears until the child data is loaded
7
+ - This enables lazy-loading patterns without one-off hacks
8
+
9
+ In the first table above, row "2021" has an empty `children` array. Click to expand it and see the inline loading state. Rows 2 and 3 have actual child data.
10
+
11
+ This prop is set to `false` by default.
12
+
13
+ ### persist_toggle_expansion_button
14
+ The `persist_toggle_expansion_button` is a boolean prop that renders the toggle-all icon in the top left header cell for complex datasets with empty `children` arrays and advanced querying logic explained in the preceding doc example. Your logic may require an additional query helper file to update data specifically from requerying via toggle all buttons.
15
+
16
+ In the second and third Advanced Tables in this code example, all 3 rows have empty children arrays. The second Advanced Table demonstrates that the toggle all button does not render (prior to an initial row expansion) without `persist_toggle_expansion_button` in place. The third Advanced Table shows the toggle all button due to `persist_toggle_expansion_button`.
17
+
18
+ This prop is set to `false` by default and should only be used in conjunction with `inline_row_loading`.
@@ -29,6 +29,7 @@ examples:
29
29
  - advanced_table_background_control_rails: Column Styling Background Color
30
30
  - advanced_table_background_colors_rails: Column Styling Individual Cell Background Color
31
31
  - advanced_table_column_border_color_rails: Column Group Border Color
32
+ - advanced_table_inline_row_loading_rails: Inline Row Loading
32
33
 
33
34
 
34
35
  react:
@@ -25,6 +25,8 @@ module Playbook
25
25
  default: false
26
26
  prop :row_styling, type: Playbook::Props::Array,
27
27
  default: []
28
+ prop :inline_row_loading, type: Playbook::Props::Boolean,
29
+ default: false
28
30
 
29
31
  def flatten_columns(columns)
30
32
  columns.flat_map do |col|
@@ -69,7 +71,24 @@ module Playbook
69
71
  end
70
72
 
71
73
  # Additional class and data attributes needed for toggle logic
72
- output << pb_rails("advanced_table/table_row", props: { table_id: table_id, row: row, column_definitions: leaf_columns, depth: current_depth, collapsible_trail: collapsible_trail, classname: additional_classes, table_data_attributes: current_data_attributes, responsive: responsive, loading: loading, selectable_rows: selectable_rows, row_id: row[:id], enable_toggle_expansion: enable_toggle_expansion, row_styling: row_styling, last_row: last_row, immediate_parent_row_id: immediate_parent_row_id })
74
+ output << pb_rails("advanced_table/table_row", props: { table_id: table_id, row: row, column_definitions: leaf_columns, depth: current_depth, collapsible_trail: collapsible_trail, classname: additional_classes, table_data_attributes: current_data_attributes, responsive: responsive, loading: loading, selectable_rows: selectable_rows, row_id: row[:id], enable_toggle_expansion: enable_toggle_expansion, row_styling: row_styling, last_row: last_row, immediate_parent_row_id: immediate_parent_row_id, inline_row_loading: inline_row_loading })
75
+
76
+ # Render inline loading row when inline_row_loading is enabled and row has empty children
77
+ if inline_row_loading
78
+ children = row_children_for(row)
79
+ if children.is_a?(::Array) && children.empty?
80
+ max_depth = cell_accessors_length(column_definitions)
81
+ if current_depth < max_depth
82
+ loading_row_data_attributes = {
83
+ advanced_table_content: "#{new_ancestor_ids.join('-')}-loading",
84
+ row_depth: current_depth + 1,
85
+ row_parent: "#{table_id}_#{row.object_id}",
86
+ inline_loading_row: true,
87
+ }
88
+ output << render_inline_loading_row(leaf_columns.length, current_depth, loading_row_data_attributes)
89
+ end
90
+ end
91
+ end
73
92
 
74
93
  if row[:children].present?
75
94
  row[:children].each do |child_row|
@@ -104,6 +123,37 @@ module Playbook
104
123
  ""
105
124
  end
106
125
 
126
+ # 3 helper methods for inline row loading
127
+ def render_inline_loading_row(column_count, depth, data_attributes)
128
+ padding_left = depth.zero? ? "0.5em" : "#{(depth + 1) * 2}em"
129
+
130
+ content_tag(:tr, class: "toggle-content inline-loading-row", data: data_attributes) do
131
+ content_tag(:td, colspan: column_count, style: "padding-left: #{padding_left}") do
132
+ pb_rails("loading_inline")
133
+ end
134
+ end
135
+ end
136
+
137
+ def row_children_for(row)
138
+ if row.respond_to?(:children)
139
+ row.children
140
+ elsif row.respond_to?(:[])
141
+ row[:children] || row["children"]
142
+ end
143
+ end
144
+
145
+ def cell_accessors_length(col_defs)
146
+ first_col = col_defs.first
147
+ return 0 unless first_col
148
+
149
+ accessors = if first_col.respond_to?(:cellAccessors)
150
+ first_col.cellAccessors
151
+ elsif first_col.respond_to?(:[])
152
+ first_col[:cellAccessors] || first_col["cellAccessors"]
153
+ end
154
+ accessors&.length || 0
155
+ end
156
+
107
157
  private
108
158
 
109
159
  def has_grouped_headers?
@@ -14,7 +14,7 @@
14
14
  <%= object.render_select_all_checkbox %>
15
15
  <% end %>
16
16
  <% end %>
17
- <% if object.enable_toggle_expansion == "header" || object.enable_toggle_expansion == "all" %>
17
+ <% if object.show_toggle_all_button? %>
18
18
  <% if loading %>
19
19
  <div class="<%= object.loading ? 'loading-toggle-icon' : '' %>"></div>
20
20
  <% else %>
@@ -19,6 +19,12 @@ module Playbook
19
19
  default: false
20
20
  prop :show_actions_bar, type: Playbook::Props::Boolean,
21
21
  default: true
22
+ prop :inline_row_loading, type: Playbook::Props::Boolean,
23
+ default: false
24
+ prop :persist_toggle_expansion_button, type: Playbook::Props::Boolean,
25
+ default: false
26
+ prop :table_data, type: Playbook::Props::Array,
27
+ default: []
22
28
 
23
29
  def classname
24
30
  additional_classes = []
@@ -135,6 +141,20 @@ module Playbook
135
141
  original_def.dig(:column_styling, :header_font_color)
136
142
  end
137
143
 
144
+ # Check if any row in the table has children
145
+ def has_any_sub_rows?
146
+ return false if table_data.blank?
147
+
148
+ table_data.any? { |row| row_has_children?(row) }
149
+ end
150
+
151
+ # Determines if the toggle-all button should be shown in the header based on inline row loading and persist toggle expansion button props
152
+ def show_toggle_all_button?
153
+ return false unless enable_toggle_expansion == "header" || enable_toggle_expansion == "all"
154
+
155
+ has_any_sub_rows? || (inline_row_loading && persist_toggle_expansion_button)
156
+ end
157
+
138
158
  # Check if header has custom background color
139
159
  def has_custom_header_background_color?(cell)
140
160
  cell[:header_background_color].present?
@@ -317,6 +337,20 @@ module Playbook
317
337
  end
318
338
  nil
319
339
  end
340
+
341
+ # 2 inline row loading helper methods
342
+ def row_has_children?(row)
343
+ children = row_children_for(row)
344
+ children.present? && children.is_a?(::Array) && !children.empty?
345
+ end
346
+
347
+ def row_children_for(row)
348
+ if row.respond_to?(:children)
349
+ row.children
350
+ elsif row.respond_to?(:[])
351
+ row[:children] || row["children"]
352
+ end
353
+ end
320
354
  end
321
355
  end
322
356
  end
@@ -32,7 +32,7 @@
32
32
  <%= object.render_row_checkbox %>
33
33
  <% end %>
34
34
  <% end %>
35
- <% if object.row[:children].present? %>
35
+ <% if object.show_expand_button? %>
36
36
  <button
37
37
  id="<%= "#{object.table_id}_#{object.row.object_id}" %>"
38
38
  class="gray-icon expand-toggle-icon"
@@ -33,6 +33,8 @@ module Playbook
33
33
  default: false
34
34
  prop :immediate_parent_row_id, type: Playbook::Props::String,
35
35
  default: ""
36
+ prop :inline_row_loading, type: Playbook::Props::Boolean,
37
+ default: false
36
38
 
37
39
  def data
38
40
  Hash(prop(:data)).merge(table_data_attributes)
@@ -186,6 +188,23 @@ module Playbook
186
188
  end
187
189
  end
188
190
 
191
+ # Determines if the row should show an expand button (true if row has children or inline_row_loading true and row has empty children array)
192
+ def show_expand_button?
193
+ children = row_children
194
+ return true if children.present?
195
+ return true if inline_row_loading && children.is_a?(::Array) && children.empty?
196
+
197
+ false
198
+ end
199
+
200
+ def row_children
201
+ if row.respond_to?(:children)
202
+ row.children
203
+ elsif row.respond_to?(:[])
204
+ row[:children] || row["children"]
205
+ end
206
+ end
207
+
189
208
  private
190
209
 
191
210
  def custom_renderer_value(column, index)
@@ -0,0 +1,68 @@
1
+ sections:
2
+ - title: "Size & Density"
3
+ examples:
4
+ - table_sm
5
+ - table_md
6
+ - table_lg
7
+ - table_multiline
8
+ - table_single_line
9
+ - table_outer_padding
10
+
11
+ - title: "Layout & Structure"
12
+ examples:
13
+ - table_responsive_table
14
+ - table_with_subcomponents
15
+ - table_with_subcomponents_as_divs
16
+ - table_with_background_kit
17
+
18
+
19
+ - title: "Sticky & Positional Behaviors"
20
+ examples:
21
+ - table_sticky
22
+ - table_sticky_left_columns
23
+ - table_sticky_right_columns
24
+ - table_sticky_columns
25
+ - table_alignment_row
26
+ - table_alignment_column
27
+ - table_alignment_shift_row
28
+ - table_alignment_shift_data
29
+ - table_side_highlight
30
+ - table_container
31
+
32
+ - title: "Collapsible & Nested Behaviors"
33
+ examples:
34
+ - table_with_collapsible
35
+ - table_with_dynamic_collapsible
36
+ - table_with_collapsible_with_custom_click
37
+ - table_with_collapsible_with_custom_content
38
+ - table_with_collapsible_with_nested_rows
39
+ - table_with_collapsible_with_nested_table
40
+
41
+ - title: "Data Presentation"
42
+ examples:
43
+ - table_data_table
44
+ - table_vertical_border
45
+ - table_striped
46
+
47
+ - title: "Header Variants"
48
+ examples:
49
+ - table_header
50
+ - table_with_header_style_borderless
51
+ - table_with_header_style_floating
52
+
53
+ - title: "Interactive Tables"
54
+ examples:
55
+ - table_with_clickable_rows
56
+ - table_with_selectable_rows
57
+ - table_with_filter_variant
58
+ - table_with_filter_variant_with_pagination
59
+ - table_disable_hover
60
+
61
+ - title: "Table Actions"
62
+ examples:
63
+ - table_one_action
64
+ - table_two_actions
65
+ - table_two_plus_actions
66
+ - table_action_middle
67
+ - table_icon_buttons
68
+
@@ -0,0 +1,39 @@
1
+ <%= pb_rails("textarea", props: {
2
+ label: "ID on Container",
3
+ id: "container-id",
4
+ name: "comment",
5
+ rows: 4
6
+ }) %>
7
+
8
+ <br/>
9
+
10
+ <%= pb_rails("textarea", props: {
11
+ label: "ID on Textarea via input_options",
12
+ input_options: { id: "textarea-id" },
13
+ name: "comment",
14
+ rows: 4
15
+ }) %>
16
+
17
+ <br/>
18
+
19
+ <%= pb_rails("textarea", props: {
20
+ label: "Both Container and Textarea IDs",
21
+ id: "container-id-2",
22
+ input_options: { id: "textarea-id-2" },
23
+ name: "comment",
24
+ rows: 4
25
+ }) %>
26
+
27
+ <br/>
28
+
29
+ <%= pb_rails("textarea", props: {
30
+ label: "Data and ARIA Attributes",
31
+ name: "description",
32
+ rows: 4,
33
+ input_options: {
34
+ 'aria-label': "Enter description",
35
+ 'aria-describedby': "help-text",
36
+ data: { controller: "textarea", action: "focus->handleFocus" },
37
+ id: "description-textarea"
38
+ }
39
+ }) %>
@@ -0,0 +1,3 @@
1
+ Use the `input_options` / `inputOptions` prop to pass additional attributes directly to the underlying `<textarea>` element instead of the outer wrapper. This is useful for applying data attributes, custom IDs, ARIA attributes, or other HTML attributes that need to be on the textarea element itself.
2
+
3
+ Additional HTML attributes (e.g. data or ARIA attributes) can also be passed directly to the `<textarea>` via `input_options`.
@@ -8,6 +8,7 @@ examples:
8
8
  - textarea_character_counter: Character Counter
9
9
  - textarea_inline: Inline
10
10
  - textarea_emoji_mask: Emoji Mask
11
+ - textarea_input_options: Input Options
11
12
 
12
13
  react:
13
14
  - textarea_default: Default
@@ -8,16 +8,10 @@
8
8
  <%= pb_rails("body", props: { dark: object.dark, status: "negative", text: object.error }) %>
9
9
  <% end %>
10
10
  <% else %>
11
- <%= text_area(
12
- :object,
13
- :method,
14
- :data => object.textarea_options[:data],
15
- :max_characters => object.max_characters,
16
- :name => object.name,
17
- :onkeyup => object.onkeyup,
18
- :placeholder => object.placeholder,
19
- :rows => object.rows,
20
- :value => object.value) %>
11
+ <%= text_area_tag(
12
+ object.name,
13
+ object.value,
14
+ object.all_textarea_attributes) %>
21
15
  <% if object.error %>
22
16
  <% if object.character_count %>
23
17
  <%= pb_rails("flex", props: { spacing: "between", vertical: "center" }) do %>
@@ -8,6 +8,8 @@ module Playbook
8
8
  prop :error
9
9
  prop :inline, type: Playbook::Props::Boolean,
10
10
  default: false
11
+ prop :input_options, type: Playbook::Props::HashProp,
12
+ default: {}
11
13
  prop :label
12
14
  prop :method
13
15
  prop :name
@@ -36,6 +38,32 @@ module Playbook
36
38
  }
37
39
  end
38
40
 
41
+ def all_textarea_attributes
42
+ # Merge data attributes: emoji_mask data + input_options data
43
+ data_attrs = textarea_options[:data] || {}
44
+ input_data = input_options[:data] || {}
45
+ merged_data = data_attrs.merge(input_data)
46
+
47
+ base_attributes = {
48
+ id: input_options[:id] || "object_method",
49
+ max_characters: max_characters,
50
+ name: name,
51
+ onkeyup: onkeyup,
52
+ placeholder: placeholder,
53
+ rows: rows,
54
+ value: value,
55
+ }
56
+
57
+ # Merge input_options (excluding data to handle separately)
58
+ input_options_without_data = input_options.except(:data)
59
+ result = base_attributes.merge(input_options_without_data)
60
+
61
+ # Add merged data if present (input_options data takes precedence over emoji_mask data)
62
+ result[:data] = merged_data unless merged_data.empty?
63
+
64
+ result
65
+ end
66
+
39
67
  private
40
68
 
41
69
  def error_class
@@ -0,0 +1,33 @@
1
+ import { testGlobalProp, testGlobalPropAbsence, testGlobalPropInvalidValues } from './globalPropsTestHelper'
2
+ import Body from '../../../pb_body/_body'
3
+ import Button from '../../../pb_button/_button'
4
+ import Card from '../../../pb_card/_card'
5
+ import Title from '../../../pb_title/_title'
6
+ import Flex from '../../../pb_flex/_flex'
7
+ import Link from '../../../pb_link/_link'
8
+ import Badge from '../../../pb_badge/_badge'
9
+
10
+ // NOTE: TextInput excluded - borderRadius is not a valid prop for input elements
11
+ testGlobalProp(
12
+ 'borderRadius',
13
+ ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'rounded'],
14
+ (v) => `border_radius_${v}`,
15
+ null,
16
+ [Body, Button, Card, Title, Flex, Link, Badge]
17
+ )
18
+
19
+ testGlobalPropAbsence(
20
+ 'borderRadius',
21
+ ['border_radius_none', 'border_radius_xs', 'border_radius_sm', 'border_radius_md', 'border_radius_lg', 'border_radius_xl', 'border_radius_rounded'],
22
+ undefined,
23
+ { skipNull: true }
24
+ )
25
+
26
+ // NOTE: Currently using skipKnownIssues: true because globalProps.ts generates classes for invalid values
27
+ testGlobalPropInvalidValues(
28
+ 'borderRadius',
29
+ ['invalid', 'bad_value', 'not_a_radius', 'special-chars!@#'],
30
+ ['border_radius_invalid', 'border_radius_bad_value', 'border_radius_not_a_radius', 'border_radius_special-chars!@#'],
31
+ undefined,
32
+ { skipKnownIssues: true }
33
+ )
@@ -0,0 +1,60 @@
1
+ import React from 'react'
2
+ import { testGlobalProp, testGlobalPropAbsence, testGlobalPropInvalidValues } from './globalPropsTestHelper'
3
+ import { render, screen } from '../../test-utils'
4
+ import Body from '../../../pb_body/_body'
5
+ import Button from '../../../pb_button/_button'
6
+ import Card from '../../../pb_card/_card'
7
+ import Title from '../../../pb_title/_title'
8
+ import Flex from '../../../pb_flex/_flex'
9
+ import Link from '../../../pb_link/_link'
10
+ import Badge from '../../../pb_badge/_badge'
11
+
12
+ const validSizes = ['xs', 'sm', 'md', 'lg', 'xl']
13
+
14
+ // NOTE: TextInput excluded - positioning props are not valid for input elements
15
+ // Test bottom prop with string values
16
+ testGlobalProp(
17
+ 'bottom',
18
+ validSizes,
19
+ (v) => `bottom_${v}`,
20
+ null,
21
+ [Body, Button, Card, Title, Flex, Link, Badge]
22
+ )
23
+
24
+ // Test bottom prop with object values (inset) - tested separately due to object value complexity
25
+ test('Global Props: bottom returns proper class name with object values (inset)', () => {
26
+ const testCases = [
27
+ { value: { value: 'md', inset: true }, expected: 'bottom_md_inset' },
28
+ { value: { value: 'lg', inset: false }, expected: 'bottom_lg' },
29
+ { value: { value: 'sm', inset: true }, expected: 'bottom_sm_inset' }
30
+ ]
31
+
32
+ testCases.forEach(({ value, expected }) => {
33
+ const testId = `body-bottom-object-${value.value}-${value.inset}`
34
+ render(
35
+ <Body
36
+ bottom={value}
37
+ data={{ testid: testId }}
38
+ text="Hi"
39
+ />
40
+ )
41
+ const kit = screen.getByTestId(testId)
42
+ expect(kit).toHaveClass(expected)
43
+ })
44
+ })
45
+
46
+ testGlobalPropAbsence(
47
+ 'bottom',
48
+ ['bottom_xs', 'bottom_sm', 'bottom_md', 'bottom_lg', 'bottom_xl'],
49
+ undefined,
50
+ { skipNull: true }
51
+ )
52
+
53
+ // NOTE: Currently using skipKnownIssues: true because globalProps.ts generates classes for invalid values
54
+ testGlobalPropInvalidValues(
55
+ 'bottom',
56
+ ['invalid', 'bad_value', 'not_a_size', 'special-chars!@#'],
57
+ ['bottom_invalid', 'bottom_bad_value', 'bottom_not_a_size', 'bottom_special-chars!@#'],
58
+ undefined,
59
+ { skipKnownIssues: true }
60
+ )
@@ -0,0 +1,42 @@
1
+ import { testGlobalProp, testGlobalPropAbsence, testGlobalPropInvalidValues } from './globalPropsTestHelper'
2
+ import { camelToSnakeCase } from '../../../utilities/text'
3
+ import Body from '../../../pb_body/_body'
4
+ import Button from '../../../pb_button/_button'
5
+ import Card from '../../../pb_card/_card'
6
+ import Title from '../../../pb_title/_title'
7
+ import TextInput from '../../../pb_text_input/_text_input'
8
+ import Flex from '../../../pb_flex/_flex'
9
+ import Link from '../../../pb_link/_link'
10
+ import Badge from '../../../pb_badge/_badge'
11
+
12
+ const validValues = [
13
+ 'auto', 'default', 'none', 'contextMenu', 'help', 'pointer', 'progress', 'wait', 'cell',
14
+ 'crosshair', 'text', 'verticalText', 'alias', 'copy', 'move', 'noDrop', 'notAllowed', 'grab',
15
+ 'grabbing', 'eResize', 'nResize', 'neResize', 'nwResize', 'sResize', 'seResize', 'swResize', 'wResize',
16
+ 'ewResize', 'nsResize', 'neswResize', 'nwseResize', 'colResize', 'rowResize', 'allScroll', 'zoomIn', 'zoomOut'
17
+ ]
18
+
19
+ testGlobalProp(
20
+ 'cursor',
21
+ validValues,
22
+ (v) => `cursor_${camelToSnakeCase(v)}`,
23
+ null,
24
+ [Body, Button, Card, Title, TextInput, Flex, Link, Badge]
25
+ )
26
+
27
+ testGlobalPropAbsence(
28
+ 'cursor',
29
+ ['cursor_auto', 'cursor_pointer', 'cursor_default', 'cursor_none'],
30
+ undefined,
31
+ { skipNull: true }
32
+ )
33
+
34
+ // NOTE: Currently using skipKnownIssues: true because globalProps.ts generates classes for invalid values
35
+ // NOTE: Using allowRenderingErrors: true because invalid types (like numbers) cause rendering errors with camelToSnakeCase
36
+ testGlobalPropInvalidValues(
37
+ 'cursor',
38
+ ['invalid', 'bad_value', 'not_a_cursor', 'special-chars!@#'],
39
+ ['cursor_invalid', 'cursor_bad_value', 'cursor_not_a_cursor', 'cursor_special-chars!@#'],
40
+ undefined,
41
+ { skipKnownIssues: true, allowRenderingErrors: true }
42
+ )