plutonium 0.33.1 → 0.34.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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/# Plutonium: The pre-alpha demo.md +4 -2
  3. data/.claude/skills/assets/SKILL.md +416 -0
  4. data/.claude/skills/connect-resource/SKILL.md +112 -0
  5. data/.claude/skills/controller/SKILL.md +302 -0
  6. data/.claude/skills/create-resource/SKILL.md +240 -0
  7. data/.claude/skills/definition/SKILL.md +218 -0
  8. data/.claude/skills/definition-actions/SKILL.md +386 -0
  9. data/.claude/skills/definition-fields/SKILL.md +474 -0
  10. data/.claude/skills/definition-query/SKILL.md +334 -0
  11. data/.claude/skills/forms/SKILL.md +439 -0
  12. data/.claude/skills/installation/SKILL.md +300 -0
  13. data/.claude/skills/interaction/SKILL.md +382 -0
  14. data/.claude/skills/model/SKILL.md +267 -0
  15. data/.claude/skills/model-features/SKILL.md +286 -0
  16. data/.claude/skills/nested-resources/SKILL.md +274 -0
  17. data/.claude/skills/package/SKILL.md +191 -0
  18. data/.claude/skills/policy/SKILL.md +352 -0
  19. data/.claude/skills/portal/SKILL.md +400 -0
  20. data/.claude/skills/resource/SKILL.md +281 -0
  21. data/.claude/skills/rodauth/SKILL.md +452 -0
  22. data/.claude/skills/views/SKILL.md +563 -0
  23. data/Appraisals +46 -4
  24. data/CHANGELOG.md +32 -1
  25. data/app/assets/plutonium.css +2 -2
  26. data/config/brakeman.ignore +239 -0
  27. data/config/initializers/action_policy.rb +1 -1
  28. data/docs/.vitepress/config.ts +132 -47
  29. data/docs/concepts/architecture.md +226 -0
  30. data/docs/concepts/auto-detection.md +254 -0
  31. data/docs/concepts/index.md +61 -0
  32. data/docs/concepts/packages-portals.md +304 -0
  33. data/docs/concepts/resources.md +224 -0
  34. data/docs/cookbook/blog.md +412 -0
  35. data/docs/cookbook/index.md +289 -0
  36. data/docs/cookbook/saas.md +481 -0
  37. data/docs/getting-started/index.md +56 -0
  38. data/docs/getting-started/installation.md +146 -0
  39. data/docs/getting-started/tutorial/01-setup.md +118 -0
  40. data/docs/getting-started/tutorial/02-first-resource.md +180 -0
  41. data/docs/getting-started/tutorial/03-authentication.md +246 -0
  42. data/docs/getting-started/tutorial/04-authorization.md +170 -0
  43. data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
  44. data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
  45. data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
  46. data/docs/getting-started/tutorial/index.md +64 -0
  47. data/docs/guides/adding-resources.md +420 -0
  48. data/docs/guides/authentication.md +551 -0
  49. data/docs/guides/authorization.md +468 -0
  50. data/docs/guides/creating-packages.md +380 -0
  51. data/docs/guides/custom-actions.md +523 -0
  52. data/docs/guides/index.md +45 -0
  53. data/docs/guides/multi-tenancy.md +302 -0
  54. data/docs/guides/nested-resources.md +411 -0
  55. data/docs/guides/search-filtering.md +266 -0
  56. data/docs/guides/theming.md +321 -0
  57. data/docs/index.md +67 -26
  58. data/docs/public/CLAUDE.md +64 -21
  59. data/docs/reference/assets/index.md +496 -0
  60. data/docs/reference/controller/index.md +363 -0
  61. data/docs/reference/definition/actions.md +400 -0
  62. data/docs/reference/definition/fields.md +350 -0
  63. data/docs/reference/definition/index.md +252 -0
  64. data/docs/reference/definition/query.md +342 -0
  65. data/docs/reference/generators/index.md +469 -0
  66. data/docs/reference/index.md +49 -0
  67. data/docs/reference/interaction/index.md +445 -0
  68. data/docs/reference/model/features.md +248 -0
  69. data/docs/reference/model/index.md +219 -0
  70. data/docs/reference/policy/index.md +385 -0
  71. data/docs/reference/portal/index.md +382 -0
  72. data/docs/reference/views/forms.md +396 -0
  73. data/docs/reference/views/index.md +479 -0
  74. data/gemfiles/rails_7.gemfile +9 -2
  75. data/gemfiles/rails_7.gemfile.lock +146 -111
  76. data/gemfiles/rails_8.0.gemfile +20 -0
  77. data/gemfiles/rails_8.0.gemfile.lock +417 -0
  78. data/gemfiles/rails_8.1.gemfile +20 -0
  79. data/gemfiles/rails_8.1.gemfile.lock +419 -0
  80. data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
  81. data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
  82. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
  83. data/lib/generators/pu/pkg/portal/USAGE +65 -0
  84. data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
  85. data/lib/generators/pu/res/conn/USAGE +71 -0
  86. data/lib/generators/pu/res/model/USAGE +106 -110
  87. data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
  88. data/lib/generators/pu/res/scaffold/USAGE +85 -0
  89. data/lib/generators/pu/rodauth/install_generator.rb +2 -6
  90. data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
  91. data/lib/generators/pu/skills/sync/USAGE +14 -0
  92. data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
  93. data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
  94. data/lib/plutonium/core/controller.rb +2 -2
  95. data/lib/plutonium/interaction/base.rb +1 -0
  96. data/lib/plutonium/package/engine.rb +2 -2
  97. data/lib/plutonium/query/adhoc_block.rb +6 -2
  98. data/lib/plutonium/query/model_scope.rb +1 -1
  99. data/lib/plutonium/railtie.rb +4 -0
  100. data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
  101. data/lib/plutonium/resource/query_object.rb +38 -8
  102. data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
  103. data/lib/plutonium/version.rb +1 -1
  104. data/lib/tasks/release.rake +19 -4
  105. data/package.json +1 -1
  106. metadata +76 -39
  107. data/brakeman.ignore +0 -28
  108. data/docs/api-examples.md +0 -49
  109. data/docs/guide/claude-code-guide.md +0 -74
  110. data/docs/guide/deep-dive/authorization.md +0 -189
  111. data/docs/guide/deep-dive/multitenancy.md +0 -256
  112. data/docs/guide/deep-dive/resources.md +0 -390
  113. data/docs/guide/getting-started/01-installation.md +0 -165
  114. data/docs/guide/index.md +0 -28
  115. data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
  116. data/docs/guide/introduction/02-core-concepts.md +0 -440
  117. data/docs/guide/tutorial/01-project-setup.md +0 -75
  118. data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
  119. data/docs/guide/tutorial/03-defining-resources.md +0 -90
  120. data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
  121. data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
  122. data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
  123. data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
  124. data/docs/markdown-examples.md +0 -85
  125. data/docs/modules/action.md +0 -244
  126. data/docs/modules/authentication.md +0 -236
  127. data/docs/modules/configuration.md +0 -599
  128. data/docs/modules/controller.md +0 -443
  129. data/docs/modules/core.md +0 -316
  130. data/docs/modules/definition.md +0 -1308
  131. data/docs/modules/display.md +0 -759
  132. data/docs/modules/form.md +0 -495
  133. data/docs/modules/generator.md +0 -400
  134. data/docs/modules/index.md +0 -167
  135. data/docs/modules/interaction.md +0 -642
  136. data/docs/modules/package.md +0 -151
  137. data/docs/modules/policy.md +0 -176
  138. data/docs/modules/portal.md +0 -710
  139. data/docs/modules/query.md +0 -297
  140. data/docs/modules/resource_record.md +0 -618
  141. data/docs/modules/routing.md +0 -690
  142. data/docs/modules/table.md +0 -301
  143. data/docs/modules/ui.md +0 -631
