layered-ui-rails 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +0 -4
- data/CHANGELOG.md +21 -1
- data/README.md +3 -4
- data/app/assets/tailwind/layered/ui/styles.css +27 -1
- data/app/helpers/layered/ui/ransack_helper.rb +137 -0
- data/app/javascript/layered_ui/controllers/l_ui/search_form_controller.js +47 -0
- data/app/javascript/layered_ui/index.js +2 -0
- data/app/views/layered_ui/shared/_search_field.html.erb +6 -0
- data/app/views/layered_ui/shared/_search_select.html.erb +8 -0
- data/config/importmap.rb +1 -0
- data/lib/layered/ui/engine.rb +1 -0
- data/lib/layered/ui/version.rb +1 -1
- metadata +25 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8e4d9560a90e896fb66753e342a555c9ce281764e2c3c7ac9d326a4a52d5ff4
|
|
4
|
+
data.tar.gz: 3c1f55587df50d2f0def13536b0adb3c8869682246932fd73ddcd146a63a4e38
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 99a8dff88d32df2645427d4802d64294744cfc51d185e3b7df7b1fbf25db5887472b654293c6ce3af78440be2730a809c73f37bf282e6166d47b39ec80e85530
|
|
7
|
+
data.tar.gz: 13f9cc999716982874cfc3a6eaff48e63a5c7a06b608e321692de90d573c74e2879229dec97bd6250f3afb5b4bdfd222fef6150643b92e7f38d77b2b28fb85f3
|
data/AGENTS.md
CHANGED
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Guidance for AI agents working in this repository.
|
|
4
4
|
|
|
5
|
-
## Project
|
|
6
|
-
|
|
7
|
-
**layered-ui-rails** - Rails 8+ engine gem: design tokens, Tailwind CSS, Stimulus controllers (theme, mobile nav, panel, modal, tabs). Pure frontend, no server-side logic.
|
|
8
|
-
|
|
9
5
|
## Architecture
|
|
10
6
|
|
|
11
7
|
- **Entry:** `require "layered-ui-rails"` → `lib/layered/ui.rb` → `lib/layered/ui/engine.rb`
|
data/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,27 @@
|
|
|
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
|
-
##
|
|
5
|
+
## [0.3.0] - 2026-04-11
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Optional Ransack search helper (`l_ui_search_form`) with styled search form wrapping Ransack's `search_form_for`
|
|
10
|
+
- Sort link helper (`l_ui_sort_link`) with styled header cells wrapping Ransack's `sort_link`
|
|
11
|
+
- Stimulus controller (`l-ui--search-form`) for clear button and Turbo Frame targeting
|
|
12
|
+
- Support for multiple independent Ransack collections on a single page using Turbo Frames and `search_key`
|
|
13
|
+
- Graceful degradation when Ransack is not installed (visible warning in development, silent fallback in production)
|
|
14
|
+
- Ransack integration documentation and search helper API reference pages in the dummy app
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Updated gem description and summary to clarify that the engine is built on Tailwind CSS and integrates with Devise, Pagy, and Ransack
|
|
19
|
+
|
|
20
|
+
## [0.2.5] - 2026-04-09
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Reduced form field text size from `text-base` (16px) to `text-sm` (14px) on desktop; remains 16px on mobile to prevent iOS auto-zoom
|
|
25
|
+
- Increased form hint text size from `text-xs` (12px) to `text-sm` (14px)
|
|
6
26
|
|
|
7
27
|
## [0.2.4] - 2026-04-06
|
|
8
28
|
|
data/README.md
CHANGED
|
@@ -106,7 +106,7 @@ For dynamic theming (e.g. per-tenant branding), use `content_for :l_ui_head` to
|
|
|
106
106
|
> <% end %>
|
|
107
107
|
> ```
|
|
108
108
|
|
|
109
|
-
See the [Colors documentation](https://layered-ui-rails.layered.ai/
|
|
109
|
+
See the [Colors documentation](https://layered-ui-rails.layered.ai/layout_colors) for the full list of tokens.
|
|
110
110
|
|
|
111
111
|
## Customising logos and icons
|
|
112
112
|
|
|
@@ -186,6 +186,8 @@ kamal deploy
|
|
|
186
186
|
|
|
187
187
|
This project is still in its early days. We welcome issues, feedback, and ideas - they genuinely help shape the direction of the project. That said, we're holding off on accepting pull requests until after the 1.0 release so we can stay focused on getting the core foundations right. Once we're there, we'd love to open things up to broader contributions. Thanks for your patience and interest!
|
|
188
188
|
|
|
189
|
+
- [CLA.md](CLA.md) - contributor license agreement
|
|
190
|
+
|
|
189
191
|
## License
|
|
190
192
|
|
|
191
193
|
Released under the [Apache 2.0 License](LICENSE).
|
|
@@ -196,6 +198,3 @@ Copyright 2026 LAYERED AI LIMITED (UK company number: 17056830). See [NOTICE](NO
|
|
|
196
198
|
|
|
197
199
|
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.
|
|
198
200
|
|
|
199
|
-
## Contributing
|
|
200
|
-
|
|
201
|
-
- [CLA.md](CLA.md) - contributor license agreement
|
|
@@ -901,16 +901,21 @@ pre.l-ui-surface {
|
|
|
901
901
|
|
|
902
902
|
.l-ui-form__hint {
|
|
903
903
|
@apply mt-2
|
|
904
|
-
text-
|
|
904
|
+
text-sm text-foreground-muted;
|
|
905
905
|
}
|
|
906
906
|
|
|
907
907
|
@utility form__field {
|
|
908
908
|
@apply block
|
|
909
909
|
w-full px-3 py-2.5 min-h-[44px]
|
|
910
|
+
/* text-base at mobile; text-sm at md+ */
|
|
910
911
|
text-base font-inter
|
|
911
912
|
text-foreground
|
|
912
913
|
border border-border-control rounded-sm
|
|
913
914
|
focus-ring;
|
|
915
|
+
|
|
916
|
+
@media (min-width: 768px) {
|
|
917
|
+
@apply text-sm;
|
|
918
|
+
}
|
|
914
919
|
}
|
|
915
920
|
|
|
916
921
|
.l-ui-form__field {
|
|
@@ -938,6 +943,17 @@ pre.l-ui-surface {
|
|
|
938
943
|
rounded-sm;
|
|
939
944
|
}
|
|
940
945
|
|
|
946
|
+
/* Search */
|
|
947
|
+
|
|
948
|
+
.l-ui-search__inline {
|
|
949
|
+
@apply flex items-center
|
|
950
|
+
gap-2;
|
|
951
|
+
|
|
952
|
+
.l-ui-form__field {
|
|
953
|
+
@apply mt-0;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
941
957
|
/* Switch */
|
|
942
958
|
|
|
943
959
|
.l-ui-switch {
|
|
@@ -1108,6 +1124,16 @@ pre.l-ui-surface {
|
|
|
1108
1124
|
text-right;
|
|
1109
1125
|
}
|
|
1110
1126
|
|
|
1127
|
+
.l-ui-table__sort-link {
|
|
1128
|
+
@apply inline-flex items-center gap-1
|
|
1129
|
+
no-underline
|
|
1130
|
+
text-foreground;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
.l-ui-table__sort-indicator {
|
|
1134
|
+
@apply text-foreground-muted;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1111
1137
|
.l-ui-table__body {
|
|
1112
1138
|
@apply h-full
|
|
1113
1139
|
bg-background
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
module Layered
|
|
2
|
+
module Ui
|
|
3
|
+
module RansackHelper
|
|
4
|
+
# Renders a styled Ransack search form.
|
|
5
|
+
#
|
|
6
|
+
# Simple usage (single input searching across fields):
|
|
7
|
+
# l_ui_search_form(@q, url: users_path, fields: [:name, :email])
|
|
8
|
+
#
|
|
9
|
+
# Custom usage (full control via block):
|
|
10
|
+
# l_ui_search_form(@q, url: users_path) do |f|
|
|
11
|
+
# render "layered_ui/shared/search_field", form: f, field: :name_cont, label: "Name"
|
|
12
|
+
# f.submit "Go", class: "l-ui-button--primary"
|
|
13
|
+
# end
|
|
14
|
+
def l_ui_search_form(query, url: nil, fields: [], predicate: :cont, combinator: :or, label: "Search", placeholder: nil, button: "Search", clear: nil, turbo_frame: nil, html: {}, &block)
|
|
15
|
+
result = require_ransack("l_ui_search_form") { |msg| tag.p(msg, class: "l-ui-notice--warning") }
|
|
16
|
+
return result unless result == true
|
|
17
|
+
|
|
18
|
+
scope = query.context&.search_key || :q
|
|
19
|
+
turbo_action = "advance"
|
|
20
|
+
html = html.merge(class: ["l-ui-form", html[:class]].compact.join(" "))
|
|
21
|
+
|
|
22
|
+
if turbo_frame
|
|
23
|
+
existing_data = (html[:data] || {}).symbolize_keys
|
|
24
|
+
existing_controller = existing_data[:controller]
|
|
25
|
+
controller = [existing_controller, "l-ui--search-form"].compact.join(" ")
|
|
26
|
+
existing_action = existing_data[:action]
|
|
27
|
+
action = [existing_action, "submit->l-ui--search-form#preserve"].compact.join(" ")
|
|
28
|
+
turbo_action = existing_data[:turbo_action] || turbo_action
|
|
29
|
+
|
|
30
|
+
html[:data] = existing_data.except(:controller, :action, :l_ui__search_form_scope_value).merge(
|
|
31
|
+
turbo_frame: turbo_frame, turbo_action: turbo_action,
|
|
32
|
+
controller: controller, action: action,
|
|
33
|
+
l_ui__search_form_scope_value: scope
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if block
|
|
38
|
+
search_form_for(query, url: url, html: html, as: scope, &block)
|
|
39
|
+
else
|
|
40
|
+
raise ArgumentError, "l_ui_search_form requires at least one field in simple mode (e.g. fields: [:name])" if fields.empty?
|
|
41
|
+
|
|
42
|
+
combined_field = fields.map(&:to_s).join("_#{combinator}_") + "_#{predicate}"
|
|
43
|
+
placeholder ||= "Search by #{fields.map { |f| f.to_s.humanize.downcase }.join(', ')}"
|
|
44
|
+
|
|
45
|
+
search_form_for(query, url: url, html: html, as: scope) do |f|
|
|
46
|
+
f.label(combined_field, label, class: "l-ui-sr-only") +
|
|
47
|
+
tag.div(class: "l-ui-search__inline") do
|
|
48
|
+
content = f.text_field(combined_field, class: "l-ui-form__field", placeholder: placeholder) +
|
|
49
|
+
f.submit(button, class: "l-ui-button--primary")
|
|
50
|
+
if clear
|
|
51
|
+
raise ArgumentError, "l_ui_search_form requires an explicit url: when clear: is set" unless url
|
|
52
|
+
clear_options = { class: "l-ui-button--outline" }
|
|
53
|
+
clear_options[:data] = { turbo_frame: turbo_frame, turbo_action: turbo_action, action: "click->l-ui--search-form#clear" } if turbo_frame
|
|
54
|
+
content += link_to(clear == true ? "Clear" : clear, url, **clear_options)
|
|
55
|
+
end
|
|
56
|
+
content
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
SORT_INDICATORS = {
|
|
63
|
+
"asc" => { symbol: "▲", label: ", sorted ascending", aria: "ascending" },
|
|
64
|
+
"desc" => { symbol: "▼", label: ", sorted descending", aria: "descending" }
|
|
65
|
+
}.freeze
|
|
66
|
+
|
|
67
|
+
# Renders a styled, accessible Ransack sort header cell.
|
|
68
|
+
#
|
|
69
|
+
# Returns a +<th>+ element containing a sort link and an accessible sort
|
|
70
|
+
# direction indicator. The +aria-sort+ attribute is set on the +<th>+
|
|
71
|
+
# so screen readers announce the current sort state.
|
|
72
|
+
#
|
|
73
|
+
# Usage:
|
|
74
|
+
# l_ui_sort_link(@q, :name)
|
|
75
|
+
# l_ui_sort_link(@q, :name, "Full name")
|
|
76
|
+
# l_ui_sort_link(@q, :created_at, "Joined", default_order: :desc)
|
|
77
|
+
# l_ui_sort_link(@q, :name, html: { data: { turbo_action: "replace" } })
|
|
78
|
+
def l_ui_sort_link(query, attribute, label = nil, default_order: nil, turbo_frame: nil, html: {})
|
|
79
|
+
label ||= attribute.to_s.humanize
|
|
80
|
+
link_class = ["l-ui-table__sort-link", html[:class]].compact.join(" ")
|
|
81
|
+
|
|
82
|
+
result = require_ransack("l_ui_sort_link") { |msg| tag.th(tag.span(label, title: msg), class: "l-ui-table__header-cell", scope: "col") }
|
|
83
|
+
return (result || tag.th(label, class: "l-ui-table__header-cell", scope: "col")) unless result == true
|
|
84
|
+
|
|
85
|
+
current_dir = sort_direction_for(query, attribute)
|
|
86
|
+
indicator = SORT_INDICATORS[current_dir]
|
|
87
|
+
aria_sort = indicator&.dig(:aria) || "none"
|
|
88
|
+
|
|
89
|
+
url = sort_url(query, attribute, { default_order: default_order }.compact)
|
|
90
|
+
link_html = html.except(:class)
|
|
91
|
+
if turbo_frame
|
|
92
|
+
existing_data = (link_html[:data] || {}).symbolize_keys
|
|
93
|
+
link_html[:data] = existing_data.merge(turbo_frame: turbo_frame, turbo_action: existing_data[:turbo_action] || "advance")
|
|
94
|
+
end
|
|
95
|
+
link = link_to(url, **link_html, class: link_class) do
|
|
96
|
+
parts = [label]
|
|
97
|
+
if indicator
|
|
98
|
+
parts << tag.span(indicator[:symbol], aria: { hidden: true }, class: "l-ui-table__sort-indicator")
|
|
99
|
+
parts << tag.span(indicator[:label], class: "l-ui-sr-only")
|
|
100
|
+
end
|
|
101
|
+
safe_join(parts)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
tag.th(link, class: "l-ui-table__header-cell l-ui-table__header-cell--sortable",
|
|
105
|
+
scope: "col", aria: { sort: aria_sort })
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def ransack_available?
|
|
111
|
+
defined?(Ransack)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Returns +true+ if Ransack is available. In development, returns the
|
|
115
|
+
# block's result so the caller can render a visible fallback. In
|
|
116
|
+
# production/test, logs and returns +nil+.
|
|
117
|
+
def require_ransack(helper_name)
|
|
118
|
+
return true if ransack_available?
|
|
119
|
+
|
|
120
|
+
message = "#{helper_name} requires the ransack gem. Add `gem \"ransack\"` to your Gemfile."
|
|
121
|
+
|
|
122
|
+
if Rails.env.development?
|
|
123
|
+
return yield(message)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
Rails.logger.warn("[layered-ui-rails] #{message} The output has been hidden.")
|
|
127
|
+
nil
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def sort_direction_for(query, attribute)
|
|
131
|
+
return unless query.respond_to?(:sorts)
|
|
132
|
+
sort = query.sorts.detect { |s| s.name == attribute.to_s }
|
|
133
|
+
sort&.dir
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Preserves query params from other search scopes when a scoped form submits
|
|
4
|
+
// or its clear link is clicked.
|
|
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.
|
|
11
|
+
export default class extends Controller {
|
|
12
|
+
static values = { scope: String }
|
|
13
|
+
|
|
14
|
+
preserve(event) {
|
|
15
|
+
this.element.querySelectorAll("[data-l-ui-preserved]").forEach(el => el.remove())
|
|
16
|
+
|
|
17
|
+
for (const [key, value] of this.#otherParams()) {
|
|
18
|
+
const input = document.createElement("input")
|
|
19
|
+
input.type = "hidden"
|
|
20
|
+
input.name = key
|
|
21
|
+
input.value = value
|
|
22
|
+
input.setAttribute("data-l-ui-preserved", "")
|
|
23
|
+
this.element.appendChild(input)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
clear(event) {
|
|
28
|
+
const link = event.currentTarget
|
|
29
|
+
const preserved = this.#otherParams()
|
|
30
|
+
const base = link.href.split("?")[0]
|
|
31
|
+
const qs = preserved.toString()
|
|
32
|
+
link.href = qs ? `${base}?${qs}` : base
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#otherParams() {
|
|
36
|
+
const currentParams = new URLSearchParams(window.location.search)
|
|
37
|
+
const scope = this.scopeValue
|
|
38
|
+
const result = new URLSearchParams()
|
|
39
|
+
|
|
40
|
+
for (const [key, value] of currentParams) {
|
|
41
|
+
if (key === scope || key.startsWith(scope + "[") || key === "commit") continue
|
|
42
|
+
result.append(key, value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -5,8 +5,10 @@ import PanelController from "layered_ui/controllers/l_ui/panel_controller"
|
|
|
5
5
|
import PanelResizeController from "layered_ui/controllers/l_ui/panel_resize_controller"
|
|
6
6
|
import PanelButtonController from "layered_ui/controllers/l_ui/panel_button_controller"
|
|
7
7
|
import ModalController from "layered_ui/controllers/l_ui/modal_controller"
|
|
8
|
+
import SearchFormController from "layered_ui/controllers/l_ui/search_form_controller"
|
|
8
9
|
import TabsController from "layered_ui/controllers/l_ui/tabs_controller"
|
|
9
10
|
|
|
11
|
+
application.register("l-ui--search-form", SearchFormController)
|
|
10
12
|
application.register("l-ui--theme", ThemeController)
|
|
11
13
|
application.register("l-ui--navigation", NavigationController)
|
|
12
14
|
application.register("l-ui--panel", PanelController)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<% include_blank = local_assigns.fetch(:include_blank, "Any") %>
|
|
2
|
+
|
|
3
|
+
<div class="l-ui-form__group">
|
|
4
|
+
<%= form.label field, label, class: "l-ui-label" %>
|
|
5
|
+
<div class="l-ui-select-wrapper">
|
|
6
|
+
<%= form.select field, options, { include_blank: include_blank }, class: "l-ui-select" %>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
data/config/importmap.rb
CHANGED
|
@@ -11,5 +11,6 @@ pin "layered_ui/controllers/l_ui/navigation_controller", to: "layered_ui/control
|
|
|
11
11
|
pin "layered_ui/controllers/l_ui/panel_controller", to: "layered_ui/controllers/l_ui/panel_controller.js"
|
|
12
12
|
pin "layered_ui/controllers/l_ui/panel_resize_controller", to: "layered_ui/controllers/l_ui/panel_resize_controller.js"
|
|
13
13
|
pin "layered_ui/controllers/l_ui/panel_button_controller", to: "layered_ui/controllers/l_ui/panel_button_controller.js"
|
|
14
|
+
pin "layered_ui/controllers/l_ui/search_form_controller", to: "layered_ui/controllers/l_ui/search_form_controller.js"
|
|
14
15
|
pin "layered_ui/controllers/l_ui/tabs_controller", to: "layered_ui/controllers/l_ui/tabs_controller.js"
|
|
15
16
|
pin "layered_ui/controllers/l_ui/theme_controller", to: "layered_ui/controllers/l_ui/theme_controller.js"
|
data/lib/layered/ui/engine.rb
CHANGED
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.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- layered.ai
|
|
@@ -107,6 +107,20 @@ dependencies:
|
|
|
107
107
|
- - "~>"
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
109
|
version: '7.0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: ransack
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '4.0'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '4.0'
|
|
110
124
|
- !ruby/object:Gem::Dependency
|
|
111
125
|
name: sqlite3
|
|
112
126
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -149,9 +163,10 @@ dependencies:
|
|
|
149
163
|
- - "~>"
|
|
150
164
|
- !ruby/object:Gem::Version
|
|
151
165
|
version: '2.0'
|
|
152
|
-
description: An open source Rails 8+ engine
|
|
153
|
-
tokens,
|
|
154
|
-
navigation, slide-out panels, modals, and tabs.
|
|
166
|
+
description: An open source Rails 8+ engine built on Tailwind CSS, providing customisable
|
|
167
|
+
WCAG 2.2 AA compliant design tokens, utility classes, and Stimulus controllers for
|
|
168
|
+
theme switching, mobile navigation, slide-out panels, modals, and tabs. Integrates
|
|
169
|
+
with the gems you already use (Devise, Pagy, Ransack).
|
|
155
170
|
email:
|
|
156
171
|
- support@layered.ai
|
|
157
172
|
executables: []
|
|
@@ -192,11 +207,13 @@ files:
|
|
|
192
207
|
- app/helpers/layered/ui/authentication_helper.rb
|
|
193
208
|
- app/helpers/layered/ui/navigation_helper.rb
|
|
194
209
|
- app/helpers/layered/ui/pagination_helper.rb
|
|
210
|
+
- app/helpers/layered/ui/ransack_helper.rb
|
|
195
211
|
- app/javascript/layered_ui/controllers/l_ui/modal_controller.js
|
|
196
212
|
- app/javascript/layered_ui/controllers/l_ui/navigation_controller.js
|
|
197
213
|
- app/javascript/layered_ui/controllers/l_ui/panel_button_controller.js
|
|
198
214
|
- app/javascript/layered_ui/controllers/l_ui/panel_controller.js
|
|
199
215
|
- app/javascript/layered_ui/controllers/l_ui/panel_resize_controller.js
|
|
216
|
+
- app/javascript/layered_ui/controllers/l_ui/search_form_controller.js
|
|
200
217
|
- app/javascript/layered_ui/controllers/l_ui/tabs_controller.js
|
|
201
218
|
- app/javascript/layered_ui/controllers/l_ui/theme_controller.js
|
|
202
219
|
- app/javascript/layered_ui/index.js
|
|
@@ -218,6 +235,8 @@ files:
|
|
|
218
235
|
- app/views/layered_ui/shared/_field_error.html.erb
|
|
219
236
|
- app/views/layered_ui/shared/_form_errors.html.erb
|
|
220
237
|
- app/views/layered_ui/shared/_label.html.erb
|
|
238
|
+
- app/views/layered_ui/shared/_search_field.html.erb
|
|
239
|
+
- app/views/layered_ui/shared/_search_select.html.erb
|
|
221
240
|
- app/views/layouts/layered_ui/_header.html.erb
|
|
222
241
|
- app/views/layouts/layered_ui/_navigation.html.erb
|
|
223
242
|
- app/views/layouts/layered_ui/_notice.html.erb
|
|
@@ -272,6 +291,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
272
291
|
requirements: []
|
|
273
292
|
rubygems_version: 4.0.6
|
|
274
293
|
specification_version: 4
|
|
275
|
-
summary: Open source, minimalist
|
|
276
|
-
dark
|
|
294
|
+
summary: Open source, minimalist Tailwind-based UI system for Rails with responsive,
|
|
295
|
+
accessible components and light/dark themes.
|
|
277
296
|
test_files: []
|