playbook_ui 16.7.0 → 16.8.0.pre.alpha.PLAY2935formbuilderrequiredindicatorbug16780

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +2 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +2 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/Components/VirtualizedTableView.tsx +5 -1
  5. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +24 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/Utilities/ColumnLayoutHelper.ts +138 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +144 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling.jsx +1 -0
  9. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling.md +6 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_width.jsx +57 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_width.md +66 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_enable_toggle_expansion_rails.html.erb +62 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_enable_toggle_expansion_rails.md +7 -0
  14. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sticky_header.jsx +12 -4
  15. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sticky_header.md +4 -0
  16. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sticky_header_rails.html.erb +16 -2
  17. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sticky_header_rails.md +4 -0
  18. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sticky_scroll_limitation.jsx +68 -0
  19. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sticky_scroll_limitation.md +7 -0
  20. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_table_props_sticky_header.html.erb +16 -2
  21. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_table_props_sticky_header.jsx +12 -5
  22. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_table_props_sticky_header_rails.md +4 -0
  23. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_table_props_sticky_header_react.md +5 -3
  24. data/app/pb_kits/playbook/pb_advanced_table/docs/_playground.json +180 -5839
  25. data/app/pb_kits/playbook/pb_advanced_table/docs/_playground.overrides.json +5 -30
  26. data/app/pb_kits/playbook/pb_advanced_table/docs/advanced_table_column_definitions_styling.json +4 -1
  27. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +3 -0
  28. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -0
  29. data/app/pb_kits/playbook/pb_card/_card.tsx +1 -1
  30. data/app/pb_kits/playbook/pb_card/card.html.erb +1 -1
  31. data/app/pb_kits/playbook/pb_currency/_currency.tsx +9 -6
  32. data/app/pb_kits/playbook/pb_currency/currency.rb +5 -10
  33. data/app/pb_kits/playbook/pb_currency/currency.test.js +44 -1
  34. data/app/pb_kits/playbook/pb_date/docs/_playground.json +13 -17
  35. data/app/pb_kits/playbook/pb_date/docs/_playground.overrides.json +13 -16
  36. data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +3 -2
  37. data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +38 -23
  38. data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +2 -1
  39. data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +31 -0
  40. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_dialog_submission.jsx +2 -2
  41. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_inline.html.erb +0 -2
  42. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_inline.jsx +0 -2
  43. data/app/pb_kits/playbook/pb_date_picker/docs/_playground.json +136 -42
  44. data/app/pb_kits/playbook/pb_date_picker/docs/_playground.overrides.json +113 -45
  45. data/app/pb_kits/playbook/pb_date_range_inline/docs/_playground.json +48 -6
  46. data/app/pb_kits/playbook/pb_date_range_inline/docs/_playground.overrides.json +57 -0
  47. data/app/pb_kits/playbook/pb_date_range_stacked/docs/_playground.json +28 -5
  48. data/app/pb_kits/playbook/pb_date_range_stacked/docs/_playground.overrides.json +38 -0
  49. data/app/pb_kits/playbook/pb_date_stacked/docs/_playground.json +1 -1
  50. data/app/pb_kits/playbook/pb_date_stacked/docs/_playground.overrides.json +1 -1
  51. data/app/pb_kits/playbook/pb_date_time/docs/_playground.json +16 -3
  52. data/app/pb_kits/playbook/pb_date_time/docs/_playground.overrides.json +16 -3
  53. data/app/pb_kits/playbook/pb_date_time_stacked/docs/_playground.json +11 -15
  54. data/app/pb_kits/playbook/pb_date_time_stacked/docs/_playground.overrides.json +11 -15
  55. data/app/pb_kits/playbook/pb_date_year_stacked/docs/_playground.json +4 -4
  56. data/app/pb_kits/playbook/pb_date_year_stacked/docs/_playground.overrides.json +4 -4
  57. data/app/pb_kits/playbook/pb_detail/docs/_playground.json +12 -18
  58. data/app/pb_kits/playbook/pb_detail/docs/_playground.overrides.json +13 -12
  59. data/app/pb_kits/playbook/pb_dialog/docs/_playground.json +108 -42
  60. data/app/pb_kits/playbook/pb_dialog/docs/_playground.overrides.json +88 -40
  61. data/app/pb_kits/playbook/pb_distribution_bar/docs/_playground.json +65 -7
  62. data/app/pb_kits/playbook/pb_distribution_bar/docs/_playground.overrides.json +45 -0
  63. data/app/pb_kits/playbook/pb_draggable/_draggable.scss +19 -0
  64. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_cards_rails.md +2 -0
  65. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_cards_react.md +1 -0
  66. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_list_rails.md +2 -0
  67. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_list_react.md +3 -1
  68. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_selectable_list_rails.md +3 -1
  69. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_selectable_list_react.md +3 -1
  70. data/app/pb_kits/playbook/pb_draggable/draggable.test.jsx +16 -0
  71. data/app/pb_kits/playbook/pb_draggable/draggable_container.html.erb +3 -1
  72. data/app/pb_kits/playbook/pb_draggable/draggable_item.html.erb +1 -0
  73. data/app/pb_kits/playbook/pb_draggable/index.js +149 -7
  74. data/app/pb_kits/playbook/pb_draggable/subcomponents/DraggableContainer.tsx +1 -0
  75. data/app/pb_kits/playbook/pb_draggable/subcomponents/DraggableItem.tsx +67 -1
  76. data/app/pb_kits/playbook/pb_draggable/touchDrag.test.js +38 -0
  77. data/app/pb_kits/playbook/pb_draggable/utilities/touchDrag.ts +173 -0
  78. data/app/pb_kits/playbook/pb_dropdown/docs/_playground.json +318 -21
  79. data/app/pb_kits/playbook/pb_dropdown/docs/_playground.overrides.json +192 -19
  80. data/app/pb_kits/playbook/pb_empty_state/docs/_playground.json +77 -12
  81. data/app/pb_kits/playbook/pb_empty_state/docs/_playground.overrides.json +79 -0
  82. data/app/pb_kits/playbook/pb_file_upload/docs/_playground.json +98 -13
  83. data/app/pb_kits/playbook/pb_file_upload/docs/_playground.overrides.json +99 -0
  84. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +20 -19
  85. data/app/pb_kits/playbook/pb_icon/_icon.scss +2 -1
  86. data/app/pb_kits/playbook/pb_icon/docs/example.yml +0 -2
  87. data/app/pb_kits/playbook/pb_icon/docs/index.js +0 -1
  88. data/app/pb_kits/playbook/pb_link/docs/_playground.json +81 -40
  89. data/app/pb_kits/playbook/pb_link/docs/_playground.overrides.json +88 -30
  90. data/app/pb_kits/playbook/pb_list/_list_item.tsx +4 -1
  91. data/app/pb_kits/playbook/pb_list/item.html.erb +1 -1
  92. data/app/pb_kits/playbook/pb_pb_circle_chart/docs/_pb_circle_chart_centered_data.html.erb +90 -0
  93. data/app/pb_kits/playbook/pb_pb_circle_chart/docs/_pb_circle_chart_centered_data.jsx +100 -0
  94. data/app/pb_kits/playbook/pb_pb_circle_chart/docs/_pb_circle_chart_centered_data.md +1 -0
  95. data/app/pb_kits/playbook/pb_pb_circle_chart/docs/_pb_circle_chart_default.jsx +1 -1
  96. data/app/pb_kits/playbook/pb_pb_circle_chart/docs/example.yml +2 -0
  97. data/app/pb_kits/playbook/pb_pb_circle_chart/docs/index.js +2 -1
  98. data/app/pb_kits/playbook/pb_phone_number_input/docs/_playground.json +4 -2
  99. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +1 -1
  100. data/app/pb_kits/playbook/pb_rich_text_editor/_tiptap_styles.scss +262 -43
  101. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_default.html.erb +1 -0
  102. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_default.md +12 -0
  103. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_simple.html.erb +9 -0
  104. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_simple.md +8 -0
  105. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +2 -0
  106. data/app/pb_kits/playbook/pb_rich_text_editor/kit.schema.json +18 -9
  107. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.html.erb +162 -0
  108. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.rb +71 -0
  109. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.js +202 -0
  110. data/app/pb_kits/playbook/pb_table/docs/_table_sticky.html.erb +85 -83
  111. data/app/pb_kits/playbook/pb_table/docs/_table_sticky.jsx +88 -86
  112. data/app/pb_kits/playbook/pb_table/docs/_table_sticky.md +3 -1
  113. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_external_filter_rails.md +1 -1
  114. data/app/pb_kits/playbook/pb_text_input/_text_input.scss +37 -0
  115. data/app/pb_kits/playbook/pb_title/docs/_playground.json +72 -23
  116. data/app/pb_kits/playbook/pb_title/docs/_playground.overrides.json +80 -16
  117. data/app/pb_kits/playbook/pb_tooltip/_tooltip.scss +133 -102
  118. data/app/pb_kits/playbook/pb_tooltip/_tooltip.tsx +54 -41
  119. data/app/pb_kits/playbook/pb_tooltip/tooltip.test.jsx +60 -2
  120. data/dist/chunks/{_pb_line_graph-CIWJe3Gr.js → _pb_line_graph-BgsTI0CL.js} +1 -1
  121. data/dist/chunks/_typeahead-DA__Kgp5.js +5 -0
  122. data/dist/chunks/{globalProps-CqO4Tko1.js → globalProps-DOB47YGB.js} +1 -1
  123. data/dist/chunks/{lib-czQnE40X.js → lib-BzglXly2.js} +2 -2
  124. data/dist/chunks/vendor.js +4 -4
  125. data/dist/menu.yml +71 -132
  126. data/dist/playbook-rails-react-bindings.js +1 -1
  127. data/dist/playbook-rails.js +1 -1
  128. data/dist/playbook.css +1 -1
  129. data/lib/playbook/forms/builder/form_field_builder.rb +2 -0
  130. data/lib/playbook/version.rb +2 -2
  131. metadata +31 -10
  132. data/app/pb_kits/playbook/pb_icon/docs/_icon_fa_kit.html.erb +0 -1
  133. data/app/pb_kits/playbook/pb_icon/docs/_icon_fa_kit.jsx +0 -21
  134. data/app/pb_kits/playbook/pb_icon/docs/_icon_fa_kit.md +0 -7
  135. data/dist/chunks/_typeahead-B_Ac4z84.js +0 -1
