plutonium 0.23.4 → 0.23.5

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/plutonium.css +2 -2
  3. data/config/initializers/sqlite_json_alias.rb +1 -1
  4. data/docs/.vitepress/config.ts +60 -19
  5. data/docs/guide/cursor-rules.md +75 -0
  6. data/docs/guide/deep-dive/authorization.md +189 -0
  7. data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
  8. data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
  9. data/docs/guide/index.md +28 -0
  10. data/docs/guide/introduction/02-core-concepts.md +440 -0
  11. data/docs/guide/tutorial/01-project-setup.md +75 -0
  12. data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
  13. data/docs/guide/tutorial/03-defining-resources.md +90 -0
  14. data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
  15. data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
  16. data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
  17. data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
  18. data/docs/index.md +24 -31
  19. data/docs/modules/action.md +190 -0
  20. data/docs/modules/authentication.md +236 -0
  21. data/docs/modules/configuration.md +599 -0
  22. data/docs/modules/controller.md +398 -0
  23. data/docs/modules/core.md +316 -0
  24. data/docs/modules/definition.md +876 -0
  25. data/docs/modules/display.md +759 -0
  26. data/docs/modules/form.md +605 -0
  27. data/docs/modules/generator.md +288 -0
  28. data/docs/modules/index.md +167 -0
  29. data/docs/modules/interaction.md +470 -0
  30. data/docs/modules/package.md +151 -0
  31. data/docs/modules/policy.md +176 -0
  32. data/docs/modules/portal.md +710 -0
  33. data/docs/modules/query.md +287 -0
  34. data/docs/modules/resource_record.md +618 -0
  35. data/docs/modules/routing.md +641 -0
  36. data/docs/modules/table.md +293 -0
  37. data/docs/modules/ui.md +631 -0
  38. data/docs/public/plutonium.mdc +667 -0
  39. data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
  40. data/lib/plutonium/ui/display/resource.rb +7 -2
  41. data/lib/plutonium/ui/table/resource.rb +8 -3
  42. data/lib/plutonium/version.rb +1 -1
  43. metadata +36 -9
  44. data/docs/guide/getting-started/authorization.md +0 -296
  45. data/docs/guide/getting-started/core-concepts.md +0 -432
  46. data/docs/guide/getting-started/index.md +0 -21
  47. data/docs/guide/tutorial.md +0 -401
  48. /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
