layered-ui-rails 0.1.4 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2e3c4b089f447a7d40452272925b236cfb43da5d8c8414dff4187c80505840a
4
- data.tar.gz: 72e580c90993476ed99f68bee967928ef31241c936582bf02b3816a2616028ab
3
+ metadata.gz: 25f7ed58266ba42853fdb266805b9f8daa8f69531bae482a13e5355e9868ba5e
4
+ data.tar.gz: 4dcd71073bf9335ef288d849e0754d3104ee750f3f049a1369537a303af39db2
5
5
  SHA512:
6
- metadata.gz: 6a6d460c812709f9063dcfa85644a0bf65b5b0c172a79bd2cc0a31e7775bb7f6f58c6e0a1090b4d20013ab142647b5836238c7726dd32325637202445f50f84c
7
- data.tar.gz: e15e51d28fc6ba387589acf87f4e5c725c2973e7360bc84d67fc7541c09d094c1258343a643ca343352eaeb8f0959d027e4214f59a496bab9172f74bf76992e2
6
+ metadata.gz: 32269b207610271eee8e2fd2c6aab706ac0c51b04d325f7e670543165ec13898ff6417e4ff966bd5bf2b3e85a692d73a7489eca18d34a873d1d7b77cf2d5ac6f
7
+ data.tar.gz: 9db4d1270d00b1cde0b5c6f406585efb7aa6334ddcfb9a8a64f3f75d46e05c2b82e4d20bb1e8f3064fdb47b04c55ce7f423abaf0b0a306f608a129fb4c542872
data/CHANGELOG.md CHANGED
@@ -2,7 +2,22 @@
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
- ## [Unreleased]
5
+ ## [0.2.0] - 2026-03-31
6
+
7
+ ### Added
8
+
9
+ - `yield :l_ui_head` in the engine layout `<head>`, allowing host apps to inject content (e.g. dynamic theme token overrides) via `content_for`
10
+ - `yield :l_ui_logo_light`, `:l_ui_logo_dark` in the header, allowing host apps to inject custom logos via `content_for`
11
+ - `@l_ui_icon_light_url`, `@l_ui_icon_dark_url`, `@l_ui_apple_touch_icon_url`, `@l_ui_panel_icon_light_url`, `@l_ui_panel_icon_dark_url` instance variables for per-request icon overrides
12
+ - `pre.l-ui-surface` style for preformatted code blocks
13
+
14
+ ### Changed
15
+
16
+ - Panel toggle icon override now uses `@l_ui_panel_icon_light_url` and `@l_ui_panel_icon_dark_url` instance variables, replacing `content_for :l_ui_panel_icon_light` and `:l_ui_panel_icon_dark`
17
+
18
+ ### Removed
19
+
20
+ - `content_for :l_ui_panel_icon_light` and `content_for :l_ui_panel_icon_dark` - use `@l_ui_panel_icon_light_url` and `@l_ui_panel_icon_dark_url` instance variables instead
6
21
 
7
22
  ## [0.1.4] - 2026-03-25
8
23
 
data/README.md CHANGED
@@ -65,6 +65,92 @@ Then update your application layout to render the engine layout:
65
65
  - **Customisable branding** - Override the default logos and icons and colors
