katalyst-content 3.0.0.alpha.1 → 3.0.0.alpha.3

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -5
  3. data/app/assets/builds/katalyst/content.esm.js +9 -4
  4. data/app/assets/builds/katalyst/content.js +9 -4
  5. data/app/assets/builds/katalyst/content.min.js +1 -1
  6. data/app/assets/builds/katalyst/content.min.js.map +1 -1
  7. data/app/assets/stylesheets/katalyst/content/_editor.scss +4 -0
  8. data/app/assets/stylesheets/katalyst/content/_frontend.scss +1 -0
  9. data/app/assets/stylesheets/katalyst/content/editor/editor.css +200 -0
  10. data/app/assets/stylesheets/katalyst/content/editor.css +4 -196
  11. data/app/assets/stylesheets/katalyst/content/frontend/frontend.css +46 -0
  12. data/app/assets/stylesheets/katalyst/content/frontend.css +1 -0
  13. data/app/components/katalyst/content/editor/item_component.html.erb +6 -4
  14. data/app/components/katalyst/content/editor/new_items_component.html.erb +10 -8
  15. data/app/components/katalyst/content/editor/new_items_component.rb +26 -2
  16. data/app/components/katalyst/content/editor_component.html.erb +2 -1
  17. data/app/components/katalyst/content/editor_component.rb +4 -6
  18. data/app/helpers/katalyst/content/frontend_helper.rb +40 -24
  19. data/app/javascript/content/editor/item_editor_controller.js +4 -2
  20. data/app/javascript/content/editor/new_items_controller.js +7 -2
  21. data/app/models/concerns/katalyst/content/has_tree.rb +16 -3
  22. data/app/models/katalyst/content/item.rb +15 -7
  23. data/app/views/katalyst/content/asides/_aside.html+form.erb +2 -2
  24. data/app/views/katalyst/content/asides/_aside.html.erb +5 -7
  25. data/app/views/katalyst/content/columns/_column.html+form.erb +2 -2
  26. data/app/views/katalyst/content/columns/_column.html.erb +3 -3
  27. data/app/views/katalyst/content/contents/_content.html+form.erb +2 -2
  28. data/app/views/katalyst/content/contents/_content.html.erb +2 -2
  29. data/app/views/katalyst/content/figures/_figure.html+form.erb +2 -2
  30. data/app/views/katalyst/content/figures/_figure.html.erb +1 -1
  31. data/app/views/katalyst/content/groups/_group.html+form.erb +2 -2
  32. data/app/views/katalyst/content/groups/_group.html.erb +1 -3
  33. data/app/views/katalyst/content/items/_item.html+form.erb +2 -2
  34. data/app/views/katalyst/content/items/edit.html.erb +6 -11
  35. data/app/views/katalyst/content/sections/_section.html+form.erb +2 -2
  36. data/app/views/katalyst/content/sections/_section.html.erb +3 -5
  37. data/app/views/katalyst/content/tables/_table.html+form.erb +2 -2
  38. data/config/locales/en.yml +11 -0
  39. data/db/migrate/20250321045027_rename_background_to_theme.rb +7 -0
  40. data/lib/katalyst/content/config.rb +4 -1
  41. data/spec/factories/katalyst/content/items.rb +1 -1
  42. metadata +11 -7
  43. data/app/assets/stylesheets/katalyst/_content.scss +0 -4
  44. data/app/assets/stylesheets/katalyst/content.css +0 -4
  45. /data/app/assets/stylesheets/katalyst/content/{icons.css → editor/icons.css} +0 -0
  46. /data/app/assets/stylesheets/katalyst/content/{status-bar.css → editor/status-bar.css} +0 -0
  47. /data/app/assets/stylesheets/katalyst/content/{table.css → editor/table.css} +0 -0
