layered-ui-rails 0.4.0 → 0.6.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/.claude/skills/layered-ui-rails/SKILL.md +15 -5
- data/.claude/skills/layered-ui-rails/references/CONTROLLERS.md +15 -2
- data/.claude/skills/layered-ui-rails/references/CSS.md +162 -142
- data/.claude/skills/layered-ui-rails/references/HELPERS.md +81 -0
- data/AGENTS.md +2 -2
- data/CHANGELOG.md +49 -0
- data/README.md +34 -22
- data/app/assets/tailwind/layered/ui/styles.css +73 -29
- data/app/helpers/layered/ui/{pagination_helper.rb → pagy_helper.rb} +1 -1
- data/app/helpers/layered/ui/table_helper.rb +20 -15
- data/app/javascript/layered_ui/controllers/l_ui/panel_button_controller.js +24 -69
- data/app/javascript/layered_ui/controllers/l_ui/search_form_controller.js +23 -9
- data/app/views/devise/confirmations/new.html.erb +1 -1
- data/app/views/devise/passwords/edit.html.erb +1 -1
- data/app/views/devise/passwords/new.html.erb +1 -1
- data/app/views/devise/registrations/new.html.erb +1 -1
- data/app/views/devise/sessions/new.html.erb +1 -1
- data/app/views/devise/unlocks/new.html.erb +1 -1
- data/app/views/layered/ui/managed_resource/_form.html.erb +1 -1
- data/app/views/layouts/layered_ui/_panel.html.erb +2 -2
- data/lib/generators/layered/ui/create_overrides_generator.rb +6 -4
- data/lib/layered/ui/engine.rb +1 -1
- data/lib/layered/ui/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8b3fa30f64b14c50db43ea23c4fbdb699903a7f4c2f957a8b22cd85772868a7b
|
|
4
|
+
data.tar.gz: 215e0d57d2403591bf31dd9d31ea05c281e5f36f83ebe822a4ae3aa902eabf0b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 890b8e734f1b573143bf40deda9d3d5c64f1eb358f075a0a5e6608475eaeeb6dd0470675e68b90ac72f55f28f7a688bb19410bc99f5f77e3c590c2025c90ab50
|
|
7
|
+
data.tar.gz: 73c86751d4a39d80879c254fa35bae780c74ae4c7a5e45fb9c41c5634a6b69ce60632750e035176439c038646415a98e0b6988f389e8368cf1354348363a4553
|
|
@@ -22,9 +22,16 @@ bin/rails generate layered:ui:install
|
|
|
22
22
|
|
|
23
23
|
The generator copies `layered_ui.css` into `app/assets/tailwind/`, adds the CSS import to `application.css`, and adds the JS import to `application.js`.
|
|
24
24
|
|
|
25
|
-
Then render the engine layout from your application layout:
|
|
25
|
+
Then render the engine layout from your application layout. Place all `content_for` blocks **above** the render call - the engine layout reads them when it renders, so they must be defined first:
|
|
26
26
|
|
|
27
27
|
```erb
|
|
28
|
+
<% content_for :l_ui_body_class, "l-ui-body--always-show-navigation" %>
|
|
29
|
+
|
|
30
|
+
<% content_for :l_ui_navigation_items do %>
|
|
31
|
+
<%= l_ui_navigation_item("Dashboard", dashboard_path) %>
|
|
32
|
+
<%= l_ui_navigation_item("Users", users_path) %>
|
|
33
|
+
<% end %>
|
|
34
|
+
|
|
28
35
|
<%= render template: "layouts/layered_ui/application" %>
|
|
29
36
|
```
|
|
30
37
|
|
|
@@ -34,7 +41,7 @@ The engine layout provides a fixed header (63px), optional sidebar navigation (2
|
|
|
34
41
|
|
|
35
42
|
### Content blocks
|
|
36
43
|
|
|
37
|
-
Populate layout regions with `content_for
|
|
44
|
+
Populate layout regions with `content_for` (always above the render call):
|
|
38
45
|
|
|
39
46
|
```erb
|
|
40
47
|
<%# Navigation sidebar items %>
|
|
@@ -102,6 +109,9 @@ Quick reference:
|
|
|
102
109
|
| `l_ui_pagy(pagy)` | Styled pagination (requires pagy gem) |
|
|
103
110
|
| `l_ui_search_form(query, url:, fields:, ...)` | Search form (requires ransack gem) |
|
|
104
111
|
| `l_ui_sort_link(query, attribute, label = nil, ...)` | Sortable table header (requires ransack gem) |
|
|
112
|
+
| `l_ui_table(records, columns:, caption:, ...)` | Styled accessible data table with optional sort and actions |
|
|
113
|
+
| `l_ui_form(record, fields:, url:, method:)` | Complete form with fields, error summary, and submit |
|
|
114
|
+
| `l_ui_normalise_field(record, config)` | Normalise a raw field config hash into canonical form |
|
|
105
115
|
| `l_ui_user_signed_in?` | Check if user is authenticated |
|
|
106
116
|
| `l_ui_current_user` | Current user object |
|
|
107
117
|
|
|
@@ -136,7 +146,7 @@ All controllers use the `l-ui--` namespace and are auto-registered via importmap
|
|
|
136
146
|
| Panel resize | `l-ui--panel-resize` | Panel width drag handle |
|
|
137
147
|
| Modal | `l-ui--modal` | Native `<dialog>` with focus trap |
|
|
138
148
|
| Tabs | `l-ui--tabs` | Accessible tabbed interface |
|
|
139
|
-
| Search form | `l-ui--search-form` | Multi-scope search with Turbo support |
|
|
149
|
+
| Search form | `l-ui--search-form` | Multi-scope search with Turbo support and pagination param preservation |
|
|
140
150
|
|
|
141
151
|
## Theming
|
|
142
152
|
|
|
@@ -154,7 +164,7 @@ Override CSS custom properties after the engine import. Values are space-separat
|
|
|
154
164
|
}
|
|
155
165
|
```
|
|
156
166
|
|
|
157
|
-
Key tokens: `--accent`, `--accent-foreground`, `--background`, `--foreground`, `--foreground-muted`, `--border`, `--border-control`, `--surface`, `--surface-
|
|
167
|
+
Key tokens: `--accent`, `--accent-foreground`, `--background`, `--foreground`, `--foreground-muted`, `--border`, `--border-control`, `--surface`, `--surface-highlighted`, `--danger`, `--header-height`.
|
|
158
168
|
|
|
159
169
|
## Asset overrides
|
|
160
170
|
|
|
@@ -164,7 +174,7 @@ Place files in `app/assets/images/layered_ui/` to replace engine defaults:
|
|
|
164
174
|
|
|
165
175
|
## Optional integrations
|
|
166
176
|
|
|
167
|
-
- **Devise** - auto-detected. Provides styled auth views, header login/register buttons, sidebar user info and logout.
|
|
177
|
+
- **Devise** - auto-detected. Provides styled auth views, header login/register buttons, sidebar user info and logout. Setup: `bundle add devise`, run `devise:install` and `devise User` generators, add `devise_for :users` to routes. Configure `Layered::Ui.current_user_method` if not using `:current_user`. Helpers: `l_ui_devise_installed?`, `l_ui_user_signed_in?`.
|
|
168
178
|
- **Pagy** - auto-detected. Use `l_ui_pagy(@pagy)` for styled pagination.
|
|
169
179
|
- **Ransack** - auto-detected. Use `l_ui_search_form` and `l_ui_sort_link` for styled search and sortable tables.
|
|
170
180
|
|
|
@@ -139,7 +139,7 @@ Drag handle for resizing the panel width on desktop.
|
|
|
139
139
|
Manages multi-scope search forms with parameter preservation and Turbo frame support.
|
|
140
140
|
|
|
141
141
|
**Values:** `scope` (String, default `"q"`)
|
|
142
|
-
**Actions:** `preserve`, `clear`
|
|
142
|
+
**Actions:** `preserve`, `clear`, `rewriteLink`
|
|
143
143
|
|
|
144
144
|
```html
|
|
145
145
|
<form data-controller="l-ui--search-form"
|
|
@@ -153,4 +153,17 @@ Manages multi-scope search forms with parameter preservation and Turbo frame sup
|
|
|
153
153
|
</form>
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
When multiple search forms exist on one page (each with a different `scope` value), submitting one form automatically preserves the other forms' query parameters.
|
|
156
|
+
When multiple search forms exist on one page (each with a different `scope` value), submitting one form automatically preserves the other forms' query parameters. The `page` param and any scoped page param matching the scope (e.g. `users_page` for scope `users_q`) are reset on submit so pagination returns to page 1.
|
|
157
|
+
|
|
158
|
+
**`rewriteLink`** - merges current URL params into a clicked link's href. Useful for pagination links inside Turbo Frames where the server-rendered href may be missing params from other scopes. Attach to a parent element (e.g. the Turbo Frame):
|
|
159
|
+
|
|
160
|
+
```html
|
|
161
|
+
<%= turbo_frame_tag "users_collection", data: { turbo_action: "advance",
|
|
162
|
+
controller: "l-ui--search-form", l_ui__search_form_scope_value: "users_q",
|
|
163
|
+
action: "click->l-ui--search-form#rewriteLink" } do %>
|
|
164
|
+
<%= l_ui_search_form(@users_q, url: users_path, fields: [:name, :email],
|
|
165
|
+
clear: true, turbo_frame: "users_collection") %>
|
|
166
|
+
<%= l_ui_table(@users, ..., query: @users_q, turbo_frame: "users_collection") %>
|
|
167
|
+
<%= l_ui_pagy(@users_pagy) %>
|
|
168
|
+
<% end %>
|
|
169
|
+
```
|
|
@@ -13,78 +13,90 @@ All classes use the `l-ui-` prefix with BEM naming. Use these in host app views.
|
|
|
13
13
|
|
|
14
14
|
## Buttons
|
|
15
15
|
|
|
16
|
+
Standalone variants (use one of these, not combined with each other):
|
|
17
|
+
|
|
16
18
|
```
|
|
17
|
-
.l-ui-button
|
|
18
|
-
.l-ui-button--primary
|
|
19
|
-
.l-ui-button--
|
|
20
|
-
.l-ui-button--outline
|
|
21
|
-
.l-ui-button--
|
|
22
|
-
.l-ui-button--icon
|
|
23
|
-
.l-ui-button--disabled Disabled appearance
|
|
19
|
+
.l-ui-button Plain button with padding and focus ring
|
|
20
|
+
.l-ui-button--primary Accent-coloured solid button
|
|
21
|
+
.l-ui-button--danger Solid red button (for destructive actions)
|
|
22
|
+
.l-ui-button--outline Bordered button
|
|
23
|
+
.l-ui-button--outline-danger Red bordered button (for destructive actions)
|
|
24
|
+
.l-ui-button--icon Icon-only button (fixed size, no text)
|
|
24
25
|
.l-ui-button--navigation-toggle Mobile navigation toggle
|
|
25
|
-
.l-ui-button--panel-close
|
|
26
|
+
.l-ui-button--panel-close Panel close button
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Modifiers (combine with a standalone variant above):
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
.l-ui-button--full Full-width (e.g. l-ui-button--primary l-ui-button--full)
|
|
26
33
|
```
|
|
27
34
|
|
|
35
|
+
Any button variant is automatically styled as disabled when the `disabled` HTML attribute is present - no extra class needed.
|
|
36
|
+
|
|
37
|
+
For destructive actions use `l-ui-button--danger` (solid) or `l-ui-button--outline-danger` (bordered).
|
|
38
|
+
|
|
28
39
|
## Surfaces
|
|
29
40
|
|
|
30
41
|
```
|
|
31
|
-
.l-ui-surface
|
|
32
|
-
.l-ui-surface--
|
|
33
|
-
.l-ui-surface--sm
|
|
34
|
-
.l-ui-surface--collapsible
|
|
35
|
-
.l-ui-surface--collapsible-
|
|
36
|
-
.l-ui-surface__summary
|
|
37
|
-
.l-ui-surface__chevron
|
|
38
|
-
.l-ui-surface__content
|
|
42
|
+
.l-ui-surface Rounded, padded container
|
|
43
|
+
.l-ui-surface--highlighted Darker background variant
|
|
44
|
+
.l-ui-surface--sm Smaller padding
|
|
45
|
+
.l-ui-surface--collapsible Wraps a <details> element
|
|
46
|
+
.l-ui-surface--collapsible-highlighted Highlighted variant
|
|
47
|
+
.l-ui-surface__summary Collapsible toggle (on <summary>)
|
|
48
|
+
.l-ui-surface__chevron Chevron indicator (rotates on open)
|
|
49
|
+
.l-ui-surface__content Collapsible content area
|
|
39
50
|
```
|
|
40
51
|
|
|
41
52
|
## Forms
|
|
42
53
|
|
|
43
54
|
```
|
|
44
|
-
.l-ui-form
|
|
45
|
-
.l-ui-form__group
|
|
55
|
+
.l-ui-form Form container
|
|
56
|
+
.l-ui-form__group Vertical field group with spacing
|
|
46
57
|
.l-ui-form__group--large-gap Larger spacing variant
|
|
47
|
-
.l-ui-form__field
|
|
48
|
-
.l-ui-form__errors
|
|
49
|
-
.l-ui-form__errors-list
|
|
50
|
-
.l-ui-
|
|
51
|
-
.l-ui-
|
|
52
|
-
.l-ui-
|
|
58
|
+
.l-ui-form__field Input/textarea styling
|
|
59
|
+
.l-ui-form__errors Error summary box
|
|
60
|
+
.l-ui-form__errors-list Bulleted error list
|
|
61
|
+
.l-ui-form__actions Right-aligned action button container (full-width buttons on mobile)
|
|
62
|
+
.l-ui-form__field-error Individual field error message
|
|
63
|
+
.l-ui-form__hint Field hint text
|
|
64
|
+
.l-ui-form__required Required indicator (*)
|
|
53
65
|
|
|
54
|
-
.l-ui-label
|
|
55
|
-
.l-ui-label--checkbox
|
|
66
|
+
.l-ui-label Form label
|
|
67
|
+
.l-ui-label--checkbox Checkbox label variant
|
|
56
68
|
|
|
57
|
-
.l-ui-select
|
|
58
|
-
.l-ui-select-wrapper
|
|
69
|
+
.l-ui-select Select dropdown
|
|
70
|
+
.l-ui-select-wrapper Select wrapper (custom arrow)
|
|
59
71
|
|
|
60
|
-
.l-ui-search__inline
|
|
72
|
+
.l-ui-search__inline Inline search form layout
|
|
61
73
|
|
|
62
|
-
.l-ui-container--checkbox
|
|
63
|
-
.l-ui-radio__group
|
|
64
|
-
.l-ui-radio__item
|
|
65
|
-
.l-ui-radio__input
|
|
66
|
-
.l-ui-radio__label
|
|
74
|
+
.l-ui-container--checkbox Checkbox container
|
|
75
|
+
.l-ui-radio__group Radio button group
|
|
76
|
+
.l-ui-radio__item Radio item wrapper
|
|
77
|
+
.l-ui-radio__input Radio input element
|
|
78
|
+
.l-ui-radio__label Radio label
|
|
67
79
|
|
|
68
|
-
.l-ui-switch
|
|
69
|
-
.l-ui-switch__input
|
|
70
|
-
.l-ui-switch__track
|
|
80
|
+
.l-ui-switch Toggle switch container
|
|
81
|
+
.l-ui-switch__input Hidden checkbox input
|
|
82
|
+
.l-ui-switch__track Visual track element
|
|
71
83
|
```
|
|
72
84
|
|
|
73
85
|
## Tables
|
|
74
86
|
|
|
75
87
|
```
|
|
76
|
-
.l-ui-table
|
|
77
|
-
.l-ui-table__header
|
|
78
|
-
.l-ui-table__header-cell
|
|
79
|
-
.l-ui-table__header-cell--action
|
|
80
|
-
.l-ui-table__sort-link
|
|
81
|
-
.l-ui-table__sort-indicator
|
|
82
|
-
.l-ui-table__body
|
|
83
|
-
.l-ui-table__cell
|
|
84
|
-
.l-ui-table__cell--primary
|
|
85
|
-
.l-ui-table__cell--action
|
|
86
|
-
.l-ui-table__action--danger
|
|
87
|
-
.l-ui-container--table
|
|
88
|
+
.l-ui-table Table element
|
|
89
|
+
.l-ui-table__header <thead> row
|
|
90
|
+
.l-ui-table__header-cell <th> cell
|
|
91
|
+
.l-ui-table__header-cell--action Right-aligned action header
|
|
92
|
+
.l-ui-table__sort-link Sortable header link
|
|
93
|
+
.l-ui-table__sort-indicator Sort direction indicator (arrow)
|
|
94
|
+
.l-ui-table__body <tbody>
|
|
95
|
+
.l-ui-table__cell Regular <td> cell
|
|
96
|
+
.l-ui-table__cell--primary Bold cell (typically first column, use <th scope="row">)
|
|
97
|
+
.l-ui-table__cell--action Right-aligned action cell
|
|
98
|
+
.l-ui-table__action--danger Danger action link
|
|
99
|
+
.l-ui-container--table Overflow wrapper for responsive tables
|
|
88
100
|
```
|
|
89
101
|
|
|
90
102
|
WCAG 2.2 AA table pattern:
|
|
@@ -116,126 +128,126 @@ WCAG 2.2 AA table pattern:
|
|
|
116
128
|
## Notices
|
|
117
129
|
|
|
118
130
|
```
|
|
119
|
-
.l-ui-notice--success
|
|
120
|
-
.l-ui-notice--warning
|
|
121
|
-
.l-ui-notice--error
|
|
131
|
+
.l-ui-notice--success Green success message
|
|
132
|
+
.l-ui-notice--warning Yellow warning message
|
|
133
|
+
.l-ui-notice--error Red error message
|
|
122
134
|
```
|
|
123
135
|
|
|
124
136
|
## Badges
|
|
125
137
|
|
|
126
138
|
```
|
|
127
|
-
.l-ui-badge
|
|
128
|
-
.l-ui-badge--rounded
|
|
129
|
-
.l-ui-badge--default
|
|
130
|
-
.l-ui-badge--success
|
|
131
|
-
.l-ui-badge--warning
|
|
132
|
-
.l-ui-badge--danger
|
|
139
|
+
.l-ui-badge Base badge
|
|
140
|
+
.l-ui-badge--rounded Pill shape
|
|
141
|
+
.l-ui-badge--default Grey
|
|
142
|
+
.l-ui-badge--success Green
|
|
143
|
+
.l-ui-badge--warning Yellow
|
|
144
|
+
.l-ui-badge--danger Red
|
|
133
145
|
```
|
|
134
146
|
|
|
135
147
|
## Tabs
|
|
136
148
|
|
|
137
149
|
```
|
|
138
|
-
.l-ui-tabs__list
|
|
139
|
-
.l-ui-tabs__tab
|
|
140
|
-
.l-ui-tabs__tab--active
|
|
141
|
-
.l-ui-tabs__panel
|
|
150
|
+
.l-ui-tabs__list Tab list container (role="tablist")
|
|
151
|
+
.l-ui-tabs__tab Tab button
|
|
152
|
+
.l-ui-tabs__tab--active Active tab with accent border
|
|
153
|
+
.l-ui-tabs__panel Tab panel content
|
|
142
154
|
```
|
|
143
155
|
|
|
144
156
|
## Modal
|
|
145
157
|
|
|
146
158
|
```
|
|
147
|
-
.l-ui-modal
|
|
148
|
-
.l-ui-modal__header
|
|
149
|
-
.l-ui-modal__body
|
|
159
|
+
.l-ui-modal <dialog> element
|
|
160
|
+
.l-ui-modal__header Modal header
|
|
161
|
+
.l-ui-modal__body Scrollable modal content
|
|
150
162
|
```
|
|
151
163
|
|
|
152
164
|
## Breadcrumbs
|
|
153
165
|
|
|
154
166
|
```
|
|
155
|
-
.l-ui-breadcrumbs
|
|
156
|
-
.l-ui-breadcrumbs__list
|
|
157
|
-
.l-ui-breadcrumbs__item
|
|
158
|
-
.l-ui-breadcrumbs__link
|
|
167
|
+
.l-ui-breadcrumbs <nav> container
|
|
168
|
+
.l-ui-breadcrumbs__list <ol> list
|
|
169
|
+
.l-ui-breadcrumbs__item <li> item
|
|
170
|
+
.l-ui-breadcrumbs__link Breadcrumb link
|
|
159
171
|
```
|
|
160
172
|
|
|
161
173
|
## Pagination
|
|
162
174
|
|
|
163
175
|
```
|
|
164
|
-
.l-ui-pagination
|
|
165
|
-
.l-ui-pagination__item
|
|
166
|
-
.l-ui-pagination__item--active
|
|
167
|
-
.l-ui-pagination__item--disabled
|
|
168
|
-
.l-ui-pagination__gap
|
|
176
|
+
.l-ui-pagination Pagination container
|
|
177
|
+
.l-ui-pagination__item Page link or span
|
|
178
|
+
.l-ui-pagination__item--active Current page
|
|
179
|
+
.l-ui-pagination__item--disabled Disabled navigation
|
|
180
|
+
.l-ui-pagination__gap Gap indicator (...)
|
|
169
181
|
```
|
|
170
182
|
|
|
171
183
|
## Navigation
|
|
172
184
|
|
|
173
185
|
```
|
|
174
|
-
.l-ui-container--navigation
|
|
175
|
-
.l-ui-container--navigation.open
|
|
176
|
-
.l-ui-backdrop--navigation
|
|
177
|
-
.l-ui-backdrop--navigation.open
|
|
178
|
-
.l-ui-navigation
|
|
179
|
-
.l-ui-navigation__links
|
|
180
|
-
.l-ui-navigation__item
|
|
181
|
-
.l-ui-navigation__item--active
|
|
182
|
-
.l-ui-navigation__secondary
|
|
183
|
-
.l-ui-navigation__user
|
|
184
|
-
.l-ui-navigation__user-name
|
|
185
|
-
.l-ui-navigation__user-email
|
|
186
|
+
.l-ui-container--navigation Sidebar container
|
|
187
|
+
.l-ui-container--navigation.open Visible sidebar
|
|
188
|
+
.l-ui-backdrop--navigation Overlay backdrop
|
|
189
|
+
.l-ui-backdrop--navigation.open Visible backdrop
|
|
190
|
+
.l-ui-navigation Nav flexbox
|
|
191
|
+
.l-ui-navigation__links Nav links list
|
|
192
|
+
.l-ui-navigation__item Nav item
|
|
193
|
+
.l-ui-navigation__item--active Active nav item (with arrow)
|
|
194
|
+
.l-ui-navigation__secondary Nested nav list
|
|
195
|
+
.l-ui-navigation__user User info section
|
|
196
|
+
.l-ui-navigation__user-name User name text
|
|
197
|
+
.l-ui-navigation__user-email User email text
|
|
186
198
|
```
|
|
187
199
|
|
|
188
200
|
## Header
|
|
189
201
|
|
|
190
202
|
```
|
|
191
|
-
.l-ui-container--header
|
|
192
|
-
.l-ui-header
|
|
193
|
-
.l-ui-header__icon
|
|
194
|
-
.l-ui-header__icon--light
|
|
195
|
-
.l-ui-header__icon--dark
|
|
196
|
-
.l-ui-header__logo
|
|
197
|
-
.l-ui-header__logo--light
|
|
198
|
-
.l-ui-header__logo--dark
|
|
199
|
-
.l-ui-theme-toggle
|
|
200
|
-
.l-ui-theme-toggle__icon--light
|
|
201
|
-
.l-ui-theme-toggle__icon--dark
|
|
203
|
+
.l-ui-container--header Fixed header container
|
|
204
|
+
.l-ui-header Header flexbox
|
|
205
|
+
.l-ui-header__icon Header icon (responsive)
|
|
206
|
+
.l-ui-header__icon--light Light theme icon
|
|
207
|
+
.l-ui-header__icon--dark Dark theme icon
|
|
208
|
+
.l-ui-header__logo Header logo (responsive)
|
|
209
|
+
.l-ui-header__logo--light Light theme logo
|
|
210
|
+
.l-ui-header__logo--dark Dark theme logo
|
|
211
|
+
.l-ui-theme-toggle Theme toggle button
|
|
212
|
+
.l-ui-theme-toggle__icon--light Sun icon (shown in dark mode)
|
|
213
|
+
.l-ui-theme-toggle__icon--dark Moon icon (shown in light mode)
|
|
202
214
|
```
|
|
203
215
|
|
|
204
216
|
## Panel
|
|
205
217
|
|
|
206
218
|
```
|
|
207
|
-
.l-ui-container--panel
|
|
208
|
-
.l-ui-container--panel.open
|
|
209
|
-
.l-ui-panel
|
|
210
|
-
.l-ui-panel__button
|
|
211
|
-
.l-ui-panel__button--dragging
|
|
212
|
-
.l-ui-panel__button--snapping
|
|
213
|
-
.l-ui-panel__icon--light
|
|
214
|
-
.l-ui-panel__icon--dark
|
|
215
|
-
.l-ui-panel__resize-handle
|
|
216
|
-
.l-ui-panel__header
|
|
217
|
-
.l-ui-panel__header-heading
|
|
218
|
-
.l-ui-panel__body
|
|
219
|
-
.l-ui-panel__input
|
|
219
|
+
.l-ui-container--panel Side panel container
|
|
220
|
+
.l-ui-container--panel.open Visible panel
|
|
221
|
+
.l-ui-panel Panel flexbox
|
|
222
|
+
.l-ui-panel__button Floating action button
|
|
223
|
+
.l-ui-panel__button--dragging During drag
|
|
224
|
+
.l-ui-panel__button--snapping Snapping to edge
|
|
225
|
+
.l-ui-panel__icon--light Panel button icon (light)
|
|
226
|
+
.l-ui-panel__icon--dark Panel button icon (dark)
|
|
227
|
+
.l-ui-panel__resize-handle Desktop resize handle
|
|
228
|
+
.l-ui-panel__header Panel header
|
|
229
|
+
.l-ui-panel__header-heading Panel title
|
|
230
|
+
.l-ui-panel__body Scrollable panel content
|
|
231
|
+
.l-ui-panel__input Panel input area (footer)
|
|
220
232
|
```
|
|
221
233
|
|
|
222
234
|
## Conversation
|
|
223
235
|
|
|
224
236
|
```
|
|
225
|
-
.l-ui-conversation
|
|
226
|
-
.l-ui-conversation__messages
|
|
227
|
-
.l-ui-conversation__composer
|
|
237
|
+
.l-ui-conversation Conversation wrapper
|
|
238
|
+
.l-ui-conversation__messages Scrollable messages area
|
|
239
|
+
.l-ui-conversation__composer Message input area
|
|
228
240
|
.l-ui-conversation__composer-input Textarea
|
|
229
|
-
.l-ui-conversation__separator
|
|
241
|
+
.l-ui-conversation__separator Date separator
|
|
230
242
|
|
|
231
|
-
.l-ui-message
|
|
232
|
-
.l-ui-message--sent
|
|
233
|
-
.l-ui-message__avatar
|
|
234
|
-
.l-ui-message__bubble
|
|
235
|
-
.l-ui-message__author
|
|
236
|
-
.l-ui-message__body
|
|
237
|
-
.l-ui-message__footer
|
|
238
|
-
.l-ui-message__timestamp
|
|
243
|
+
.l-ui-message Message wrapper
|
|
244
|
+
.l-ui-message--sent Sent message (right-aligned)
|
|
245
|
+
.l-ui-message__avatar User avatar
|
|
246
|
+
.l-ui-message__bubble Message bubble
|
|
247
|
+
.l-ui-message__author Author name
|
|
248
|
+
.l-ui-message__body Message content
|
|
249
|
+
.l-ui-message__footer Metadata footer
|
|
250
|
+
.l-ui-message__timestamp Timestamp
|
|
239
251
|
```
|
|
240
252
|
|
|
241
253
|
## Markdown
|
|
@@ -256,36 +268,43 @@ WCAG 2.2 AA table pattern:
|
|
|
256
268
|
## Utility classes
|
|
257
269
|
|
|
258
270
|
```
|
|
259
|
-
.l-ui-utility--mt-0 through --mt-8
|
|
260
|
-
.l-ui-utility--mt-sm/md/lg/xl/2xl
|
|
261
|
-
.l-ui-utility--mb-0
|
|
262
|
-
.l-ui-sr-only
|
|
263
|
-
.l-ui-skip-link
|
|
264
|
-
.l-ui-list
|
|
265
|
-
.l-ui-container--grid
|
|
266
|
-
.l-ui-container--spread
|
|
267
|
-
.l-ui-container--pagy
|
|
268
|
-
.l-ui-scroll-lock
|
|
271
|
+
.l-ui-utility--mt-0 through --mt-8 Margin top (fixed scale)
|
|
272
|
+
.l-ui-utility--mt-sm/md/lg/xl/2xl Responsive margin top
|
|
273
|
+
.l-ui-utility--mb-0 Margin bottom zero
|
|
274
|
+
.l-ui-sr-only Visually hidden, screen reader only
|
|
275
|
+
.l-ui-skip-link Accessibility skip link
|
|
276
|
+
.l-ui-list Styled list
|
|
277
|
+
.l-ui-container--grid 1-col mobile, 2-col desktop grid
|
|
278
|
+
.l-ui-container--spread Flex row with space-between
|
|
279
|
+
.l-ui-container--pagy Pagination wrapper
|
|
280
|
+
.l-ui-scroll-lock Prevent body scroll (mobile panels/modals)
|
|
269
281
|
```
|
|
270
282
|
|
|
271
283
|
## Theming tokens
|
|
272
284
|
|
|
273
|
-
All
|
|
285
|
+
All color values are space-separated HSL channels (e.g. `220 80% 55%`). Override after importing the engine CSS.
|
|
274
286
|
|
|
287
|
+
Tier 1 - Accent (quick branding):
|
|
288
|
+
```
|
|
289
|
+
--accent Primary action color
|
|
290
|
+
--accent-foreground Text on accent backgrounds
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Tier 2 - Full palette (override individually as needed):
|
|
275
294
|
```
|
|
276
|
-
--accent Primary action colour
|
|
277
|
-
--accent-foreground Text on accent backgrounds
|
|
278
295
|
--background Page background
|
|
279
|
-
--foreground Primary text
|
|
296
|
+
--foreground Primary text color
|
|
280
297
|
--foreground-muted Secondary/muted text
|
|
281
|
-
--border Default border
|
|
298
|
+
--border Default border color
|
|
282
299
|
--border-control Form control border
|
|
283
|
-
--ring Focus ring
|
|
300
|
+
--ring Focus ring color
|
|
284
301
|
--surface Card/surface background
|
|
285
|
-
--surface-
|
|
286
|
-
--
|
|
302
|
+
--surface-highlighted Highlighted surface
|
|
303
|
+
--button-primary-bg Primary button background (defaults to --accent)
|
|
304
|
+
--button-primary-text Primary button and floating icon text (defaults to --accent-foreground)
|
|
305
|
+
--danger Danger/error color
|
|
287
306
|
--danger-light Light danger background
|
|
288
|
-
--danger-text Danger text
|
|
307
|
+
--danger-text Danger text color
|
|
289
308
|
--success-bg Success background
|
|
290
309
|
--success-text Success text
|
|
291
310
|
--switch-track-checked Checked switch track
|
|
@@ -293,6 +312,7 @@ All colour values are space-separated HSL channels (e.g. `220 80% 55%`). Overrid
|
|
|
293
312
|
--warning-text Warning text
|
|
294
313
|
--error-bg Error background
|
|
295
314
|
--error-text Error text
|
|
296
|
-
--backdrop Backdrop overlay colour
|
|
297
315
|
--header-height Header height (default 63px)
|
|
298
316
|
```
|
|
317
|
+
|
|
318
|
+
Override --button-primary-text when your accent color needs a different text/icon color on buttons (e.g. a pink accent with white button text in dark mode).
|
|
@@ -120,6 +120,87 @@ Returns a `<th>` element with sort link and ARIA sort attributes.
|
|
|
120
120
|
</table>
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
## Table
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
l_ui_table(records, columns:, caption: nil, actions: nil,
|
|
127
|
+
actions_label: "Actions", query: nil, url: nil, turbo_frame: nil)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- `records` (ActiveRecord::Relation or Array) - the collection to render
|
|
131
|
+
- `columns` (Array<Hash>) - column definitions (see below)
|
|
132
|
+
- `caption` (String, optional) - visually hidden table caption for accessibility
|
|
133
|
+
- `actions` (Proc, optional) - receives (record), returns action cell content
|
|
134
|
+
- `actions_label` (String) - header text for the actions column, default "Actions"
|
|
135
|
+
- `query` (Ransack::Search, optional) - enables sortable column headers
|
|
136
|
+
- `url` (String, optional) - sort link URL (passed to `l_ui_sort_link`)
|
|
137
|
+
- `turbo_frame` (String, optional) - turbo frame target for sort links
|
|
138
|
+
|
|
139
|
+
Column options:
|
|
140
|
+
- `attribute` (Symbol) - used for label generation and sort links
|
|
141
|
+
- `label` (String, optional) - custom header text; defaults to humanised attribute
|
|
142
|
+
- `primary` (Boolean, optional) - renders as `<th scope="row">`; defaults to first column
|
|
143
|
+
- `sortable` (Boolean, optional) - show sort link when `query:` is provided; defaults to true
|
|
144
|
+
- `render` (Proc, required) - receives (record), returns cell content
|
|
145
|
+
|
|
146
|
+
### `l_ui_format_datetime(value)`
|
|
147
|
+
|
|
148
|
+
Formats a date/time value as `"%-d %b %Y, %H:%M"` (e.g. "15 Apr 2026, 10:30"). Returns `nil` for `nil` input. Useful inside `render:` procs for date columns.
|
|
149
|
+
|
|
150
|
+
```erb
|
|
151
|
+
<%= l_ui_table(@users,
|
|
152
|
+
columns: [
|
|
153
|
+
{ attribute: :name, primary: true, render: ->(r) { link_to r.name, user_path(r) } },
|
|
154
|
+
{ attribute: :email, render: ->(r) { r.email } },
|
|
155
|
+
{ attribute: :created_at, label: "Joined", render: ->(r) { l_ui_format_datetime(r.created_at) } },
|
|
156
|
+
],
|
|
157
|
+
actions: ->(r) { link_to "Edit", edit_user_path(r) },
|
|
158
|
+
caption: "Users",
|
|
159
|
+
query: @q,
|
|
160
|
+
turbo_frame: "users") %>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Form
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
l_ui_form(record, fields:, url:, method: nil)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- `record` (ActiveRecord) - the model instance
|
|
170
|
+
- `fields` (Array<Hash>) - field definitions (see below)
|
|
171
|
+
- `url` (String) - form action URL
|
|
172
|
+
- `method` (Symbol, optional) - HTTP method override
|
|
173
|
+
|
|
174
|
+
Renders a complete form with all fields, error summary, and submit button via the `layered/ui/managed_resource/form` partial.
|
|
175
|
+
|
|
176
|
+
Field options:
|
|
177
|
+
- `attribute` (Symbol) - model attribute
|
|
178
|
+
- `as` (Symbol, optional) - field type; auto-detected from column type. Supported: `:string`, `:text`, `:email`, `:number`, `:date`, `:datetime`, `:select`, `:checkbox`, `:hidden`
|
|
179
|
+
- `label` (String, optional) - custom label text; defaults to humanised attribute
|
|
180
|
+
- `required` (Boolean, optional) - marks field as required; default false
|
|
181
|
+
- `hint` (String, optional) - help text below the field
|
|
182
|
+
- `collection` (Array, optional) - required for `:select` type; e.g. `[['Label', value], ...]`
|
|
183
|
+
- `placeholder` (String, optional) - input placeholder text
|
|
184
|
+
|
|
185
|
+
```erb
|
|
186
|
+
<%= l_ui_form(@post,
|
|
187
|
+
fields: [
|
|
188
|
+
{ attribute: :title, required: true },
|
|
189
|
+
{ attribute: :body, as: :text },
|
|
190
|
+
{ attribute: :category, as: :select, collection: Category.pluck(:name, :id) },
|
|
191
|
+
{ attribute: :published, as: :checkbox },
|
|
192
|
+
],
|
|
193
|
+
url: posts_path) %>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Form utility helpers
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
l_ui_normalise_field(record, config) # Normalise a raw field config into canonical form
|
|
200
|
+
l_ui_field_error_id(record, attribute) # Error element ID for aria-describedby
|
|
201
|
+
l_ui_field_hint_id(record, attribute) # Hint element ID for aria-describedby
|
|
202
|
+
```
|
|
203
|
+
|
|
123
204
|
## Authentication
|
|
124
205
|
|
|
125
206
|
```ruby
|
data/AGENTS.md
CHANGED
|
@@ -5,7 +5,7 @@ Guidance for AI agents working in this repository.
|
|
|
5
5
|
## Architecture
|
|
6
6
|
|
|
7
7
|
- **Entry:** `require "layered-ui-rails"` → `lib/layered/ui.rb` → `lib/layered/ui/engine.rb`
|
|
8
|
-
- **Engine:** importmap, assets, Pagy helpers when present; helpers: `AuthenticationHelper`, `NavigationHelper`, `
|
|
8
|
+
- **Engine:** importmap, assets, Pagy helpers when present; helpers: `AuthenticationHelper`, `NavigationHelper`, `PagyHelper`
|
|
9
9
|
- **CSS** `app/assets/tailwind/layered/ui/styles.css`: HSL tokens, `.dark` on `<html>`, `@theme` utilities (`bg-background`, etc.), BEM components (`.l-ui-button--primary`, etc.). Layout: 63px header, 240px sidebar, 320px panel. WCAG 2.2 AA.
|
|
10
10
|
- **CSS `@apply`:** Multi-line with grouping (layout → spacing → typography → colors → effects). Single utilities may stay on one line.
|
|
11
11
|
- **Generators:** `bin/rails generate layered:ui:install` (copy CSS, import CSS, import JS)
|
|
@@ -23,7 +23,7 @@ Guidance for AI agents working in this repository.
|
|
|
23
23
|
- Use normal dashes '-' not long ones '—'
|
|
24
24
|
- Locale: Favour en-GB (British English), unless terms are defined by technical standards (e.g. LICENSE, COLOR).
|
|
25
25
|
- Titles: capitalise first word only (e.g. "This title")
|
|
26
|
-
- Document new styles in the dummy app
|
|
26
|
+
- Document new styles in the dummy app. When adding a new CSS class or helper, add a working example to the relevant dummy app documentation page (in `test/dummy/app/views/pages/`) following the existing style (heading, description paragraph, live example, "View code" modal). Update the relevant skill reference file in `.claude/skills/layered-ui-rails/references/` to include the new class or helper.
|
|
27
27
|
- Use l-ui classes only in engine views, with no additional Tailwind utilities, as Tailwind classes referenced only inside the engine will not be generated by the host app’s build
|
|
28
28
|
- Dummy app documentation pages may use additional Tailwind utilities, but should favour l-ui classes where possible
|
|
29
29
|
- Importmap for JS (no bundler)
|