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.
- checksums.yaml +4 -4
- data/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- data/docs/modules/ui.md +0 -631
data/docs/public/CLAUDE.md
CHANGED
|
@@ -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
|
|
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
|
-
#
|
|
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
|
|
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
|
|
142
|
-
rails generate pu:res:conn
|
|
149
|
+
# Use full class name for namespaced resources
|
|
150
|
+
rails generate pu:res:conn BlogManagement::Post BlogManagement::Comment --dest=admin_portal
|
|
143
151
|
|
|
144
|
-
#
|
|
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
|
-
###
|
|
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
|
-
#
|
|
416
|
-
|
|
428
|
+
# Nullable fields
|
|
429
|
+
'name:string?' # null: true
|
|
430
|
+
'parent:belongs_to?' # null: true + optional: true in model
|
|
417
431
|
|
|
418
|
-
#
|
|
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
|
-
|
|
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=
|
|
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)
|