better_page 2.0.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 +7 -0
- data/CHANGELOG.md +62 -0
- data/MIT-LICENSE +20 -0
- data/README.md +357 -0
- data/Rakefile +3 -0
- data/docs/00-README.md +17 -0
- data/docs/01-getting-started.md +137 -0
- data/docs/02-component-registry.md +192 -0
- data/docs/03-base-pages.md +238 -0
- data/docs/04-schema-validation.md +180 -0
- data/docs/05-turbo-support.md +220 -0
- data/docs/06-compliance-analyzer.md +147 -0
- data/docs/07-configuration.md +157 -0
- data/guide/00-README.md +32 -0
- data/guide/01-quick-start.md +148 -0
- data/guide/02-building-index-page.md +258 -0
- data/guide/03-building-show-page.md +266 -0
- data/guide/04-building-form-page.md +309 -0
- data/guide/05-custom-pages.md +325 -0
- data/guide/06-best-practices.md +311 -0
- data/lib/better_page/base_page.rb +161 -0
- data/lib/better_page/compliance/analyzer.rb +409 -0
- data/lib/better_page/component_registry.rb +393 -0
- data/lib/better_page/config.rb +165 -0
- data/lib/better_page/configuration.rb +153 -0
- data/lib/better_page/custom_base_page.rb +85 -0
- data/lib/better_page/default_components.rb +200 -0
- data/lib/better_page/form_base_page.rb +170 -0
- data/lib/better_page/index_base_page.rb +69 -0
- data/lib/better_page/railtie.rb +34 -0
- data/lib/better_page/show_base_page.rb +120 -0
- data/lib/better_page/validation_error.rb +7 -0
- data/lib/better_page/version.rb +3 -0
- data/lib/better_page.rb +80 -0
- data/lib/generators/better_page/component_generator.rb +131 -0
- data/lib/generators/better_page/install_generator.rb +160 -0
- data/lib/generators/better_page/page_generator.rb +101 -0
- data/lib/generators/better_page/sync_generator.rb +109 -0
- data/lib/generators/better_page/templates/application_page.rb.tt +12 -0
- data/lib/generators/better_page/templates/better_page_initializer.rb.tt +53 -0
- data/lib/generators/better_page/templates/custom_base_page.rb.tt +83 -0
- data/lib/generators/better_page/templates/custom_page.rb.tt +31 -0
- data/lib/generators/better_page/templates/edit_page.rb.tt +46 -0
- data/lib/generators/better_page/templates/form_base_page.rb.tt +126 -0
- data/lib/generators/better_page/templates/index_base_page.rb.tt +65 -0
- data/lib/generators/better_page/templates/index_page.rb.tt +56 -0
- data/lib/generators/better_page/templates/javascript/controllers/app_nav_controller.js +57 -0
- data/lib/generators/better_page/templates/javascript/controllers/drawer_controller.js +99 -0
- data/lib/generators/better_page/templates/javascript/controllers/dropdown_controller.js +60 -0
- data/lib/generators/better_page/templates/javascript/controllers/index.js +36 -0
- data/lib/generators/better_page/templates/javascript/controllers/modal_controller.js +70 -0
- data/lib/generators/better_page/templates/javascript/controllers/sidebar_controller.js +152 -0
- data/lib/generators/better_page/templates/javascript/controllers/table_controller.js +60 -0
- data/lib/generators/better_page/templates/javascript/controllers/tabs_controller.js +89 -0
- data/lib/generators/better_page/templates/new_page.rb.tt +46 -0
- data/lib/generators/better_page/templates/show_base_page.rb.tt +117 -0
- data/lib/generators/better_page/templates/show_page.rb.tt +45 -0
- data/lib/generators/better_page/templates/view_components/application_view_component.rb.tt +7 -0
- data/lib/generators/better_page/templates/view_components/custom_view_component.html.erb.tt +21 -0
- data/lib/generators/better_page/templates/view_components/custom_view_component.rb.tt +21 -0
- data/lib/generators/better_page/templates/view_components/form_view_component.html.erb.tt +25 -0
- data/lib/generators/better_page/templates/view_components/form_view_component.rb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/index_view_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/index_view_component.rb.tt +29 -0
- data/lib/generators/better_page/templates/view_components/show_view_component.html.erb.tt +29 -0
- data/lib/generators/better_page/templates/view_components/show_view_component.rb.tt +25 -0
- data/lib/generators/better_page/templates/view_components/ui/alerts_component.html.erb.tt +47 -0
- data/lib/generators/better_page/templates/view_components/ui/alerts_component.rb.tt +47 -0
- data/lib/generators/better_page/templates/view_components/ui/content_section_component.html.erb.tt +42 -0
- data/lib/generators/better_page/templates/view_components/ui/content_section_component.rb.tt +34 -0
- data/lib/generators/better_page/templates/view_components/ui/drawer_component.html.erb.tt +73 -0
- data/lib/generators/better_page/templates/view_components/ui/drawer_component.rb.tt +78 -0
- data/lib/generators/better_page/templates/view_components/ui/errors_component.html.erb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/ui/errors_component.rb.tt +18 -0
- data/lib/generators/better_page/templates/view_components/ui/field_component.html.erb.tt +65 -0
- data/lib/generators/better_page/templates/view_components/ui/field_component.rb.tt +91 -0
- data/lib/generators/better_page/templates/view_components/ui/footer_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/ui/footer_component.rb.tt +32 -0
- data/lib/generators/better_page/templates/view_components/ui/header_component.html.erb.tt +55 -0
- data/lib/generators/better_page/templates/view_components/ui/header_component.rb.tt +39 -0
- data/lib/generators/better_page/templates/view_components/ui/modal_component.html.erb.tt +70 -0
- data/lib/generators/better_page/templates/view_components/ui/modal_component.rb.tt +54 -0
- data/lib/generators/better_page/templates/view_components/ui/overview_component.html.erb.tt +22 -0
- data/lib/generators/better_page/templates/view_components/ui/overview_component.rb.tt +71 -0
- data/lib/generators/better_page/templates/view_components/ui/pagination_component.html.erb.tt +63 -0
- data/lib/generators/better_page/templates/view_components/ui/pagination_component.rb.tt +69 -0
- data/lib/generators/better_page/templates/view_components/ui/panel_component.html.erb.tt +31 -0
- data/lib/generators/better_page/templates/view_components/ui/panel_component.rb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/ui/statistics_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/ui/statistics_component.rb.tt +51 -0
- data/lib/generators/better_page/templates/view_components/ui/table_component.html.erb.tt +112 -0
- data/lib/generators/better_page/templates/view_components/ui/table_component.rb.tt +88 -0
- data/lib/generators/better_page/templates/view_components/ui/tabs_component.html.erb.tt +52 -0
- data/lib/generators/better_page/templates/view_components/ui/tabs_component.rb.tt +76 -0
- data/lib/generators/better_page/templates/view_components/ui/widget_component.html.erb.tt +72 -0
- data/lib/generators/better_page/templates/view_components/ui/widget_component.rb.tt +34 -0
- data/lib/tasks/better_page.rake +70 -0
- data/lib/tasks/better_page_tasks.rake +4 -0
- metadata +188 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Component Registry
|
|
2
|
+
|
|
3
|
+
BetterPage uses a hybrid component registration system with three levels.
|
|
4
|
+
|
|
5
|
+
### Registration Levels
|
|
6
|
+
|
|
7
|
+
Components can be registered at three levels:
|
|
8
|
+
|
|
9
|
+
1. **Global Configuration** - In `config/initializers/better_page.rb`
|
|
10
|
+
2. **Base Page Classes** - In local base pages like `IndexBasePage`
|
|
11
|
+
3. **Individual Pages** - In specific page classes
|
|
12
|
+
|
|
13
|
+
--------------------------------
|
|
14
|
+
|
|
15
|
+
### Global Configuration (Initializer)
|
|
16
|
+
|
|
17
|
+
Register components globally in your initializer. These are available to all pages of the mapped type.
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# config/initializers/better_page.rb
|
|
21
|
+
BetterPage.configure do |config|
|
|
22
|
+
# Add a custom component with schema
|
|
23
|
+
config.register_component :sidebar, default: { enabled: false } do
|
|
24
|
+
optional(:enabled).filled(:bool)
|
|
25
|
+
optional(:items).array(:hash)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Map to page types
|
|
29
|
+
config.allow_components :index, :sidebar
|
|
30
|
+
config.allow_components :show, :sidebar
|
|
31
|
+
|
|
32
|
+
# Make required for specific page types
|
|
33
|
+
config.require_components :index, :sidebar
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
--------------------------------
|
|
38
|
+
|
|
39
|
+
### Base Page Classes (page_type DSL)
|
|
40
|
+
|
|
41
|
+
Local base classes use `page_type` to inherit components from global configuration.
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# app/pages/index_base_page.rb
|
|
45
|
+
class IndexBasePage < ApplicationPage
|
|
46
|
+
page_type :index
|
|
47
|
+
|
|
48
|
+
# Add component only for index pages
|
|
49
|
+
register_component :quick_filters, default: []
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
--------------------------------
|
|
54
|
+
|
|
55
|
+
### Individual Pages
|
|
56
|
+
|
|
57
|
+
Register components specific to a single page.
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
class Admin::Users::IndexPage < IndexBasePage
|
|
61
|
+
# Component only for this page
|
|
62
|
+
register_component :user_stats, default: nil
|
|
63
|
+
|
|
64
|
+
def user_stats
|
|
65
|
+
{ active_count: @users.active.count }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
--------------------------------
|
|
71
|
+
|
|
72
|
+
### Register Component with Schema
|
|
73
|
+
|
|
74
|
+
Use dry-schema for validation. Required components raise errors if nil.
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
register_component :header, required: true do
|
|
78
|
+
required(:title).filled(:string)
|
|
79
|
+
optional(:description).filled(:string)
|
|
80
|
+
optional(:breadcrumbs).array(:hash)
|
|
81
|
+
optional(:actions).array(:hash)
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
--------------------------------
|
|
86
|
+
|
|
87
|
+
### Register Optional Component with Default
|
|
88
|
+
|
|
89
|
+
Optional components use default values when method is not defined.
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
register_component :alerts, default: []
|
|
93
|
+
|
|
94
|
+
register_component :pagination, default: { enabled: false } do
|
|
95
|
+
optional(:enabled).filled(:bool)
|
|
96
|
+
optional(:page).filled(:integer)
|
|
97
|
+
optional(:total_pages).filled(:integer)
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
--------------------------------
|
|
102
|
+
|
|
103
|
+
### Implement Component Methods
|
|
104
|
+
|
|
105
|
+
Define methods matching the component names in your page class.
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
class Admin::Users::IndexPage < IndexBasePage
|
|
109
|
+
def initialize(users, current_user)
|
|
110
|
+
@users = users
|
|
111
|
+
@current_user = current_user
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def header
|
|
117
|
+
{ title: "Users", breadcrumbs: breadcrumbs_config }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def table
|
|
121
|
+
{ items: @users, columns: columns_config }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Optional - uses default [] if not defined
|
|
125
|
+
def statistics
|
|
126
|
+
[{ label: "Total", value: @users.count, icon: "users" }]
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
--------------------------------
|
|
132
|
+
|
|
133
|
+
### Build Page Method
|
|
134
|
+
|
|
135
|
+
The build_page method collects all registered components.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
def index
|
|
139
|
+
build_page
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Returns:
|
|
143
|
+
# {
|
|
144
|
+
# header: { title: "Users", breadcrumbs: [...] },
|
|
145
|
+
# table: { items: [...], columns: [...] },
|
|
146
|
+
# statistics: [...],
|
|
147
|
+
# pagination: { enabled: false },
|
|
148
|
+
# alerts: [],
|
|
149
|
+
# ...
|
|
150
|
+
# }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
--------------------------------
|
|
154
|
+
|
|
155
|
+
### Component Inheritance
|
|
156
|
+
|
|
157
|
+
Components are inherited through the class hierarchy and merged with global configuration.
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
# Global configuration provides components for each page_type
|
|
161
|
+
# app/pages/index_base_page.rb inherits from ApplicationPage and uses page_type :index
|
|
162
|
+
# Individual pages inherit from their base class
|
|
163
|
+
|
|
164
|
+
class Admin::Products::IndexPage < IndexBasePage
|
|
165
|
+
# Gets components from:
|
|
166
|
+
# 1. Global config mapped to :index (header, table, statistics, pagination, etc.)
|
|
167
|
+
# 2. IndexBasePage local components
|
|
168
|
+
# 3. ApplicationPage components (alerts, footer)
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Check for New Components
|
|
173
|
+
|
|
174
|
+
When upgrading BetterPage, check for new default components:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
rails generate better_page:sync
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
--------------------------------
|
|
181
|
+
|
|
182
|
+
### Validation Error Handling
|
|
183
|
+
|
|
184
|
+
Validation errors raise in development, log warnings in production.
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
# Development - raises exception
|
|
188
|
+
BetterPage::ValidationError: Component :header validation failed: {:title=>["is missing"]}
|
|
189
|
+
|
|
190
|
+
# Production - logs warning
|
|
191
|
+
[BetterPage] Component :header validation failed: {:title=>["is missing"]}
|
|
192
|
+
```
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Base Pages Reference
|
|
2
|
+
|
|
3
|
+
### Page Types Overview
|
|
4
|
+
|
|
5
|
+
BetterPage provides four base page classes, generated locally in your `app/pages/` folder.
|
|
6
|
+
|
|
7
|
+
| Type | Base Class | Required Components | Use Case |
|
|
8
|
+
|------|-----------|---------------------|----------|
|
|
9
|
+
| Index | `IndexBasePage` | `header`, `table` | List views |
|
|
10
|
+
| Show | `ShowBasePage` | `header` | Detail views |
|
|
11
|
+
| Form | `FormBasePage` | `header`, `panels` | New/Edit forms |
|
|
12
|
+
| Custom | `CustomBasePage` | `content` | Dashboards, reports |
|
|
13
|
+
|
|
14
|
+
### Inheritance Hierarchy
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
BetterPage::BasePage (gem)
|
|
18
|
+
│
|
|
19
|
+
▼
|
|
20
|
+
ApplicationPage (app/pages/)
|
|
21
|
+
│
|
|
22
|
+
├── IndexBasePage (page_type :index)
|
|
23
|
+
├── ShowBasePage (page_type :show)
|
|
24
|
+
├── FormBasePage (page_type :form)
|
|
25
|
+
└── CustomBasePage (page_type :custom)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
--------------------------------
|
|
29
|
+
|
|
30
|
+
### IndexBasePage Components
|
|
31
|
+
|
|
32
|
+
Components available for list/index pages.
|
|
33
|
+
|
|
34
|
+
| Component | Required | Default | Description |
|
|
35
|
+
|-----------|----------|---------|-------------|
|
|
36
|
+
| `header` | Yes | - | Page header with title, breadcrumbs, actions |
|
|
37
|
+
| `table` | Yes | - | Table with items, columns, actions |
|
|
38
|
+
| `alerts` | No | `[]` | Alert messages |
|
|
39
|
+
| `statistics` | No | `[]` | Statistic cards |
|
|
40
|
+
| `metrics` | No | `[]` | Metric displays |
|
|
41
|
+
| `tabs` | No | `{ enabled: false }` | Tab navigation |
|
|
42
|
+
| `search` | No | `{ enabled: false }` | Search configuration |
|
|
43
|
+
| `pagination` | No | `{ enabled: false }` | Pagination settings |
|
|
44
|
+
| `footer` | No | `{ enabled: false }` | Footer section |
|
|
45
|
+
|
|
46
|
+
--------------------------------
|
|
47
|
+
|
|
48
|
+
### IndexBasePage Example
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
class Admin::Users::IndexPage < IndexBasePage
|
|
52
|
+
def initialize(users, metadata = {})
|
|
53
|
+
@users = users
|
|
54
|
+
@user = metadata[:user]
|
|
55
|
+
super(users, metadata)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def header
|
|
61
|
+
{ title: "Users", actions: [{ label: "New", path: new_admin_user_path }] }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def table
|
|
65
|
+
{ items: @users, columns: table_columns, empty_state: empty_config }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def statistics
|
|
69
|
+
[
|
|
70
|
+
{ label: "Total", value: @users.size, icon: "users" },
|
|
71
|
+
{ label: "Active", value: @users.count(&:active?), icon: "check" }
|
|
72
|
+
]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def pagination
|
|
76
|
+
{ enabled: true, page: 1, total_pages: 10 }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
--------------------------------
|
|
82
|
+
|
|
83
|
+
### ShowBasePage Components
|
|
84
|
+
|
|
85
|
+
Components available for detail/show pages.
|
|
86
|
+
|
|
87
|
+
| Component | Required | Default | Description |
|
|
88
|
+
|-----------|----------|---------|-------------|
|
|
89
|
+
| `header` | Yes | - | Page header with title, metadata, actions |
|
|
90
|
+
| `alerts` | No | `[]` | Alert messages |
|
|
91
|
+
| `statistics` | No | `[]` | Statistic cards |
|
|
92
|
+
| `overview` | No | `{ enabled: false }` | Overview section |
|
|
93
|
+
| `content_sections` | No | `[]` | Content sections (info grids, text) |
|
|
94
|
+
| `footer` | No | `{ enabled: false }` | Footer section |
|
|
95
|
+
|
|
96
|
+
--------------------------------
|
|
97
|
+
|
|
98
|
+
### ShowBasePage Helper Methods
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
# Convert hash to info grid format
|
|
102
|
+
info_grid_content_format({ "Name" => "John", "Email" => "john@example.com" })
|
|
103
|
+
# => [{ name: "Name", value: "John" }, { name: "Email", value: "john@example.com" }]
|
|
104
|
+
|
|
105
|
+
# Build content section
|
|
106
|
+
content_section_format(
|
|
107
|
+
title: "Details",
|
|
108
|
+
icon: "info",
|
|
109
|
+
color: "blue",
|
|
110
|
+
type: :info_grid,
|
|
111
|
+
content: { "Name" => "John" }
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Build statistic
|
|
115
|
+
statistic_format(label: "Total", value: 100, icon: "users", color: "blue")
|
|
116
|
+
|
|
117
|
+
# Build action button
|
|
118
|
+
action_format(path: edit_path, label: "Edit", icon: "edit", style: "primary")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
--------------------------------
|
|
122
|
+
|
|
123
|
+
### FormBasePage Components
|
|
124
|
+
|
|
125
|
+
Components available for new/edit form pages.
|
|
126
|
+
|
|
127
|
+
| Component | Required | Default | Description |
|
|
128
|
+
|-----------|----------|---------|-------------|
|
|
129
|
+
| `header` | Yes | - | Form header with title, description |
|
|
130
|
+
| `panels` | Yes | - | Form panels with fields |
|
|
131
|
+
| `alerts` | No | `[]` | Alert messages |
|
|
132
|
+
| `errors` | No | `nil` | Custom error configuration |
|
|
133
|
+
| `footer` | No | `{ primary_action: {...} }` | Form footer with buttons |
|
|
134
|
+
|
|
135
|
+
--------------------------------
|
|
136
|
+
|
|
137
|
+
### FormBasePage Helper Methods
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
# Build a field
|
|
141
|
+
field_format(name: :email, type: :email, label: "Email", required: true)
|
|
142
|
+
|
|
143
|
+
# Build a panel
|
|
144
|
+
panel_format(title: "Basic Info", fields: [...], description: "Enter details")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Important**: Checkbox and radio fields must be in separate panels.
|
|
148
|
+
|
|
149
|
+
--------------------------------
|
|
150
|
+
|
|
151
|
+
### FormBasePage Example
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
class Admin::Users::NewPage < FormBasePage
|
|
155
|
+
def initialize(user, metadata = {})
|
|
156
|
+
@user = user
|
|
157
|
+
@current_user = metadata[:user]
|
|
158
|
+
super(user, metadata)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def header
|
|
164
|
+
{ title: "New User", description: "Create a new user account" }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def panels
|
|
168
|
+
[
|
|
169
|
+
panel_format(
|
|
170
|
+
title: "Account Details",
|
|
171
|
+
fields: [
|
|
172
|
+
field_format(name: :email, type: :email, label: "Email", required: true),
|
|
173
|
+
field_format(name: :name, type: :text, label: "Name", required: true)
|
|
174
|
+
]
|
|
175
|
+
),
|
|
176
|
+
panel_format(
|
|
177
|
+
title: "Settings",
|
|
178
|
+
fields: [
|
|
179
|
+
field_format(name: :active, type: :checkbox, label: "Active")
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
]
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
--------------------------------
|
|
188
|
+
|
|
189
|
+
### CustomBasePage Components
|
|
190
|
+
|
|
191
|
+
Components available for custom pages.
|
|
192
|
+
|
|
193
|
+
| Component | Required | Default | Description |
|
|
194
|
+
|-----------|----------|---------|-------------|
|
|
195
|
+
| `header` | No | `nil` | Optional page header |
|
|
196
|
+
| `content` | Yes | - | Custom content configuration |
|
|
197
|
+
| `footer` | No | `nil` | Optional footer |
|
|
198
|
+
|
|
199
|
+
--------------------------------
|
|
200
|
+
|
|
201
|
+
### CustomBasePage Helper Methods
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
# Build a widget
|
|
205
|
+
widget_format(title: "Stats", type: :chart, data: {...})
|
|
206
|
+
|
|
207
|
+
# Build a chart
|
|
208
|
+
chart_format(title: "Revenue", type: :line, data: { labels: [...], datasets: [...] })
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
--------------------------------
|
|
212
|
+
|
|
213
|
+
### CustomBasePage Example
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
class Admin::DashboardPage < CustomBasePage
|
|
217
|
+
def initialize(data, metadata = {})
|
|
218
|
+
@data = data
|
|
219
|
+
@user = metadata[:user]
|
|
220
|
+
super(data, metadata)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
private
|
|
224
|
+
|
|
225
|
+
def header
|
|
226
|
+
{ title: "Dashboard" }
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def content
|
|
230
|
+
{
|
|
231
|
+
widgets: [
|
|
232
|
+
widget_format(title: "Users", type: :counter, data: { value: @data[:users_count] }),
|
|
233
|
+
chart_format(title: "Revenue", type: :line, data: @data[:revenue_chart])
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
```
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Schema Validation
|
|
2
|
+
|
|
3
|
+
BetterPage uses [dry-schema](https://dry-rb.org/gems/dry-schema/) for component validation.
|
|
4
|
+
|
|
5
|
+
### String Type
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
register_component :header, required: true do
|
|
9
|
+
required(:title).filled(:string)
|
|
10
|
+
optional(:subtitle).filled(:string)
|
|
11
|
+
end
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
--------------------------------
|
|
15
|
+
|
|
16
|
+
### Integer Type
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
register_component :pagination do
|
|
20
|
+
optional(:page).filled(:integer)
|
|
21
|
+
optional(:per_page).filled(:integer)
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
--------------------------------
|
|
26
|
+
|
|
27
|
+
### Boolean Type
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
register_component :tabs do
|
|
31
|
+
optional(:enabled).filled(:bool)
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
--------------------------------
|
|
36
|
+
|
|
37
|
+
### Array Type
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
register_component :header do
|
|
41
|
+
optional(:breadcrumbs).array(:hash)
|
|
42
|
+
optional(:actions).array(:hash)
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
--------------------------------
|
|
47
|
+
|
|
48
|
+
### Hash Type with Nested Schema
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
register_component :table do
|
|
52
|
+
optional(:empty_state).hash do
|
|
53
|
+
optional(:icon).filled(:string)
|
|
54
|
+
optional(:title).filled(:string)
|
|
55
|
+
optional(:message).filled(:string)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
--------------------------------
|
|
61
|
+
|
|
62
|
+
### Required vs Optional Fields
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
# Must be present and non-empty
|
|
66
|
+
required(:title).filled(:string)
|
|
67
|
+
|
|
68
|
+
# If present, must be non-empty string
|
|
69
|
+
optional(:subtitle).filled(:string)
|
|
70
|
+
|
|
71
|
+
# Array of hash objects
|
|
72
|
+
optional(:columns).array(:hash)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
--------------------------------
|
|
76
|
+
|
|
77
|
+
### Complex Nested Structure
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
register_component :footer do
|
|
81
|
+
optional(:primary_action).hash do
|
|
82
|
+
required(:label).filled(:string)
|
|
83
|
+
optional(:style).filled(:string)
|
|
84
|
+
end
|
|
85
|
+
optional(:secondary_actions).array(:hash)
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
--------------------------------
|
|
90
|
+
|
|
91
|
+
### Header Component Schema Example
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
register_component :header, required: true do
|
|
95
|
+
required(:title).filled(:string)
|
|
96
|
+
optional(:description).filled(:string)
|
|
97
|
+
optional(:breadcrumbs).array(:hash)
|
|
98
|
+
optional(:metadata).array(:hash)
|
|
99
|
+
optional(:actions).array(:hash)
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Valid:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
def header
|
|
107
|
+
{
|
|
108
|
+
title: "Users",
|
|
109
|
+
breadcrumbs: [
|
|
110
|
+
{ label: "Home", path: "/" },
|
|
111
|
+
{ label: "Users", path: "/users" }
|
|
112
|
+
],
|
|
113
|
+
actions: [
|
|
114
|
+
{ label: "New", path: "/users/new", icon: "plus" }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
--------------------------------
|
|
121
|
+
|
|
122
|
+
### Table Component Schema Example
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
register_component :table, required: true do
|
|
126
|
+
required(:items).value(:array)
|
|
127
|
+
optional(:columns).array(:hash)
|
|
128
|
+
optional(:actions)
|
|
129
|
+
optional(:empty_state).hash do
|
|
130
|
+
optional(:icon).filled(:string)
|
|
131
|
+
optional(:title).filled(:string)
|
|
132
|
+
optional(:message).filled(:string)
|
|
133
|
+
optional(:action).hash
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Valid:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
def table
|
|
142
|
+
{
|
|
143
|
+
items: @users,
|
|
144
|
+
columns: [
|
|
145
|
+
{ key: :name, label: "Name", type: :link },
|
|
146
|
+
{ key: :email, label: "Email", type: :text }
|
|
147
|
+
],
|
|
148
|
+
empty_state: {
|
|
149
|
+
icon: "users",
|
|
150
|
+
title: "No users found",
|
|
151
|
+
message: "Create your first user to get started"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
--------------------------------
|
|
158
|
+
|
|
159
|
+
### Validation Error Handling
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
# Development - raises exception
|
|
163
|
+
BetterPage::ValidationError: Component :header validation failed: {:title=>["is missing"]}
|
|
164
|
+
|
|
165
|
+
# Production - logs warning
|
|
166
|
+
[BetterPage] Component :header validation failed: {:title=>["is missing"]}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
--------------------------------
|
|
170
|
+
|
|
171
|
+
### Components Without Schema
|
|
172
|
+
|
|
173
|
+
Components can be registered without schema validation.
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
register_component :alerts, default: []
|
|
177
|
+
register_component :custom_data, default: nil
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
These components accept any value without validation.
|