@@ -20,11 +20,13 @@ rails generate pu:rodauth:account user
20
20
  rails generate pu:pkg:package blog_management
21
21
  rails generate pu:pkg:portal admin_portal
22
22
 
23
- # Create complete resource
23
+ # Create complete resource (always specify --dest to avoid prompts)
24
24
  rails generate pu:res:scaffold Post user:belongs_to title:string content:text --dest=blog_management
25
+ rails generate pu:res:scaffold User name:string email:string:uniq --dest=main_app # main app resource
25
26
 
26
27
  # Connect to portal
27
28
  rails generate pu:res:conn BlogManagement::Post --dest=admin_portal
29
+ rails generate pu:res:conn User --dest=admin_portal # main app resource (no namespace)
28
30
  ```
29
31
 
30
32
  ### Start Building
@@ -43,7 +45,7 @@ bin/dev # Visit http://localhost:3000
43
45
  - **Entity Scoping**: Built-in multi-tenancy support
44
46
 
45
47
  ### 2. Key Components
46
- - **Models**: ActiveRecord + `Plutonium::Resource::Record` for enhanced functionality
48
+ - **Models**: ActiveRecord with `ResourceRecord` base class for enhanced functionality
47
49
  - **Definitions**: Declarative UI configuration (how fields render)
48
50
  - **Policies**: Authorization control (what users can access)
49
51
  - **Interactions**: Business logic encapsulation (what actions do)
@@ -123,26 +125,32 @@ rails generate pu:pkg:portal admin_dashboard
123
125
  ```