@@ -0,0 +1,631 @@
1
+ ---
2
+ title: UI Module
3
+ ---
4
+
5
+ # UI Module
6
+
7
+ The UI module provides a comprehensive set of user interface components and layouts for Plutonium applications. Built on top of Phlex, it offers a component-based architecture with consistent theming, responsive design, and modern web interactions.
8
+
9
+ ::: tip
10
+ The UI module is located in `lib/plutonium/ui/`.
11
+ :::
12
+
13
+ ## Overview
14
+
15
+ - **Component-Based Architecture**: Phlex-powered components with automatic rendering.
16
+ - **Responsive Layouts**: Mobile-first responsive design patterns.
17
+ - **Theme System**: Consistent styling with dark mode support.
18
+ - **Modern Interactions**: Hotwire/Turbo integration for dynamic experiences.
19
+
20
+ ## Core Architecture
21
+
22
+ ### Component Base & Behaviour
23
+
24
+ All UI components inherit from `Plutonium::UI::Component::Base`, which is built on Phlex. It automatically includes `Plutonium::UI::Component::Behaviour`, providing shared functionality.
25
+
26
+ ::: code-group
27
+ ```ruby [Component Base]
28
+ # lib/plutonium/ui/component/base.rb
29
+ # All components inherit from this base class.
30
+ class Plutonium::UI::Component::Base < Phlex::HTML
31
+ include Plutonium::UI::Component::Behaviour
32
+ end
33
+ ```
34
+ ```ruby [Component Behaviour]
35
+ # lib/plutonium/ui/component/behaviour.rb
36
+ # This concern provides shared logic.
37
+ module Plutonium::UI::Component::Behaviour
38
+ extend ActiveSupport::Concern
39
+ include Plutonium::UI::Component::Methods # Helper methods
40
+ include Plutonium::UI::Component::Kit # `render ComponentName(...)` syntax
41
+ # ... and more
42
+ end
43
+ ```
44
+ ```ruby [Example Component]
45
+ class MyComponent < Plutonium::UI::Component::Base
46
+ def initialize(title:)
47
+ @title = title
48
+ end
49
+
50
+ def view_template
51
+ div(class: "my-component") do
52
+ h2 { @title }
53
+ end
54
+ end
55
+ end
56
+ ```
57
+ :::
58
+
59
+ ### Component Kit
60
+
61
+ The `ComponentKit` allows you to render components using a conventional `ComponentName(...)` syntax from within another component, which automatically builds and renders the component.
62
+
63
+ ::: code-group
64
+ ```ruby [Usage]
65
+ class MyView < Plutonium::UI::Component::Base
66
+ def view_template
67
+ # These methods automatically instantiate and render components
68
+ # defined in the Kit.
69
+ PageHeader(title: "Dashboard")
70
+ Panel { "My panel content" }
71
+ end
72
+ end
73
+ ```
74
+ ```ruby [Implementation]
75
+ # lib/plutonium/ui/component/kit.rb
76
+ module Plutonium::UI::Component::Kit
77
+ # Uses method_missing to find a corresponding Build* method.
78
+ def method_missing(method_name, *args, **kwargs, &block)
79
+ build_method = "Build#{method_name}"
80
+ if self.class.method_defined?(build_method)
81
+ render send(build_method, *args, **kwargs, &block)
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+ # Builders are defined for all core UI components.
88
+ def BuildPageHeader(...) = Plutonium::UI::PageHeader.new(...)
89
+ def BuildPanel(...) = Plutonium::UI::Panel.new(...)
90
+ # ... and many more
91
+ end
92
+ ```
93
+ :::
94
+
95
+ ## Layout System
96
+
97
+ Plutonium provides a flexible layout system built on Phlex components.
98
+
99
+ ### Base Layout
100
+
101
+ The `Plutonium::UI::Layout::Base` class is the foundation for all layouts. It renders the `<html>`, `<head>`, and `<body>` tags and provides hooks for customization.
102
+
103
+ ::: details Base Layout Structure
104
+ ```ruby
105
+ class Plutonium::UI::Layout::Base < Plutonium::UI::Component::Base
106
+ def view_template(&block)
107
+ doctype
108
+ html do
109
+ render_head
110
+ render_body(&block)
111
+ end
112
+ end
113
+
114
+ def render_head
115
+ head do
116
+ render_title
117
+ render_metatags # CSRF, CSP, Turbo
118
+ render_assets # CSS, JS, favicon
119
+ end
120
+ end
121
+
122
+ def render_body(&block)
123
+ body do
124
+ render_before_main
125
+ render_main(&block)
126
+ render_after_main
127
+ end
128
+ end
129
+
130
+ def render_main(&block)
131
+ main do
132
+ render_flash
133
+ render_content(&block)
134
+ end
135
+ end
136
+ end
137
+ ```
138
+ :::
139
+
140
+ ### Specialized Layouts
141
+
142
+ Plutonium provides specialized layouts for different contexts.
143
+
144
+ ::: code-group
145
+ ```ruby [Resource Layout]
146
+ # lib/plutonium/ui/layout/resource_layout.rb
147
+ # Used for resource CRUD pages.
148
+ # Includes a fixed header and a sidebar.
149
+ class Plutonium::UI::Layout::ResourceLayout < Base
150
+ def render_before_main
151
+ render partial("resource_header")
152
+ render partial("resource_sidebar")
153
+ end
154
+ end
155
+ ```
156
+ ```ruby [Rodauth Layout]
157
+ # lib/plutonium/ui/layout/rodauth_layout.rb
158
+ # A centered layout for authentication pages (login, signup, etc.).
159
+ class Plutonium::UI::Layout::RodauthLayout < Base
160
+ def render_main(&block)
161
+ main(class: "flex flex-col items-center justify-center") do
162
+ render_logo
163
+ div(class: "w-full sm:max-w-md", &block)
164
+ end
165
+ end
166
+ end
167
+ ```
168
+ :::
169
+
170
+ ## Page Components
171
+
172
+ Page components structure the content within a layout.
173
+
174
+ ::: code-group
175
+ ```ruby [Base Page]
176
+ # lib/plutonium/ui/page/base.rb
177
+ # The foundation for all page components.
178
+ class Plutonium::UI::Page::Base < Plutonium::UI::Component::Base
179
+ def initialize(page_title: nil, page_description: nil, page_actions: nil)
180
+ # ...
181
+ end
182
+
183
+ def view_template(&block)
184
+ PageHeader(
185
+ title: -> { page_title },
186
+ description: -> { page_description },
187
+ actions: -> { page_actions }
188
+ )
189
+ render_default_content(&block)
190
+ end
191
+ end
192
+ ```
193
+ ```ruby [Resource Index Page]
194
+ # lib/plutonium/ui/page/resource/index.rb
195
+ # Renders the main table for a resource index.
196
+ class Plutonium::UI::Page::Resource::Index < Base
197
+ def render_default_content
198
+ ResourceTable(
199
+ records: @resource_records,
200
+ pagy: @pagy
201
+ )
202
+ end
203
+ end
204
+ ```
205
+ ```ruby [Resource Form Page]
206
+ # lib/plutonium/ui/page/resource/form.rb
207
+ # Renders the form for a new/edit resource page.
208
+ class Plutonium::UI::Page::Resource::Form < Base
209
+ def render_default_content
210
+ ResourceForm(
211
+ current_record,
212
+ resource_definition: current_definition
213
+ )
214
+ end
215
+ end
216
+ ```
217
+ :::
218
+
219
+ ## Navigation Components
220
+
221
+ ### Header (`lib/plutonium/ui/layout/header.rb`)
222
+
223
+ Responsive application header with brand and actions using Phlex::Slotable:
224
+
225
+ ```ruby
226
+ class Plutonium::UI::Layout::Header < Base
227
+ include Phlex::Slotable
228
+ include Phlex::Rails::Helpers::Routes
229
+
230
+ # Define slots for flexible content
231
+ slot :brand_name
232
+ slot :brand_logo
233
+ slot :action, collection: true
234
+
235
+ def view_template
236
+ nav(
237
+ class: "bg-white border-b border-gray-200 px-4 py-2.5 dark:bg-gray-800 dark:border-gray-700 fixed left-0 right-0 top-0 z-50",
238
+ data: {
239
+ controller: "resource-header",
240
+ resource_header_sidebar_outlet: "#sidebar-navigation"
241
+ }
242
+ ) do
243
+ div(class: "flex flex-wrap justify-between items-center") do
244
+ render_brand_section
245
+ render_actions if action_slots?
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ # Usage
252
+ Header.new do |header|
253
+ header.with_brand_logo { resource_logo_tag(classname: "h-10") }
254
+
255
+ header.with_action do
256
+ NavGridMenu.new(label: "Apps", icon: Phlex::TablerIcons::Apps) do |menu|
257
+ menu.with_item(name: "Dashboard", icon: Phlex::TablerIcons::Dashboard, href: "/")
258
+ menu.with_item(name: "Settings", icon: Phlex::TablerIcons::Settings, href: "/settings")
259
+ end
260
+ end
261
+
262
+ header.with_action do
263
+ NavUser.new(name: current_user.name, email: current_user.email) do |nav|
264
+ nav.with_section do |section|
265
+ section.with_link(label: "Profile", href: "/profile")
266
+ section.with_link(label: "Sign out", href: logout_url)
267
+ end
268
+ end
269
+ end
270
+ end
271
+ ```
272
+
273
+ ### Sidebar Menu (`lib/plutonium/ui/sidebar_menu.rb`)
274
+
275
+ Collapsible sidebar navigation with nested items:
276
+
277
+ ```ruby
278
+ SidebarMenu.new(
279
+ Phlexi::Menu::Builder.new do |m|
280
+ m.item "Dashboard", url: root_path, icon: Phlex::TablerIcons::Home
281
+
282
+ m.item "Resources", icon: Phlex::TablerIcons::GridDots do |submenu|
283
+ registered_resources.each do |resource|
284
+ submenu.item resource.model_name.human.pluralize,
285
+ url: resource_url_for(resource)
286
+ end
287
+ end
288
+
289
+ m.item "Settings", icon: Phlex::TablerIcons::Settings do |submenu|
290
+ submenu.item "General", url: "/settings"
291
+ submenu.item "Users", url: "/settings/users"
292
+ end
293
+ end
294
+ )
295
+ ```
296
+
297
+ ### Breadcrumbs (`lib/plutonium/ui/breadcrumbs.rb`)
298
+
299
+ Hierarchical navigation breadcrumbs:
300
+
301
+ ```ruby
302
+ # Automatic breadcrumb generation
303
+ class PostDefinition < Plutonium::Resource::Definition
304
+ # Enable breadcrumbs globally
305
+ breadcrumbs true
306
+
307
+ # Or configure per-page
308
+ show_page_breadcrumbs true
309
+ edit_page_breadcrumbs false
310
+ end
311
+
312
+ # Manual breadcrumb usage
313
+ Breadcrumbs() # Automatically generates based on current context
314
+ ```
315
+
316
+ ### Tab List (`lib/plutonium/ui/tab_list.rb`)
317
+
318
+ Interactive tabbed interfaces:
319
+
320
+ ```ruby
321
+ TabList(tabs: [
322
+ { identifier: :details, title: "Details", block: -> { render_details } },
323
+ { identifier: :settings, title: "Settings", block: -> { render_settings } },
324
+ { identifier: :history, title: "History", block: -> { render_history } }
325
+ ])
326
+ ```
327
+
328
+ ## Interactive Components
329
+
330
+ ### DynaFrame Content (`lib/plutonium/ui/dyna_frame/content.rb`)
331
+
332
+ Turbo Frame wrapper for dynamic content updates:
333
+
334
+ ```ruby
335
+ class Plutonium::UI::DynaFrame::Content < Plutonium::UI::Component::Base
336
+ include Phlex::Rails::Helpers::TurboFrameTag
337
+
338
+ def view_template
339
+ if current_turbo_frame.present?
340
+ turbo_frame_tag(current_turbo_frame) do
341
+ render partial("flash")
342
+ yield
343
+ end
344
+ else
345
+ yield
346
+ end
347
+ end
348
+ end
349
+
350
+ # Usage
351
+ DynaFrameContent do
352
+ # Content that can be dynamically updated
353
+ render_form_or_content
354
+ end
355
+ ```
356
+
357
+ ### DynaFrame Host (`lib/plutonium/ui/dyna_frame/host.rb`)
358
+
359
+ Turbo Frame host component for lazy loading:
360
+
361
+ ```ruby
362
+ class Plutonium::UI::DynaFrame::Host < Plutonium::UI::Component::Base
363
+ include Phlex::Rails::Helpers::TurboFrameTag
364
+
365
+ def initialize(src:, loading:, **attributes)
366
+ @id = attributes.delete(:id) || SecureRandom.alphanumeric(8, chars: [*"a".."z"])
367
+ @src = src
368
+ @loading = loading
369
+ @attributes = attributes
370
+ end
371
+
372
+ def view_template(&block)
373
+ turbo_frame_tag(@id, src: @src, loading: @loading, **@attributes, class: "dyna", refresh: "morph", &block)
374
+ end
375
+ end
376
+ ```
377
+
378
+ ### Action Button (`lib/plutonium/ui/action_button.rb`)
379
+
380
+ Consistent button styling for actions:
381
+
382
+ ```ruby
383
+ ActionButton(action, url: post_path(@post), variant: :default)
384
+ ActionButton(action, url: edit_post_path(@post), variant: :table)
385
+
386
+ # Automatically handles:
387
+ # - GET vs POST requests
388
+ # - Confirmation dialogs
389
+ # - Turbo frame targeting
390
+ # - Icon and label rendering
391
+ ```
392
+
393
+ ## Form Components
394
+
395
+ ### Enhanced Form Controls
396
+
397
+ Plutonium extends Phlexi forms with additional components:
398
+
399
+ ```ruby
400
+ # International telephone input
401
+ field(:phone).int_tel_input_tag
402
+
403
+ # Rich text editor (EasyMDE)
404
+ field(:content).easymde_tag
405
+
406
+ # Date/time picker
407
+ field(:published_at).flatpickr_tag
408
+
409
+ # File upload with Uppy
410
+ field(:avatar).uppy_tag
411
+ ```
412
+
413
+ ### Form Integration
414
+
415
+ ```ruby
416
+ class PostForm < Plutonium::UI::Form::Resource
417
+ def form_template
418
+ field(:title).input_tag(as: :string)
419
+ field(:content).input_tag(as: :easymde)
420
+ field(:published_at).input_tag(as: :flatpickr)
421
+ field(:featured_image).input_tag(as: :uppy)
422
+ field(:author_phone).input_tag(as: :int_tel_input)
423
+ end
424
+ end
425
+ ```
426
+
427
+ ## Display Components
428
+
429
+ ### Base Display (`lib/plutonium/ui/display/base.rb`)
430
+
431
+ Enhanced display components for showing data:
432
+
433
+ ```ruby
434
+ class PostDisplay < Plutonium::UI::Display::Base
435
+ def display_template
436
+ field(:title).string_tag
437
+ field(:content).markdown_tag
438
+ field(:author).association_tag
439
+ field(:attachments).attachment_tag
440
+
441
+ # Custom component with block syntax
442
+ field(:chart_data) do |f|
443
+ if f.value.present?
444
+ render ChartComponent.new(data: f.value, class: f.dom.css_class)
445
+ else
446
+ span(class: "text-gray-500") { "No chart data" }
447
+ end
448
+ end
449
+ end
450
+ end
451
+ ```
452
+
453
+ ### Specialized Display Components
454
+
455
+ ```ruby
456
+ # Markdown rendering
457
+ field(:description).markdown_tag
458
+
459
+ # Association display with links
460
+ field(:author).association_tag
461
+
462
+ # File attachment display
463
+ field(:documents).attachment_tag
464
+
465
+ # Custom component with conditional logic
466
+ field(:status) do |f|
467
+ case f.value
468
+ when 'active'
469
+ span(class: "badge bg-green-100 text-green-800") { "Active" }
470
+ when 'pending'
471
+ span(class: "badge bg-yellow-100 text-yellow-800") { "Pending" }
472
+ else
473
+ render f.string_tag
474
+ end
475
+ end
476
+ ```
477
+
478
+ ## Table Components
479
+
480
+ ### Resource Tables
481
+
482
+ Tables with built-in pagination, sorting, and filtering:
483
+
484
+ ```ruby
485
+ class PostTable < Plutonium::UI::Table::Resource
486
+ def table_template
487
+ column(:title).sortable
488
+ column(:author).association
489
+ column(:published_at).datetime.sortable
490
+ column(:status).badge
491
+ column(:actions).actions
492
+ end
493
+ end
494
+ ```
495
+
496
+ ### Pagination (`lib/plutonium/ui/table/components/pagy_pagination.rb`)
497
+
498
+ Responsive pagination with Pagy integration:
499
+
500
+ ```ruby
501
+ class Plutonium::UI::Table::Components::PagyPagination < Plutonium::UI::Component::Base
502
+ include Pagy::Frontend
503
+
504
+ def initialize(pagy)
505
+ @pagy = pagy
506
+ end
507
+
508
+ def view_template
509
+ nav(aria_label: "Page navigation", class: "flex justify-center mt-4") do
510
+ ul(class: "inline-flex -space-x-px text-sm") do
511
+ prev_link
512
+ page_links
513
+ next_link
514
+ end
515
+ end
516
+ end
517
+ end
518
+
519
+ # Usage
520
+ PagyPagination.new(@pagy)
521
+ ```
522
+
523
+ ## Theme System
524
+
525
+ ### Display Theme (`lib/plutonium/ui/display/theme.rb`)
526
+
527
+ Consistent styling across display components:
528
+
529
+ ```ruby
530
+ class Plutonium::UI::Display::Theme < Phlexi::Display::Theme
531
+ def self.theme
532
+ super.merge({
533
+ fields_wrapper: "p-6 grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-6 gap-y-10 grid-flow-row-dense",
534
+ label: "text-base font-bold text-gray-500 dark:text-gray-400 mb-1",
535
+ string: "text-md text-gray-900 dark:text-white mb-1 whitespace-pre-line",
536
+ link: "text-primary-600 dark:text-primary-500 whitespace-pre-line",
537
+ markdown: "format dark:format-invert format-primary",
538
+ color_indicator: "w-10 h-10 rounded-full mr-2",
539
+ json: "text-sm text-gray-900 dark:text-white mb-1 whitespace-pre font-mono shadow-inner p-4"
540
+ # ... more theme definitions
541
+ })
542
+ end
543
+ end
544
+ ```
545
+
546
+ ### Component Theming
547
+
548
+ ```ruby
549
+ class MyComponent < Plutonium::UI::Component::Base
550
+ def view_template
551
+ div(class: themed(:wrapper)) do
552
+ h2(class: themed(:title)) { @title }
553
+ p(class: themed(:content)) { @content }
554
+ end
555
+ end
556
+
557
+ private
558
+
559
+ def theme
560
+ {
561
+ wrapper: "bg-white dark:bg-gray-800 p-6 rounded-lg shadow",
562
+ title: "text-xl font-semibold text-gray-900 dark:text-white",
563
+ content: "text-gray-600 dark:text-gray-300"
564
+ }
565
+ end
566
+ end
567
+ ```
568
+
569
+ ## Custom Components
570
+
571
+ ### Creating Custom Components
572
+
573
+ ```ruby
574
+ class CustomCard < Plutonium::UI::Component::Base
575
+ def initialize(title:, variant: :default, **options)
576
+ @title = title
577
+ @variant = variant
578
+ @options = options
579
+ end
580
+
581
+ def view_template(&block)
582
+ div(class: card_classes) do
583
+ header(class: "p-4 border-b") do
584
+ h3(class: "text-lg font-semibold") { @title }
585
+ end
586
+
587
+ div(class: "p-4", &block)
588
+ end
589
+ end
590
+
591
+ private
592
+
593
+ def card_classes
594
+ tokens(
595
+ "bg-white dark:bg-gray-800 rounded-lg shadow",
596
+ variant_classes
597
+ )
598
+ end
599
+
600
+ def variant_classes
601
+ case @variant
602
+ when :success then "border-green-200 dark:border-green-700"
603
+ when :warning then "border-yellow-200 dark:border-yellow-700"
604
+ when :error then "border-red-200 dark:border-red-700"
605
+ else "border-gray-200 dark:border-gray-700"
606
+ end
607
+ end
608
+ end
609
+
610
+ # Usage
611
+ CustomCard(title: "Success", variant: :success) do
612
+ p { "Operation completed successfully!" }
613
+ end
614
+ ```
615
+
616
+ ### Component Registration
617
+
618
+ ```ruby
619
+ # Register component for automatic rendering
620
+ class MyView < Plutonium::UI::Component::Base
621
+ def BuildCustomCard(title:, **options)
622
+ CustomCard.new(title: title, **options)
623
+ end
624
+
625
+ def view_template
626
+ CustomCard(title: "Auto-rendered") do
627
+ content
628
+ end
629
+ end
630
+ end
631
+ ```