layered-ui-rails 0.5.0 → 0.7.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 +16 -7
- data/.claude/skills/layered-ui-rails/references/CONTROLLERS.md +15 -2
- data/.claude/skills/layered-ui-rails/references/CSS.md +152 -142
- data/AGENTS.md +1 -1
- data/CHANGELOG.md +42 -0
- data/README.md +34 -24
- data/app/assets/tailwind/layered/ui/styles.css +63 -23
- data/app/helpers/layered/ui/{pagination_helper.rb → pagy_helper.rb} +1 -1
- 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/layouts/layered_ui/_panel.html.erb +12 -4
- data/lib/generators/layered/ui/create_overrides_generator.rb +14 -6
- 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: b538d1b28b8718c6bcbaa3e9e21b8294b2e0556ca869433245d1e587ac7679a9
|
|
4
|
+
data.tar.gz: 775ec4083eb6e729eee0aa2c83c3ed5001fe258dd79c78ed476e103910797dda
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bfd719cd045e9ceb2a065c8ab8de722f6bc9187e541580dd39e7f11bd3672aae14628b04252fd0dc0de44dcc0656cb61df01954c210227a611a6bf4c5d187e1e
|
|
7
|
+
data.tar.gz: 4ac05f4a41d0cb312d67f0337136f77a3eecfb944089ce65be0c78fe15fe7a326edd7f06fa790c0b5cec1cb57cb2275c99d14d39015be3288ef35da7dffd706e
|
|
@@ -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 %>
|
|
@@ -139,11 +146,11 @@ All controllers use the `l-ui--` namespace and are auto-registered via importmap
|
|
|
139
146
|
| Panel resize | `l-ui--panel-resize` | Panel width drag handle |
|
|
140
147
|
| Modal | `l-ui--modal` | Native `<dialog>` with focus trap |
|
|
141
148
|
| Tabs | `l-ui--tabs` | Accessible tabbed interface |
|
|
142
|
-
| 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 |
|
|
143
150
|
|
|
144
151
|
## Theming
|
|
145
152
|
|
|
146
|
-
Override CSS custom properties after the engine import. Values are space-separated HSL channels (
|
|
153
|
+
Override CSS custom properties after the engine import. Values are space-separated HSL channels (e.g. `220 80% 55%`, or `0 0% 100%` for white). Keywords and hex/rgb won't work - tokens are wrapped in `hsl()` when consumed. A converter such as https://colorpicker.dev/ can help translate hex/rgb values.
|
|
147
154
|
|
|
148
155
|
```css
|
|
149
156
|
@import "./layered_ui";
|
|
@@ -157,17 +164,19 @@ Override CSS custom properties after the engine import. Values are space-separat
|
|
|
157
164
|
}
|
|
158
165
|
```
|
|
159
166
|
|
|
160
|
-
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`.
|
|
161
168
|
|
|
162
169
|
## Asset overrides
|
|
163
170
|
|
|
164
171
|
Place files in `app/assets/images/layered_ui/` to replace engine defaults:
|
|
165
172
|
|
|
166
|
-
`logo_light.svg`, `logo_dark.svg`, `icon_light.svg`, `icon_dark.svg`, `apple_touch_icon.png
|
|
173
|
+
`logo_light.svg`, `logo_dark.svg`, `icon_light.svg`, `icon_dark.svg`, `apple_touch_icon.png`.
|
|
174
|
+
|
|
175
|
+
The panel toggle button uses an inline SVG that inherits `currentColor`. Recolor it by overriding the `--button-primary-icon` Tier 2 token, or replace the image by setting both `@l_ui_panel_icon_light_url` and `@l_ui_panel_icon_dark_url` (per-request).
|
|
167
176
|
|
|
168
177
|
## Optional integrations
|
|
169
178
|
|
|
170
|
-
- **Devise** - auto-detected. Provides styled auth views, header login/register buttons, sidebar user info and logout.
|
|
179
|
+
- **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?`.
|
|
171
180
|
- **Pagy** - auto-detected. Use `l_ui_pagy(@pagy)` for styled pagination.
|
|
172
181
|
- **Ransack** - auto-detected. Use `l_ui_search_form` and `l_ui_sort_link` for styled search and sortable tables.
|
|
173
182
|
|
|
@@ -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
|
+
```
|
|
@@ -16,14 +16,14 @@ All classes use the `l-ui-` prefix with BEM naming. Use these in host app views.
|
|
|
16
16
|
Standalone variants (use one of these, not combined with each other):
|
|
17
17
|
|
|
18
18
|
```
|
|
19
|
-
.l-ui-button
|
|
20
|
-
.l-ui-button--primary
|
|
21
|
-
.l-ui-button--danger
|
|
22
|
-
.l-ui-button--outline
|
|
23
|
-
.l-ui-button--outline-danger
|
|
24
|
-
.l-ui-button--icon
|
|
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)
|
|
25
25
|
.l-ui-button--navigation-toggle Mobile navigation toggle
|
|
26
|
-
.l-ui-button--panel-close
|
|
26
|
+
.l-ui-button--panel-close Panel close button
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Modifiers (combine with a standalone variant above):
|
|
@@ -39,64 +39,64 @@ For destructive actions use `l-ui-button--danger` (solid) or `l-ui-button--outli
|
|
|
39
39
|
## Surfaces
|
|
40
40
|
|
|
41
41
|
```
|
|
42
|
-
.l-ui-surface
|
|
43
|
-
.l-ui-surface--
|
|
44
|
-
.l-ui-surface--sm
|
|
45
|
-
.l-ui-surface--collapsible
|
|
46
|
-
.l-ui-surface--collapsible-
|
|
47
|
-
.l-ui-surface__summary
|
|
48
|
-
.l-ui-surface__chevron
|
|
49
|
-
.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
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
## Forms
|
|
53
53
|
|
|
54
54
|
```
|
|
55
|
-
.l-ui-form
|
|
56
|
-
.l-ui-form__group
|
|
55
|
+
.l-ui-form Form container
|
|
56
|
+
.l-ui-form__group Vertical field group with spacing
|
|
57
57
|
.l-ui-form__group--large-gap Larger spacing variant
|
|
58
|
-
.l-ui-form__field
|
|
59
|
-
.l-ui-form__errors
|
|
60
|
-
.l-ui-form__errors-list
|
|
61
|
-
.l-ui-form__actions
|
|
62
|
-
.l-ui-form__field-error
|
|
63
|
-
.l-ui-form__hint
|
|
64
|
-
.l-ui-form__required
|
|
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 (*)
|
|
65
65
|
|
|
66
|
-
.l-ui-label
|
|
67
|
-
.l-ui-label--checkbox
|
|
66
|
+
.l-ui-label Form label
|
|
67
|
+
.l-ui-label--checkbox Checkbox label variant
|
|
68
68
|
|
|
69
|
-
.l-ui-select
|
|
70
|
-
.l-ui-select-wrapper
|
|
69
|
+
.l-ui-select Select dropdown
|
|
70
|
+
.l-ui-select-wrapper Select wrapper (custom arrow)
|
|
71
71
|
|
|
72
|
-
.l-ui-search__inline
|
|
72
|
+
.l-ui-search__inline Inline search form layout
|
|
73
73
|
|
|
74
|
-
.l-ui-container--checkbox
|
|
75
|
-
.l-ui-radio__group
|
|
76
|
-
.l-ui-radio__item
|
|
77
|
-
.l-ui-radio__input
|
|
78
|
-
.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
|
|
79
79
|
|
|
80
|
-
.l-ui-switch
|
|
81
|
-
.l-ui-switch__input
|
|
82
|
-
.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
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
## Tables
|
|
86
86
|
|
|
87
87
|
```
|
|
88
|
-
.l-ui-table
|
|
89
|
-
.l-ui-table__header
|
|
90
|
-
.l-ui-table__header-cell
|
|
91
|
-
.l-ui-table__header-cell--action
|
|
92
|
-
.l-ui-table__sort-link
|
|
93
|
-
.l-ui-table__sort-indicator
|
|
94
|
-
.l-ui-table__body
|
|
95
|
-
.l-ui-table__cell
|
|
96
|
-
.l-ui-table__cell--primary
|
|
97
|
-
.l-ui-table__cell--action
|
|
98
|
-
.l-ui-table__action--danger
|
|
99
|
-
.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
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
WCAG 2.2 AA table pattern:
|
|
@@ -128,126 +128,127 @@ WCAG 2.2 AA table pattern:
|
|
|
128
128
|
## Notices
|
|
129
129
|
|
|
130
130
|
```
|
|
131
|
-
.l-ui-notice--success
|
|
132
|
-
.l-ui-notice--warning
|
|
133
|
-
.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
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
## Badges
|
|
137
137
|
|
|
138
138
|
```
|
|
139
|
-
.l-ui-badge
|
|
140
|
-
.l-ui-badge--rounded
|
|
141
|
-
.l-ui-badge--default
|
|
142
|
-
.l-ui-badge--success
|
|
143
|
-
.l-ui-badge--warning
|
|
144
|
-
.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
|
|
145
145
|
```
|
|
146
146
|
|
|
147
147
|
## Tabs
|
|
148
148
|
|
|
149
149
|
```
|
|
150
|
-
.l-ui-tabs__list
|
|
151
|
-
.l-ui-tabs__tab
|
|
152
|
-
.l-ui-tabs__tab--active
|
|
153
|
-
.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
|
|
154
154
|
```
|
|
155
155
|
|
|
156
156
|
## Modal
|
|
157
157
|
|
|
158
158
|
```
|
|
159
|
-
.l-ui-modal
|
|
160
|
-
.l-ui-modal__header
|
|
161
|
-
.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
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
## Breadcrumbs
|
|
165
165
|
|
|
166
166
|
```
|
|
167
|
-
.l-ui-breadcrumbs
|
|
168
|
-
.l-ui-breadcrumbs__list
|
|
169
|
-
.l-ui-breadcrumbs__item
|
|
170
|
-
.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
|
|
171
171
|
```
|
|
172
172
|
|
|
173
173
|
## Pagination
|
|
174
174
|
|
|
175
175
|
```
|
|
176
|
-
.l-ui-pagination
|
|
177
|
-
.l-ui-pagination__item
|
|
178
|
-
.l-ui-pagination__item--active
|
|
179
|
-
.l-ui-pagination__item--disabled
|
|
180
|
-
.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 (...)
|
|
181
181
|
```
|
|
182
182
|
|
|
183
183
|
## Navigation
|
|
184
184
|
|
|
185
185
|
```
|
|
186
|
-
.l-ui-container--navigation
|
|
187
|
-
.l-ui-container--navigation.open
|
|
188
|
-
.l-ui-backdrop--navigation
|
|
189
|
-
.l-ui-backdrop--navigation.open
|
|
190
|
-
.l-ui-navigation
|
|
191
|
-
.l-ui-navigation__links
|
|
192
|
-
.l-ui-navigation__item
|
|
193
|
-
.l-ui-navigation__item--active
|
|
194
|
-
.l-ui-navigation__secondary
|
|
195
|
-
.l-ui-navigation__user
|
|
196
|
-
.l-ui-navigation__user-name
|
|
197
|
-
.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
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
## Header
|
|
201
201
|
|
|
202
202
|
```
|
|
203
|
-
.l-ui-container--header
|
|
204
|
-
.l-ui-header
|
|
205
|
-
.l-ui-header__icon
|
|
206
|
-
.l-ui-header__icon--light
|
|
207
|
-
.l-ui-header__icon--dark
|
|
208
|
-
.l-ui-header__logo
|
|
209
|
-
.l-ui-header__logo--light
|
|
210
|
-
.l-ui-header__logo--dark
|
|
211
|
-
.l-ui-theme-toggle
|
|
212
|
-
.l-ui-theme-toggle__icon--light
|
|
213
|
-
.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)
|
|
214
214
|
```
|
|
215
215
|
|
|
216
216
|
## Panel
|
|
217
217
|
|
|
218
218
|
```
|
|
219
|
-
.l-ui-container--panel
|
|
220
|
-
.l-ui-container--panel.open
|
|
221
|
-
.l-ui-panel
|
|
222
|
-
.l-ui-panel__button
|
|
223
|
-
.l-ui-panel__button--dragging
|
|
224
|
-
.l-ui-panel__button--snapping
|
|
225
|
-
.l-ui-panel__icon
|
|
226
|
-
.l-ui-panel__icon--
|
|
227
|
-
.l-ui-
|
|
228
|
-
.l-ui-
|
|
229
|
-
.l-ui-panel__header
|
|
230
|
-
.l-ui-
|
|
231
|
-
.l-ui-
|
|
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 Panel button inline SVG icon
|
|
226
|
+
.l-ui-panel__icon--light Panel button icon (light, for custom image override)
|
|
227
|
+
.l-ui-panel__icon--dark Panel button icon (dark, for custom image override)
|
|
228
|
+
.l-ui-panel__resize-handle Desktop resize handle
|
|
229
|
+
.l-ui-panel__header Panel header
|
|
230
|
+
.l-ui-panel__header-heading Panel title
|
|
231
|
+
.l-ui-panel__body Scrollable panel content
|
|
232
|
+
.l-ui-panel__input Panel input area (footer)
|
|
232
233
|
```
|
|
233
234
|
|
|
234
235
|
## Conversation
|
|
235
236
|
|
|
236
237
|
```
|
|
237
|
-
.l-ui-conversation
|
|
238
|
-
.l-ui-conversation__messages
|
|
239
|
-
.l-ui-conversation__composer
|
|
238
|
+
.l-ui-conversation Conversation wrapper
|
|
239
|
+
.l-ui-conversation__messages Scrollable messages area
|
|
240
|
+
.l-ui-conversation__composer Message input area
|
|
240
241
|
.l-ui-conversation__composer-input Textarea
|
|
241
|
-
.l-ui-conversation__separator
|
|
242
|
+
.l-ui-conversation__separator Date separator
|
|
242
243
|
|
|
243
|
-
.l-ui-message
|
|
244
|
-
.l-ui-message--sent
|
|
245
|
-
.l-ui-message__avatar
|
|
246
|
-
.l-ui-message__bubble
|
|
247
|
-
.l-ui-message__author
|
|
248
|
-
.l-ui-message__body
|
|
249
|
-
.l-ui-message__footer
|
|
250
|
-
.l-ui-message__timestamp
|
|
244
|
+
.l-ui-message Message wrapper
|
|
245
|
+
.l-ui-message--sent Sent message (right-aligned)
|
|
246
|
+
.l-ui-message__avatar User avatar
|
|
247
|
+
.l-ui-message__bubble Message bubble
|
|
248
|
+
.l-ui-message__author Author name
|
|
249
|
+
.l-ui-message__body Message content
|
|
250
|
+
.l-ui-message__footer Metadata footer
|
|
251
|
+
.l-ui-message__timestamp Timestamp
|
|
251
252
|
```
|
|
252
253
|
|
|
253
254
|
## Markdown
|
|
@@ -268,36 +269,44 @@ WCAG 2.2 AA table pattern:
|
|
|
268
269
|
## Utility classes
|
|
269
270
|
|
|
270
271
|
```
|
|
271
|
-
.l-ui-utility--mt-0 through --mt-8
|
|
272
|
-
.l-ui-utility--mt-sm/md/lg/xl/2xl
|
|
273
|
-
.l-ui-utility--mb-0
|
|
274
|
-
.l-ui-sr-only
|
|
275
|
-
.l-ui-skip-link
|
|
276
|
-
.l-ui-list
|
|
277
|
-
.l-ui-container--grid
|
|
278
|
-
.l-ui-container--spread
|
|
279
|
-
.l-ui-container--pagy
|
|
280
|
-
.l-ui-scroll-lock
|
|
272
|
+
.l-ui-utility--mt-0 through --mt-8 Margin top (fixed scale)
|
|
273
|
+
.l-ui-utility--mt-sm/md/lg/xl/2xl Responsive margin top
|
|
274
|
+
.l-ui-utility--mb-0 Margin bottom zero
|
|
275
|
+
.l-ui-sr-only Visually hidden, screen reader only
|
|
276
|
+
.l-ui-skip-link Accessibility skip link
|
|
277
|
+
.l-ui-list Styled list
|
|
278
|
+
.l-ui-container--grid 1-col mobile, 2-col desktop grid
|
|
279
|
+
.l-ui-container--spread Flex row with space-between
|
|
280
|
+
.l-ui-container--pagy Pagination wrapper
|
|
281
|
+
.l-ui-scroll-lock Prevent body scroll (mobile panels/modals)
|
|
281
282
|
```
|
|
282
283
|
|
|
283
284
|
## Theming tokens
|
|
284
285
|
|
|
285
|
-
All
|
|
286
|
+
All color values are space-separated HSL channels (e.g. `220 80% 55%`, or `0 0% 100%` for white). Override after importing the engine CSS. Keywords and hex/rgb won't work - tokens are wrapped in `hsl()` when consumed. A converter such as https://colorpicker.dev/ can help translate hex/rgb values.
|
|
286
287
|
|
|
288
|
+
Tier 1 - Accent (quick branding):
|
|
289
|
+
```
|
|
290
|
+
--accent Primary action color
|
|
291
|
+
--accent-foreground Text on accent backgrounds
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Tier 2 - Full palette (override individually as needed):
|
|
287
295
|
```
|
|
288
|
-
--accent Primary action colour
|
|
289
|
-
--accent-foreground Text on accent backgrounds
|
|
290
296
|
--background Page background
|
|
291
|
-
--foreground Primary text
|
|
297
|
+
--foreground Primary text color
|
|
292
298
|
--foreground-muted Secondary/muted text
|
|
293
|
-
--border Default border
|
|
299
|
+
--border Default border color
|
|
294
300
|
--border-control Form control border
|
|
295
|
-
--ring Focus ring
|
|
301
|
+
--ring Focus ring color
|
|
296
302
|
--surface Card/surface background
|
|
297
|
-
--surface-
|
|
298
|
-
--
|
|
303
|
+
--surface-highlighted Highlighted surface
|
|
304
|
+
--button-primary-bg Primary button background (defaults to --accent)
|
|
305
|
+
--button-primary-text Primary button text (defaults to --accent-foreground)
|
|
306
|
+
--button-primary-icon Icon color on filled icon buttons (defaults to --button-primary-text)
|
|
307
|
+
--danger Danger/error color
|
|
299
308
|
--danger-light Light danger background
|
|
300
|
-
--danger-text Danger text
|
|
309
|
+
--danger-text Danger text color
|
|
301
310
|
--success-bg Success background
|
|
302
311
|
--success-text Success text
|
|
303
312
|
--switch-track-checked Checked switch track
|
|
@@ -305,6 +314,7 @@ All colour values are space-separated HSL channels (e.g. `220 80% 55%`). Overrid
|
|
|
305
314
|
--warning-text Warning text
|
|
306
315
|
--error-bg Error background
|
|
307
316
|
--error-text Error text
|
|
308
|
-
--backdrop Backdrop overlay colour
|
|
309
317
|
--header-height Header height (default 63px)
|
|
310
318
|
```
|
|
319
|
+
|
|
320
|
+
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). Override --button-primary-icon instead when only the icon color should change.
|
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)
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. This project follows [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## [0.7.0] - 2026-04-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `--button-primary-icon` Tier 2 token for filled icon buttons (e.g. `l-ui-panel__button`, `l-ui-scroll-to-bottom`). Defaults to `--button-primary-text`; override to recolor icons independently of button text.
|
|
10
|
+
- Inline SVG default for the panel toggle button icon - inherits `currentColor` so it picks up `--button-primary-icon`. Per-request `@l_ui_panel_icon_light_url` / `@l_ui_panel_icon_dark_url` still replace the default image, and each theme falls back to the inline SVG independently when its variable is unset.
|
|
11
|
+
- Documentation of the HSL-channel format for theme tokens (space-separated channels such as `220 80% 55%`) in `SKILL.md`, `references/CSS.md`, and the overrides generator, with a pointer to a hex/rgb converter.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- `l-ui-scroll-to-bottom` now bakes the chevron-down icon into CSS via `mask-image` (with `-webkit-mask` prefixes and a URL-encoded SVG for Safari compatibility) and colours it with `--button-primary-icon`. Host markup is a plain empty `<button class="l-ui-scroll-to-bottom" aria-label="...">` - no inner `<svg>`/`<img>` required.
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
|
|
19
|
+
- Static-file panel icon overrides (`panel_icon_light.svg`, `panel_icon_dark.svg` in `app/assets/images/layered_ui/`). Use `@l_ui_panel_icon_light_url` / `@l_ui_panel_icon_dark_url` instance variables to supply a custom image.
|
|
20
|
+
|
|
21
|
+
## [0.6.0] - 2026-04-19
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- `rewriteLink` action on `l-ui--search-form` controller to preserve URL params in pagination links across Turbo Frames
|
|
26
|
+
- Paginated Ransack demo in the dummy app with scoped `page_key` params
|
|
27
|
+
- Thin scrollbars on all scrollable elements within `l-ui-body` (for Windows compatibility)
|
|
28
|
+
- `--button-primary-bg` and `--button-primary-text` tokens documented as independently overridable (e.g. pink accent with white button text in dark mode)
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Renamed `PaginationHelper` to `PagyHelper` to match the underlying gem
|
|
33
|
+
- Renamed `--surface-active` to `--surface-highlighted` (and all related CSS classes: `l-ui-surface--active` to `l-ui-surface--highlighted`, `l-ui-surface--collapsible-active` to `l-ui-surface--collapsible-highlighted`)
|
|
34
|
+
- Scoped page params (e.g. `users_page`) are now reset when a Ransack search or sort submits, returning to page 1
|
|
35
|
+
- Floating icon buttons (`l-ui-panel__button`, `l-ui-scroll-to-bottom`) now use solid `bg-button-primary-bg` instead of semi-transparent with shadow
|
|
36
|
+
- Navigation backdrop uses blur-only effect instead of a color overlay
|
|
37
|
+
- Navigation secondary border changed from `border-surface` to `border-border`
|
|
38
|
+
- Dummy app uses default Devise routes instead of custom path names
|
|
39
|
+
- Agent skill section moved higher in README and dummy app home page
|
|
40
|
+
- Documentation clarifies that `content_for` blocks must appear above the layout render call
|
|
41
|
+
- Color token documentation reorganised into Tier 1 (accent) and Tier 2 (full palette)
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
|
|
45
|
+
- `--backdrop` CSS custom property - backdrops now use blur-only styling
|
|
46
|
+
|
|
5
47
|
## [0.5.0] - 2026-04-19
|
|
6
48
|
|
|
7
49
|
### Added
|
data/README.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# layered-ui-rails
|
|
2
2
|
|
|
3
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
3
4
|
[](https://github.com/layered-ai-public/layered-ui-rails/actions/workflows/ci.yml)
|
|
4
5
|
[](https://www.w3.org/WAI/WCAG22/quickref/)
|
|
5
|
-
[](https://opensource.org/licenses/Apache-2.0)
|
|
6
6
|
[](https://www.layered.ai/)
|
|
7
7
|
[](https://github.com/layered-ai-public/layered-ui-rails)
|
|
8
8
|
[](https://discord.gg/aCGqz9Bx)
|
|
9
|
+
[](https://www.youtube.com/@UseLayeredAi)
|
|
10
|
+
[](https://x.com/UseLayeredAi)
|
|
11
|
+
[](https://www.linkedin.com/company/uselayeredai/)
|
|
9
12
|
|
|
10
13
|
An open source, Rails 8+ engine that provides WCAG 2.2 AA compliant design tokens, Tailwind CSS utilities, and Stimulus controllers for theme switching, mobile navigation, slide-out panels, modals, and tabs. See the [live demo](https://layered-ui-rails.layered.ai).
|
|
11
14
|
|
|
@@ -42,12 +45,36 @@ The install generator will:
|
|
|
42
45
|
- Add `@import "./layered_ui";` to your `application.css`
|
|
43
46
|
- Add `import "layered_ui"` to your `application.js`
|
|
44
47
|
|
|
45
|
-
Then update your application layout to render the engine layout:
|
|
48
|
+
Then update your application layout to render the engine layout. Place any `content_for` blocks **above** the render call - the engine layout reads them when it renders, so they must be defined first:
|
|
46
49
|
|
|
47
50
|
```erb
|
|
51
|
+
<% content_for :l_ui_body_class, "l-ui-body--always-show-navigation" %>
|
|
52
|
+
|
|
53
|
+
<% content_for :l_ui_navigation_items do %>
|
|
54
|
+
<%= l_ui_navigation_item "Home", root_path %>
|
|
55
|
+
<% end %>
|
|
56
|
+
|
|
48
57
|
<%= render template: "layouts/layered_ui/application" %>
|
|
49
58
|
```
|
|
50
59
|
|
|
60
|
+
## Agent skill
|
|
61
|
+
|
|
62
|
+
An [agent skill](https://agentskills.io) is included so AI coding agents can work with `layered-ui-rails` in your project. Once installed, the agent can handle the full setup - just ask it to add `layered-ui-rails` to your app and it will install the gem, run the generator, and configure your layout.
|
|
63
|
+
|
|
64
|
+
**Project install** - scoped to a single repo, available to all contributors:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bin/rails generate layered:ui:install_agent_skill
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Global install** - available across all your projects:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
./install-skill.sh
|
|
74
|
+
# or install remotely without cloning the repo:
|
|
75
|
+
curl -fsSL https://raw.githubusercontent.com/layered-ai-public/layered-ui-rails/main/install-skill.sh | sh
|
|
76
|
+
```
|
|
77
|
+
|
|
51
78
|
## Requirements
|
|
52
79
|
|
|
53
80
|
- Ruby on Rails >= 8.0
|
|
@@ -67,7 +94,10 @@ Then update your application layout to render the engine layout:
|
|
|
67
94
|
|
|
68
95
|
## Customising theme tokens
|
|
69
96
|
|
|
70
|
-
All colors are CSS custom properties on `:root
|
|
97
|
+
All colors are CSS custom properties on `:root` using a two-tier system:
|
|
98
|
+
|
|
99
|
+
- **Tier 1 - Accent:** Set `--accent` and `--accent-foreground` for quick branding.
|
|
100
|
+
- **Tier 2 - Full palette:** Override any individual token. Includes `--button-primary-bg` and `--button-primary-text` which default to the accent pair but can be overridden independently (e.g. a pink accent that needs white button text in dark mode).
|
|
71
101
|
|
|
72
102
|
```css
|
|
73
103
|
/* app/assets/tailwind/application.css */
|
|
@@ -81,6 +111,7 @@ All colors are CSS custom properties on `:root`. Override any token in your styl
|
|
|
81
111
|
.dark {
|
|
82
112
|
--accent: 220 80% 65%;
|
|
83
113
|
--accent-foreground: 0 0% 9%;
|
|
114
|
+
--button-primary-text: 0 0% 100%; /* white icons/text on colored buttons */
|
|
84
115
|
}
|
|
85
116
|
```
|
|
86
117
|
|
|
@@ -119,8 +150,6 @@ Replace the defaults by placing files with the same names in `app/assets/images/
|
|
|
119
150
|
| `icon_light.svg` | Favicon and header icon (light theme) |
|
|
120
151
|
| `icon_dark.svg` | Favicon and header icon (dark theme) |
|
|
121
152
|
| `apple_touch_icon.png` | Apple touch icon |
|
|
122
|
-
| `panel_icon_light.svg` | Panel toggle button (light theme) |
|
|
123
|
-
| `panel_icon_dark.svg` | Panel toggle button (dark theme) |
|
|
124
153
|
|
|
125
154
|
layered-ui-rails uses two patterns for per-request overrides:
|
|
126
155
|
|
|
@@ -153,24 +182,6 @@ For per-request icons, set instance variables - the engine renders `<link>` and
|
|
|
153
182
|
|
|
154
183
|
> **Security:** Rails HTML-escapes URL values, so XSS via attribute injection is mitigated. However, if values are tenant-controlled, validate that they are legitimate URLs - reject `javascript:` schemes and ensure values point to expected origins.
|
|
155
184
|
|
|
156
|
-
## Agent skill
|
|
157
|
-
|
|
158
|
-
An [agent skill](https://agentskills.io) is included so AI coding agents like Claude Code can work with layered-ui-rails in your project.
|
|
159
|
-
|
|
160
|
-
**Project install** - scoped to a single repo, available to all contributors:
|
|
161
|
-
|
|
162
|
-
```bash
|
|
163
|
-
bin/rails generate layered:ui:install_agent_skill
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**Global install** - available across all your projects:
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
./install-skill.sh
|
|
170
|
-
# or install remotely without cloning the repo:
|
|
171
|
-
curl -fsSL https://raw.githubusercontent.com/layered-ai-public/layered-ui-rails/main/install-skill.sh | sh
|
|
172
|
-
```
|
|
173
|
-
|
|
174
185
|
## Documentation
|
|
175
186
|
|
|
176
187
|
An online version of the documentation is available at **[layered-ui-rails.layered.ai](https://layered-ui-rails.layered.ai)**.
|
|
@@ -215,4 +226,3 @@ Copyright 2026 LAYERED AI LIMITED (UK company number: 17056830). See [NOTICE](NO
|
|
|
215
226
|
## Trademarks
|
|
216
227
|
|
|
217
228
|
The source code is fully open, but the layered.ai name, logo, and brand assets are trademarks of LAYERED AI LIMITED. The Apache 2.0 license does not grant rights to use the layered.ai branding. Forks and redistributions must use a distinct name. See [TRADEMARK.md](TRADEMARK.md) for the full policy.
|
|
218
|
-
|
|
@@ -32,12 +32,25 @@
|
|
|
32
32
|
|
|
33
33
|
@variant dark (&:where(.dark, .dark *));
|
|
34
34
|
|
|
35
|
-
/*
|
|
35
|
+
/*
|
|
36
|
+
* Color tokens (HSL channels, e.g. "220 80% 55%")
|
|
37
|
+
*
|
|
38
|
+
* Tier 1 - Accent: --accent, --accent-foreground
|
|
39
|
+
* Tier 2 - Full palette: background, foreground, border, surface, etc.
|
|
40
|
+
* Also includes --button-primary-bg and --button-primary-text which
|
|
41
|
+
* default to the accent pair but can be overridden independently
|
|
42
|
+
* (e.g. a pink accent that needs white button text in dark mode).
|
|
43
|
+
*
|
|
44
|
+
* See the generator (bin/rails generate layered:ui:create_overrides) for a
|
|
45
|
+
* ready-made overrides file with all tokens commented out.
|
|
46
|
+
*/
|
|
36
47
|
|
|
37
48
|
@layer base {
|
|
38
49
|
:root {
|
|
50
|
+
/* Tier 1 - Accent */
|
|
39
51
|
--accent: 0 0% 9%;
|
|
40
52
|
--accent-foreground: 0 0% 100%;
|
|
53
|
+
/* Tier 2 - Full palette */
|
|
41
54
|
--background: 0 0% 100%;
|
|
42
55
|
--foreground: 0 0% 13%;
|
|
43
56
|
--foreground-muted: 0 0% 29%;
|
|
@@ -45,9 +58,10 @@
|
|
|
45
58
|
--border-control: 0 0% 55%;
|
|
46
59
|
--ring: 0 0% 13%;
|
|
47
60
|
--surface: 0 0% 96%;
|
|
48
|
-
--surface-
|
|
61
|
+
--surface-highlighted: 0 0% 91%;
|
|
49
62
|
--button-primary-bg: var(--accent);
|
|
50
63
|
--button-primary-text: var(--accent-foreground);
|
|
64
|
+
--button-primary-icon: var(--button-primary-text);
|
|
51
65
|
--danger: 0 72% 38%;
|
|
52
66
|
--danger-light: 0 100% 97%;
|
|
53
67
|
--danger-text: 0 72% 35%;
|
|
@@ -58,13 +72,14 @@
|
|
|
58
72
|
--warning-text: 48 96% 15%;
|
|
59
73
|
--error-bg: 0 84% 75%;
|
|
60
74
|
--error-text: 0 93% 12%;
|
|
61
|
-
--backdrop: 0 0% 0%;
|
|
62
75
|
--header-height: 63px;
|
|
63
76
|
}
|
|
64
77
|
|
|
65
78
|
.dark {
|
|
79
|
+
/* Tier 1 - Accent */
|
|
66
80
|
--accent: 0 0% 100%;
|
|
67
81
|
--accent-foreground: 0 0% 9%;
|
|
82
|
+
/* Tier 2 - Full palette */
|
|
68
83
|
--background: 0 0% 0%;
|
|
69
84
|
--foreground: 0 0% 89%;
|
|
70
85
|
--foreground-muted: 0 0% 71%;
|
|
@@ -72,9 +87,10 @@
|
|
|
72
87
|
--border-control: 0 0% 40%;
|
|
73
88
|
--ring: 0 0% 89%;
|
|
74
89
|
--surface: 0 0% 8%;
|
|
75
|
-
--surface-
|
|
90
|
+
--surface-highlighted: 0 0% 16%;
|
|
76
91
|
--button-primary-bg: var(--accent);
|
|
77
92
|
--button-primary-text: var(--accent-foreground);
|
|
93
|
+
--button-primary-icon: var(--button-primary-text);
|
|
78
94
|
--danger: 0 85% 60%;
|
|
79
95
|
--danger-light: 0 93% 15%;
|
|
80
96
|
--danger-text: 0 85% 64%;
|
|
@@ -85,7 +101,6 @@
|
|
|
85
101
|
--warning-text: 48 96% 80%;
|
|
86
102
|
--error-bg: 0 93% 12%;
|
|
87
103
|
--error-text: 0 84% 75%;
|
|
88
|
-
--backdrop: 0 0% 0%;
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
/* Typography */
|
|
@@ -127,6 +142,14 @@
|
|
|
127
142
|
@apply whitespace-pre overflow-x-auto
|
|
128
143
|
text-foreground-muted;
|
|
129
144
|
}
|
|
145
|
+
|
|
146
|
+
/* Scrollbars */
|
|
147
|
+
|
|
148
|
+
.l-ui-body,
|
|
149
|
+
.l-ui-body * {
|
|
150
|
+
scrollbar-width: thin;
|
|
151
|
+
scrollbar-color: hsl(var(--foreground-muted) / 0.3) transparent;
|
|
152
|
+
}
|
|
130
153
|
}
|
|
131
154
|
|
|
132
155
|
/* Theme */
|
|
@@ -142,9 +165,10 @@
|
|
|
142
165
|
--color-border: hsl(var(--border));
|
|
143
166
|
--color-border-control: hsl(var(--border-control));
|
|
144
167
|
--color-surface: hsl(var(--surface));
|
|
145
|
-
--color-surface-
|
|
168
|
+
--color-surface-highlighted: hsl(var(--surface-highlighted));
|
|
146
169
|
--color-button-primary-bg: hsl(var(--button-primary-bg));
|
|
147
170
|
--color-button-primary-text: hsl(var(--button-primary-text));
|
|
171
|
+
--color-button-primary-icon: hsl(var(--button-primary-icon));
|
|
148
172
|
--color-danger: hsl(var(--danger));
|
|
149
173
|
--color-danger-light: hsl(var(--danger-light));
|
|
150
174
|
--color-danger-text: hsl(var(--danger-text));
|
|
@@ -155,7 +179,6 @@
|
|
|
155
179
|
--color-warning-text: hsl(var(--warning-text));
|
|
156
180
|
--color-error-bg: hsl(var(--error-bg));
|
|
157
181
|
--color-error-text: hsl(var(--error-text));
|
|
158
|
-
--color-backdrop: hsl(var(--backdrop));
|
|
159
182
|
--color-ring: hsl(var(--ring));
|
|
160
183
|
}
|
|
161
184
|
|
|
@@ -346,7 +369,7 @@
|
|
|
346
369
|
.l-ui-markdown code {
|
|
347
370
|
@apply px-1.5 py-0.5
|
|
348
371
|
text-xs
|
|
349
|
-
bg-surface-
|
|
372
|
+
bg-surface-highlighted
|
|
350
373
|
rounded;
|
|
351
374
|
}
|
|
352
375
|
|
|
@@ -593,7 +616,7 @@
|
|
|
593
616
|
.l-ui-backdrop--navigation {
|
|
594
617
|
@apply fixed top-[var(--header-height)] left-0 right-0 bottom-0
|
|
595
618
|
z-[45]
|
|
596
|
-
|
|
619
|
+
backdrop-blur-sm
|
|
597
620
|
opacity-0 pointer-events-none
|
|
598
621
|
transition-opacity duration-200;
|
|
599
622
|
}
|
|
@@ -647,7 +670,7 @@
|
|
|
647
670
|
.l-ui-navigation__secondary {
|
|
648
671
|
@apply flex flex-col
|
|
649
672
|
pl-4
|
|
650
|
-
border-l-4 border-
|
|
673
|
+
border-l-4 border-border;
|
|
651
674
|
|
|
652
675
|
.l-ui-navigation__item--active {
|
|
653
676
|
@apply bg-transparent
|
|
@@ -854,9 +877,9 @@ pre.l-ui-surface {
|
|
|
854
877
|
}
|
|
855
878
|
}
|
|
856
879
|
|
|
857
|
-
.l-ui-surface--
|
|
880
|
+
.l-ui-surface--highlighted {
|
|
858
881
|
@apply surface
|
|
859
|
-
bg-surface-
|
|
882
|
+
bg-surface-highlighted;
|
|
860
883
|
}
|
|
861
884
|
|
|
862
885
|
.l-ui-surface--collapsible {
|
|
@@ -865,14 +888,14 @@ pre.l-ui-surface {
|
|
|
865
888
|
bg-surface;
|
|
866
889
|
}
|
|
867
890
|
|
|
868
|
-
.l-ui-surface--collapsible-
|
|
891
|
+
.l-ui-surface--collapsible-highlighted {
|
|
869
892
|
@apply surface
|
|
870
893
|
p-0
|
|
871
|
-
bg-surface-
|
|
894
|
+
bg-surface-highlighted;
|
|
872
895
|
}
|
|
873
896
|
|
|
874
897
|
.l-ui-surface--collapsible,
|
|
875
|
-
.l-ui-surface--collapsible-
|
|
898
|
+
.l-ui-surface--collapsible-highlighted {
|
|
876
899
|
&[open] > .l-ui-surface__summary .l-ui-surface__chevron {
|
|
877
900
|
@apply rotate-90;
|
|
878
901
|
}
|
|
@@ -1252,7 +1275,7 @@ pre.l-ui-surface {
|
|
|
1252
1275
|
|
|
1253
1276
|
@utility pagination__item--active {
|
|
1254
1277
|
@apply font-bold
|
|
1255
|
-
bg-surface-
|
|
1278
|
+
bg-surface-highlighted
|
|
1256
1279
|
text-foreground
|
|
1257
1280
|
pointer-events-none;
|
|
1258
1281
|
}
|
|
@@ -1334,7 +1357,7 @@ pre.l-ui-surface {
|
|
|
1334
1357
|
|
|
1335
1358
|
.l-ui-badge--default {
|
|
1336
1359
|
@apply badge
|
|
1337
|
-
bg-surface-
|
|
1360
|
+
bg-surface-highlighted
|
|
1338
1361
|
text-foreground-muted;
|
|
1339
1362
|
}
|
|
1340
1363
|
|
|
@@ -1363,9 +1386,9 @@ pre.l-ui-surface {
|
|
|
1363
1386
|
w-14 h-14
|
|
1364
1387
|
z-40
|
|
1365
1388
|
cursor-pointer
|
|
1366
|
-
bg-button-primary-bg
|
|
1367
|
-
text-button-primary-
|
|
1368
|
-
rounded-full
|
|
1389
|
+
bg-button-primary-bg
|
|
1390
|
+
text-button-primary-icon
|
|
1391
|
+
rounded-full
|
|
1369
1392
|
focus-ring
|
|
1370
1393
|
transition-all;
|
|
1371
1394
|
touch-action: none;
|
|
@@ -1391,6 +1414,10 @@ pre.l-ui-surface {
|
|
|
1391
1414
|
opacity-0 pointer-events-none;
|
|
1392
1415
|
}
|
|
1393
1416
|
|
|
1417
|
+
.l-ui-panel__icon {
|
|
1418
|
+
@apply w-7 h-7;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1394
1421
|
.l-ui-panel__icon--light {
|
|
1395
1422
|
@apply block
|
|
1396
1423
|
dark:invert-0;
|
|
@@ -1632,15 +1659,28 @@ pre.l-ui-surface {
|
|
|
1632
1659
|
.l-ui-scroll-to-bottom {
|
|
1633
1660
|
@apply
|
|
1634
1661
|
sticky bottom-2 flex items-center justify-center
|
|
1635
|
-
|
|
1662
|
+
ml-auto mr-0 -mt-9 h-9 w-9
|
|
1636
1663
|
rounded-full
|
|
1637
1664
|
cursor-pointer
|
|
1638
|
-
bg-button-primary-bg
|
|
1665
|
+
bg-button-primary-bg text-button-primary-icon
|
|
1639
1666
|
focus-ring
|
|
1640
1667
|
opacity-0 pointer-events-none
|
|
1641
1668
|
transition-opacity duration-200;
|
|
1642
1669
|
}
|
|
1643
1670
|
|
|
1671
|
+
.l-ui-scroll-to-bottom::before {
|
|
1672
|
+
content: "";
|
|
1673
|
+
@apply block w-4 h-4 bg-button-primary-icon;
|
|
1674
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
|
|
1675
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
|
|
1676
|
+
-webkit-mask-size: contain;
|
|
1677
|
+
mask-size: contain;
|
|
1678
|
+
-webkit-mask-repeat: no-repeat;
|
|
1679
|
+
mask-repeat: no-repeat;
|
|
1680
|
+
-webkit-mask-position: center;
|
|
1681
|
+
mask-position: center;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1644
1684
|
.l-ui-scroll-to-bottom[data-visible] {
|
|
1645
1685
|
@apply
|
|
1646
1686
|
opacity-100 pointer-events-auto;
|
|
@@ -1704,7 +1744,7 @@ pre.l-ui-surface {
|
|
|
1704
1744
|
}
|
|
1705
1745
|
|
|
1706
1746
|
.l-ui-modal::backdrop {
|
|
1707
|
-
@apply
|
|
1747
|
+
@apply backdrop-blur-sm;
|
|
1708
1748
|
}
|
|
1709
1749
|
|
|
1710
1750
|
.l-ui-modal__header {
|
|
@@ -5,7 +5,6 @@ import { getHeaderHeight, getPadding, getLeftEdge } from "layered_ui/utilities/l
|
|
|
5
5
|
const BUTTON_SIZE = 56
|
|
6
6
|
const DRAG_THRESHOLD = 5
|
|
7
7
|
const SNAP_TIMEOUT = 400
|
|
8
|
-
const TOGGLE_DELAY = 160
|
|
9
8
|
const TOP_LEFT = "top-left"
|
|
10
9
|
const TOP_RIGHT = "top-right"
|
|
11
10
|
const BOTTOM_LEFT = "bottom-left"
|
|
@@ -25,7 +24,6 @@ export default class extends Controller {
|
|
|
25
24
|
})
|
|
26
25
|
}
|
|
27
26
|
this.boundKeyboardShortcuts = this.handleKeyboardShortcuts.bind(this)
|
|
28
|
-
this.toggleTimeout = null
|
|
29
27
|
|
|
30
28
|
this.restorePosition()
|
|
31
29
|
window.addEventListener('resize', this.boundWindowResize)
|
|
@@ -33,7 +31,6 @@ export default class extends Controller {
|
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
disconnect() {
|
|
36
|
-
clearTimeout(this.toggleTimeout)
|
|
37
34
|
cancelAnimationFrame(this._resizeFrame)
|
|
38
35
|
window.removeEventListener('resize', this.boundWindowResize)
|
|
39
36
|
document.removeEventListener('keydown', this.boundKeyboardShortcuts)
|
|
@@ -49,11 +46,13 @@ export default class extends Controller {
|
|
|
49
46
|
}
|
|
50
47
|
}
|
|
51
48
|
|
|
52
|
-
// Restore position from localStorage or apply default
|
|
53
49
|
restorePosition() {
|
|
54
50
|
const position = storageGetJSON("panelButtonPosition")
|
|
55
51
|
|
|
56
|
-
if (position && position.
|
|
52
|
+
if (position && position.corner) {
|
|
53
|
+
this.moveToCorner(position.corner)
|
|
54
|
+
} else if (position && position.edge && typeof position.top === "number") {
|
|
55
|
+
this.corner = null
|
|
57
56
|
const topPx = this.clampTop(position.top / 100 * window.innerHeight)
|
|
58
57
|
this.element.style.top = `${topPx}px`
|
|
59
58
|
if (position.edge === "left") {
|
|
@@ -68,56 +67,8 @@ export default class extends Controller {
|
|
|
68
67
|
}
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (event.detail > 1) return
|
|
74
|
-
|
|
75
|
-
clearTimeout(this.toggleTimeout)
|
|
76
|
-
this.toggleTimeout = setTimeout(() => {
|
|
77
|
-
this.dispatch("toggle", { bubbles: true })
|
|
78
|
-
}, TOGGLE_DELAY)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Double-click cycles the floating button around screen corners
|
|
82
|
-
cycleCorner(event) {
|
|
83
|
-
event.preventDefault()
|
|
84
|
-
event.stopPropagation()
|
|
85
|
-
clearTimeout(this.toggleTimeout)
|
|
86
|
-
|
|
87
|
-
const current = this.currentCorner()
|
|
88
|
-
const next = this.nextCorner(current)
|
|
89
|
-
this.moveToCorner(next)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
currentCorner() {
|
|
93
|
-
const topPx = this.element.style.top
|
|
94
|
-
? parseFloat(this.element.style.top)
|
|
95
|
-
: this.element.getBoundingClientRect().top
|
|
96
|
-
|
|
97
|
-
const topEdge = this.clampTop(getHeaderHeight() + getPadding())
|
|
98
|
-
const bottomEdge = this.clampTop(window.innerHeight - BUTTON_SIZE - getPadding())
|
|
99
|
-
const midY = (topEdge + bottomEdge) / 2
|
|
100
|
-
const isTop = topPx <= midY
|
|
101
|
-
|
|
102
|
-
const isRight = this.element.style.right && this.element.style.right !== "auto"
|
|
103
|
-
if (isTop && isRight) return TOP_RIGHT
|
|
104
|
-
if (!isTop && isRight) return BOTTOM_RIGHT
|
|
105
|
-
if (isTop) return TOP_LEFT
|
|
106
|
-
return BOTTOM_LEFT
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
nextCorner(corner) {
|
|
110
|
-
switch (corner) {
|
|
111
|
-
case TOP_LEFT:
|
|
112
|
-
return TOP_RIGHT
|
|
113
|
-
case TOP_RIGHT:
|
|
114
|
-
return BOTTOM_RIGHT
|
|
115
|
-
case BOTTOM_RIGHT:
|
|
116
|
-
return BOTTOM_LEFT
|
|
117
|
-
case BOTTOM_LEFT:
|
|
118
|
-
default:
|
|
119
|
-
return TOP_LEFT
|
|
120
|
-
}
|
|
70
|
+
toggle() {
|
|
71
|
+
this.dispatch("toggle", { bubbles: true })
|
|
121
72
|
}
|
|
122
73
|
|
|
123
74
|
// Move the button directly to a corner via keyboard shortcut
|
|
@@ -148,6 +99,7 @@ export default class extends Controller {
|
|
|
148
99
|
}
|
|
149
100
|
|
|
150
101
|
moveToCorner(corner) {
|
|
102
|
+
this.corner = corner
|
|
151
103
|
const leftEdge = getLeftEdge()
|
|
152
104
|
const rightEdge = getPadding()
|
|
153
105
|
const topEdge = this.clampTop(getHeaderHeight() + getPadding())
|
|
@@ -158,40 +110,38 @@ export default class extends Controller {
|
|
|
158
110
|
this.element.style.left = `${leftEdge}px`
|
|
159
111
|
this.element.style.right = "auto"
|
|
160
112
|
this.element.style.top = `${topEdge}px`
|
|
161
|
-
this.savePosition("left", topEdge)
|
|
162
113
|
break
|
|
163
114
|
case TOP_RIGHT:
|
|
164
115
|
this.element.style.left = "auto"
|
|
165
116
|
this.element.style.right = `${rightEdge}px`
|
|
166
117
|
this.element.style.top = `${topEdge}px`
|
|
167
|
-
this.savePosition("right", topEdge)
|
|
168
118
|
break
|
|
169
119
|
case BOTTOM_LEFT:
|
|
170
120
|
this.element.style.left = `${leftEdge}px`
|
|
171
121
|
this.element.style.right = "auto"
|
|
172
122
|
this.element.style.top = `${bottomEdge}px`
|
|
173
|
-
this.savePosition("left", bottomEdge)
|
|
174
123
|
break
|
|
175
124
|
case BOTTOM_RIGHT:
|
|
176
125
|
this.element.style.left = "auto"
|
|
177
126
|
this.element.style.right = `${rightEdge}px`
|
|
178
127
|
this.element.style.top = `${bottomEdge}px`
|
|
179
|
-
this.savePosition("right", bottomEdge)
|
|
180
128
|
break
|
|
181
129
|
}
|
|
130
|
+
|
|
131
|
+
this.saveCornerPosition(corner)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
saveCornerPosition(corner) {
|
|
135
|
+
storageSet("panelButtonPosition", JSON.stringify({ corner }))
|
|
182
136
|
}
|
|
183
137
|
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
storageSet("panelButtonPosition", JSON.stringify({ edge, top
|
|
138
|
+
saveDragPosition(edge) {
|
|
139
|
+
const top = parseFloat(this.element.style.top) / window.innerHeight * 100
|
|
140
|
+
storageSet("panelButtonPosition", JSON.stringify({ edge, top }))
|
|
187
141
|
}
|
|
188
142
|
|
|
189
|
-
// Apply the default bottom-right position
|
|
190
143
|
applyDefault() {
|
|
191
|
-
|
|
192
|
-
this.element.style.right = `${padding}px`
|
|
193
|
-
this.element.style.left = "auto"
|
|
194
|
-
this.element.style.top = `${window.innerHeight - BUTTON_SIZE - padding}px`
|
|
144
|
+
this.moveToCorner(BOTTOM_RIGHT)
|
|
195
145
|
}
|
|
196
146
|
|
|
197
147
|
// Start tracking a potential drag
|
|
@@ -274,7 +224,8 @@ export default class extends Controller {
|
|
|
274
224
|
this.element.style.left = `${targetLeft}px`
|
|
275
225
|
this.element.style.right = "auto"
|
|
276
226
|
|
|
277
|
-
this.
|
|
227
|
+
this.corner = null
|
|
228
|
+
this.saveDragPosition(edge)
|
|
278
229
|
|
|
279
230
|
const onTransitionEnd = () => {
|
|
280
231
|
this.element.classList.remove("l-ui-panel__button--snapping")
|
|
@@ -316,8 +267,12 @@ export default class extends Controller {
|
|
|
316
267
|
}, 0)
|
|
317
268
|
}
|
|
318
269
|
|
|
319
|
-
// Constrain position to viewport bounds after resize
|
|
320
270
|
constrainPosition() {
|
|
271
|
+
if (this.corner) {
|
|
272
|
+
this.moveToCorner(this.corner)
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
321
276
|
if (!this.element.style.top) return
|
|
322
277
|
|
|
323
278
|
const topPx = this.clampTop(parseFloat(this.element.style.top))
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// When multiple Ransack collections share one page, each form only knows about
|
|
7
|
-
// its own fields. This controller reads the current URL at submit time and
|
|
8
|
-
// injects hidden inputs for params that belong to other scopes, so the
|
|
9
|
-
// resulting URL reflects the full state of all collections. The clear action
|
|
10
|
-
// rewrites the clear link's href to include the same preserved params.
|
|
3
|
+
// When multiple scoped Ransack collections share one page, preserves other
|
|
4
|
+
// scopes' params across form submits (preserve), clear links (clear), and
|
|
5
|
+
// pagination clicks (rewriteLink).
|
|
11
6
|
export default class extends Controller {
|
|
12
7
|
static values = { scope: String }
|
|
13
8
|
|
|
@@ -32,16 +27,35 @@ export default class extends Controller {
|
|
|
32
27
|
link.href = qs ? `${base}?${qs}` : base
|
|
33
28
|
}
|
|
34
29
|
|
|
30
|
+
rewriteLink(event) {
|
|
31
|
+
const link = event.target.closest(".l-ui-container--pagy a[href]")
|
|
32
|
+
if (!link) return
|
|
33
|
+
|
|
34
|
+
const url = new URL(link.href, window.location.origin)
|
|
35
|
+
const linkKeys = new Set(url.searchParams.keys())
|
|
36
|
+
|
|
37
|
+
for (const [key, value] of this.#otherParams()) {
|
|
38
|
+
if (!linkKeys.has(key)) url.searchParams.append(key, value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
link.href = url.pathname + url.search
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
#otherParams() {
|
|
36
45
|
const currentParams = new URLSearchParams(window.location.search)
|
|
37
46
|
const scope = this.scopeValue
|
|
38
47
|
const result = new URLSearchParams()
|
|
39
48
|
|
|
40
49
|
for (const [key, value] of currentParams) {
|
|
41
|
-
if (key === scope || key.startsWith(scope + "[") || key === "commit") continue
|
|
50
|
+
if (key === scope || key.startsWith(scope + "[") || key === "commit" || key === "page" || key === this.#pageParam) continue
|
|
42
51
|
result.append(key, value)
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
return result
|
|
46
55
|
}
|
|
56
|
+
|
|
57
|
+
get #pageParam() {
|
|
58
|
+
const scope = this.scopeValue
|
|
59
|
+
return scope.endsWith("_q") ? scope.slice(0, -2) + "_page" : null
|
|
60
|
+
}
|
|
47
61
|
}
|
|
@@ -60,12 +60,20 @@
|
|
|
60
60
|
aria-controls="panel"
|
|
61
61
|
aria-keyshortcuts="Control+I Meta+I Control+Alt+1 Meta+Alt+1 Control+Alt+2 Meta+Alt+2 Control+Alt+3 Meta+Alt+3 Control+Alt+4 Meta+Alt+4"
|
|
62
62
|
tabindex="-1"
|
|
63
|
-
title="Toggle panel (Ctrl/⌘+i). Move to corners (Ctrl/⌘ + Alt/⌥ + 1/2/3/4)
|
|
63
|
+
title="Toggle panel (Ctrl/⌘+i). Move to corners (Ctrl/⌘ + Alt/⌥ + 1/2/3/4) or drag."
|
|
64
64
|
data-controller="l-ui--panel-button"
|
|
65
|
-
data-action="click->l-ui--panel-button#
|
|
65
|
+
data-action="click->l-ui--panel-button#toggle mousedown->l-ui--panel-button#startDrag touchstart->l-ui--panel-button#startDrag"
|
|
66
66
|
data-l-ui--panel-target="actionButton"
|
|
67
67
|
>
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
<% if @l_ui_panel_icon_light_url.present? %>
|
|
69
|
+
<%= image_tag(@l_ui_panel_icon_light_url, alt: "", class: "l-ui-icon--lg l-ui-panel__icon--light", aria: { hidden: true }) %>
|
|
70
|
+
<% else %>
|
|
71
|
+
<svg class="l-ui-panel__icon l-ui-panel__icon--light" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
|
|
72
|
+
<% end %>
|
|
73
|
+
<% if @l_ui_panel_icon_dark_url.present? %>
|
|
74
|
+
<%= image_tag(@l_ui_panel_icon_dark_url, alt: "", class: "l-ui-icon--lg l-ui-panel__icon--dark", aria: { hidden: true }) %>
|
|
75
|
+
<% else %>
|
|
76
|
+
<svg class="l-ui-panel__icon l-ui-panel__icon--dark" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
|
|
77
|
+
<% end %>
|
|
70
78
|
</button>
|
|
71
79
|
</div>
|
|
@@ -26,8 +26,10 @@ module Layered
|
|
|
26
26
|
* This file is NOT overwritten by the install generator, so your
|
|
27
27
|
* changes are preserved when you upgrade layered-ui-rails.
|
|
28
28
|
*
|
|
29
|
-
* Values are HSL channels
|
|
30
|
-
*
|
|
29
|
+
* Values are space-separated HSL channels (e.g. `220 80% 55%`,
|
|
30
|
+
* or `0 0% 100%` for white). Keywords and hex/rgb won't work -
|
|
31
|
+
* tokens are wrapped in `hsl()` when consumed. A converter such
|
|
32
|
+
* as https://colorpicker.dev/ can help translate hex/rgb values.
|
|
31
33
|
*/
|
|
32
34
|
|
|
33
35
|
/* ----------------------------------------------------------------
|
|
@@ -35,6 +37,12 @@ module Layered
|
|
|
35
37
|
*
|
|
36
38
|
* Set a single brand color. Primary buttons, active tabs, and
|
|
37
39
|
* active navigation items all inherit from these two variables.
|
|
40
|
+
*
|
|
41
|
+
* If your accent color needs a different text/icon color on
|
|
42
|
+
* buttons (e.g. a pink accent with white button text in dark
|
|
43
|
+
* mode), override --button-primary-text in Tier 2 below. To
|
|
44
|
+
* recolor only the icon (leaving button text unchanged),
|
|
45
|
+
* override --button-primary-icon instead.
|
|
38
46
|
* ---------------------------------------------------------------- */
|
|
39
47
|
|
|
40
48
|
:root {
|
|
@@ -62,9 +70,10 @@ module Layered
|
|
|
62
70
|
--border-control: 0 0% 55%;
|
|
63
71
|
--ring: 0 0% 13%;
|
|
64
72
|
--surface: 0 0% 96%;
|
|
65
|
-
--surface-
|
|
73
|
+
--surface-highlighted: 0 0% 91%;
|
|
66
74
|
--button-primary-bg: var(--accent);
|
|
67
75
|
--button-primary-text: var(--accent-foreground);
|
|
76
|
+
--button-primary-icon: var(--button-primary-text);
|
|
68
77
|
--danger: 0 72% 38%;
|
|
69
78
|
--danger-light: 0 100% 97%;
|
|
70
79
|
--danger-text: 0 72% 35%;
|
|
@@ -74,7 +83,6 @@ module Layered
|
|
|
74
83
|
--warning-text: 48 96% 15%;
|
|
75
84
|
--error-bg: 0 84% 75%;
|
|
76
85
|
--error-text: 0 93% 12%;
|
|
77
|
-
--backdrop: 0 0% 0%;
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
.dark {
|
|
@@ -85,9 +93,10 @@ module Layered
|
|
|
85
93
|
--border-control: 0 0% 40%;
|
|
86
94
|
--ring: 0 0% 89%;
|
|
87
95
|
--surface: 0 0% 8%;
|
|
88
|
-
--surface-
|
|
96
|
+
--surface-highlighted: 0 0% 16%;
|
|
89
97
|
--button-primary-bg: var(--accent);
|
|
90
98
|
--button-primary-text: var(--accent-foreground);
|
|
99
|
+
--button-primary-icon: var(--button-primary-text);
|
|
91
100
|
--danger: 0 85% 60%;
|
|
92
101
|
--danger-light: 0 93% 15%;
|
|
93
102
|
--danger-text: 0 85% 64%;
|
|
@@ -97,7 +106,6 @@ module Layered
|
|
|
97
106
|
--warning-text: 48 96% 80%;
|
|
98
107
|
--error-bg: 0 93% 12%;
|
|
99
108
|
--error-text: 0 84% 75%;
|
|
100
|
-
--backdrop: 0 0% 0%;
|
|
101
109
|
}
|
|
102
110
|
*/
|
|
103
111
|
|
data/lib/layered/ui/engine.rb
CHANGED
|
@@ -29,7 +29,7 @@ module Layered
|
|
|
29
29
|
helper Layered::Ui::AuthenticationHelper
|
|
30
30
|
helper Layered::Ui::BreadcrumbsHelper
|
|
31
31
|
helper Layered::Ui::NavigationHelper
|
|
32
|
-
helper Layered::Ui::
|
|
32
|
+
helper Layered::Ui::PagyHelper
|
|
33
33
|
helper Layered::Ui::TableHelper
|
|
34
34
|
helper Layered::Ui::FormHelper
|
|
35
35
|
helper Layered::Ui::RansackHelper
|
data/lib/layered/ui/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: layered-ui-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- layered.ai
|
|
@@ -212,7 +212,7 @@ files:
|
|
|
212
212
|
- app/helpers/layered/ui/breadcrumbs_helper.rb
|
|
213
213
|
- app/helpers/layered/ui/form_helper.rb
|
|
214
214
|
- app/helpers/layered/ui/navigation_helper.rb
|
|
215
|
-
- app/helpers/layered/ui/
|
|
215
|
+
- app/helpers/layered/ui/pagy_helper.rb
|
|
216
216
|
- app/helpers/layered/ui/ransack_helper.rb
|
|
217
217
|
- app/helpers/layered/ui/table_helper.rb
|
|
218
218
|
- app/javascript/layered_ui/controllers/l_ui/modal_controller.js
|