124
126
 
125
127
  ### Resource Generators (Most Important)
128
+
129
+ **Always specify `--dest`** to avoid interactive prompts:
130
+
126
131
  ```bash
127
- # Complete resource scaffold (preferred) - use --dest for package
132
+ # Main app resource (not in a package)
133
+ rails generate pu:res:scaffold Post user:belongs_to title:string 'content:text?' --dest=main_app
134
+
135
+ # Package resource
128
136
  rails generate pu:res:scaffold Post user:belongs_to title:string content:text 'published_at:datetime?' --dest=blogging
129
137
 
130
138
  # Referencing namespaced models in associations
131
139
  rails generate pu:res:scaffold Comment user:belongs_to blogging/post:belongs_to body:text --dest=comments
132
140
  rails generate pu:res:scaffold Order customer:belongs_to inventory/product:belongs_to quantity:integer --dest=commerce
133
141
 
134
- # Model only - use --dest for package
142
+ # Model only
135
143
  rails generate pu:res:model Article title:string body:text author:belongs_to --dest=blogging
136
144
  rails generate pu:res:model Review user:belongs_to inventory/product:belongs_to rating:integer --dest=reviews
137
145
 
138
146
  # CRITICAL: Use connection generator to expose resources in portals
139
147
  # Always use the generator - NEVER manually connect resources
140
148
 
141
- # Use full namespaced path (package_name/model_name format)
142
- rails generate pu:res:conn blog_management/post blog_management/comment --dest=admin_portal
149
+ # Use full class name for namespaced resources
150
+ rails generate pu:res:conn BlogManagement::Post BlogManagement::Comment --dest=admin_portal
143
151
 
144
- # Interactive mode (will prompt for resource and portal selection)
145
- rails generate pu:res:conn
152
+ # Main app resources (not namespaced)
153
+ rails generate pu:res:conn Post Comment --dest=admin_portal
146
154
 
147
155
  # The generator handles all routing, controller, and policy connections automatically
148
156
  ```
@@ -406,17 +414,49 @@ end
406
414
  ### Database Setup
407
415
  - Use standard Rails migration conventions
408
416
  - Always inline indexes and constraints in create_table blocks
409
- - Use nullable fields with `'field:type?'` syntax
410
- - Reference namespaced models: `package_name/model:belongs_to`
411
417
  - Leverage Rails associations (`belongs_to`, `has_many`, etc.)
412
418
 
