plutonium 0.49.1 → 0.51.0

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +85 -102
  3. data/.claude/skills/plutonium-app/SKILL.md +572 -0
  4. data/.claude/skills/plutonium-auth/SKILL.md +163 -300
  5. data/.claude/skills/plutonium-behavior/SKILL.md +838 -0
  6. data/.claude/skills/plutonium-resource/SKILL.md +1176 -0
  7. data/.claude/skills/plutonium-tenancy/SKILL.md +655 -0
  8. data/.claude/skills/plutonium-testing/SKILL.md +6 -5
  9. data/.claude/skills/plutonium-ui/SKILL.md +900 -0
  10. data/CHANGELOG.md +37 -0
  11. data/Rakefile +2 -1
  12. data/app/assets/plutonium.css +1 -11
  13. data/app/assets/plutonium.js +1323 -1184
  14. data/app/assets/plutonium.js.map +4 -4
  15. data/app/assets/plutonium.min.js +50 -49
  16. data/app/assets/plutonium.min.js.map +4 -4
  17. data/app/views/plutonium/_resource_header.html.erb +4 -4
  18. data/app/views/plutonium/_resource_sidebar.html.erb +9 -9
  19. data/app/views/resource/_resource_grid.html.erb +1 -0
  20. data/config/brakeman.ignore +25 -2
  21. data/docs/.vitepress/config.ts +37 -27
  22. data/docs/getting-started/index.md +22 -29
  23. data/docs/getting-started/installation.md +37 -80
  24. data/docs/getting-started/tutorial/index.md +4 -5
  25. data/docs/guides/adding-resources.md +66 -377
  26. data/docs/guides/authentication.md +94 -463
  27. data/docs/guides/authorization.md +124 -370
  28. data/docs/guides/creating-packages.md +94 -296
  29. data/docs/guides/custom-actions.md +121 -441
  30. data/docs/guides/index.md +22 -42
  31. data/docs/guides/multi-tenancy.md +116 -187
  32. data/docs/guides/nested-resources.md +103 -431
  33. data/docs/guides/search-filtering.md +123 -240
  34. data/docs/guides/testing.md +5 -4
  35. data/docs/guides/theming.md +157 -407
  36. data/docs/guides/troubleshooting.md +5 -3
  37. data/docs/guides/user-invites.md +106 -425
  38. data/docs/guides/user-profile.md +76 -243
  39. data/docs/index.md +1 -1
  40. data/docs/reference/app/generators.md +517 -0
  41. data/docs/reference/app/index.md +158 -0
  42. data/docs/reference/app/packages.md +146 -0
  43. data/docs/reference/app/portals.md +377 -0
  44. data/docs/reference/auth/accounts.md +230 -0
  45. data/docs/reference/auth/index.md +88 -0
  46. data/docs/reference/auth/profile.md +185 -0
  47. data/docs/reference/behavior/controllers.md +395 -0
  48. data/docs/reference/behavior/index.md +22 -0
  49. data/docs/reference/behavior/interactions.md +341 -0
  50. data/docs/reference/behavior/policies.md +417 -0
  51. data/docs/reference/index.md +56 -49
  52. data/docs/reference/resource/actions.md +423 -0
  53. data/docs/reference/resource/definition.md +508 -0
  54. data/docs/reference/resource/index.md +50 -0
  55. data/docs/reference/resource/model.md +348 -0
  56. data/docs/reference/resource/query.md +305 -0
  57. data/docs/reference/tenancy/entity-scoping.md +361 -0
  58. data/docs/reference/tenancy/index.md +36 -0
  59. data/docs/reference/tenancy/invites.md +393 -0
  60. data/docs/reference/tenancy/nested-resources.md +267 -0
  61. data/docs/reference/testing/index.md +287 -0
  62. data/docs/reference/ui/assets.md +400 -0
  63. data/docs/reference/ui/components.md +165 -0
  64. data/docs/reference/ui/displays.md +104 -0
  65. data/docs/reference/ui/forms.md +284 -0
  66. data/docs/reference/ui/index.md +30 -0
  67. data/docs/reference/ui/layouts.md +106 -0
  68. data/docs/reference/ui/pages.md +189 -0
  69. data/docs/reference/ui/tables.md +117 -0
  70. data/docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md +841 -0
  71. data/docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md.tasks.json +103 -0
  72. data/docs/superpowers/specs/2026-05-07-ui-layout-overhaul-design.md +270 -0
  73. data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
  74. data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
  75. data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
  76. data/gemfiles/rails_7.gemfile.lock +1 -1
  77. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  78. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  79. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +1 -0
  80. data/lib/generators/pu/invites/install_generator.rb +1 -0
  81. data/lib/generators/pu/lite/rails_pulse/rails_pulse_generator.rb +54 -5
  82. data/lib/plutonium/action/base.rb +44 -1
  83. data/lib/plutonium/action/interactive.rb +1 -1
  84. data/lib/plutonium/configuration.rb +4 -0
  85. data/lib/plutonium/definition/actions.rb +3 -0
  86. data/lib/plutonium/definition/base.rb +8 -0
  87. data/lib/plutonium/definition/index_views.rb +95 -0
  88. data/lib/plutonium/definition/metadata.rb +40 -0
  89. data/lib/plutonium/helpers/turbo_helper.rb +12 -1
  90. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
  91. data/lib/plutonium/interaction/response/redirect.rb +1 -1
  92. data/lib/plutonium/query/base.rb +8 -0
  93. data/lib/plutonium/query/filters/association.rb +30 -8
  94. data/lib/plutonium/query/filters/boolean.rb +5 -0
  95. data/lib/plutonium/resource/controller.rb +1 -0
  96. data/lib/plutonium/resource/controllers/crud_actions.rb +19 -1
  97. data/lib/plutonium/resource/controllers/presentable.rb +11 -2
  98. data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
  99. data/lib/plutonium/resource/definition.rb +42 -0
  100. data/lib/plutonium/resource/policy.rb +7 -0
  101. data/lib/plutonium/resource/query_object.rb +64 -6
  102. data/lib/plutonium/routing/mapper_extensions.rb +15 -0
  103. data/lib/plutonium/testing/resource_definition.rb +2 -2
  104. data/lib/plutonium/ui/action_button.rb +4 -2
  105. data/lib/plutonium/ui/component/kit.rb +12 -0
  106. data/lib/plutonium/ui/component/methods.rb +4 -0
  107. data/lib/plutonium/ui/display/base.rb +3 -1
  108. data/lib/plutonium/ui/display/resource.rb +109 -25
  109. data/lib/plutonium/ui/display/theme.rb +2 -1
  110. data/lib/plutonium/ui/dyna_frame/content.rb +8 -14
  111. data/lib/plutonium/ui/empty_card.rb +1 -1
  112. data/lib/plutonium/ui/form/base.rb +35 -3
  113. data/lib/plutonium/ui/form/components/hidden_wrapper.rb +25 -0
  114. data/lib/plutonium/ui/form/components/json.rb +58 -0
  115. data/lib/plutonium/ui/form/components/resource_select.rb +133 -1
  116. data/lib/plutonium/ui/form/components/secure_association.rb +105 -24
  117. data/lib/plutonium/ui/form/components/sticky_footer.rb +17 -0
  118. data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
  119. data/lib/plutonium/ui/form/resource.rb +45 -10
  120. data/lib/plutonium/ui/form/theme.rb +1 -1
  121. data/lib/plutonium/ui/frame_navigator_panel.rb +7 -4
  122. data/lib/plutonium/ui/grid/card.rb +235 -0
  123. data/lib/plutonium/ui/grid/resource.rb +149 -0
  124. data/lib/plutonium/ui/layout/base.rb +38 -1
  125. data/lib/plutonium/ui/layout/header.rb +1 -2
  126. data/lib/plutonium/ui/layout/icon_rail.rb +212 -0
  127. data/lib/plutonium/ui/layout/resource_layout.rb +10 -3
  128. data/lib/plutonium/ui/layout/sidebar.rb +12 -24
  129. data/lib/plutonium/ui/layout/topbar.rb +100 -0
  130. data/lib/plutonium/ui/modal/base.rb +109 -0
  131. data/lib/plutonium/ui/modal/centered.rb +21 -0
  132. data/lib/plutonium/ui/modal/slideover.rb +26 -0
  133. data/lib/plutonium/ui/page/base.rb +18 -6
  134. data/lib/plutonium/ui/page/edit.rb +13 -1
  135. data/lib/plutonium/ui/page/index.rb +40 -1
  136. data/lib/plutonium/ui/page/interactive_action.rb +8 -39
  137. data/lib/plutonium/ui/page/new.rb +13 -1
  138. data/lib/plutonium/ui/page/show.rb +8 -1
  139. data/lib/plutonium/ui/page_header.rb +8 -13
  140. data/lib/plutonium/ui/panel.rb +10 -19
  141. data/lib/plutonium/ui/sidebar_menu.rb +2 -25
  142. data/lib/plutonium/ui/tab_list.rb +29 -7
  143. data/lib/plutonium/ui/table/base.rb +106 -0
  144. data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +12 -4
  145. data/lib/plutonium/ui/table/components/filter_form.rb +171 -0
  146. data/lib/plutonium/ui/table/components/filter_pills.rb +89 -0
  147. data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +13 -12
  148. data/lib/plutonium/ui/table/components/scopes_pills.rb +67 -0
  149. data/lib/plutonium/ui/table/components/selection_column.rb +2 -11
  150. data/lib/plutonium/ui/table/components/toolbar.rb +104 -0
  151. data/lib/plutonium/ui/table/components/view_switcher.rb +81 -0
  152. data/lib/plutonium/ui/table/resource.rb +158 -89
  153. data/lib/plutonium/ui/table/theme.rb +14 -5
  154. data/lib/plutonium/version.rb +1 -1
  155. data/lib/plutonium.rb +14 -0
  156. data/lib/tasks/release.rake +15 -1
  157. data/package.json +10 -10
  158. data/src/css/components.css +304 -131
  159. data/src/css/slim_select.css +4 -0
  160. data/src/css/tokens.css +101 -85
  161. data/src/js/controllers/autosubmit_controller.js +24 -0
  162. data/src/js/controllers/bulk_actions_controller.js +15 -16
  163. data/src/js/controllers/capture_url_controller.js +14 -0
  164. data/src/js/controllers/filter_panel_controller.js +77 -19
  165. data/src/js/controllers/frame_navigator_controller.js +34 -6
  166. data/src/js/controllers/icon_rail_controller.js +22 -0
  167. data/src/js/controllers/icon_rail_flyout_controller.js +128 -0
  168. data/src/js/controllers/register_controllers.js +16 -0
  169. data/src/js/controllers/resource_tab_list_controller.js +56 -3
  170. data/src/js/controllers/row_click_controller.js +21 -0
  171. data/src/js/controllers/slim_select_controller.js +61 -0
  172. data/src/js/controllers/table_column_menu_controller.js +43 -0
  173. data/src/js/controllers/table_header_controller.js +16 -0
  174. data/src/js/controllers/view_switcher_controller.js +29 -0
  175. data/src/js/turbo/turbo_actions.js +33 -0
  176. data/yarn.lock +553 -543
  177. metadata +71 -32
  178. data/.claude/skills/plutonium-assets/SKILL.md +0 -512
  179. data/.claude/skills/plutonium-controller/SKILL.md +0 -396
  180. data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
  181. data/.claude/skills/plutonium-definition/SKILL.md +0 -1138
  182. data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
  183. data/.claude/skills/plutonium-forms/SKILL.md +0 -465
  184. data/.claude/skills/plutonium-installation/SKILL.md +0 -325
  185. data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
  186. data/.claude/skills/plutonium-invites/SKILL.md +0 -408
  187. data/.claude/skills/plutonium-model/SKILL.md +0 -440
  188. data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
  189. data/.claude/skills/plutonium-package/SKILL.md +0 -198
  190. data/.claude/skills/plutonium-policy/SKILL.md +0 -456
  191. data/.claude/skills/plutonium-portal/SKILL.md +0 -410
  192. data/.claude/skills/plutonium-views/SKILL.md +0 -592
  193. data/docs/reference/assets/index.md +0 -496
  194. data/docs/reference/controller/index.md +0 -412
  195. data/docs/reference/definition/actions.md +0 -449
  196. data/docs/reference/definition/fields.md +0 -383
  197. data/docs/reference/definition/index.md +0 -268
  198. data/docs/reference/definition/query.md +0 -351
  199. data/docs/reference/generators/index.md +0 -648
  200. data/docs/reference/interaction/index.md +0 -449
  201. data/docs/reference/model/features.md +0 -248
  202. data/docs/reference/model/index.md +0 -218
  203. data/docs/reference/policy/index.md +0 -456
  204. data/docs/reference/portal/index.md +0 -379
  205. data/docs/reference/views/forms.md +0 -411
  206. data/docs/reference/views/index.md +0 -501