@@ -0,0 +1,200 @@
1
+ @import url("icons.css");
2
+ @import url("status-bar.css");
3
+ @import url("table.css");
4
+
5
+ .content--editor {
6
+ --row-inset: 2rem;
7
+ --row-hover-bg: var(--color-tint, #fff0eb);
8
+ --row-padding: 0 var(--space-2xs, 0.25rem);
9
+
10
+ position: relative;
11
+ }
12
+
13
+ /* Rows */
14
+
15
+ .content--editor--item {
16
+ /* https://github.com/react-dnd/react-dnd/issues/832 */
17
+ transform: translate3d(0, 0, 0);
18
+
19
+ &:hover {
20
+ background: var(--row-hover-bg);
21
+ }
22
+
23
+ &[draggable] {
24
+ cursor: grab;
25
+ }
26
+
27
+ /* Dragged visuals */
28
+
29
+ &[data-dragging] {
30
+ box-shadow: inset 0 0 0 2px var(--color-tint, #fff0eb);
31
+
32
+ > * {
33
+ visibility: hidden;
34
+ }
35
+ }
36
+
37
+ /* Hidden items */
38
+
39
+ [data-invisible] {
40
+ color: var(--color-mid, #aaa);
41
+ }
42
+
43
+ /* row item controller */
44
+
45
+ > [data-controller] {
46
+ display: grid;
47
+ grid-template-columns: 1fr auto;
48
+ align-items: baseline;
49
+ padding: var(--row-padding);
50
+ }
51
+
52
+ /* row content */
53
+
54
+ .tree {
55
+ display: flex;
56
+ gap: var(--space-xs, 0.5rem);
57
+ align-items: baseline;
58
+
59
+ text-overflow: ellipsis;
60
+ overflow: hidden;
61
+ white-space: nowrap;
62
+ }
63
+
64
+ .actions-group {
65
+ display: flex;
66
+ gap: var(--space-3xs, 0.25em);
67
+ align-items: baseline;
68
+ }
69
+
70
+ /* Theme identifier */
71
+
72
+ .item-theme {
73
+ display: inline-flex;
74
+ background-color: var(--theme-background-color);
75
+ color: var(--theme-text-color);
76
+ aspect-ratio: 1;
77
+ padding: var(--space-3xs);
78
+ border-radius: var(--radius-s);
79
+ }
80
+
81
+ [data-content-theme="light"] {
82
+ --theme-text-color: black;
83
+ --theme-background-color: white;
84
+ }
85
+
86
+ [data-content-theme="dark"] {
87
+ --theme-text-color: white;
88
+ --theme-background-color: black;
89
+ }
90
+
91
+ /* Depth */
92
+
93
+ &[data-content-depth="1"] .tree {
94
+ padding-left: calc(var(--row-inset) * 1);
95
+ }
96
+
97
+ &[data-content-depth="2"] .tree {
98
+ padding-left: calc(var(--row-inset) * 2);
99
+ }
100
+
101
+ &[data-content-depth="3"] .tree {
102
+ padding-left: calc(var(--row-inset) * 3);
103
+ }
104
+
105
+ &[data-content-depth="4"] .tree {
106
+ padding-left: calc(var(--row-inset) * 4);
107
+ }
108
+
109
+ &[data-content-depth="5"] .tree {
110
+ padding-left: calc(var(--row-inset) * 5);
111
+ }
112
+
113
+ &[data-content-depth="6"] .tree {
114
+ padding-left: calc(var(--row-inset) * 6);
115
+ }
116
+
117
+ /* Lower opacity for buttons you can't use */
118
+
119
+ &[data-deny-de-nest] .button:has([data-icon="outdent"]),
120
+ &[data-deny-nest] .button:has([data-icon="indent"]),
121
+ &[data-deny-remove] .button:has([data-icon="remove"]),
122
+ &[data-deny-drag] .button:has([data-icon="drag"]),
123
+ &[data-deny-edit] .button:has([data-icon="edit"]) {
124
+ opacity: 0.2;
125
+ pointer-events: none;
126
+ }
127
+
128
+ &:not(:hover) .actions-group .button {
129
+ opacity: 0;
130
+ }
131
+
132
+ /* Only show 1 of the collapse / expand button */
133
+
134
+ &[data-deny-collapse] .button:has([data-icon="collapse"]),
135
+ &[data-deny-expand] .button:has([data-icon="expand"]) {
136
+ display: none !important;
137
+ pointer-events: none;
138
+ }
139
+
140
+ /* Always show the expand button when there are hidden children */
141
+
142
+ &:has([data-content-children]) .button:has([data-icon="expand"]) {
143
+ opacity: 1 !important;
144
+ }
145
+ }
146
+
147
+ /* Inline add link */
148
+
149
+ .content--editor--inline-add {
150
+ position: absolute;
151
+ left: 0;
152
+ right: 0;
153
+ top: 0;
154
+ height: 0;
155
+ z-index: 1;
156
+ outline: 1px solid var(--color-dark-glare, #555);
157
+
158
+ button {
159
+ position: absolute;
160
+ top: 0;
161
+ left: 50%;
162
+ margin: 0;
163
+ padding: 0.25rem;
164
+
165
+ animation: content--editor--pulse 200ms ease;
166
+ transform: translate(-50%, -50%) scale(1);
167
+ }
168
+
169
+ button:active {
170
+ transform: translate(-50%, -50%) scale(99%);
171
+ }
172
+
173
+ &[hidden] button {
174
+ transform: translate(-50%, -50%) scale(0);
175
+ }
176
+ }
177
+
178
+ @keyframes content--editor--pulse {
179
+ 0% {
180
+ transform: translate(-50%, -50%) scale(0);
181
+ }
182
+
183
+ 80% {
184
+ transform: translate(-50%, -50%) scale(1.1);
185
+ }
186
+
187
+ 100% {
188
+ transform: translate(-50%, -50%) scale(1);
189
+ }
190
+ }
191
+
192
+ /* New items picker */
193
+
194
+ .content--editor--new-items {
195
+ .items-list {
196
+ display: flex;
197
+ gap: var(--space-2xs, 0.25rem);
198
+ flex-wrap: wrap;
199
+ }
200
+ }
@@ -1,196 +1,4 @@
1
- .content--editor {
2
- --row-inset: 2rem;
3
- --row-hover-bg: var(--color-tint, #fff0eb);
4
- --row-padding: 0 var(--space-2xs, 0.25rem);
5
-
6
- position: relative;
7
- }
8
-
9
- /* Rows */
10
-
11
- .content--editor--item {
12
- /* https://github.com/react-dnd/react-dnd/issues/832 */
13
- transform: translate3d(0, 0, 0);
14
-
15
- &:hover {
16
- background: var(--row-hover-bg);
17
- }
18
-
19
- &[draggable] {
20
- cursor: grab;
21
- }
22
-
23
- /* Dragged visuals */
24
-
25
- &[data-dragging] {
26
- box-shadow: inset 0 0 0 2px var(--color-tint, #fff0eb);
27
-
28
- > * {
29
- visibility: hidden;
30
- }
31
- }
32
-
33
- /* Hidden items */
34
-
35
- [data-invisible] {
36
- color: var(--color-mid, #aaa);
37
- }
38
-
39
- /* row item controller */
40
-
41
- > [data-controller] {
42
- display: grid;
43
- grid-template-columns: 1fr auto;
44
- align-items: baseline;
45
- padding: var(--row-padding);
46
- }
47
-
48
- /* row content */
49
-
50
- .tree {
51
- display: flex;
52
- gap: var(--space-xs, 0.5rem);
53
- align-items: baseline;
54
-
55
- text-overflow: ellipsis;
56
- overflow: hidden;
57
- white-space: nowrap;
58
- }
59
-
60
- .actions-group {
61
- display: flex;
62
- gap: var(--space-3xs, 0.25em);
63
- align-items: baseline;
64
- }
65
-
66
- /* Theme identifier */
67
-
68
- .item-background {
69
- display: inline-flex;
70
- background-color: var(--theme-background-color);
71
- color: var(--theme-text-color);
72
- aspect-ratio: 1;
73
- padding: var(--space-3xs);
74
- border-radius: var(--radius-s);
75
- }
76
-
77
- .item-background.light {
78
- --theme-text-color: black;
79
- --theme-background-color: white;
80
- }
81
-
82
- .item-background.dark {
83
- --theme-text-color: white;
84
- --theme-background-color: black;
85
- }
86
-
87
- /* Depth */
88
-
89
- &[data-content-depth="1"] .tree {
90
- padding-left: calc(var(--row-inset) * 1);
91
- }
92
-
93
- &[data-content-depth="2"] .tree {
94
- padding-left: calc(var(--row-inset) * 2);
95
- }
96
-
97
- &[data-content-depth="3"] .tree {
98
- padding-left: calc(var(--row-inset) * 3);
99
- }
100
-
101
- &[data-content-depth="4"] .tree {
102
- padding-left: calc(var(--row-inset) * 4);
103
- }
104
-
105
- &[data-content-depth="5"] .tree {
106
- padding-left: calc(var(--row-inset) * 5);
107
- }
108
-
109
- &[data-content-depth="6"] .tree {
110
- padding-left: calc(var(--row-inset) * 6);
111
- }
112
-
113
- /* Lower opacity for buttons you can't use */
114
-
115
- &[data-deny-de-nest] .button:has([data-icon="outdent"]),
116
- &[data-deny-nest] .button:has([data-icon="indent"]),
117
- &[data-deny-remove] .button:has([data-icon="remove"]),
118
- &[data-deny-drag] .button:has([data-icon="drag"]),
119
- &[data-deny-edit] .button:has([data-icon="edit"]) {
120
- opacity: 0.2;
121
- pointer-events: none;
122
- }
123
-
124
- &:not(:hover) .actions-group .button {
125
- opacity: 0;
126
- }
127
-
128
- /* Only show 1 of the collapse / expand button */
129
-
130
- &[data-deny-collapse] .button:has([data-icon="collapse"]),
131
- &[data-deny-expand] .button:has([data-icon="expand"]) {
132
- display: none !important;
133
- pointer-events: none;
134
- }
135
-
136
- /* Always show the expand button when there are hidden children */
137
-
138
- &:has([data-content-children]) .button:has([data-icon="expand"]) {
139
- opacity: 1 !important;
140
- }
141
- }
142
-
143
- /* Inline add link */
144
-
145
- .content--editor--inline-add {
146
- position: absolute;
147
- left: 0;
148
- right: 0;
149
- top: 0;
150
- height: 0;
151
- z-index: 1;
152
- outline: 1px solid var(--color-dark-glare, #555);
153
-
154
- button {
155
- position: absolute;
156
- top: 0;
157
- left: 50%;
158
- margin: 0;
159
- padding: 0.25rem;
160
-
161
- animation: content--editor--pulse 200ms ease;
162
- transform: translate(-50%, -50%) scale(1);
163
- }
164
-
165
- button:active {
166
- transform: translate(-50%, -50%) scale(99%);
167
- }
168
-
169
- &[hidden] button {
170
- transform: translate(-50%, -50%) scale(0);
171
- }
172
- }
173
-
174
- @keyframes content--editor--pulse {
175
- 0% {
176
- transform: translate(-50%, -50%) scale(0);
177
- }
178
-
179
- 80% {
180
- transform: translate(-50%, -50%) scale(1.1);
181
- }
182
-
183
- 100% {
184
- transform: translate(-50%, -50%) scale(1);
185
- }
186
- }
187
-
188
- /* New items picker */
189
-
190
- .content--editor--new-items {
191
- .items-list {
192
- display: flex;
193
- gap: var(--space-2xs, 0.25rem);
194
- flex-wrap: wrap;
195
- }
196
- }
1
+ @import url("editor/editor.css");
2
+ @import url("editor/icons.css");
3
+ @import url("editor/status-bar.css");
4
+ @import url("editor/table.css");
@@ -0,0 +1,46 @@
1
+ /* Spacing */
2
+
3
+ .content-item {
4
+ --flow-space: var(--space-m);
5
+ margin-block: var(--block-space, var(--space-m));
6
+ }
7
+
8
+ /* Themes */
9
+
10
+ [data-content-theme] {
11
+ display: flow-root;
12
+ }
13
+
14
+ [data-content-theme] [data-content-theme] {
15
+ margin-inline: calc(-1 * var(--gutter));
16
+ padding-inline: var(--gutter);
17
+ }
18
+
19
+ [data-content-theme="light"] {
20
+ background: var(--background, var(--background-color));
21
+ color: var(--text-color);
22
+
23
+ --text-color: var(--color-dark, black);
24
+ --background: var(--color-light, black);
25
+ --link-color: var(--color-dark, black);
26
+ }
27
+
28
+ [data-content-theme="dark"] {
29
+ background: var(--background, var(--background-color));
30
+ color: var(--text-color);
31
+
32
+ --text-color: var(--color-light, white);
33
+ --background: var(--color-dark, black);
34
+ --link-color: var(--color-light, white);
35
+ }
36
+
37
+ /* Figures */
38
+
39
+ .content-item[data-content-item-type="figure"] {
40
+ margin-inline: unset;
41
+ text-align: center;
42
+
43
+ img {
44
+ margin-inline: auto;
45
+ }
46
+ }
@@ -0,0 +1 @@
1
+ @import url("frontend/frontend.css");
@@ -4,9 +4,11 @@
4
4
 
5
5
  <strong class="heading" title="<%= item.heading %>"><%= item.heading %></strong>
6
6
 
7
- <span class="item-background <%= item.background %>">
8
- <icon class="icon" data-icon="theme" role="img">&nbsp;</icon>
9
- </span>
7
+ <% if item.theme.present? %>
8
+ <span class="item-theme" data-content-theme="<%= item.theme %>">
9
+ <icon class="icon" data-icon="theme" role="img">&nbsp;</icon>
10
+ </span>
11
+ <% end %>
10
12
 
11
13
  <% unless item.visible? %>
12
14
  <icon class="icon" data-icon="hidden" title="Hidden" role="img">&nbsp;</icon>
@@ -27,7 +29,7 @@
27
29
  <icon class="icon" data-icon="indent" role="img">&nbsp;</icon>
28
30
  </button>
29
31
  <%= link_to(edit_item_link, class: "button", title: "Edit", value: "edit",
30
- data: { text_button: "", button_padding: "tight", turbo_frame: "content--editor--item-editor" }) do %>
32
+ data: { text_button: "", button_padding: "tight", turbo_frame: "content--editor--item-editor" }) do %>
31
33
  <icon class="icon" data-icon="edit" role="img">&nbsp;</icon>
32
34
  <% end %>
33
35
  <button class="button" data-button-padding="tight" data-text-button data-action="content--editor--container#remove" title="Remove">
@@ -7,7 +7,7 @@
7
7
  data-button-padding="tight"
8
8
  data-text-button>
9
9
  <icon aria-hidden="true" class="icon" data-icon="add">&nbsp;</icon>
10
- Add item
10
+ <%= t(".add_item") %>
11
11
  </button>
12
12
  <div class="content--editor--inline-add" data-content--editor--new-items-target="inline" hidden>
13
13
  <button aria-controls="content--editor--new-items-dialog"
@@ -15,23 +15,25 @@
15
15
  data-action="content--editor--new-items#open"
16
16
  data-button-padding="tight">
17
17
  <icon aria-hidden="true" class="icon" data-icon="add">&nbsp;</icon>
18
- <span class="visually-hidden">Add item here</span>
18
+ <span class="visually-hidden"><%= t(".add_item_inline") %></span>
19
19
  </button>
20
20
  </div>
21
- <dialog id="content--editor--new-items-dialog" class="modal" data-action="click->content--editor--new-items#close">
22
- <article class="flow" data-action="click->content--editor--new-items#noop:stop">
21
+ <dialog id="content--editor--new-items-dialog" class="modal" data-action="click->content--editor--new-items#click">
22
+ <article class="flow">
23
23
  <header class="repel" data-nowrap>
24
- <h2>Add item</h2>
24
+ <h2><%= t(".add_item") %></h2>
25
25
  <button class="button"
26
26
  data-action="click->content--editor--new-items#close"
27
27
  data-button-padding="tight"
28
28
  data-text-button>
29
29
  <icon aria-hidden="true" class="icon" data-icon="close">&nbsp;</icon>
30
- <span class="visually-hidden">Close</span>
30
+ <span class="visually-hidden"><%= t(".close") %></span>
31
31
  </button>
32
32
  </header>
33
- <main>
34
- <%= tag.div(role: "list", data: { action: "click->content--editor--new-items#noop:stop" }) do %>
33
+ <main class="flow">
34
+ <% if content? %>
35
+ <%= content %>
36
+ <% else %>
35
37
  <ul role="list" class="items-list">
36
38
  <%= render Katalyst::Content::Editor::NewItemComponent.with_collection(items) %>
37
39
  </ul>
@@ -6,12 +6,36 @@ module Katalyst
6
6
  class NewItemsComponent < BaseComponent
7
7
  renders_many :items, Editor::NewItemComponent
8
8
 
9
+ attr_reader :item_types
10
+
11
+ def initialize(container:, **)
12
+ super
13
+
14
+ @item_types = Katalyst::Content.config.items.map do |item_class|
15
+ item_class.is_a?(String) ? item_class.safe_constantize : item_class
16
+ end.index_by do |item_class|
17
+ item_class.new.item_type.to_sym
18
+ end
19
+ end
20
+
9
21
  def items
10
- Katalyst::Content.config.items.map do |item_class|
11
- item_class = item_class.safe_constantize if item_class.is_a?(String)
22
+ item_types.values.map do |item_class|
12
23
  item_class.new(container:)
13
24
  end
14
25
  end
26
+
27
+ def item(type)
28
+ item = item_class_for(type).new(container:)
29
+ render Editor::NewItemComponent.new(item:)
30
+ end
31
+
32
+ private
33
+
34
+ def item_class_for(type)
35
+ item_types.fetch(type) do
36
+ raise ArgumentError, "Unknown type: #{type.inspect}, supported types: #{item_types.keys.join(', ')}"
37
+ end
38
+ end
15
39
  end
16
40
  end
17
41
  end
@@ -7,7 +7,8 @@
7
7
  <%= container.draft_items&.each { |item| list.with_item(item) } %>
8
8
  <% end %>
9
9
 
10
- <%= render Katalyst::Content::Editor::NewItemsComponent.new(container:) %>
10
+ <%= content %>
11
+ <%= new_items %>
11
12
  <% end %>
12
13
 
13
14
  <%= turbo_frame_tag(
@@ -5,6 +5,10 @@ module Katalyst
5
5
  class EditorComponent < Editor::BaseComponent
6
6
  include ::Turbo::FramesHelper
7
7
 
8
+ renders_one :new_items, -> do
9
+ Katalyst::Content::Editor::NewItemsComponent.new(container:)
10
+ end
11
+
8
12
  attr_reader :url, :scope
9
13
 
10
14
  def initialize(container:, url: [:admin, container], scope: :container, **)
@@ -18,12 +22,6 @@ module Katalyst
18
22
  Editor::StatusBarComponent.new(container:)
19
23
  end
20
24
 
21
- # @deprecated this component is now part of the editor
22
- def new_items
23
- # no-op, no longer required
24
- Class.new { define_method(:render_in) { |_| nil } }.new
25
- end
26
-
27
25
  def item_editor(item:)
28
26
  Editor::ItemEditorComponent.new(container:, item:)
29
27
  end
@@ -5,6 +5,8 @@ module Katalyst
5
5
  module FrontendHelper
6
6
  include TableHelper
7
7
 
8
+ using Katalyst::HtmlAttributes::HasHtmlAttributes
9
+
8
10
  # Render all items from a content version as HTML
9
11
  # @param version [Katalyst::Content::Version] The content version to render
10
12
  # @return [ActiveSupport::SafeBuffer,String,nil] Content as HTML
@@ -14,16 +16,24 @@ module Katalyst
14
16
  capture do
15
17
  cache version do
16
18
  without_partial_path_prefix do
17
- concat render partial: version.tree.select(&:visible?)
19
+ concat(render_content_items(*version.tree.select(&:visible?), class: "flow"))
18
20
  end
19
21
  end
20
22
  end
21
23
  end
22
24
 
23
- def render_content_items(items)
24
- items = items.select(&:visible?)
25
- without_partial_path_prefix do
26
- render partial: items if items.any?
25
+ def render_content_items(*items, tag: :div, theme: nil, **)
26
+ items = items.flatten.select(&:visible?)
27
+
28
+ grouped_items = items.slice_when { |first, second| first.theme != second.theme }
29
+
30
+ grouped_items.each do |siblings|
31
+ content_theme = (siblings.first.theme if siblings.first.theme != theme)
32
+ concat(content_items_tag(tag, content_theme:, **) do
33
+ without_partial_path_prefix do
34
+ concat(render(partial: siblings))
35
+ end
36
+ end)
27
37
  end
28
38
  end
29
39
 
@@ -31,7 +41,15 @@ module Katalyst
31
41
  FrontendBuilder.new(self, item).render(...)
32
42
  end
33
43
 
34
- private
44
+ def content_items_tag(tag, content_theme:, **attributes, &)
45
+ html_attributes = {
46
+ class: "content-items",
47
+ data: {
48
+ content_theme:,
49
+ },
50
+ }.merge_html(attributes)
51
+ content_tag(tag, **html_attributes, &)
52
+ end
35
53
 
36
54
  def without_partial_path_prefix
37
55
  current = prefix_partial_path_with_controller_namespace
@@ -44,6 +62,8 @@ module Katalyst
44
62
  end
45
63
 
46
64
  class FrontendBuilder
65
+ include Katalyst::HtmlAttributes
66
+
47
67
  attr_accessor :template, :item
48
68
 
49
69
  delegate_missing_to :@template
@@ -53,32 +73,28 @@ module Katalyst
53
73
  self.item = item
54
74
  end
55
75
 
56
- def render(**, &)
57
- content_tag tag, **default_options(**) do
58
- content_tag(:div, &)
59
- end
76
+ def render(tag: :div, **, &)
77
+ update_html_attributes(**)
78
+
79
+ content_tag(tag, **html_attributes, &)
60
80
  end
61
81
 
62
82
  private
63
83
 
64
- def default_options(**options)
84
+ def default_html_attributes
65
85
  {
66
- id: item.heading&.parameterize,
67
- class: ["content-item", item.model_name.param_key, item.background, options[:class]],
68
- data: { content_index: item.index, content_depth: item.depth, **options.fetch(:data, {}) },
69
- **options.except(:class, :data, :root),
86
+ id: item.dom_id,
87
+ class: ["content-item", wrapper_class],
88
+ data: {
89
+ content_index: item.index,
90
+ content_depth: item.depth,
91
+ content_item_type: item.item_type,
92
+ },
70
93
  }
71
94
  end
72
95
 
73
- def tag
74
- case item
75
- when Figure
76
- :figure
77
- when Section
78
- :section
79
- else
80
- :div
81
- end
96
+ def wrapper_class
97
+ "wrapper" if item.depth.zero?
82
98
  end
83
99
  end
84
100
  end