413
- ### Cross-Package Associations
419
+ ### Generator Field Syntax
420
+
421
+ **IMPORTANT**: Quote fields containing `?` or `{}` to prevent shell expansion.
422
+
423
+ **IMPORTANT**: Always specify `--dest` to avoid interactive prompts:
424
+ - `--dest=main_app` for resources in the main application
425
+ - `--dest=package_name` for resources in a feature package
426
+
414
427
  ```bash
415
- # When generating models that reference other packages
416
- rails generate pu:res:scaffold Comment user:belongs_to blogging/post:belongs_to body:text --dest=comments
428
+ # Nullable fields
429
+ 'name:string?' # null: true
430
+ 'parent:belongs_to?' # null: true + optional: true in model
417
431
 
418
- # This creates the correct association:
432
+ # Decimal precision (only works for decimal type)
433
+ 'price:decimal{10,2}' # precision: 10, scale: 2
434
+ 'amount:decimal?{10,2}' # nullable with precision
435
+
436
+ # For default values on other types (boolean, integer, etc.),
437
+ # edit the migration manually after generation
438
+
439
+ # Indexes
440
+ email:string:index # Regular index
441
+ email:string:uniq # Unique index
442
+
443
+ # Cross-package references
444
+ blogging/post:belongs_to # belongs_to :post, class_name: "Blogging::Post"
445
+ ```
446
+
447
+ ### Cross-Package Associations
448
+ ```bash
449
+ # Reference models from other packages using package/model syntax
450
+ rails generate pu:res:scaffold Comment \
451
+ user:belongs_to \
452
+ blogging/post:belongs_to \
453
+ 'parent:belongs_to?' \
454
+ body:text \
455
+ --dest=comments
456
+
457
+ # Generates:
419
458
  # belongs_to :post, class_name: "Blogging::Post"
459
+ # belongs_to :parent, optional: true
420
460
  ```
421
461
 
422
462
  ### Entity Scoping (Multi-Tenancy) Setup
@@ -426,13 +466,15 @@ Plutonium provides powerful multi-tenancy through Entity Scoping, which automati
426
466
  #### 1. Configure Portal Engine
427
467
  ```ruby
428
468
  # In packages/admin_portal/lib/engine.rb
429
- scope_to_entity Organization, strategy: :path # URLs: /organizations/:organization_id/posts
469
+ config.after_initialize do
470
+ scope_to_entity Organization, strategy: :path # URLs: /organizations/:organization_id/posts
430
471
 
431
- # Custom strategy (subdomain-based)
432
- scope_to_entity Organization, strategy: :current_organization # URLs: /posts on acme.app.com
472
+ # Custom strategy (subdomain-based)
473
+ scope_to_entity Organization, strategy: :current_organization # URLs: /posts on acme.app.com
433
474
 
434
- # Custom parameter name
435
- scope_to_entity Client, strategy: :path, param_key: :client_slug # URLs: /clients/:client_slug/posts
475
+ # Custom parameter name
476
+ scope_to_entity Client, strategy: :path, param_key: :client_slug # URLs: /clients/:client_slug/posts
477
+ end
436
478
  ```
437
479
 
438
480
  #### 2. Implement Custom Strategy Methods
@@ -502,9 +544,10 @@ end
502
544
  ## Quick Reference Commands
503
545
 