@@ -1,501 +0,0 @@
1
- # Views Reference
2
-
3
- Complete reference for UI customization with Phlex components.
4
-
5
- ## Overview
6
-
7
- Plutonium uses [Phlex](https://www.phlex.fun/) for all view components. Customization happens through:
8
- - Definition nested classes (pages, forms, tables, displays)
9
- - Page hooks for injecting content
10
- - Custom Phlex components
11
- - ERB view overrides
12
-
13
- ## Architecture
14
-
15
- ```
16
- Definition
17
- ├── IndexPage → renders Table
18
- ├── ShowPage → renders Display
19
- ├── NewPage → renders Form
20
- ├── EditPage → renders Form
21
- └── InteractiveActionPage → renders Interaction Form
22
- ```
23
-
24
- ## Page Configuration
25
-
26
- Set titles and descriptions in definitions:
27
-
28
- ```ruby
29
- class PostDefinition < ResourceDefinition
30
- # Page titles
31
- index_page_title "Blog Posts"
32
- index_page_description "Manage all published articles"
33
-
34
- show_page_title "Article Details"
35
- new_page_title "Write New Article"
36
- edit_page_title "Edit Article"
37
-
38
- # Breadcrumbs
39
- breadcrumbs true # Global default
40
- index_page_breadcrumbs false # Per-page override
41
- show_page_breadcrumbs true
42
- end
43
- ```
44
-
45
- ## Custom Page Classes
46
-
47
- Override page rendering by defining nested classes in your definition:
48
-
49
- ```ruby
50
- class PostDefinition < ResourceDefinition
51
- class ShowPage < ShowPage
52
- private
53
-
54
- # Custom title logic
55
- def page_title
56
- "#{object.title} - #{object.author.name}"
57
- end
58
-
59
- # Add content before the main area
60
- def render_before_content
61
- div(class: "alert alert-info") {
62
- "This post has #{object.comments.count} comments"
63
- }
64
- end
65
-
66
- # Add content after
67
- def render_after_content
68
- render RelatedPostsComponent.new(post: object)
69
- end
70
-
71
- # Override the toolbar
72
- def render_toolbar
73
- div(class: "flex gap-2") {
74
- button(class: "btn") { "Preview" }
75
- button(class: "btn btn-primary") { "Publish" }
76
- }
77
- end
78
- end
79
- end
80
- ```
81
-
82
- ## Page Hooks
83
-
84
- All pages inherit these customization hooks:
85
-
86
- | Hook | Purpose |
87
- |------|---------|
88
- | `render_before_header` | Before entire header section |
89
- | `render_after_header` | After entire header section |
90
- | `render_before_breadcrumbs` | Before breadcrumbs |
91
- | `render_after_breadcrumbs` | After breadcrumbs |
92
- | `render_before_page_header` | Before title/actions |
93
- | `render_after_page_header` | After title/actions |
94
- | `render_before_toolbar` | Before toolbar |
95
- | `render_after_toolbar` | After toolbar |
96
- | `render_before_content` | Before main content |
97
- | `render_after_content` | After main content |
98
- | `render_before_footer` | Before footer |
99
- | `render_after_footer` | After footer |
100
-
101
- ## Form Customization
102
-
103
- Override form rendering in your definition:
104
-
105
- ```ruby
106
- class PostDefinition < ResourceDefinition
107
- class Form < Form
108
- def form_template
109
- # Custom layout with sections
110
- div(class: "grid grid-cols-2 gap-6") {
111
- div {
112
- h3(class: "text-lg font-medium") { "Basic Info" }
113
- render_resource_field :title
114
- render_resource_field :slug
115
- }
116
-
117
- div {
118
- h3(class: "text-lg font-medium") { "Content" }
119
- render_resource_field :content
120
- }
121
- }
122
-
123
- div(class: "mt-6") {
124
- h3(class: "text-lg font-medium") { "Publishing" }
125
- render_resource_field :published_at
126
- render_resource_field :category
127
- }
128
-
129
- render_actions
130
- end
131
- end
132
- end
133
- ```
134
-
135
- ### Form Methods
136
-
137
- | Method | Purpose |
138
- |--------|---------|
139
- | `render_fields` | Render all permitted fields |
140
- | `render_resource_field(name)` | Render a single field |
141
- | `render_actions` | Render submit buttons |
142
- | `record` / `object` | The form object |
143
- | `resource_fields` | List of permitted field names |
144
- | `resource_definition` | The definition instance |
145
-
146
- ## Display Customization
147
-
148
- Override show page detail rendering:
149
-
150
- ```ruby
151
- class PostDefinition < ResourceDefinition
152
- class Display < Display
153
- def display_template
154
- # Hero section
155
- div(class: "bg-gradient-to-r from-blue-500 to-purple-600 p-8 rounded-lg text-white mb-6") {
156
- h1(class: "text-3xl font-bold") { object.title }
157
- p(class: "mt-2 opacity-90") { object.excerpt }
158
- }
159
-
160
- # Main content
161
- Block do
162
- fields_wrapper do
163
- render_resource_field :author
164
- render_resource_field :published_at
165
- render_resource_field :category
166
- end
167
- end
168
-
169
- # Full-width content
170
- Block do
171
- div(class: "prose max-w-none") {
172
- raw object.content
173
- }
174
- end
175
-
176
- # Associations (tabs)
177
- render_associations if present_associations?
178
- end
179
- end
180
- end
181
- ```
182
-
183
- ### Display Methods
184
-
185
- | Method | Purpose |
186
- |--------|---------|
187
- | `render_fields` | Render all permitted fields in a block |
188
- | `render_resource_field(name)` | Render single field |
189
- | `render_associations` | Render association tabs |
190
- | `object` | The record being displayed |
191
- | `resource_fields` | List of permitted field names |
192
- | `resource_associations` | List of permitted associations |
193
-
194
- ## Table Customization
195
-
196
- Override index page table:
197
-
198
- ```ruby
199
- class PostDefinition < ResourceDefinition
200
- class Table < Table
201
- def view_template
202
- render_search_bar
203
- render_scopes_bar
204
-
205
- if collection.empty?
206
- render_empty_card
207
- else
208
- # Custom card grid instead of table
209
- div(class: "grid grid-cols-3 gap-4") {
210
- collection.each do |post|
211
- render PostCardComponent.new(post:)
212
- end
213
- }
214
- end
215
-
216
- render_footer
217
- end
218
- end
219
- end
220
- ```
221
-
222
- ### Table Methods
223
-
224
- | Method | Purpose |
225
- |--------|---------|
226
- | `render_search_bar` | Search input |
227
- | `render_scopes_bar` | Scope tabs |
228
- | `render_table` | Default table |
229
- | `render_empty_card` | Empty state |
230
- | `render_footer` | Pagination |
231
- | `collection` | The paginated records |
232
- | `resource_fields` | Column field names |
233
-
234
- ## Component Kit
235
-
236
- Plutonium provides shorthand methods for common components:
237
-
238
- ```ruby
239
- class MyPage < Plutonium::UI::Page::Base
240
- def view_template
241
- # These are automatically rendered
242
- PageHeader(title: "Dashboard")
243
-
244
- Panel(class: "mt-4") {
245
- p { "Content here" }
246
- }
247
-
248
- Block {
249
- TabList(items: tabs)
250
- }
251
-
252
- EmptyCard("No items found")
253
-
254
- ActionButton(action, url: "/posts/new")
255
- end
256
- end
257
- ```
258
-
259
- Available kit methods:
260
-
261
- | Method | Purpose |
262
- |--------|---------|
263
- | `Breadcrumbs()` | Navigation breadcrumbs |
264
- | `PageHeader(title:, description:, actions:)` | Page header with actions |
265
- | `Panel(**attrs)` | Content panel |
266
- | `Block(**attrs)` | Content block |
267
- | `TabList(items:)` | Tab navigation |
268
- | `EmptyCard(message)` | Empty state card |
269
- | `ActionButton(action, url:)` | Action button |
270
- | `DynaFrameHost(src:, loading:)` | Lazy-loading turbo frame |
271
- | `DynaFrameContent(content) { \|frame\| ... }` | Frame-aware content wrapper |
272
- | `TableSearchBar()` | Search bar for tables |
273
- | `TableScopesBar()` | Scope tabs for tables |
274
- | `TableInfo(pagy)` | Pagination info |
275
- | `TablePagination(pagy)` | Pagination links |
276
- | `FrameNavigatorPanel(title:, src:, panel_id:)` | Frame navigation panel |
277
-
278
- ## DynaFrameContent Pattern
279
-
280
- `DynaFrameContent` enables frame-aware rendering. For turbo-frame requests, only the content is rendered inside the frame. For regular requests, the full page renders with header/footer.
281
-
282
- ```ruby
283
- # How Page::Base uses DynaFrameContent
284
- def view_template(&block)
285
- DynaFrameContent(page_content(block)) do |frame|
286
- render_header # Skipped for frame requests
287
- frame.render_content # Always rendered (wrapped in turbo-frame for frame requests)
288
- render_footer # Skipped for frame requests
289
- end
290
- end
291
- ```
292
-
293
- This pattern means:
294
- - **Regular page load**: Full page with header, content, footer
295
- - **Turbo-frame request**: Only `<turbo-frame id="...">` with content inside
296
-
297
- All pages automatically inherit this behavior - modals, lazy-loaded frames, and navigation all work correctly without special handling.
298
-
299
- ## Custom Components
300
-
301
- ### Creating a Phlex Component
302
-
303
- ```ruby
304
- # app/components/post_card_component.rb
305
- class PostCardComponent < Plutonium::UI::Component::Base
306
- def initialize(post:)
307
- @post = post
308
- end
309
-
310
- def view_template
311
- div(class: "bg-white rounded-lg shadow p-4") {
312
- h3(class: "font-bold") { @post.title }
313
- p(class: "text-gray-600 mt-2") { @post.excerpt }
314
-
315
- div(class: "mt-4 flex justify-between items-center") {
316
- span(class: "text-sm text-gray-500") { @post.published_at&.strftime("%B %d, %Y") }
317
- a(href: resource_url_for(@post), class: "text-blue-600") { "Read more" }
318
- }
319
- }
320
- end
321
- end
322
- ```
323
-
324
- ### Using Components in Definitions
325
-
326
- ```ruby
327
- class PostDefinition < ResourceDefinition
328
- # Custom display component
329
- display :status, as: StatusBadgeComponent
330
-
331
- # Custom input component
332
- input :color, as: ColorPickerComponent
333
-
334
- # Block with component
335
- display :metrics do |field|
336
- MetricsChartComponent.new(data: field.value)
337
- end
338
- end
339
- ```
340
-
341
- ## Layout Customization
342
-
343
- ### Custom Layout Class
344
-
345
- ```ruby
346
- # packages/admin_portal/app/views/layouts/admin_portal/resource_layout.rb
347
- module AdminPortal
348
- class ResourceLayout < Plutonium::UI::Layout::ResourceLayout
349
- private
350
-
351
- # Custom main content area classes
352
- def main_attributes
353
- mix(super, { class: "pt-20 lg:ml-64" })
354
- end
355
-
356
- # Add custom header content
357
- def render_before_main
358
- super
359
- render AnnouncementBanner.new if Announcement.active.any?
360
- end
361
-
362
- # Custom scripts
363
- def render_body_scripts
364
- super
365
- script(src: "/custom-analytics.js")
366
- end
367
- end
368
- end
369
- ```
370
-
371
- ### Layout Hooks
372
-
373
- | Hook | Purpose |
374
- |------|---------|
375
- | `render_before_main` | Before main content area |
376
- | `render_after_main` | After main (modals, etc.) |
377
- | `render_head` | HTML head section |
378
- | `render_title` | Page title tag |
379
- | `render_assets` | CSS/JS assets |
380
- | `render_body_scripts` | Scripts at end of body |
381
-
382
- ## Custom ERB Views
383
-
384
- For complete control, create custom ERB view files:
385
-
386
- ```
387
- # Main app (for a PostsController)
388
- app/views/posts/index.html.erb
389
- app/views/posts/show.html.erb
390
-
391
- # Portal-specific
392
- packages/admin_portal/app/views/admin_portal/posts/show.html.erb
393
- ```
394
-
395
- The default views render the page class:
396
-
397
- ```erb
398
- <%# app/views/resource/show.html.erb %>
399
- <%= render current_definition.show_page_class.new %>
400
- ```
401
-
402
- Custom view example:
403
-
404
- ```erb
405
- <%# app/views/posts/show.html.erb %>
406
- <div class="max-w-4xl mx-auto">
407
- <article class="prose lg:prose-xl">
408
- <h1><%= resource_record!.title %></h1>
409
- <%= raw resource_record!.content %>
410
- </article>
411
-
412
- <div class="mt-8">
413
- <%= link_to "Edit", resource_url_for(resource_record!, action: :edit), class: "btn" %>
414
- <%= link_to "Back", resource_url_for(Post), class: "btn" %>
415
- </div>
416
- </div>
417
- ```
418
-
419
- ## Available Context
420
-
421
- ### Resource Methods
422
-
423
- | Method | Description |
424
- |--------|-------------|
425
- | `resource_class` | The model class (e.g., `Post`) |
426
- | `resource_record!` | Current record (raises if not found) |
427
- | `resource_record?` | Current record (nil if not found) |
428
- | `current_parent` | Parent record for nested routes |
429
- | `current_scoped_entity` | Entity for multi-tenant portals |
430
-
431
- ### Definition & Policy
432
-
433
- | Method | Description |
434
- |--------|-------------|
435
- | `current_definition` | Definition instance for current resource |
436
- | `current_policy` | Policy instance for current record |
437
- | `current_authorized_scope` | Scoped collection user can access |
438
-
439
- ### URL Helpers
440
-
441
- | Method | Description |
442
- |--------|-------------|
443
- | `resource_url_for(record)` | URL for a record |
444
- | `resource_url_for(record, action: :edit)` | Action URL for record |
445
- | `resource_url_for(Model)` | Index URL for model |
446
- | `resource_url_for(Model, action: :new)` | New URL for model |
447
- | `resource_url_for(record, parent: parent)` | Nested resource URL |
448
-
449
- ### Display Helpers
450
-
451
- | Method | Description |
452
- |--------|-------------|
453
- | `display_name_of(record)` | Human-readable name for record |
454
- | `resource_name(klass)` | Singular model name |
455
- | `resource_name_plural(klass)` | Plural model name |
456
-
457
- ### In Phlex Components
458
-
459
- ```ruby
460
- class MyComponent < Plutonium::UI::Component::Base
461
- def view_template
462
- # Plutonium methods work directly
463
- current_user
464
- resource_record!
465
- resource_url_for(@post)
466
-
467
- # Rails helpers via helpers proxy
468
- helpers.link_to(...)
469
- helpers.image_tag(...)
470
- end
471
- end
472
- ```
473
-
474
- ## Portal-Specific Views
475
-
476
- Each portal can override views:
477
-
478
- ```ruby
479
- # Base definition
480
- class PostDefinition < ResourceDefinition
481
- class ShowPage < ShowPage
482
- # Default behavior
483
- end
484
- end
485
-
486
- # Admin portal override
487
- class AdminPortal::PostDefinition < ::PostDefinition
488
- class ShowPage < ShowPage # Inherits from ::PostDefinition::ShowPage
489
- def render_after_content
490
- super
491
- render AdminOnlySection.new(post: object)
492
- end
493
- end
494
- end
495
- ```
496
-
497
- ## Related
498
-
499
- - [Forms Reference](./forms) - Custom form templates and field builders
500
- - [Theming Guide](/guides/theming) - TailwindCSS and styling
501
- - [Fields Reference](/reference/definition/fields) - Field configuration