relay_ui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +0 -13
- data/lib/relay_ui/engine.rb +6 -0
- data/lib/relay_ui/version.rb +1 -1
- data/lib/rui/buttons/base.rb +4 -3
- data/lib/rui/buttons/primary.rb +4 -1
- data/lib/rui/forms/helpers.rb +19 -0
- data/lib/rui/forms/tailwind_form_builder.rb +142 -0
- data/lib/rui/layout/main.rb +1 -1
- data/lib/rui/navigation/sidebar.rb +1 -1
- data/lib/rui/table.rb +12 -22
- data/lib/rui/tailwind_merger.rb +17 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bd18a48694c8b8d66e4183e74a7bee705ace0d0b821adba7e037750a49124cb
|
4
|
+
data.tar.gz: d972223721bae1f638121a4ec446cf4eb26ed70f4a6358ab956e1e0273e1d709
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7429340803af5c5c886cec99ae2bd8c78a65033c3f1037318359acdf275c0907f67fb09423ecf3c8ba29a6916b62d3c3e82576863b0de8f10f47eef43e273e39
|
7
|
+
data.tar.gz: 595e27eab36b6c538b96fe30aff5d8d3b3637516a5723e48ff907a39dd845e683af8df449688f49cf79a59febad94dc0ad8eb09cf92c9726c06e8b14b449771d
|
data/README.md
CHANGED
@@ -25,13 +25,6 @@ All of RelayUI's components are housed in the `RUI::` namespace. This turns your
|
|
25
25
|
|
26
26
|
With this in mind, we prefer pulling basic variants up to the model level. Whereas many UI kits may specify variants via parameters (eg: `Component.new(variant: :primary)`), we prefer to give major variants class-level importance. So, we'll opt for patterns like `RUI::Buttons::Primary` and `RUI::Buttons::Secondary` instead.
|
27
27
|
|
28
|
-
## Using TailwindCSS
|
29
|
-
|
30
|
-
RelayUI uses [TailwindCSS v4](https://tailwindcss.com/) for styling under the hood. One of the challenges we aimed to solve is how to include the styles Tailwind provides in a way that doesn't collide with any other CSS styles or frameworks being used. For example, we wanted to make sure RelayUI still worked well in projects that used Tailwind v3, or even Bootstrap.
|
31
|
-
|
32
|
-
For that reason, we've decided to prefix our CSS classes with `rui:`. This way, RelayUI is able to come out of the box with all of the styles you need to make our components work, but you can choose to use any CSS framework you want and not risk any CSS class conflicts or collisions.
|
33
|
-
STRING
|
34
|
-
|
35
28
|
# Installation
|
36
29
|
|
37
30
|
### First, add the gem to your `Gemfile`:
|
@@ -52,12 +45,6 @@ bundle install
|
|
52
45
|
gem install relay_ui
|
53
46
|
```
|
54
47
|
|
55
|
-
### Include the gem's stylesheet in your application layout:
|
56
|
-
|
57
|
-
```ruby
|
58
|
-
stylesheet_link_tag "relay_ui/relay_ui", media: "all"
|
59
|
-
```
|
60
|
-
|
61
48
|
That's it! All of the basic functionality of the UI kit is now available to you. For certain components that require additional elements (like stimulus controllers), you'll need to include those separately. They will be documented in the component's usage instructions.
|
62
49
|
|
63
50
|
# Usage
|
data/lib/relay_ui/engine.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rui/forms/tailwind_form_builder"
|
4
|
+
|
3
5
|
module RUI
|
4
6
|
if defined?(Rails)
|
5
7
|
class Engine < ::Rails::Engine
|
6
8
|
isolate_namespace RUI
|
7
9
|
|
10
|
+
initializer "relay_ui.set_default_form_builder" do |app|
|
11
|
+
app.config.action_view.default_form_builder = RUI::Forms::TailwindFormBuilder
|
12
|
+
end
|
13
|
+
|
8
14
|
initializer "relay_ui.autoload.components" do
|
9
15
|
Rails.autoloaders.main.push_dir(
|
10
16
|
"#{Gem::Specification.find_by_name('relay_ui').gem_dir}/lib/rui",
|
data/lib/relay_ui/version.rb
CHANGED
data/lib/rui/buttons/base.rb
CHANGED
@@ -6,6 +6,9 @@ class RUI::Buttons::Base < RUI::Base
|
|
6
6
|
@attrs = attrs
|
7
7
|
end
|
8
8
|
|
9
|
+
# Style string public for use in forms
|
10
|
+
STYLE = "inline-block px-2 py-1 hover:cursor-pointer rounded-md transition duration-200 ease-in-out".freeze
|
11
|
+
|
9
12
|
def view_template
|
10
13
|
button(class: classes, **@attrs) do
|
11
14
|
div(class: "flex flex-row items-center gap-2") do
|
@@ -22,8 +25,6 @@ class RUI::Buttons::Base < RUI::Base
|
|
22
25
|
private
|
23
26
|
|
24
27
|
def classes
|
25
|
-
"#{
|
28
|
+
"#{STYLE} #{variant_classes}"
|
26
29
|
end
|
27
|
-
|
28
|
-
def base_classes = "inline-block px-2 py-1 hover:cursor-pointer rounded-md transition duration-200 ease-in-out"
|
29
30
|
end
|
data/lib/rui/buttons/primary.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class RUI::Buttons::Primary < RUI::Buttons::Base
|
4
|
+
# Style string public for use in forms
|
5
|
+
STYLE = "bg-blue-700 hover:bg-blue-900 text-white border border-blue-700 hover:border-blue-900".freeze
|
6
|
+
|
4
7
|
private
|
5
8
|
|
6
|
-
def variant_classes =
|
9
|
+
def variant_classes = STYLE
|
7
10
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RUI::Forms::Helpers
|
2
|
+
DEFAULT_CLASSES = "flex flex-col gap-4".freeze
|
3
|
+
|
4
|
+
def rui_form_with(**options, &block)
|
5
|
+
if options.present? && options[:html][:class].present?
|
6
|
+
form_with_merged_classes(options[:html][:class], **options, &block)
|
7
|
+
else
|
8
|
+
# TODO: Remove reference to builder
|
9
|
+
form_with(**options.merge(builder: TailwindFormBuilder, html: { class: DEFAULT_CLASSES }), &block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def form_with_merged_classes(provided_classes, **options, &block)
|
16
|
+
css = RUI::TailwindMerger.instance.merge(DEFAULT_CLASSES, provided_classes)
|
17
|
+
form_with(**options.merge(html: { class: css }), &block)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module RUI::Forms
|
2
|
+
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
|
3
|
+
include ActionView::Helpers::TagHelper
|
4
|
+
|
5
|
+
class_attribute :text_field_helpers, default: field_helpers - [ :label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field ]
|
6
|
+
|
7
|
+
TEXT_FIELD_STYLE = "bg-white ring ring-zinc-100 hover:ring-zinc-400 rounded px-2 py-1".freeze
|
8
|
+
SELECT_FIELD_STYLE = "block bg-white ring ring-zinc-100 hover:ring-zinc-400 rounded px-2 py-1".freeze
|
9
|
+
|
10
|
+
text_field_helpers.each do |field_method|
|
11
|
+
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
12
|
+
def #{field_method}(method, options = {})
|
13
|
+
if options.delete(:tailwindified)
|
14
|
+
super
|
15
|
+
else
|
16
|
+
text_like_field(#{field_method.inspect}, method, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
RUBY_EVAL
|
20
|
+
end
|
21
|
+
|
22
|
+
def submit(value = nil, options = {})
|
23
|
+
custom_opts, opts = partition_custom_opts(options)
|
24
|
+
style_classes = RUI::TailwindMerger.instance.merge(
|
25
|
+
RUI::Buttons::Base::STYLE,
|
26
|
+
RUI::Buttons::Primary::STYLE
|
27
|
+
)
|
28
|
+
classes = apply_style_classes(style_classes, custom_opts)
|
29
|
+
|
30
|
+
@template.content_tag("div", super(value, { class: classes }.merge(opts)))
|
31
|
+
end
|
32
|
+
|
33
|
+
def select(method, choices = nil, options = {}, html_options = {}, &block)
|
34
|
+
custom_opts, opts = partition_custom_opts(options)
|
35
|
+
classes = apply_style_classes(SELECT_FIELD_STYLE, custom_opts, method)
|
36
|
+
|
37
|
+
labels = labels(method, custom_opts[:label], options)
|
38
|
+
field = super(method, choices, opts, html_options.merge({ class: classes }), &block)
|
39
|
+
|
40
|
+
labels + field
|
41
|
+
end
|
42
|
+
|
43
|
+
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
|
44
|
+
custom_opts = partition_custom_opts(options)
|
45
|
+
|
46
|
+
check_boxes = @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block)
|
47
|
+
|
48
|
+
labels = labels(method, custom_opts, options)
|
49
|
+
|
50
|
+
@template.content_tag("div", labels + check_boxes, { class: "flex flex-col gap-3 items-start justify-middle" })
|
51
|
+
end
|
52
|
+
|
53
|
+
# def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
|
54
|
+
# custom_opts = partition_custom_opts(options)
|
55
|
+
|
56
|
+
# buttons = @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block)
|
57
|
+
|
58
|
+
# labels = labels(method, custom_opts, options)
|
59
|
+
|
60
|
+
# @template.content_tag("div", labels + buttons, { class: "flex flex-col gap-3 items-start justify-middle" })
|
61
|
+
# end
|
62
|
+
|
63
|
+
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
64
|
+
@template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options))
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def text_like_field(field_method, object_method, options = {})
|
70
|
+
custom_opts, opts = partition_custom_opts(options)
|
71
|
+
|
72
|
+
classes = apply_style_classes(TEXT_FIELD_STYLE, custom_opts, object_method)
|
73
|
+
|
74
|
+
field = send(field_method, object_method, {
|
75
|
+
class: classes,
|
76
|
+
title: errors_for(object_method)&.join(" ")
|
77
|
+
}.compact.merge(opts).merge({ tailwindified: true }))
|
78
|
+
|
79
|
+
labels = labels(object_method, custom_opts[:label], options)
|
80
|
+
|
81
|
+
@template.content_tag("div", labels + field, { class: "flex flex-col gap-1" })
|
82
|
+
end
|
83
|
+
|
84
|
+
def labels(object_method, label_options, field_options)
|
85
|
+
label = tailwind_label(object_method, label_options, field_options)
|
86
|
+
error_label = error_label(object_method, field_options)
|
87
|
+
|
88
|
+
@template.content_tag("div", label + error_label, { class: "flex flex-col items-start" })
|
89
|
+
end
|
90
|
+
|
91
|
+
def tailwind_label(object_method, label_options, field_options)
|
92
|
+
text, label_opts = if label_options.present?
|
93
|
+
[ label_options[:text], label_options.except(:text) ]
|
94
|
+
else
|
95
|
+
[ nil, {} ]
|
96
|
+
end
|
97
|
+
|
98
|
+
label_classes = label_opts[:class] || "text-sm font-semibold"
|
99
|
+
label_classes += " font-zinc-500" if field_options[:disabled]
|
100
|
+
label(object_method, text, {
|
101
|
+
class: label_classes
|
102
|
+
}.merge(label_opts.except(:class)))
|
103
|
+
end
|
104
|
+
|
105
|
+
def error_label(object_method, options)
|
106
|
+
if errors_for(object_method).present?
|
107
|
+
error_message = @object.errors[object_method].collect(&:titleize).join(", ")
|
108
|
+
tailwind_label(object_method, { text: error_message, class: " font-bold text-red-500" }, options)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def border_color_classes(object_method)
|
113
|
+
if errors_for(object_method).present?
|
114
|
+
" border-2 border-red-400 focus:border-rose-200"
|
115
|
+
else
|
116
|
+
" border border-gray-300 focus:border-yellow-700"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def apply_style_classes(classes, custom_opts, object_method = nil)
|
121
|
+
classes + border_color_classes(object_method) + " #{custom_opts[:class]}"
|
122
|
+
end
|
123
|
+
|
124
|
+
CUSTOM_OPTS = [ :label, :class ].freeze
|
125
|
+
def partition_custom_opts(opts)
|
126
|
+
opts.partition { |k, v| CUSTOM_OPTS.include?(k) }.map(&:to_h)
|
127
|
+
end
|
128
|
+
|
129
|
+
def errors_for(object_method)
|
130
|
+
return unless @object.present? && object_method.present?
|
131
|
+
|
132
|
+
@object.errors[object_method]
|
133
|
+
end
|
134
|
+
|
135
|
+
def check_box_classes(method, field_classes = nil)
|
136
|
+
classes = <<~CLASSES.strip
|
137
|
+
block rounded size-3.5 focus:ring focus:ring-success checked:bg-success checked:hover:bg-success/90 cursor-pointer focus:ring-opacity-50
|
138
|
+
CLASSES
|
139
|
+
"#{classes} #{field_classes} #{border_color_classes(method)}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/rui/layout/main.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class RUI::Navigation::Sidebar < RUI::Base
|
4
4
|
def view_template
|
5
|
-
div(class: "p-10 pb-36 hidden lg:flex flex-col bg-white z-50 lg:z-auto fixed
|
5
|
+
div(class: "lg:w-72 p-10 pb-36 hidden lg:flex flex-col bg-white z-50 lg:z-auto fixed w-screen md:w-auto h-screen lg:max-h-full overflow-y-auto", data: { "navigation-target": "sidebar" }) do
|
6
6
|
yield
|
7
7
|
end
|
8
8
|
button(class: "lg:hidden hidden bg-black/75 fixed w-screen h-screen", data: { navigation_target: "curtain", action: "navigation#toggle" })
|
data/lib/rui/table.rb
CHANGED
@@ -7,48 +7,38 @@ class RUI::Table < RUI::Base
|
|
7
7
|
def view_template(&)
|
8
8
|
vanish(&)
|
9
9
|
|
10
|
-
table(class: "bg-white w-full table-auto border-collapse") do
|
10
|
+
table(class: "bg-white w-full table-auto border-collapse rounded") do
|
11
11
|
thead(class: "bg-blue-50") do
|
12
12
|
@columns.each do |column|
|
13
|
-
th(class:
|
13
|
+
th(class: classes(column[:attrs][:class])) do
|
14
14
|
column[:header]
|
15
15
|
end
|
16
16
|
end
|
17
|
-
th(class: "border border-zinc-300 px-2 text-center") { "" }
|
18
17
|
end
|
19
18
|
|
20
19
|
tbody do
|
21
20
|
@rows.each do |row|
|
22
|
-
tr(class: "hover:bg-zinc-
|
21
|
+
tr(class: "odd:bg-white even:bg-zinc-100 hover:bg-zinc-200") do
|
23
22
|
@columns.each do |column|
|
24
|
-
td(class:
|
23
|
+
td(class: classes(column[:attrs][:class])) do
|
25
24
|
column[:content].call(row)
|
26
25
|
end
|
27
26
|
end
|
28
|
-
td(class: "border border-zinc-300 py-1 px-2 text-center") do
|
29
|
-
div(class: "flex flex-row justify-center gap-3") do
|
30
|
-
render RUI::Buttons::Ghost.new(icon: "edit")
|
31
|
-
render RUI::Buttons::Ghost.new(icon: "archive")
|
32
|
-
end
|
33
|
-
end
|
34
27
|
end
|
35
28
|
end
|
36
29
|
end
|
37
30
|
end
|
38
31
|
end
|
39
32
|
|
40
|
-
def column(header
|
41
|
-
@columns << { header:,
|
33
|
+
def column(header = "", **attrs, &content)
|
34
|
+
@columns << { header:, attrs:, content: }
|
42
35
|
end
|
43
36
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
else
|
51
|
-
"text-left"
|
52
|
-
end
|
37
|
+
private
|
38
|
+
|
39
|
+
def base_classes = "border border-zinc-300 py-1 px-2 text-left"
|
40
|
+
|
41
|
+
def classes(custom_classes)
|
42
|
+
RUI::TailwindMerger.instance.merge(base_classes, custom_classes)
|
53
43
|
end
|
54
44
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal
|
2
|
+
|
3
|
+
require "tailwind_merge"
|
4
|
+
|
5
|
+
class RUI::TailwindMerger
|
6
|
+
include Singleton
|
7
|
+
include TailwindMerge
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@merger = TailwindMerge::Merger.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def merge(base_classes, given_classes)
|
14
|
+
return base_classes if given_classes.nil?
|
15
|
+
@merger.merge([ base_classes, given_classes ])
|
16
|
+
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: relay_ui
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- logicrelay
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-04-04 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: phlex
|
@@ -79,6 +79,20 @@ dependencies:
|
|
79
79
|
- - "~>"
|
80
80
|
- !ruby/object:Gem::Version
|
81
81
|
version: '1.18'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: tailwind_merge
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '1.1'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.1'
|
82
96
|
- !ruby/object:Gem::Dependency
|
83
97
|
name: rake
|
84
98
|
requirement: !ruby/object:Gem::Requirement
|
@@ -215,11 +229,13 @@ files:
|
|
215
229
|
- lib/rui/forms/checkbox.rb
|
216
230
|
- lib/rui/forms/email.rb
|
217
231
|
- lib/rui/forms/field_group.rb
|
232
|
+
- lib/rui/forms/helpers.rb
|
218
233
|
- lib/rui/forms/label.rb
|
219
234
|
- lib/rui/forms/password.rb
|
220
235
|
- lib/rui/forms/phone.rb
|
221
236
|
- lib/rui/forms/radio.rb
|
222
237
|
- lib/rui/forms/select.rb
|
238
|
+
- lib/rui/forms/tailwind_form_builder.rb
|
223
239
|
- lib/rui/forms/text.rb
|
224
240
|
- lib/rui/forms/textarea.rb
|
225
241
|
- lib/rui/helpers.rb
|
@@ -247,6 +263,7 @@ files:
|
|
247
263
|
- lib/rui/navigation/top.rb
|
248
264
|
- lib/rui/slideout.rb
|
249
265
|
- lib/rui/table.rb
|
266
|
+
- lib/rui/tailwind_merger.rb
|
250
267
|
- lib/rui/text.rb
|
251
268
|
homepage: https://www.relayui.com
|
252
269
|
licenses:
|