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.
- checksums.yaml +4 -4
- data/app/assets/plutonium.css +2 -2
- data/config/initializers/sqlite_json_alias.rb +1 -1
- data/docs/.vitepress/config.ts +60 -19
- data/docs/guide/cursor-rules.md +75 -0
- data/docs/guide/deep-dive/authorization.md +189 -0
- data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
- data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
- data/docs/guide/index.md +28 -0
- data/docs/guide/introduction/02-core-concepts.md +440 -0
- data/docs/guide/tutorial/01-project-setup.md +75 -0
- data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
- data/docs/guide/tutorial/03-defining-resources.md +90 -0
- data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
- data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
- data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
- data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
- data/docs/index.md +24 -31
- data/docs/modules/action.md +190 -0
- data/docs/modules/authentication.md +236 -0
- data/docs/modules/configuration.md +599 -0
- data/docs/modules/controller.md +398 -0
- data/docs/modules/core.md +316 -0
- data/docs/modules/definition.md +876 -0
- data/docs/modules/display.md +759 -0
- data/docs/modules/form.md +605 -0
- data/docs/modules/generator.md +288 -0
- data/docs/modules/index.md +167 -0
- data/docs/modules/interaction.md +470 -0
- data/docs/modules/package.md +151 -0
- data/docs/modules/policy.md +176 -0
- data/docs/modules/portal.md +710 -0
- data/docs/modules/query.md +287 -0
- data/docs/modules/resource_record.md +618 -0
- data/docs/modules/routing.md +641 -0
- data/docs/modules/table.md +293 -0
- data/docs/modules/ui.md +631 -0
- data/docs/public/plutonium.mdc +667 -0
- data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
- data/lib/plutonium/ui/display/resource.rb +7 -2
- data/lib/plutonium/ui/table/resource.rb +8 -3
- data/lib/plutonium/version.rb +1 -1
- metadata +36 -9
- data/docs/guide/getting-started/authorization.md +0 -296
- data/docs/guide/getting-started/core-concepts.md +0 -432
- data/docs/guide/getting-started/index.md +0 -21
- data/docs/guide/tutorial.md +0 -401
- /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
data/docs/modules/ui.md
ADDED
@@ -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
|
+
```
|