504
546
  ```bash
505
- # Essential workflow
547
+ # Essential workflow (always use --dest to avoid prompts)
506
548
  rails generate pu:pkg:package feature_name # Create feature package
507
- rails generate pu:res:scaffold Resource --dest=feature_name # Create complete resource
549
+ rails generate pu:res:scaffold Resource --dest=main_app # Main app resource
550
+ rails generate pu:res:scaffold Resource --dest=feature_name # Package resource
508
551
  rails generate pu:pkg:portal portal_name # Create portal
509
552
  rails generate pu:res:conn Feature::Resource --dest=portal # CRITICAL: Always use generator to connect
510
553
 
@@ -0,0 +1,496 @@
1
+ # Assets Reference
2
+
3
+ Complete reference for styling, theming, and frontend assets.
4
+
5
+ ## Overview
6
+
7
+ Plutonium uses:
8
+ - **TailwindCSS** for styling
9
+ - **Stimulus** for JavaScript interactions
10
+ - **Turbo** for navigation and forms
11
+
12
+ ## Setup Custom Assets
13
+
14
+ Run the assets generator to set up your own TailwindCSS build:
15
+
16
+ ```bash
17
+ rails generate pu:core:assets
18
+ ```
19
+
20
+ This:
21
+ 1. Installs required npm packages (`@radioactive-labs/plutonium`, TailwindCSS plugins)
22
+ 2. Creates `tailwind.config.js` that extends Plutonium's config
23
+ 3. Imports Plutonium CSS into your `application.tailwind.css`
24
+ 4. Registers Plutonium's Stimulus controllers
25
+ 5. Updates Plutonium config to use your assets
26
+
27
+ ## Asset Configuration
28
+
29
+ Configure assets in the initializer:
30
+
31
+ ```ruby
32
+ # config/initializers/plutonium.rb
33
+ Plutonium.configure do |config|
34
+ config.load_defaults 1.0
35
+
36
+ # Custom assets
37
+ config.assets.stylesheet = "application" # Your CSS file
38
+ config.assets.script = "application" # Your JS file
39
+ config.assets.logo = "my_logo.png" # Logo image
40
+ config.assets.favicon = "my_favicon.ico" # Favicon
41
+ end
42
+ ```
43
+
44
+ ## TailwindCSS Configuration
45
+
46
+ ### Generated Config
47
+
48
+ ```javascript
49
+ // tailwind.config.js
50
+ const { execSync } = require('child_process');
51
+ const plutoniumGemPath = execSync("bundle show plutonium").toString().trim();
52
+ const plutoniumTailwindConfig = require(`${plutoniumGemPath}/tailwind.options.js`)
53
+
54
+ module.exports = {
55
+ darkMode: plutoniumTailwindConfig.darkMode,
56
+ plugins: [
57
+ // Add your plugins here
58
+ ].concat(plutoniumTailwindConfig.plugins),
59
+ theme: plutoniumTailwindConfig.merge(
60
+ plutoniumTailwindConfig.theme,
61
+ {
62
+ // Your custom theme overrides
63
+ },
64
+ ),
65
+ content: [
66
+ `${__dirname}/app/**/*.{erb,haml,html,slim,rb}`,
67
+ `${__dirname}/app/javascript/**/*.js`,
68
+ `${__dirname}/packages/**/app/**/*.{erb,haml,html,slim,rb}`,
69
+ ].concat(plutoniumTailwindConfig.content),
70
+ }
71
+ ```
72
+
73
+ ### CSS Imports
74
+
75
+ ```css
76
+ /* app/assets/stylesheets/application.tailwind.css */
77
+ @import "gem:plutonium/src/css/plutonium.css";
78
+
79
+ @import "tailwindcss";
80
+ @config '../../../tailwind.config.js';
81
+
82
+ /* Your custom styles */
83
+ ```
84
+
85
+ ### What Plutonium CSS Includes
86
+
87
+ - Core utility classes
88
+ - EasyMDE (markdown editor) styles
89
+ - Slim Select styles
90
+ - International telephone input styles
91
+ - Flatpickr (date picker) styles
92
+
93
+ ## Color System
94
+
95
+ ### Customizing Colors
96
+
97
+ Override Plutonium's color palette in your Tailwind config:
98
+
99
+ ```javascript
100
+ // tailwind.config.js
101
+ theme: plutoniumTailwindConfig.merge(
102
+ plutoniumTailwindConfig.theme,
103
+ {
104
+ extend: {
105
+ colors: {
106
+ primary: {
107
+ 50: '#eff6ff',
108
+ 100: '#dbeafe',
109
+ 200: '#bfdbfe',
110
+ 300: '#93c5fd',
111
+ 400: '#60a5fa',
112
+ 500: '#3b82f6', // Your brand color
113
+ 600: '#2563eb',
114
+ 700: '#1d4ed8',
115
+ 800: '#1e40af',
116
+ 900: '#1e3a8a',
117
+ 950: '#172554',
118
+ },
119
+ },
120
+ },
121
+ },
122
+ ),
123
+ ```
124
+
125
+ ### Default Color Palette
126
+
127
+ Plutonium includes these semantic colors:
128
+
129
+ | Color | Usage |
130
+ |-------|-------|
131
+ | `primary` | Primary brand color (turquoise by default) |
132
+ | `secondary` | Secondary color (navy by default) |
133
+ | `success` | Success states (green) |
134
+ | `info` | Informational states (blue) |
135
+ | `warning` | Warning states (amber) |
136
+ | `danger` | Error/danger states (red) |
137
+ | `accent` | Accent highlights (coral pink) |
138
+
139
+ ## Dark Mode
140
+
141
+ Plutonium uses `selector` strategy for dark mode:
142
+
143
+ ```javascript
144
+ darkMode: "selector"
145
+ ```
146
+
147
+ Toggle dark mode by adding/removing the `dark` class on `<html>`:
148
+
149
+ ```javascript
150
+ document.documentElement.classList.toggle('dark');
151
+ ```
152
+
153
+ Plutonium includes a `color-mode` Stimulus controller that handles this automatically.
154
+
155
+ ## Component Themes
156
+
157
+ Plutonium components use a theme system based on Phlexi. Each component type has a theme class with named style tokens.
158
+
159
+ ### Form Theme
160
+
161
+ ```ruby
162
+ class PostDefinition < ResourceDefinition
163
+ class Form < Form
164
+ class Theme < Plutonium::UI::Form::Theme
165
+ def self.theme
166
+ super.merge({
167
+ # Container
168
+ base: "bg-white dark:bg-gray-800 shadow-md rounded-lg p-6",
169
+ fields_wrapper: "grid grid-cols-2 gap-6",
170
+ actions_wrapper: "flex justify-end mt-6 space-x-2",
171
+
172
+ # Labels
173
+ label: "block mb-2 text-base font-bold",
174
+ invalid_label: "text-red-700 dark:text-red-500",
175
+ valid_label: "text-green-700 dark:text-green-500",
176
+ neutral_label: "text-gray-500 dark:text-gray-400",
177
+
178
+ # Inputs
179
+ input: "w-full p-2 border rounded-md shadow-sm",
180
+ invalid_input: "bg-red-50 border-red-500 text-red-900",
181
+ valid_input: "bg-green-50 border-green-500 text-green-900",
182
+ neutral_input: "border-gray-300 dark:border-gray-600",
183
+
184
+ # Hints & Errors
185
+ hint: "mt-2 text-sm text-gray-500",
186
+ error: "mt-2 text-sm text-red-600",
187
+
188
+ # Buttons
189
+ button: "px-4 py-2 bg-primary-600 text-white rounded-md hover:bg-primary-700",
190
+ })
191
+ end
192
+ end
193
+ end
194
+ end
195
+ ```
196
+
197
+ ### Display Theme
198
+
199
+ ```ruby
200
+ class PostDefinition < ResourceDefinition
201
+ class Display < Display
202
+ class Theme < Plutonium::UI::Display::Theme
203
+ def self.theme
204
+ super.merge({
205
+ fields_wrapper: "grid grid-cols-3 gap-8",
206
+ label: "text-sm font-bold text-gray-500 mb-1",
207
+ string: "text-lg text-gray-900 dark:text-white",
208
+ link: "text-primary-600 hover:underline",
209
+ markdown: "prose dark:prose-invert max-w-none",
210
+ })
211
+ end
212
+ end
213
+ end
214
+ end
215
+ ```
216
+
217
+ ### Table Theme
218
+
219
+ ```ruby
220
+ class PostDefinition < ResourceDefinition
221
+ class Table < Table
222
+ class Theme < Plutonium::UI::Table::Theme
223
+ def self.theme
224
+ super.merge({
225
+ wrapper: "overflow-x-auto shadow-md rounded-lg",
226
+ base: "w-full text-sm text-gray-500",
227
+ header: "text-xs uppercase bg-gray-100 dark:bg-gray-700",
228
+ header_cell: "px-6 py-3",
229
+ body_row: "bg-white border-b dark:bg-gray-800",
230
+ body_cell: "px-6 py-4",
231
+ })
232
+ end
233
+ end
234
+ end
235
+ end
236
+ ```
237
+
238
+ ### Theme Keys Reference
239
+
240
+ #### Form Theme Keys
241
+
242
+ | Key | Description |
243
+ |-----|-------------|
244
+ | `base` | Form container |
245
+ | `fields_wrapper` | Grid wrapper for fields |
246
+ | `actions_wrapper` | Submit button container |
247
+ | `wrapper` | Individual field wrapper |
248
+ | `inner_wrapper` | Inner field wrapper |
249
+ | `label` | Label base styles |
250
+ | `invalid_label` | Label when field invalid |
251
+ | `valid_label` | Label when field valid |
252
+ | `neutral_label` | Label default state |
253
+ | `input` | Input base styles |
254
+ | `invalid_input` | Input when invalid |
255
+ | `valid_input` | Input when valid |
256
+ | `neutral_input` | Input default state |
257
+ | `hint` | Hint text |
258
+ | `error` | Error message |
259
+ | `button` | Submit button |
260
+ | `checkbox` | Checkbox input |
261
+ | `select` | Select dropdown |
262
+
263
+ #### Display Theme Keys
264
+
265
+ | Key | Description |
266
+ |-----|-------------|
267
+ | `fields_wrapper` | Grid wrapper |
268
+ | `label` | Field label |
269
+ | `description` | Field description |
270
+ | `string` | String values |
271
+ | `text` | Text values |
272
+ | `link` | URL links |
273
+ | `email` | Email links |
274
+ | `phone` | Phone links |
275
+ | `markdown` | Markdown content |
276
+ | `json` | JSON display |
277
+
278
+ #### Table Theme Keys
279
+
280
+ | Key | Description |
281
+ |-----|-------------|
282
+ | `wrapper` | Table container |
283
+ | `base` | Table element |
284
+ | `header` | Header row |
285
+ | `header_cell` | Header cell |
286
+ | `body_row` | Body row |
287
+ | `body_cell` | Body cell |
288
+ | `sort_icon` | Sort indicator |
289
+
290
+ ## Using Tokens in Components
291
+
292
+ ### The `tokens` Helper
293
+
294
+ Conditionally apply classes:
295
+
296
+ ```ruby
297
+ class MyComponent < Plutonium::UI::Component::Base
298
+ def initialize(active:)
299
+ @active = active
300
+ end
301
+
302
+ def view_template
303
+ div(class: tokens(
304
+ "base-class",
305
+ active?: "bg-primary-500 text-white",
306
+ inactive?: "bg-gray-200 text-gray-700"
307
+ )) {
308
+ "Content"
309
+ }
310
+ end
311
+
312
+ private
313
+
314
+ def active? = @active
315
+ def inactive? = !@active
316
+ end
317
+ ```
318
+
319
+ ### The `classes` Helper
320
+
321
+ Returns a hash suitable for splatting:
322
+
323
+ ```ruby
324
+ div(**classes("p-4", "rounded", active?: "ring-2")) { }
325
+ # => <div class="p-4 rounded ring-2">
326
+ ```
327
+
328
+ ### Conditional Tokens with Then/Else
329
+
330
+ For if/else class logic, pass a hash with `:then` and `:else` keys:
331
+
332
+ ```ruby
333
+ class MyComponent < Plutonium::UI::Component::Base
334
+ def initialize(status:)
335
+ @status = status
336
+ end
337
+
338
+ def view_template
339
+ div(class: tokens(
340
+ "badge",
341
+ published?: { then: "bg-green-500", else: "bg-gray-500" }
342
+ )) { @status }
343
+ end
344
+
345
+ private
346
+
347
+ def published? = @status == "published"
348
+ end
349
+ ```
350
+
351
+ ## Stimulus Controllers
352
+
353
+ Plutonium includes Stimulus controllers. Register them in your application:
354
+
355
+ ```javascript
356
+ // app/javascript/controllers/index.js
357
+ import { application } from "./application"
358
+
359
+ import { registerControllers } from "@radioactive-labs/plutonium"
360
+ registerControllers(application)
361
+
362
+ // Your custom controllers...
363
+ ```
364
+
365
+ ### Built-in Controllers
366
+
367
+ See the [register_controllers.js source](https://github.com/radioactive-labs/plutonium-core/blob/master/src/js/controllers/register_controllers.js) for the current list of controllers.
368
+
369
+ Key controllers include:
370
+
371
+ | Controller | Purpose |
372
+ |------------|---------|
373
+ | `form` | Form handling (pre-submit, etc.) |
374
+ | `nested-resource-form-fields` | Nested form management |
375
+ | `slim-select` | Enhanced select boxes |
376
+ | `flatpickr` | Date/time pickers |
377
+ | `easymde` | Markdown editor |
378
+ | `color-mode` | Dark/light mode toggle |
379
+ | `sidebar` | Sidebar navigation |
380
+ | `remote-modal` | Remote modal dialogs |
381
+ | `intl-tel-input` | International phone input |
382
+ | `attachment-input` | File attachment handling |
383
+
384
+ ### Custom Controllers
385
+
386
+ Add your own controllers alongside Plutonium's:
387
+
388
+ ```javascript
389
+ // app/javascript/controllers/custom_controller.js
390
+ import { Controller } from "@hotwired/stimulus"
391
+
392
+ export default class extends Controller {
393
+ connect() {
394
+ console.log("Custom controller connected")
395
+ }
396
+ }
397
+ ```
398
+
399
+ Register in your index:
400
+
401
+ ```javascript
402
+ import CustomController from "./custom_controller"
403
+ application.register("custom", CustomController)
404
+ ```
405
+
406
+ ## Icons
407
+
408
+ Plutonium uses Tabler Icons via Phlex:
409
+
410
+ ```ruby
411
+ # In views
412
+ render Phlex::TablerIcons::Home.new(class: "w-5 h-5")
413
+ render Phlex::TablerIcons::User.new(class: "w-5 h-5 text-gray-500")
414
+ ```
415
+
416
+ ### Icon Sizes
417
+
418
+ ```ruby
419
+ Phlex::TablerIcons::Home.new(class: "w-4 h-4") # Small
420
+ Phlex::TablerIcons::Home.new(class: "w-5 h-5") # Default
421
+ Phlex::TablerIcons::Home.new(class: "w-6 h-6") # Large
422
+ ```
423
+
424
+ ## Typography
425
+
426
+ Plutonium uses Lato font by default. The layout loads it from Google Fonts.
427
+
428
+ Override in your layout:
429
+
430
+ ```ruby
431
+ class MyLayout < Plutonium::UI::Layout::ResourceLayout
432
+ def render_fonts
433
+ # Your custom fonts
434
+ link(rel: "preconnect", href: "https://fonts.googleapis.com")
435
+ link(href: "https://fonts.googleapis.com/css2?family=Inter&display=swap", rel: "stylesheet")
436
+ end
437
+ end
438
+ ```
439
+
440
+ Update Tailwind config:
441
+
442
+ ```javascript
443
+ theme: {
444
+ fontFamily: {
445
+ 'body': ['Inter', 'sans-serif'],
446
+ 'sans': ['Inter', 'sans-serif'],
447
+ }
448
+ }
449
+ ```
450
+
451
+ ## Custom Styles
452
+
453
+ ### Adding Custom Utilities
454
+
455
+ ```css
456
+ @layer utilities {
457
+ .text-gradient {
458
+ background: linear-gradient(to right, var(--tw-gradient-from), var(--tw-gradient-to));
459
+ -webkit-background-clip: text;
460
+ -webkit-text-fill-color: transparent;
461
+ }
462
+
463
+ .animate-fade-in {
464
+ animation: fadeIn 0.3s ease-in-out;
465
+ }
466
+ }
467
+
468
+ @keyframes fadeIn {
469
+ from { opacity: 0; }
470
+ to { opacity: 1; }
471
+ }
472
+ ```
473
+
474
+ ### Component Classes
475
+
476
+ ```css
477
+ @layer components {
478
+ .btn {
479
+ @apply px-4 py-2 rounded font-medium transition-colors;
480
+ }
481
+
482
+ .btn-primary {
483
+ @apply bg-primary-600 text-white hover:bg-primary-700;
484
+ }
485
+
486
+ .card {
487
+ @apply bg-white rounded-lg shadow p-6;
488
+ }
489
+ }
490
+ ```
491
+
492
+ ## Related
493
+
494
+ - [Theming Guide](/guides/theming)
495
+ - [Views Reference](/reference/views/)
496
+ - [Forms Reference](/reference/views/forms)