baldur 0.2.5 → 0.3.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/README.md +10 -0
- data/TODO.md +27 -7
- data/app/assets/javascripts/baldur/controllers/segmented_tabs_controller.js +53 -2
- data/app/assets/javascripts/baldur/controllers/theme_controller.js +4 -3
- data/app/assets/stylesheets/baldur/application/components/auth-page.css +5 -6
- data/app/assets/stylesheets/baldur/application/components/table.css +17 -7
- data/app/helpers/baldur/ui_helper.rb +11 -2
- data/app/helpers/baldur/ui_helper_feedback.rb +19 -2
- data/app/views/baldur/components/_button.html.erb +4 -0
- data/app/views/baldur/components/_segmented_buttons.html.erb +14 -7
- data/app/views/baldur/components/_snackbar_stack.html.erb +10 -6
- data/app/views/baldur/components/_table.html.erb +6 -4
- data/app/views/baldur/optional/_auth_page.html.erb +2 -2
- data/baldur.gemspec +4 -1
- data/context7.json +17 -0
- data/docs/alerts-and-snackbars.md +72 -0
- data/docs/auth.md +66 -0
- data/docs/forms.md +267 -0
- data/docs/installation.md +63 -0
- data/docs/marketing.md +77 -0
- data/docs/modals-and-panels.md +55 -0
- data/docs/security.md +11 -0
- data/docs/sidebar.md +105 -0
- data/docs/styling.md +34 -0
- data/docs/tables.md +173 -0
- data/docs/tabs-and-segmented-controls.md +509 -0
- data/docs/theme.md +118 -0
- data/lib/baldur/version.rb +1 -1
- data/llms-full.txt +179 -0
- data/llms.txt +35 -0
- data/test/hidden_field_helper_test.rb +23 -0
- data/test/segmented_buttons_helper_test.rb +85 -0
- data/test/snackbar_stack_helper_test.rb +121 -0
- data/test/table_helper_test.rb +118 -0
- data/test/text_field_helper_test.rb +40 -0
- data/test/theme_toggle_helper_test.rb +2 -0
- metadata +22 -2
data/docs/forms.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Forms
|
|
2
|
+
|
|
3
|
+
## Text Field Basics
|
|
4
|
+
|
|
5
|
+
Use `ui_text_field_tag` for Baldur-styled single-line inputs and textareas.
|
|
6
|
+
|
|
7
|
+
```erb
|
|
8
|
+
<%= ui_text_field_tag(
|
|
9
|
+
:company_name,
|
|
10
|
+
"Acme",
|
|
11
|
+
label: "Company name",
|
|
12
|
+
supporting_text: "Shown in billing and exports."
|
|
13
|
+
) %>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Use Baldur-owned named parameters for common concerns:
|
|
17
|
+
|
|
18
|
+
- `label:`
|
|
19
|
+
- `supporting_text:`
|
|
20
|
+
- `type:`
|
|
21
|
+
- `required:`
|
|
22
|
+
- `disabled:`
|
|
23
|
+
- `wrapper_class:`
|
|
24
|
+
- `input_class:`
|
|
25
|
+
- `multiline:`
|
|
26
|
+
- `prefix:`
|
|
27
|
+
- `suffix:`
|
|
28
|
+
|
|
29
|
+
## HTML5 Input Attributes via `input_options`
|
|
30
|
+
|
|
31
|
+
For raw HTML input attributes, pass them through `input_options:`.
|
|
32
|
+
|
|
33
|
+
This is the recommended way to set HTML5 attributes such as:
|
|
34
|
+
|
|
35
|
+
- `step`
|
|
36
|
+
- `min`
|
|
37
|
+
- `max`
|
|
38
|
+
- `inputmode`
|
|
39
|
+
- `autocomplete`
|
|
40
|
+
- `pattern`
|
|
41
|
+
- `maxlength`
|
|
42
|
+
- `readonly`
|
|
43
|
+
- `data`
|
|
44
|
+
- `aria`
|
|
45
|
+
- custom `id`
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
|
|
49
|
+
```erb
|
|
50
|
+
<%= ui_text_field_tag(
|
|
51
|
+
:price,
|
|
52
|
+
12.5,
|
|
53
|
+
label: "Price",
|
|
54
|
+
type: :number,
|
|
55
|
+
input_options: {
|
|
56
|
+
step: :any,
|
|
57
|
+
min: 0,
|
|
58
|
+
inputmode: "decimal",
|
|
59
|
+
autocomplete: "off"
|
|
60
|
+
}
|
|
61
|
+
) %>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Another example with pattern and max length:
|
|
65
|
+
|
|
66
|
+
```erb
|
|
67
|
+
<%= ui_text_field_tag(
|
|
68
|
+
:sku,
|
|
69
|
+
nil,
|
|
70
|
+
label: "SKU",
|
|
71
|
+
input_options: {
|
|
72
|
+
pattern: "[A-Z0-9-]+",
|
|
73
|
+
maxlength: 32,
|
|
74
|
+
autocomplete: "off"
|
|
75
|
+
}
|
|
76
|
+
) %>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`ui_text_field_tag` forwards `input_options` directly to the underlying `<input>` or `<textarea>` after Baldur applies its own base classes, generated `id`, and accessibility wiring.
|
|
80
|
+
|
|
81
|
+
## Hidden State Fields
|
|
82
|
+
|
|
83
|
+
Use hidden fields to preserve UI state that should survive submits, validation rerenders, or Turbo refreshes.
|
|
84
|
+
|
|
85
|
+
Common examples:
|
|
86
|
+
|
|
87
|
+
- active tab
|
|
88
|
+
- selected filter
|
|
89
|
+
- stepped form state
|
|
90
|
+
- selected panel
|
|
91
|
+
|
|
92
|
+
For Baldur-flavored app code, prefer `ui_hidden_field_tag`:
|
|
93
|
+
|
|
94
|
+
```erb
|
|
95
|
+
<%= ui_hidden_field_tag :planner_tab, "targets" %>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
It is a thin wrapper around Rails `hidden_field_tag`, so both are valid.
|
|
99
|
+
|
|
100
|
+
### When to Use `ui_hidden_field_tag`
|
|
101
|
+
|
|
102
|
+
Prefer `ui_hidden_field_tag` when:
|
|
103
|
+
|
|
104
|
+
- hidden state is part of a documented Baldur interaction pattern
|
|
105
|
+
- you want host app helper usage to stay consistently `ui_*`
|
|
106
|
+
- the hidden field sits beside other Baldur controls such as segmented buttons, menu selects, or submit flows
|
|
107
|
+
|
|
108
|
+
### When Plain `hidden_field_tag` Is Acceptable
|
|
109
|
+
|
|
110
|
+
Plain Rails `hidden_field_tag` is still acceptable when:
|
|
111
|
+
|
|
112
|
+
- the field is simple Rails glue and Baldur adds no extra meaning
|
|
113
|
+
- you are already inside host-specific form builder or low-level Rails form code
|
|
114
|
+
- the hidden field is not part of a reusable Baldur interaction pattern
|
|
115
|
+
|
|
116
|
+
Rule of thumb: if the hidden field is helping a Baldur control preserve UI state, reach for `ui_hidden_field_tag`. If it is generic Rails plumbing, plain `hidden_field_tag` is fine.
|
|
117
|
+
|
|
118
|
+
### URL Params vs Hidden State
|
|
119
|
+
|
|
120
|
+
Prefer URL params for state that should be shareable, bookmarkable, or navigable:
|
|
121
|
+
|
|
122
|
+
- GET-driven tabs
|
|
123
|
+
- filters in index screens
|
|
124
|
+
- state that should survive refresh and copy-pasted URLs
|
|
125
|
+
|
|
126
|
+
Prefer hidden fields for state that primarily belongs to an in-form workflow:
|
|
127
|
+
|
|
128
|
+
- current step in a stepped form
|
|
129
|
+
- active tab inside a POST form
|
|
130
|
+
- selected panel within a multi-surface editor
|
|
131
|
+
|
|
132
|
+
Avoid storing the same state in both params and hidden inputs unless the flow truly needs both.
|
|
133
|
+
|
|
134
|
+
## Hidden State Cookbook
|
|
135
|
+
|
|
136
|
+
### Active Tab
|
|
137
|
+
|
|
138
|
+
Inside a form, store the selected tab so rerenders return the user to the same panel:
|
|
139
|
+
|
|
140
|
+
```erb
|
|
141
|
+
<% current_tab = params[:planner_tab].presence || "global" %>
|
|
142
|
+
|
|
143
|
+
<%= form_with url: planner_path, method: :post do %>
|
|
144
|
+
<%= ui_hidden_field_tag :planner_tab,
|
|
145
|
+
current_tab,
|
|
146
|
+
data: { planner_tabs_target: "hiddenInput" } %>
|
|
147
|
+
|
|
148
|
+
<%= ui_segmented_buttons(
|
|
149
|
+
aria_label: "Planner tabs",
|
|
150
|
+
items: [
|
|
151
|
+
{
|
|
152
|
+
label: "Global",
|
|
153
|
+
value: "global",
|
|
154
|
+
selected: current_tab == "global",
|
|
155
|
+
data: {
|
|
156
|
+
action: "click->planner-tabs#switch",
|
|
157
|
+
planner_tabs_target: "tab",
|
|
158
|
+
planner_tab_value: "global"
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
label: "Targets",
|
|
163
|
+
value: "targets",
|
|
164
|
+
selected: current_tab == "targets",
|
|
165
|
+
data: {
|
|
166
|
+
action: "click->planner-tabs#switch",
|
|
167
|
+
planner_tabs_target: "tab",
|
|
168
|
+
planner_tab_value: "targets"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
) %>
|
|
173
|
+
<% end %>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
See `docs/tabs-and-segmented-controls.md` for the full tabs cookbook.
|
|
177
|
+
|
|
178
|
+
### Filters
|
|
179
|
+
|
|
180
|
+
For POST-backed filter forms or mixed interactive controls, hidden state can hold the selected filter key:
|
|
181
|
+
|
|
182
|
+
```erb
|
|
183
|
+
<%= form_with url: reports_path, method: :post do %>
|
|
184
|
+
<%= ui_hidden_field_tag :status_filter,
|
|
185
|
+
params[:status_filter].presence || "active",
|
|
186
|
+
data: { report_filters_target: "hiddenInput" } %>
|
|
187
|
+
|
|
188
|
+
<%= ui_segmented_buttons(
|
|
189
|
+
aria_label: "Status filters",
|
|
190
|
+
items: [
|
|
191
|
+
{
|
|
192
|
+
label: "Active",
|
|
193
|
+
value: "active",
|
|
194
|
+
selected: params[:status_filter] != "archived",
|
|
195
|
+
data: {
|
|
196
|
+
action: "click->report-filters#select",
|
|
197
|
+
report_filter_value: "active"
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
label: "Archived",
|
|
202
|
+
value: "archived",
|
|
203
|
+
selected: params[:status_filter] == "archived",
|
|
204
|
+
data: {
|
|
205
|
+
action: "click->report-filters#select",
|
|
206
|
+
report_filter_value: "archived"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
) %>
|
|
211
|
+
<% end %>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If the filter should be shareable in the URL, prefer GET params instead of hidden POST state.
|
|
215
|
+
|
|
216
|
+
### Stepped Form State
|
|
217
|
+
|
|
218
|
+
For multi-step forms, hidden state can track current step across validation failures and submit cycles:
|
|
219
|
+
|
|
220
|
+
```erb
|
|
221
|
+
<% current_step = params[:step].presence || "details" %>
|
|
222
|
+
|
|
223
|
+
<%= form_with url: onboarding_path, method: :post do %>
|
|
224
|
+
<%= ui_hidden_field_tag :step,
|
|
225
|
+
current_step,
|
|
226
|
+
data: { onboarding_target: "currentStep" } %>
|
|
227
|
+
|
|
228
|
+
<% if current_step == "details" %>
|
|
229
|
+
...details fields...
|
|
230
|
+
<%= ui_button(label: "Continue", type: :submit, data: { action: "click->onboarding#setStep", onboarding_step_value: "billing" }) %>
|
|
231
|
+
<% elsif current_step == "billing" %>
|
|
232
|
+
...billing fields...
|
|
233
|
+
<%= ui_button(label: "Review", type: :submit, data: { action: "click->onboarding#setStep", onboarding_step_value: "review" }) %>
|
|
234
|
+
<% end %>
|
|
235
|
+
<% end %>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Server rerender should read `params[:step]` and send the same value back so the correct step remains visible.
|
|
239
|
+
|
|
240
|
+
### Selected Panel
|
|
241
|
+
|
|
242
|
+
When a page has multiple host-owned panels, hidden state can preserve which panel was open when the form submitted:
|
|
243
|
+
|
|
244
|
+
```erb
|
|
245
|
+
<%= form_with url: workspace_path, method: :post do %>
|
|
246
|
+
<%= ui_hidden_field_tag :selected_panel,
|
|
247
|
+
params[:selected_panel].presence || "summary",
|
|
248
|
+
data: { workspace_panels_target: "hiddenInput" } %>
|
|
249
|
+
|
|
250
|
+
<%= ui_button(label: "Show Details", type: :button, data: { action: "click->workspace-panels#select", workspace_panel_value: "details" }) %>
|
|
251
|
+
<%= ui_button(label: "Show Summary", type: :button, data: { action: "click->workspace-panels#select", workspace_panel_value: "summary" }) %>
|
|
252
|
+
<% end %>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
This is useful when visibility is host-owned but the selected panel should survive submit-driven rerenders.
|
|
256
|
+
|
|
257
|
+
## When to Use Raw Rails Helpers
|
|
258
|
+
|
|
259
|
+
Prefer raw Rails helpers when Baldur is not adding meaningful value to the field shape.
|
|
260
|
+
|
|
261
|
+
Good examples:
|
|
262
|
+
|
|
263
|
+
- simple hidden fields with no Baldur interaction coupling
|
|
264
|
+
- low-level form builder integrations
|
|
265
|
+
- one-off HTML attributes that already fit naturally inside `input_options:`
|
|
266
|
+
|
|
267
|
+
Prefer Baldur helpers when you want shared label, support text, styling, component structure, or a documented interaction pattern.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
## Add the Gem
|
|
4
|
+
|
|
5
|
+
Add to the host `Gemfile`:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem "baldur", ">= 0.1.3"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Baldur declares `tailwindcss-rails >= 4.3.0` as a dependency, so hosts do not need to add it separately unless they want to enforce their own minimum version.
|
|
12
|
+
|
|
13
|
+
## Install and Generate
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bundle install
|
|
17
|
+
bundle exec rails tailwindcss:engines
|
|
18
|
+
bundle exec rails generate baldur:install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`tailwindcss:engines` creates `app/assets/builds/tailwind/baldur.css` from the engine-owned Tailwind entrypoint.
|
|
22
|
+
|
|
23
|
+
`baldur:install` imports that generated build into the host Tailwind entrypoint.
|
|
24
|
+
|
|
25
|
+
If the host already runs `tailwindcss:build` or `tailwindcss:watch`, those commands will also create the engine build once the entrypoint exists.
|
|
26
|
+
|
|
27
|
+
## Rebuild Tailwind
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
bundle exec rails tailwindcss:build
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Optional Surfaces
|
|
34
|
+
|
|
35
|
+
Install only what you need:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
bundle exec rails generate baldur:install_panel_secondary
|
|
39
|
+
bundle exec rails generate baldur:install_google_auth
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`baldur:install` already includes `Baldur::Optional::AuthPageHelper` in the generated `UiHelper`, so `ui_auth_page` is available by default after base install. The generators above are only required when using those specific surfaces.
|
|
43
|
+
|
|
44
|
+
## Installer Assumptions
|
|
45
|
+
|
|
46
|
+
- Tailwind entrypoint exists at `app/assets/tailwind/application.css`
|
|
47
|
+
- Host app gets `tailwindcss-rails` through Baldur or its own Gemfile and uses engine builds
|
|
48
|
+
- Host app uses importmap Stimulus boot with `app/javascript/controllers`
|
|
49
|
+
- Host app gets `app/assets/stylesheets/fonts.css` for font loading and `app/assets/stylesheets/theme.css` for brand and font-token overrides
|
|
50
|
+
- Host app can import `app/assets/builds/tailwind/baldur.css` from `app/assets/tailwind/application.css`
|
|
51
|
+
|
|
52
|
+
Default install behavior keeps Geist loaded through the host `fonts.css` scaffold. If the host wants a different stack, update `fonts.css` and then map the loaded families in `theme.css`.
|
|
53
|
+
|
|
54
|
+
## Smoke Check
|
|
55
|
+
|
|
56
|
+
After installation, run from the host app root:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
bundle exec rails tailwindcss:build
|
|
60
|
+
bundle exec ruby "$(bundle show baldur)/script/verify_host_install"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
That verifies the host can render core helpers, confirms the Tailwind entrypoint contains the required Baldur imports, and checks that Baldur template utility classes (e.g. `px-6`, `py-4`, `uppercase`, `tracking-wide`) are present in the compiled CSS. If utility classes are missing, the engine `@source` paths in `engine.css` may be misconfigured — ensure they resolve correctly from `app/assets/tailwind/baldur/` to `app/helpers` and `app/views`.
|
data/docs/marketing.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Marketing
|
|
2
|
+
|
|
3
|
+
## When to Use
|
|
4
|
+
|
|
5
|
+
Use marketing helpers for landing pages, pricing, and feature surfaces. Keep marketing-page templates separate from app UI primitives. Low-level atoms live under `ui_*`, while full-page marketing surfaces use dedicated `ui_marketing_*` helpers.
|
|
6
|
+
|
|
7
|
+
## Helpers
|
|
8
|
+
|
|
9
|
+
- `ui_marketing_top_nav`
|
|
10
|
+
- `ui_marketing_hero_section(variant: :solar_system, ...)`
|
|
11
|
+
- `ui_marketing_features_section`
|
|
12
|
+
- `ui_marketing_testimonials_section(variant: :bento, ...)`
|
|
13
|
+
- `ui_marketing_faq_section`
|
|
14
|
+
- `ui_marketing_cta_banner`
|
|
15
|
+
- `ui_marketing_pricing_tables`
|
|
16
|
+
- `ui_marketing_footer`
|
|
17
|
+
|
|
18
|
+
Use the existing landing and pricing templates as canonical v1 variants. Future hero or testimonial layouts should be added as new variants, not folded into the existing default markup.
|
|
19
|
+
|
|
20
|
+
## Branding
|
|
21
|
+
|
|
22
|
+
Marketing nav/footer branding comes from `config.marketing_brand` in the Baldur initializer. Hosts can override that deployment-level default per render by passing `brand:` into `ui_marketing_top_nav` or `ui_marketing_footer`.
|
|
23
|
+
|
|
24
|
+
If a host needs tenant-specific or whitelabel branding, resolve/cache that in the app and pass the resolved values through `brand:` rather than teaching Baldur about tenant lookup.
|
|
25
|
+
|
|
26
|
+
`config.marketing_brand` supports `name`, `wordmark`, `logo_src`, `logo_alt`, and optional `href`. Hosts should treat that config as the canonical branding contract instead of relying on helper-method coupling.
|
|
27
|
+
|
|
28
|
+
## Controllers
|
|
29
|
+
|
|
30
|
+
Interactive marketing templates ship with Baldur-owned Stimulus controllers. `baldur:install` generates `marketing_tabs_controller.js` and `marketing_pricing_controller.js` shims so features tabs and pricing billing toggles do not depend on host-specific controller names.
|
|
31
|
+
|
|
32
|
+
## Examples
|
|
33
|
+
|
|
34
|
+
Hero:
|
|
35
|
+
|
|
36
|
+
```erb
|
|
37
|
+
<%= ui_marketing_hero_section(
|
|
38
|
+
variant: :solar_system,
|
|
39
|
+
headline: "Turn Every Data Point Into ROI",
|
|
40
|
+
body: "Connect fragmented data into one decision engine.",
|
|
41
|
+
primary_action: { label: "Book a Demo", variant: :primary, href: dashboard_path },
|
|
42
|
+
secondary_action: { label: "See Use Cases", variant: :outline, href: "#use-cases" },
|
|
43
|
+
supporting_action: { href: "#", label: "Watch walkthrough", data: { open_modal: "#walkthrough-modal" } },
|
|
44
|
+
callouts: [
|
|
45
|
+
{ label: "Unified decision context" },
|
|
46
|
+
{ label: "Prioritized recommendations" },
|
|
47
|
+
{ label: "Impact-aware next actions" }
|
|
48
|
+
],
|
|
49
|
+
orbit_sources: [
|
|
50
|
+
{ name: "Shopify", asset_path: "/landing/source-logos/shopify.svg" },
|
|
51
|
+
{ name: "HubSpot", asset_path: "/landing/source-logos/hubspot.svg" }
|
|
52
|
+
],
|
|
53
|
+
centerpiece_image: { src: "/branding/logo.png", alt: "Acme logo" }
|
|
54
|
+
) %>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Features:
|
|
58
|
+
|
|
59
|
+
```erb
|
|
60
|
+
<%= ui_marketing_features_section(
|
|
61
|
+
title: "What Mimir unlocks for your teams",
|
|
62
|
+
description: "Tailored to your business model and decision priorities.",
|
|
63
|
+
tabs: [
|
|
64
|
+
{
|
|
65
|
+
value: "ecommerce",
|
|
66
|
+
label: "E-commerce",
|
|
67
|
+
selected: true,
|
|
68
|
+
panel_title: "E-commerce",
|
|
69
|
+
panel_body: "Priority ROI plays for commerce teams.",
|
|
70
|
+
cards: [
|
|
71
|
+
{ title: "Which products should I run campaigns for?", body: "Rank SKUs by incremental margin potential." }
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
cta: { label: "Get a demo tailored for you", variant: :primary, href: dashboard_path }
|
|
76
|
+
) %>
|
|
77
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Modals and Panels
|
|
2
|
+
|
|
3
|
+
## Modals
|
|
4
|
+
|
|
5
|
+
Prefer `ui_modal` directly:
|
|
6
|
+
|
|
7
|
+
```erb
|
|
8
|
+
<%= ui_modal(id: "confirm-delete", title: "Delete item") do %>
|
|
9
|
+
<p>This action cannot be undone.</p>
|
|
10
|
+
<% end %>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If a host app keeps a shared wrapper partial around `ui_modal`, treat `modal_body:` as the wrapper-local input and let the wrapper pass that content into `ui_modal`. Avoid calling a wrapper with `body:` through `render`, since `body` collides with Rails render options.
|
|
14
|
+
|
|
15
|
+
## Panels
|
|
16
|
+
|
|
17
|
+
`ui_panel_secondary` is part of the optional panel surface. Install it if needed:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
bundle exec rails generate baldur:install_panel_secondary
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Example usage:
|
|
24
|
+
|
|
25
|
+
```erb
|
|
26
|
+
<%= ui_panel_secondary(id: "assistant", title: "Assistant", trigger_label: "Open") do %>
|
|
27
|
+
<p>Panel content</p>
|
|
28
|
+
<% end %>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
External triggers can open a Baldur panel declaratively:
|
|
32
|
+
|
|
33
|
+
```erb
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
data-open-panel="#assistant"
|
|
37
|
+
data-panel-payload="<%= json_escape({ source: "dashboard" }.to_json) %>">
|
|
38
|
+
Open assistant
|
|
39
|
+
</button>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`panel-secondary` emits `baldur:panel:opened` and `baldur:panel:closed` on the panel shell and `window`. The event detail includes `id`, `selector`, `trigger`, and parsed `payload`.
|
|
43
|
+
|
|
44
|
+
## Action Rows
|
|
45
|
+
|
|
46
|
+
For horizontal primary/secondary CTA groups, prefer `ui_action_row`:
|
|
47
|
+
|
|
48
|
+
```erb
|
|
49
|
+
<%= ui_action_row(
|
|
50
|
+
secondary_button: { label: "Back", variant: :outline, href: settings_path },
|
|
51
|
+
primary_button: { label: "Save", variant: :primary, type: :submit }
|
|
52
|
+
) %>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The action row owns the responsive layout and keeps the primary CTA last on the right.
|
data/docs/security.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
- New Baldur releases require MFA for RubyGems owners via gem metadata starting with `0.1.2`.
|
|
4
|
+
- Release artifacts should be installed from RubyGems or GitHub releases and can be verified with the published `.sha512` checksum file.
|
|
5
|
+
- Report vulnerabilities privately through GitHub Security Advisories.
|
|
6
|
+
|
|
7
|
+
## Verify a Release Artifact
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
sha512sum -c baldur-0.1.3.gem.sha512
|
|
11
|
+
```
|
data/docs/sidebar.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Sidebar
|
|
2
|
+
|
|
3
|
+
## When to Use
|
|
4
|
+
|
|
5
|
+
Use `ui_sidebar` for authenticated application shells. It is part of the base install and includes:
|
|
6
|
+
|
|
7
|
+
- desktop and mobile sidebar shell markup
|
|
8
|
+
- `sidebar` and `mobile-sidebar` controller wiring
|
|
9
|
+
- default brand rendering from `config.marketing_brand`
|
|
10
|
+
- default nav rendering for primary and secondary links
|
|
11
|
+
|
|
12
|
+
Host apps provide the route arrays, active-state logic, and any optional slot content.
|
|
13
|
+
|
|
14
|
+
## Quick Example
|
|
15
|
+
|
|
16
|
+
```erb
|
|
17
|
+
<%= ui_sidebar(
|
|
18
|
+
brand_path: root_path,
|
|
19
|
+
primary_links: [
|
|
20
|
+
{ name: "Dashboard", path: root_path, icon: "layout-dashboard", active: current_page?(root_path) }
|
|
21
|
+
]
|
|
22
|
+
) do |_sidebar| %>
|
|
23
|
+
<main class="flex-1 p-6">
|
|
24
|
+
<h1>Dashboard</h1>
|
|
25
|
+
</main>
|
|
26
|
+
<% end %>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Link Options
|
|
30
|
+
|
|
31
|
+
Each link accepts:
|
|
32
|
+
|
|
33
|
+
- `name` required label text
|
|
34
|
+
- `path` required destination
|
|
35
|
+
- `icon` optional Lucide icon name, defaults to `circle`
|
|
36
|
+
- `active` optional boolean for `aria-current="page"`
|
|
37
|
+
- `title` optional hover title
|
|
38
|
+
- `method` optional Rails link method
|
|
39
|
+
- `data` optional data attributes hash
|
|
40
|
+
- `html_options` optional extra HTML attributes hash
|
|
41
|
+
|
|
42
|
+
## Brand Behavior
|
|
43
|
+
|
|
44
|
+
- `brand_name`, `brand_wordmark`, and `brand_logo` are optional
|
|
45
|
+
- when omitted, Baldur resolves them from `config.marketing_brand`
|
|
46
|
+
- `brand_path` defaults to `#` if the host app does not provide one
|
|
47
|
+
|
|
48
|
+
## Mobile Behavior
|
|
49
|
+
|
|
50
|
+
- mobile nav mirrors desktop primary and secondary links automatically
|
|
51
|
+
- the install generator ships both `sidebar_controller.js` and `mobile_sidebar_controller.js`
|
|
52
|
+
|
|
53
|
+
## Slots vs Params
|
|
54
|
+
|
|
55
|
+
You can adopt `ui_sidebar` with no slots. The block content becomes the main app surface beside the sidebar:
|
|
56
|
+
|
|
57
|
+
```erb
|
|
58
|
+
<%= ui_sidebar(
|
|
59
|
+
brand_path: root_path,
|
|
60
|
+
primary_links: sidebar_primary_links,
|
|
61
|
+
secondary_links: sidebar_secondary_links,
|
|
62
|
+
secondary_label: "Admin"
|
|
63
|
+
) do |_sidebar| %>
|
|
64
|
+
<main id="main-content" class="flex-1 p-6">
|
|
65
|
+
<%= yield %>
|
|
66
|
+
</main>
|
|
67
|
+
<% end %>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For host-specific sidebar surfaces, prefer slots. `*_content` params are also supported for incremental adoption.
|
|
71
|
+
|
|
72
|
+
```erb
|
|
73
|
+
<%= ui_sidebar(
|
|
74
|
+
brand_path: root_path,
|
|
75
|
+
primary_links: sidebar_primary_links,
|
|
76
|
+
secondary_links: sidebar_secondary_links,
|
|
77
|
+
secondary_label: "Admin"
|
|
78
|
+
) do |sidebar| %>
|
|
79
|
+
<% sidebar.with_header do %>
|
|
80
|
+
<%= render "layouts/tenant_switcher" %>
|
|
81
|
+
<% end %>
|
|
82
|
+
|
|
83
|
+
<% sidebar.with_footer do %>
|
|
84
|
+
<div class="space-y-2">
|
|
85
|
+
<p class="text-sm text-muted"><%= current_user.email %></p>
|
|
86
|
+
<%= ui_button(label: "Sign out", href: logout_path, method: :delete, variant: :text, size: :sm, icon: "log-out") %>
|
|
87
|
+
</div>
|
|
88
|
+
<% end %>
|
|
89
|
+
<% end %>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Ownership
|
|
93
|
+
|
|
94
|
+
Host apps own:
|
|
95
|
+
|
|
96
|
+
- route definitions
|
|
97
|
+
- active-state logic
|
|
98
|
+
- app-specific header/footer content
|
|
99
|
+
|
|
100
|
+
Baldur owns:
|
|
101
|
+
|
|
102
|
+
- desktop and mobile sidebar shell markup
|
|
103
|
+
- toggle behavior wiring
|
|
104
|
+
- default brand rendering
|
|
105
|
+
- default nav rendering
|
data/docs/styling.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Styling
|
|
2
|
+
|
|
3
|
+
## Ownership
|
|
4
|
+
|
|
5
|
+
Tailwind provides the utility/base layer. Baldur is the source of truth for shared design-system primitives.
|
|
6
|
+
|
|
7
|
+
Host app responsibilities:
|
|
8
|
+
|
|
9
|
+
- Load `fonts.css` before Tailwind to control font families.
|
|
10
|
+
- Import the generated Baldur Tailwind engine build into the host Tailwind entrypoint.
|
|
11
|
+
- Override only base palette inputs and font-token mapping in `theme.css` after the Baldur build.
|
|
12
|
+
- Add host-app styles only for app-specific surfaces after Baldur.
|
|
13
|
+
|
|
14
|
+
Host app restrictions:
|
|
15
|
+
|
|
16
|
+
- Do not re-import or override host-local copies of Baldur-owned primitives such as buttons, forms, snackbars, or tables.
|
|
17
|
+
- Do not keep duplicate copies of Baldur-owned primitives under `app/assets/stylesheets/application/`; leave only app-specific files there.
|
|
18
|
+
- Do not keep host copies of Baldur semantic theme files such as `theme/light.css` or `theme/dark.css`.
|
|
19
|
+
- Keep shared elevation semantics in Baldur-owned `--elev-*` tokens. If a host needs softer or stronger shared shadows, change the Baldur token source instead of swapping raw Tailwind shadow utilities into Baldur-owned primitives.
|
|
20
|
+
|
|
21
|
+
## Brand tokens
|
|
22
|
+
|
|
23
|
+
Baldur derives all semantic colors from four brand input tokens. Override only these in your host `theme.css` — see `docs/theme.md` for the full token reference and customization guide.
|
|
24
|
+
|
|
25
|
+
In short:
|
|
26
|
+
|
|
27
|
+
| Token | Purpose |
|
|
28
|
+
|-------|---------|
|
|
29
|
+
| `--_primary-base` | Primary actions, CTA |
|
|
30
|
+
| `--_secondary-base` | Sidebar, nav, subdued |
|
|
31
|
+
| `--_accent-base` | Highlights, badges |
|
|
32
|
+
| `--_neutral-base` | Surfaces, text, borders |
|
|
33
|
+
|
|
34
|
+
Do not override semantic `--color-*` tokens (e.g. `--color-primary`, `--color-surface`). Baldur maps brand inputs to semantic outputs automatically for both light and dark modes.
|