@@ -0,0 +1,162 @@
1
+ <%# TipTap loads via full URL dynamic import() in rich_text_editor_rails.js (esm.sh) — no import map, so no clash with Vite/host maps. %>
2
+ <%= pb_content_tag(:div, id: object.container_id, class: object.classname, data: { pb_rte_tiptap: true, input_id: object.input_id, initial_html: object.initial_html, rte_simple: object.simple }) do %>
3
+ <div class="pb_rich_text_editor_kit">
4
+ <% if object.label.present? %>
5
+ <label for="<%= object.input_id %>">
6
+ <% if object.required_indicator %>
7
+ <%= pb_rails("caption", props: { color: "lighter", text: object.label, dark: object.dark }) %><span style="color: #DA0014;"> *</span>
8
+ <% else %>
9
+ <%= pb_rails("caption", props: { color: "lighter", text: object.label, dark: object.dark }) %>
10
+ <% end %>
11
+ </label>
12
+ <% end %>
13
+ <input type="hidden" name="<%= object.input_name %>" id="<%= object.input_id %>" value="" />
14
+ <div class="pb_rich_text_editor_advanced_container toolbar-active<%= " pb_rich_text_editor_rte--simple" if object.simple %>">
15
+ <% if object.simple %>
16
+ <%# Compact toolbar: Bold/Italic + Undo/Redo — no block-style Popover (avoids dialog positioning issues). %>
17
+ <div class="pb_background_kit pb_background_color_white toolbar rte-rails-toolbar-layout rte-rails-toolbar-layout--simple" id="<%= object.toolbar_id %>">
18
+ <div class="rte-rails-toolbar-row">
19
+ <div class="toolbar_block rte-toolbar-left">
20
+ <button type="button" class="toolbar_button" data-action="bold" title="Bold" role="button" tabindex="0">
21
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
22
+ <%= pb_rails("icon", props: { icon: "bold", size: "lg" }) %>
23
+ <% end %>
24
+ </button>
25
+ <button type="button" class="toolbar_button" data-action="italic" title="Italic" role="button" tabindex="0">
26
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
27
+ <%= pb_rails("icon", props: { icon: "italic", size: "lg" }) %>
28
+ <% end %>
29
+ </button>
30
+ </div>
31
+ <div class="toolbar_block rte-toolbar-right">
32
+ <button type="button" class="toolbar_button" data-action="undo" title="Undo" role="button" tabindex="0">
33
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
34
+ <%= pb_rails("icon", props: { icon: "undo", size: "lg" }) %>
35
+ <% end %>
36
+ </button>
37
+ <button type="button" class="toolbar_button" data-action="redo" title="Redo" role="button" tabindex="0">
38
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
39
+ <%= pb_rails("icon", props: { icon: "redo", size: "lg" }) %>
40
+ <% end %>
41
+ </button>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ <% else %>
46
+ <% block_style_options = [
47
+ { value: "paragraph", text: "Paragraph", icon: "paragraph" },
48
+ { value: "heading-1", text: "Heading 1", icon: "h1" },
49
+ { value: "heading-2", text: "Heading 2", icon: "h2" },
50
+ { value: "heading-3", text: "Heading 3", icon: "h3" },
51
+ { value: "bulletList", text: "Bullet List", icon: "list" },
52
+ { value: "orderedList", text: "Ordered List", icon: "list-ol" },
53
+ { value: "blockquote", text: "Block Quote", icon: "block-quote" },
54
+ ] %>
55
+ <div class="pb_background_kit pb_background_color_white toolbar rte-rails-toolbar-layout" id="<%= object.toolbar_id %>">
56
+ <div class="rte-rails-toolbar-row">
57
+ <div class="toolbar_block rte-toolbar-left">
58
+ <span class="pb_popover_reference_wrapper">
59
+ <%# Button kit wraps content in <span class="pb_button_content"> — block <div>s inside are invalid and break layout. Single <span> row + JS sync from templates. %>
60
+ <%= pb_rails("button", props: {
61
+ id: object.rte_block_style_trigger_id,
62
+ variant: "secondary",
63
+ classname: "editor-dropdown-button",
64
+ html_options: {
65
+ type: "button",
66
+ "aria-label": "Text style",
67
+ "aria-haspopup": "true",
68
+ },
69
+ }) do %>
70
+ <span class="pb_flex_kit pb_flex_kit_orientation_row pb_flex_kit_justify_content_left pb_flex_kit_align_items_center pb_flex_kit_spacing_none pb_flex_kit_gap_xs gap_xs rte-block-style-trigger-inner" data-rte-block-trigger>
71
+ <span class="rte-block-style-trigger-icon">
72
+ <%= pb_rails("icon", props: { icon: "paragraph", size: "lg" }) %>
73
+ </span>
74
+ <span class="rte-block-style-trigger-label">Paragraph</span>
75
+ <span class="display_inline_flex rte-block-style-chevron">
76
+ <%= pb_rails("icon", props: { icon: "chevron-down", fixed_width: true }) %>
77
+ </span>
78
+ </span>
79
+ <% end %>
80
+ </span>
81
+ <%= pb_rails("popover", props: {
82
+ trigger_element_id: object.rte_block_style_trigger_id,
83
+ tooltip_id: object.rte_block_style_tooltip_id,
84
+ position: "bottom",
85
+ padding: "none",
86
+ close_on_click: "any",
87
+ offset: true,
88
+ }) do %>
89
+ <%= pb_rails("nav", props: { variant: "subtle", padding_top: "xs", padding_bottom: "xs" }) do %>
90
+ <% block_style_options.each do |opt| %>
91
+ <%= pb_rails("nav/item", props: {
92
+ link: "##{opt[:value]}",
93
+ text: opt[:text],
94
+ icon_left: opt[:icon],
95
+ margin: "none",
96
+ padding_top: "xxs",
97
+ padding_bottom: "xxs",
98
+ }) %>
99
+ <% end %>
100
+ <% end %>
101
+ <% end %>
102
+ <%= pb_rails("section_separator", props: { orientation: "vertical" }) %>
103
+ <button type="button" class="toolbar_button" data-action="bold" title="Bold" role="button" tabindex="0">
104
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
105
+ <%= pb_rails("icon", props: { icon: "bold", size: "lg" }) %>
106
+ <% end %>
107
+ </button>
108
+ <button type="button" class="toolbar_button" data-action="italic" title="Italic" role="button" tabindex="0">
109
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
110
+ <%= pb_rails("icon", props: { icon: "italic", size: "lg" }) %>
111
+ <% end %>
112
+ </button>
113
+ <button type="button" class="toolbar_button" data-action="strike" title="Strikethrough" role="button" tabindex="0">
114
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
115
+ <%= pb_rails("icon", props: { icon: "strikethrough", size: "lg" }) %>
116
+ <% end %>
117
+ </button>
118
+ <%= pb_rails("section_separator", props: { orientation: "vertical" }) %>
119
+ <button type="button" class="toolbar_button" data-action="codeBlock" title="Code block" role="button" tabindex="0">
120
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
121
+ <%= pb_rails("icon", props: { icon: "code", size: "lg" }) %>
122
+ <% end %>
123
+ </button>
124
+ <button type="button" class="toolbar_button" data-action="link" title="Link" role="button" tabindex="0">
125
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
126
+ <%= pb_rails("icon", props: { icon: "link", size: "lg" }) %>
127
+ <% end %>
128
+ </button>
129
+ </div>
130
+ <div class="toolbar_block rte-toolbar-right">
131
+ <button type="button" class="toolbar_button" data-action="undo" title="Undo" role="button" tabindex="0">
132
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
133
+ <%= pb_rails("icon", props: { icon: "undo", size: "lg" }) %>
134
+ <% end %>
135
+ </button>
136
+ <button type="button" class="toolbar_button" data-action="redo" title="Redo" role="button" tabindex="0">
137
+ <%= pb_rails("flex", props: { align: "center", justify: "center", classname: "toolbar_button_icon" }) do %>
138
+ <%= pb_rails("icon", props: { icon: "redo", size: "lg" }) %>
139
+ <% end %>
140
+ </button>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ <div id="<%= object.container_id %>-block-icon-templates" hidden aria-hidden="true">
145
+ <% block_style_options.each do |opt| %>
146
+ <span data-block-template-for="<%= opt[:value] %>" data-label="<%= opt[:text] %>">
147
+ <%= pb_rails("icon", props: { icon: opt[:icon], size: "lg" }) %>
148
+ </span>
149
+ <% end %>
150
+ </div>
151
+ <% end %>
152
+ <div class="rte-editor-wrap">
153
+ <div id="<%= object.editor_node_id %>"></div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ <% end %>
158
+
159
+ <%# Module script: rich_text_editor_rails.js %>
160
+ <script type="module">
161
+ <%= File.read(Playbook.kit_path("rich_text_editor", "rich_text_editor_rails.js")).html_safe %>
162
+ </script>
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playbook
4
+ module PbRichTextEditor
5
+ # Rails rich text editor: TipTap (vanilla JS), no React. Content syncs to a hidden input for form submission.
6
+ class RichTextEditor < Playbook::KitBase
7
+ prop :value
8
+ prop :placeholder
9
+ prop :input_options, type: Playbook::Props::HashProp, default: {}
10
+ prop :label
11
+ prop :required_indicator, type: Playbook::Props::Boolean, default: false
12
+ # When true, TipTap toolbar matches React `simple`: Bold + Italic only (no block-style Popover).
13
+ # Use in modals or narrow layouts where the block dropdown misbehaves.
14
+ prop :simple, type: Playbook::Props::Boolean, default: false
15
+
16
+ # Match React default (globalProps maxWidth "md").
17
+ def max_width
18
+ v = values[:max_width] || values["max_width"]
19
+ v.nil? || v == "" ? "md" : v
20
+ end
21
+
22
+ def classname
23
+ generate_classname("pb_rich_text_editor_kit", "rte-container")
24
+ end
25
+
26
+ def input_id
27
+ return input_options[:id].presence if input_options[:id].present?
28
+ return "#{id}-input" if id.present?
29
+
30
+ # Unique per kit instance — multiple editors without a kit `id` would otherwise share the same DOM id.
31
+ "rich_text_editor-input-#{object_id}"
32
+ end
33
+
34
+ def input_name
35
+ return input_options[:name].presence if input_options[:name].present?
36
+ return "#{id}_content" if id.present?
37
+
38
+ # Last-resort default; two editors with neither `id` nor `input_options[:name]` still collide — set one of them.
39
+ "content"
40
+ end
41
+
42
+ def initial_html
43
+ raw = value.present? ? value.to_s.strip : ""
44
+ return "<p></p>" if raw.blank?
45
+
46
+ raw.start_with?("<") ? raw : "<p>#{raw}</p>"
47
+ end
48
+
49
+ def container_id
50
+ id.present? ? "rte-tiptap-#{id}" : "rte-tiptap-#{input_id.gsub(/[^a-z0-9_-]/i, '')}"
51
+ end
52
+
53
+ def editor_node_id
54
+ "#{container_id}-editor"
55
+ end
56
+
57
+ def toolbar_id
58
+ "#{container_id}-toolbar"
59
+ end
60
+
61
+ # Stable DOM ids for TipTap toolbar popover (used in ERB + module script; must be kit methods — not ERB locals).
62
+ def rte_block_style_trigger_id
63
+ "#{toolbar_id}-block-trigger"
64
+ end
65
+
66
+ def rte_block_style_tooltip_id
67
+ "#{toolbar_id}-block-tooltip"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,202 @@
1
+ // Rails TipTap: dynamic import() from esm.sh (no import map — avoids 2nd map ignored by Firefox vs Vite/host).
2
+ // Idempotent: data-pb-rte-initialized / data-pb-rte-pending.
3
+
4
+ const RTE_TIPTAP_VERSION = "2.8.0";
5
+ const RTE_TIPTAP_ESM = (pkg) => `https://esm.sh/${pkg}@${RTE_TIPTAP_VERSION}`;
6
+
7
+ async function initPlaybookRichTextEditorRails(container) {
8
+ if (!container || container.dataset.pbRteInitialized || container.dataset.pbRtePending) return;
9
+ container.dataset.pbRtePending = "true";
10
+
11
+ const inputId = container.dataset.inputId;
12
+ let initialHtml = container.dataset.initialHtml || "<p></p>";
13
+ if (initialHtml && !initialHtml.trim().startsWith("<")) {
14
+ initialHtml = "<p>" + initialHtml + "</p>";
15
+ }
16
+ const containerId = container.id;
17
+ const hiddenInput = document.getElementById(inputId);
18
+ const editorNode = document.getElementById(`${containerId}-editor`);
19
+ const toolbar = document.getElementById(`${containerId}-toolbar`);
20
+ const rteSimple = container.dataset.rteSimple === "true";
21
+ const blockTooltipId = `${containerId}-toolbar-block-tooltip`;
22
+ const iconTemplatesRoot = rteSimple
23
+ ? null
24
+ : document.getElementById(`${containerId}-block-icon-templates`);
25
+
26
+ if (!editorNode || !hiddenInput || !toolbar) {
27
+ delete container.dataset.pbRtePending;
28
+ return;
29
+ }
30
+
31
+ function syncToHiddenInput(editor) {
32
+ if (editor && hiddenInput) {
33
+ hiddenInput.value = editor.getHTML();
34
+ }
35
+ }
36
+
37
+ try {
38
+ const { Editor } = await import(RTE_TIPTAP_ESM("@tiptap/core"));
39
+ const { default: StarterKit } = await import(RTE_TIPTAP_ESM("@tiptap/starter-kit"));
40
+ const { default: Link } = await import(RTE_TIPTAP_ESM("@tiptap/extension-link"));
41
+
42
+ const editor = new Editor({
43
+ element: editorNode,
44
+ extensions: [
45
+ StarterKit.configure({ heading: { levels: [1, 2, 3] } }),
46
+ Link.configure({ openOnClick: false, HTMLAttributes: { target: "_blank", rel: "noopener" } }),
47
+ ],
48
+ content: initialHtml,
49
+ editable: true,
50
+ onUpdate: ({ editor: ed }) => syncToHiddenInput(ed),
51
+ });
52
+
53
+ syncToHiddenInput(editor);
54
+
55
+ const actionToChain = {
56
+ bold: "toggleBold",
57
+ italic: "toggleItalic",
58
+ strike: "toggleStrike",
59
+ codeBlock: "toggleCodeBlock",
60
+ };
61
+
62
+ const getCurrentBlockValue = () => {
63
+ let value = "paragraph";
64
+ if (editor.isActive("heading", { level: 1 })) value = "heading-1";
65
+ else if (editor.isActive("heading", { level: 2 })) value = "heading-2";
66
+ else if (editor.isActive("heading", { level: 3 })) value = "heading-3";
67
+ else if (editor.isActive("bulletList")) value = "bulletList";
68
+ else if (editor.isActive("orderedList")) value = "orderedList";
69
+ else if (editor.isActive("blockquote")) value = "blockquote";
70
+ return value;
71
+ };
72
+
73
+ const syncBlockTrigger = () => {
74
+ if (rteSimple) return;
75
+ const current = getCurrentBlockValue();
76
+ const triggerRoot = toolbar.querySelector("[data-rte-block-trigger]");
77
+ let tpl =
78
+ iconTemplatesRoot &&
79
+ [...iconTemplatesRoot.children].find(
80
+ (el) => el.getAttribute("data-block-template-for") === current
81
+ );
82
+ if (!tpl && iconTemplatesRoot) {
83
+ tpl = [...iconTemplatesRoot.children].find(
84
+ (el) => el.getAttribute("data-block-template-for") === "paragraph"
85
+ );
86
+ }
87
+ if (triggerRoot && tpl) {
88
+ const iconWrap = triggerRoot.querySelector(".rte-block-style-trigger-icon");
89
+ const labelEl = triggerRoot.querySelector(".rte-block-style-trigger-label");
90
+ if (iconWrap) iconWrap.innerHTML = tpl.innerHTML;
91
+ if (labelEl) labelEl.textContent = tpl.getAttribute("data-label") || "";
92
+ }
93
+ const tooltip = document.getElementById(blockTooltipId);
94
+ if (tooltip) {
95
+ tooltip.querySelectorAll("a.pb_nav_list_item_link").forEach((a) => {
96
+ const href = a.getAttribute("href") || "";
97
+ const v = href.startsWith("#") ? href.slice(1) : "";
98
+ a.classList.toggle("is-active", v === current);
99
+ });
100
+ }
101
+ };
102
+
103
+ const applyBlockType = (value) => {
104
+ const chain = editor.chain().focus();
105
+ if (value === "paragraph") chain.setParagraph().run();
106
+ else if (value === "heading-1") chain.toggleHeading({ level: 1 }).run();
107
+ else if (value === "heading-2") chain.toggleHeading({ level: 2 }).run();
108
+ else if (value === "heading-3") chain.toggleHeading({ level: 3 }).run();
109
+ else if (value === "bulletList") chain.toggleBulletList().run();
110
+ else if (value === "orderedList") chain.toggleOrderedList().run();
111
+ else if (value === "blockquote") chain.toggleBlockquote().run();
112
+ };
113
+
114
+ const updateActiveStates = () => {
115
+ syncBlockTrigger();
116
+ toolbar.querySelectorAll("button[data-action]").forEach((btn) => {
117
+ const action = btn.dataset.action;
118
+ let active = false;
119
+ if (action === "bold") active = editor.isActive("bold");
120
+ else if (action === "italic") active = editor.isActive("italic");
121
+ else if (action === "strike") active = editor.isActive("strike");
122
+ else if (action === "codeBlock") active = editor.isActive("codeBlock");
123
+ else if (action === "link") active = editor.isActive("link");
124
+ btn.classList.toggle("is-active", active);
125
+ });
126
+ toolbar.querySelectorAll("button[data-action='undo']").forEach((btn) => {
127
+ btn.disabled = !editor.can().undo();
128
+ });
129
+ toolbar.querySelectorAll("button[data-action='redo']").forEach((btn) => {
130
+ btn.disabled = !editor.can().redo();
131
+ });
132
+ };
133
+
134
+ if (!rteSimple) {
135
+ const blockStyleTooltip = document.getElementById(blockTooltipId);
136
+ if (blockStyleTooltip) {
137
+ blockStyleTooltip.addEventListener("click", (e) => {
138
+ const a = e.target.closest("a[href^='#']");
139
+ if (!a || !blockStyleTooltip.contains(a)) return;
140
+ e.preventDefault();
141
+ const href = a.getAttribute("href") || "";
142
+ const v = href.startsWith("#") ? href.slice(1) : "";
143
+ if (!v) return;
144
+ applyBlockType(v);
145
+ updateActiveStates();
146
+ });
147
+ }
148
+ }
149
+
150
+ toolbar.addEventListener("click", (e) => {
151
+ const btn = e.target.closest("button[data-action]");
152
+ if (!btn) return;
153
+ e.preventDefault();
154
+ const action = btn.dataset.action;
155
+
156
+ if (action === "undo") {
157
+ editor.chain().focus().undo().run();
158
+ } else if (action === "redo") {
159
+ editor.chain().focus().redo().run();
160
+ } else if (action === "link") {
161
+ const previousUrl = editor.getAttributes("link").href || "";
162
+ const url = window.prompt("URL", previousUrl);
163
+ if (url === null) return;
164
+ if (url === "") {
165
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
166
+ } else {
167
+ editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
168
+ }
169
+ } else {
170
+ const chainMethod = actionToChain[action];
171
+ if (chainMethod && typeof editor.chain().focus()[chainMethod] === "function") {
172
+ editor.chain().focus()[chainMethod]().run();
173
+ }
174
+ }
175
+ updateActiveStates();
176
+ });
177
+
178
+ editor.on("selectionUpdate", updateActiveStates);
179
+ editor.on("transaction", updateActiveStates);
180
+ updateActiveStates();
181
+
182
+ container.dataset.pbRteInitialized = "true";
183
+ } finally {
184
+ delete container.dataset.pbRtePending;
185
+ }
186
+ }
187
+
188
+ function mountAllPlaybookRichTextEditorRails() {
189
+ document.querySelectorAll("[data-pb-rte-tiptap]").forEach((el) => {
190
+ void initPlaybookRichTextEditorRails(el);
191
+ });
192
+ }
193
+
194
+ if (document.readyState === "loading") {
195
+ document.addEventListener("DOMContentLoaded", mountAllPlaybookRichTextEditorRails);
196
+ } else {
197
+ mountAllPlaybookRichTextEditorRails();
198
+ }
199
+
200
+ document.addEventListener("turbo:load", mountAllPlaybookRichTextEditorRails);
201
+
202
+ export { initPlaybookRichTextEditorRails, mountAllPlaybookRichTextEditorRails };
@@ -1,83 +1,85 @@
1
- <%= pb_rails("table", props: { sticky: true }) do %>
2
- <thead>
3
- <tr>
4
- <th>Column 1</th>
5
- <th>Column 2</th>
6
- <th>Column 3</th>
7
- <th>Column 4</th>
8
- <th>Column 5</th>
9
- </tr>
10
- </thead>
11
- <tbody>
12
- <tr>
13
- <td>Value 1</td>
14
- <td>Value 2</td>
15
- <td>Value 3</td>
16
- <td>Value 4</td>
17
- <td>Value 5</td>
18
- </tr>
19
- <tr>
20
- <td>Value 1</td>
21
- <td>Value 2</td>
22
- <td>Value 3</td>
23
- <td>Value 4</td>
24
- <td>Value 5</td>
25
- </tr>
26
- <tr>
27
- <td>Value 1</td>
28
- <td>Value 2</td>
29
- <td>Value 3</td>
30
- <td>Value 4</td>
31
- <td>Value 5</td>
32
- </tr>
33
- <tr>
34
- <td>Value 1</td>
35
- <td>Value 2</td>
36
- <td>Value 3</td>
37
- <td>Value 4</td>
38
- <td>Value 5</td>
39
- </tr>
40
- <tr>
41
- <td>Value 1</td>
42
- <td>Value 2</td>
43
- <td>Value 3</td>
44
- <td>Value 4</td>
45
- <td>Value 5</td>
46
- </tr>
47
- <tr>
48
- <td>Value 1</td>
49
- <td>Value 2</td>
50
- <td>Value 3</td>
51
- <td>Value 4</td>
52
- <td>Value 5</td>
53
- </tr>
54
- <tr>
55
- <td>Value 1</td>
56
- <td>Value 2</td>
57
- <td>Value 3</td>
58
- <td>Value 4</td>
59
- <td>Value 5</td>
60
- </tr>
61
- <tr>
62
- <td>Value 1</td>
63
- <td>Value 2</td>
64
- <td>Value 3</td>
65
- <td>Value 4</td>
66
- <td>Value 5</td>
67
- </tr>
68
- <tr>
69
- <td>Value 1</td>
70
- <td>Value 2</td>
71
- <td>Value 3</td>
72
- <td>Value 4</td>
73
- <td>Value 5</td>
74
- </tr>
75
- <tr>
76
- <td>Value 1</td>
77
- <td>Value 2</td>
78
- <td>Value 3</td>
79
- <td>Value 4</td>
80
- <td>Value 5</td>
81
- </tr>
82
- </tbody>
83
- <% end %>
1
+ <div style="max-height: 320px; overflow-y: auto;">
2
+ <%= pb_rails("table", props: { sticky: true }) do %>
3
+ <thead>
4
+ <tr>
5
+ <th>Column 1</th>
6
+ <th>Column 2</th>
7
+ <th>Column 3</th>
8
+ <th>Column 4</th>
9
+ <th>Column 5</th>
10
+ </tr>
11
+ </thead>
12
+ <tbody>
13
+ <tr>
14
+ <td>Value 1</td>
15
+ <td>Value 2</td>
16
+ <td>Value 3</td>
17
+ <td>Value 4</td>
18
+ <td>Value 5</td>
19
+ </tr>
20
+ <tr>
21
+ <td>Value 1</td>
22
+ <td>Value 2</td>
23
+ <td>Value 3</td>
24
+ <td>Value 4</td>
25
+ <td>Value 5</td>
26
+ </tr>
27
+ <tr>
28
+ <td>Value 1</td>
29
+ <td>Value 2</td>
30
+ <td>Value 3</td>
31
+ <td>Value 4</td>
32
+ <td>Value 5</td>
33
+ </tr>
34
+ <tr>
35
+ <td>Value 1</td>
36
+ <td>Value 2</td>
37
+ <td>Value 3</td>
38
+ <td>Value 4</td>
39
+ <td>Value 5</td>
40
+ </tr>
41
+ <tr>
42
+ <td>Value 1</td>
43
+ <td>Value 2</td>
44
+ <td>Value 3</td>
45
+ <td>Value 4</td>
46
+ <td>Value 5</td>
47
+ </tr>
48
+ <tr>
49
+ <td>Value 1</td>
50
+ <td>Value 2</td>
51
+ <td>Value 3</td>
52
+ <td>Value 4</td>
53
+ <td>Value 5</td>
54
+ </tr>
55
+ <tr>
56
+ <td>Value 1</td>
57
+ <td>Value 2</td>
58
+ <td>Value 3</td>
59
+ <td>Value 4</td>
60
+ <td>Value 5</td>
61
+ </tr>
62
+ <tr>
63
+ <td>Value 1</td>
64
+ <td>Value 2</td>
65
+ <td>Value 3</td>
66
+ <td>Value 4</td>
67
+ <td>Value 5</td>
68
+ </tr>
69
+ <tr>
70
+ <td>Value 1</td>
71
+ <td>Value 2</td>
72
+ <td>Value 3</td>
73
+ <td>Value 4</td>
74
+ <td>Value 5</td>
75
+ </tr>
76
+ <tr>
77
+ <td>Value 1</td>
78
+ <td>Value 2</td>
79
+ <td>Value 3</td>
80
+ <td>Value 4</td>
81
+ <td>Value 5</td>
82
+ </tr>
83
+ </tbody>
84
+ <% end %>
85
+ </div>