66
66
  - **Google Lighthouse** - `layered-ui-rails` scores a [perfect 100](https://github.com/layered-ai-public/layered-ui-rails/raw/refs/heads/main/test/dummy/app/assets/images/lighthouse.webp) across all four Google Lighthouse categories - performance, accessibility, best practices, and SEO
67
67
 
68
+ ## Customising theme tokens
69
+
70
+ All colors are CSS custom properties on `:root`. Override any token in your stylesheet (after importing the engine CSS):
71
+
72
+ ```css
73
+ /* app/assets/tailwind/application.css */
74
+ @import "./layered_ui";
75
+
76
+ :root {
77
+ --accent: 220 80% 55%;
78
+ --accent-foreground: 0 0% 100%;
79
+ }
80
+
81
+ .dark {
82
+ --accent: 220 80% 65%;
83
+ --accent-foreground: 0 0% 9%;
84
+ }
85
+ ```
86
+
87
+ For dynamic theming (e.g. per-tenant branding), use `content_for :l_ui_head` to inject content into the layout `<head>`:
88
+
89
+ ```erb
90
+ <% content_for :l_ui_head do %>
91
+ <style>
92
+ :root { --accent: <%= @tenant.accent_hsl %>; --accent-foreground: 0 0% 100%; }
93
+ </style>
94
+ <% end %>
95
+ ```
96
+
97
+ > **Security:** never interpolate user-supplied strings directly into a `<style>` tag - this allows CSS injection (Important: Validate or sanitise any user-derived values before interpolation).
98
+
99
+ > **CSP compatibility:** inline `<style>` blocks are blocked by a strict `Content-Security-Policy: style-src 'self'` header. If your app enforces a strict CSP, add a nonce to the style tag using Rails' `content_security_policy_nonce` helper - Rails automatically includes the matching nonce in the CSP header:
100
+ >
101
+ > ```erb
102
+ > <% content_for :l_ui_head do %>
103
+ > <style nonce="<%= content_security_policy_nonce %>">
104
+ > :root { --accent: <%= @tenant.accent_hsl %>; --accent-foreground: 0 0% 100%; }
105
+ > </style>
106
+ > <% end %>
107
+ > ```
108
+
109
+ See the [Colors documentation](https://layered-ui-rails.layered.ai/pages/layout_colors) for the full list of tokens.
110
+
111
+ ## Customising logos and icons
112
+
113
+ Replace the defaults by placing files with the same names in `app/assets/images/layered_ui/` in your host app:
114
+
115
+ | File | Used for |
116
+ |---|---|
117
+ | `logo_light.svg` | Header logo (light theme) |
118
+ | `logo_dark.svg` | Header logo (dark theme) |
119
+ | `icon_light.svg` | Favicon and header icon (light theme) |
120
+ | `icon_dark.svg` | Favicon and header icon (dark theme) |
121
+ | `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
+
125
+ layered-ui-rails uses two patterns for per-request overrides:
126
+
127
+ - **Instance variables** (`@l_ui_*`) - used for small changes like URLs. The engine renders the surrounding markup and just swaps the src/href.
128
+ - **`content_for`** - used when the full markup needs to change. You supply the complete tag, so you can set classes, attributes, or wrap elements as needed.
129
+
130
+ For per-request logos (e.g. per-tenant branding), use `content_for` because the `<img>` tag itself carries classes that control layout and theme switching:
131
+
132
+ ```erb
133
+ <% content_for :l_ui_logo_light do %>
134
+ <%= image_tag @tenant.logo_light_url, alt: "", class: "l-ui-header__logo l-ui-header__logo--light" %>
135
+ <% end %>
136
+
137
+ <% content_for :l_ui_logo_dark do %>
138
+ <%= image_tag @tenant.logo_dark_url, alt: "", class: "l-ui-header__logo l-ui-header__logo--dark" %>
139
+ <% end %>
140
+ ```
141
+
142
+ For per-request icons, set instance variables - the engine renders `<link>` and `<img>` tags that only need a URL to vary:
143
+
144
+ ```ruby
145
+ @l_ui_icon_light_url = @tenant.icon_light_url
146
+ @l_ui_icon_dark_url = @tenant.icon_dark_url
147
+ @l_ui_apple_touch_icon_url = @tenant.apple_touch_icon_url
148
+ @l_ui_panel_icon_light_url = @tenant.panel_icon_light_url
149
+ @l_ui_panel_icon_dark_url = @tenant.panel_icon_dark_url
150
+ ```
151
+
152
+ > **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.
153
+
68
154
  ## Documentation
69
155
 
70
156
  An online version of the documentation is available at **[layered-ui-rails.layered.ai](https://layered-ui-rails.layered.ai)**.
@@ -805,6 +805,14 @@
805
805
  bg-surface;
806
806
  }
807
807
 
808
+ pre.l-ui-surface {
809
+ @apply m-0 overflow-auto;
810
+
811
+ > code {
812
+ background: transparent;
813
+ }
814
+ }
815
+
808
816
  .l-ui-surface--active {
809
817
  @apply surface
810
818
  bg-surface-active;
@@ -1,10 +1,10 @@
1
1
  <header class="l-ui-container--header" role="banner">
2
2
  <div class="l-ui-header">
3
3
  <%= link_to "/", "aria-label": "Home" do %>
4
- <%= image_tag("layered_ui/icon_light.svg", alt: "", class: "l-ui-header__icon l-ui-header__icon--light") %>
5
- <%= image_tag("layered_ui/icon_dark.svg", alt: "", class: "l-ui-header__icon l-ui-header__icon--dark") %>
6
- <%= image_tag("layered_ui/logo_light.svg", alt: "", class: "l-ui-header__logo l-ui-header__logo--light") %>
7
- <%= image_tag("layered_ui/logo_dark.svg", alt: "", class: "l-ui-header__logo l-ui-header__logo--dark") %>
4
+ <%= image_tag(@l_ui_icon_light_url.presence || "layered_ui/icon_light.svg", alt: "", class: "l-ui-header__icon l-ui-header__icon--light") %>
5
+ <%= image_tag(@l_ui_icon_dark_url.presence || "layered_ui/icon_dark.svg", alt: "", class: "l-ui-header__icon l-ui-header__icon--dark") %>
6
+ <%= yield(:l_ui_logo_light).presence || image_tag("layered_ui/logo_light.svg", alt: "", class: "l-ui-header__logo l-ui-header__logo--light") %>
7
+ <%= yield(:l_ui_logo_dark).presence || image_tag("layered_ui/logo_dark.svg", alt: "", class: "l-ui-header__logo l-ui-header__logo--dark") %>
8
8
  <% end %>
9
9
 
10
10
  <nav class="l-ui-header__navigation" aria-label="Header navigation">
@@ -65,7 +65,7 @@
65
65
  data-action="click->l-ui--panel-button#queueToggle dblclick->l-ui--panel-button#cycleCorner mousedown->l-ui--panel-button#startDrag touchstart->l-ui--panel-button#startDrag"
66
66
  data-l-ui--panel-target="actionButton"
67
67
  >
68
- <%= image_tag("layered_ui/panel_icon_light.svg", alt: "", class: "l-ui-icon--lg l-ui-panel__icon--light", aria: { hidden: true }) %>
69
- <%= image_tag("layered_ui/panel_icon_dark.svg", alt: "", class: "l-ui-icon--lg l-ui-panel__icon--dark", aria: { hidden: true }) %>
68
+ <%= image_tag(@l_ui_panel_icon_light_url.presence || "layered_ui/panel_icon_light.svg", alt: "", class: "l-ui-icon--lg l-ui-panel__icon--light", aria: { hidden: true }) %>
69
+ <%= image_tag(@l_ui_panel_icon_dark_url.presence || "layered_ui/panel_icon_dark.svg", alt: "", class: "l-ui-icon--lg l-ui-panel__icon--dark", aria: { hidden: true }) %>
70
70
  </button>
71
71
  </div>
@@ -14,11 +14,12 @@
14
14
  <%= csrf_meta_tags %>
15
15
  <%= csp_meta_tag %>
16
16
 
17
- <link rel="icon" href="<%= asset_path('layered_ui/icon_light.svg') %>" media="(prefers-color-scheme: light)">
18
- <link rel="icon" href="<%= asset_path('layered_ui/icon_dark.svg') %>" media="(prefers-color-scheme: dark)">
19
- <link rel="apple-touch-icon" href="<%= asset_path('layered_ui/apple_touch_icon.png') %>">
17
+ <%= tag.link(rel: "icon", href: @l_ui_icon_light_url.presence || asset_path('layered_ui/icon_light.svg'), media: "(prefers-color-scheme: light)") %>
18
+ <%= tag.link(rel: "icon", href: @l_ui_icon_dark_url.presence || asset_path('layered_ui/icon_dark.svg'), media: "(prefers-color-scheme: dark)") %>
19
+ <%= tag.link(rel: "apple-touch-icon", href: @l_ui_apple_touch_icon_url.presence || asset_path('layered_ui/apple_touch_icon.png')) %>
20
20
 
21
21
  <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
22
+ <%= yield(:l_ui_head) %>
22
23
  <%= javascript_importmap_tags %>
23
24
  </head>
24
25
 
@@ -1,5 +1,5 @@
1
1
  module Layered
2
2
  module Ui
3
- VERSION = "0.1.4"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: layered-ui-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - layered.ai
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-25 00:00:00.000000000 Z
11
+ date: 2026-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails