aeros 0.0.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +230 -0
- data/Rakefile +8 -0
- data/app/assets/stylesheets/aeros/application.css +15 -0
- data/app/assets/stylesheets/aeros/application.tailwind.css +7 -0
- data/app/assets/stylesheets/aeros/tailwind.css +1100 -0
- data/app/components/aeros/application_view_component.rb +75 -0
- data/app/components/aeros/button/component.rb +128 -0
- data/app/components/aeros/button/controller.js +7 -0
- data/app/components/aeros/card/component.html.erb +3 -0
- data/app/components/aeros/card/component.rb +7 -0
- data/app/components/aeros/dropdown/component.html.erb +26 -0
- data/app/components/aeros/dropdown/component.rb +66 -0
- data/app/components/aeros/empty/component.html.erb +12 -0
- data/app/components/aeros/empty/component.rb +5 -0
- data/app/components/aeros/form_builder.rb +71 -0
- data/app/components/aeros/input_password/component.html.erb +43 -0
- data/app/components/aeros/input_password/component.rb +6 -0
- data/app/components/aeros/input_password/controller.js +17 -0
- data/app/components/aeros/input_select/component.html.erb +43 -0
- data/app/components/aeros/input_select/component.rb +24 -0
- data/app/components/aeros/input_text/component.html.erb +25 -0
- data/app/components/aeros/input_text/component.rb +5 -0
- data/app/components/aeros/input_wrapper/component.html.erb +20 -0
- data/app/components/aeros/input_wrapper/component.rb +12 -0
- data/app/components/aeros/page/component.html.erb +24 -0
- data/app/components/aeros/page/component.rb +9 -0
- data/app/components/aeros/spinner/component.rb +55 -0
- data/app/components/aeros/table/component.html.erb +10 -0
- data/app/components/aeros/table/component.rb +64 -0
- data/app/controllers/aeros/application_controller.rb +4 -0
- data/app/controllers/aeros/showcase_controller.rb +4 -0
- data/app/helpers/aeros/application_helper.rb +16 -0
- data/app/javascript/aeros/application.js +3 -0
- data/app/javascript/aeros/controllers/application.js +5 -0
- data/app/javascript/aeros/controllers/index.js +5 -0
- data/app/javascript/aeros/controllers/loader.js +62 -0
- data/app/jobs/aeros/application_job.rb +4 -0
- data/app/models/aeros/application_record.rb +5 -0
- data/app/views/aeros/showcase/index.html.erb +1 -0
- data/app/views/layouts/aeros/application.html.erb +20 -0
- data/config/importmap.rb +15 -0
- data/config/routes.rb +3 -0
- data/lib/aeros/engine.rb +37 -0
- data/lib/aeros/engine_helpers.rb +44 -0
- data/lib/aeros/version.rb +3 -0
- data/lib/aeros.rb +9 -0
- data/lib/tasks/aeros_tasks.rake +21 -0
- metadata +220 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module Aeros
|
|
2
|
+
class ApplicationViewComponent < ViewComponentContrib::Base
|
|
3
|
+
extend(Dry::Initializer)
|
|
4
|
+
include(Aeros::ApplicationHelper)
|
|
5
|
+
include(ViewComponentContrib::StyleVariants)
|
|
6
|
+
|
|
7
|
+
style_config.postprocess_with do |classes|
|
|
8
|
+
TailwindMerge::Merger.new.merge(classes.join(" "))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
option(:css, optional: true)
|
|
12
|
+
def default_styles
|
|
13
|
+
[style, css].join(" ")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def named
|
|
18
|
+
@named ||= self.name.sub(/::Component$/, "").underscore.split("/").join("--").gsub("_", "-")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def controller_name
|
|
23
|
+
# Match JS autoload naming for components/controllers:
|
|
24
|
+
# - aeros/components/button/button_controller -> aeros--button
|
|
25
|
+
name = self.class.name
|
|
26
|
+
.sub(/^Aeros::/, "")
|
|
27
|
+
.sub(/::Component$/, "")
|
|
28
|
+
.underscore
|
|
29
|
+
|
|
30
|
+
"aeros--#{name.gsub('/', '--').gsub('_', '-')}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def data_target_key
|
|
34
|
+
"#{controller_name}-target"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Helper methods for Stimulus attributes
|
|
38
|
+
# These return keys suited for Rails `data:` hashes (no `data-` prefix)
|
|
39
|
+
def stimulus_controller
|
|
40
|
+
{ controller: controller_name }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def stimulus_target(name)
|
|
44
|
+
{ "#{controller_name}-target" => name }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def stimulus_action(event, method = nil)
|
|
48
|
+
method ||= event
|
|
49
|
+
{ action: "#{event}->#{controller_name}##{method}" }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def stimulus_value(name, value)
|
|
53
|
+
{ "#{controller_name}-#{name}-value" => value }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def stimulus_class(name, css_class)
|
|
57
|
+
{ "#{controller_name}-#{name}-class" => css_class }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Attribute helpers for raw tag helpers (already include `data-` prefix)
|
|
61
|
+
def stimulus_attr_target(name)
|
|
62
|
+
{ "data-#{controller_name}-target" => name }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def merged_data
|
|
66
|
+
return default_data unless respond_to?(:data) && data.keys
|
|
67
|
+
|
|
68
|
+
data.merge(**default_data)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def default_data
|
|
72
|
+
{ controller: controller_name }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Aeros::Button
|
|
2
|
+
class Component < ::Aeros::ApplicationViewComponent
|
|
3
|
+
option(:css, optional: true)
|
|
4
|
+
option(:type, optional: true)
|
|
5
|
+
option(:label, optional: true)
|
|
6
|
+
option(:method, optional: true)
|
|
7
|
+
option(:href, optional: true)
|
|
8
|
+
option(:data, default: proc { {} })
|
|
9
|
+
option(:icon, optional: true)
|
|
10
|
+
option(:variant, default: proc { :default })
|
|
11
|
+
option(:disabled, default: proc { false })
|
|
12
|
+
option(:full, default: proc { false })
|
|
13
|
+
option(:size, default: proc { nil })
|
|
14
|
+
option(:as, default: proc { :button })
|
|
15
|
+
option(:target, optional: true)
|
|
16
|
+
|
|
17
|
+
style do
|
|
18
|
+
base do
|
|
19
|
+
%w[
|
|
20
|
+
rounded-md
|
|
21
|
+
px-3.5
|
|
22
|
+
py-2.5
|
|
23
|
+
font-semibold
|
|
24
|
+
inline-flex
|
|
25
|
+
items-center
|
|
26
|
+
space-x-1
|
|
27
|
+
hover:bg-stone-500
|
|
28
|
+
focus-visible:outline
|
|
29
|
+
focus-visible:outline-2
|
|
30
|
+
focus-visible:outline-offset-2
|
|
31
|
+
focus-visible:outline-stone-600
|
|
32
|
+
cursor-pointer
|
|
33
|
+
[&>span]:truncate
|
|
34
|
+
[&>span]:flex-shrink
|
|
35
|
+
[&>svg]:flex-shrink-0
|
|
36
|
+
[&>svg]:w-4
|
|
37
|
+
[&>svg]:h-4
|
|
38
|
+
[&.loading]:opacity-50
|
|
39
|
+
[&.loading]:pointer-events-none
|
|
40
|
+
[&.loading_.icon]:hidden
|
|
41
|
+
[&.loading_.spinner]:flex
|
|
42
|
+
]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
variants do
|
|
46
|
+
variant do
|
|
47
|
+
white { "text-stone-600 bg-white hover:bg-stone-50 text-stone-800 border border-gray-200 shadow-sm !text-gray-500" }
|
|
48
|
+
default { "bg-slate-600 text-white" }
|
|
49
|
+
light { "bg-stone-50 text-stone-600 hover:bg-stone-100" }
|
|
50
|
+
outline { "bg-stone-50 text-stone-600 ring ring-1 ring-stone-400 rounded-full px-6 hover:bg-stone-100" }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
disabled do
|
|
54
|
+
yes { "pointer-events-none opacity-50" }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
full do
|
|
58
|
+
yes { "w-full justify-center" }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
size do
|
|
62
|
+
xsmall do
|
|
63
|
+
%w[
|
|
64
|
+
px-2.5
|
|
65
|
+
py-1
|
|
66
|
+
[&>span]:text-xs
|
|
67
|
+
[&>svg]:pl-[-10px]
|
|
68
|
+
[&>svg]:flex-shrink-0
|
|
69
|
+
[&>svg]:w-3
|
|
70
|
+
[&>svg]:h-3
|
|
71
|
+
space-x-1
|
|
72
|
+
]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
small do
|
|
76
|
+
%w[
|
|
77
|
+
text-lg
|
|
78
|
+
px-2.5
|
|
79
|
+
py-1.5
|
|
80
|
+
space-x-1
|
|
81
|
+
text-sm
|
|
82
|
+
[&>svg]:pl-[-10px]
|
|
83
|
+
[&>svg]:flex-shrink-0
|
|
84
|
+
[&>svg]:w-4
|
|
85
|
+
[&>svg]:h-4
|
|
86
|
+
]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
large do
|
|
90
|
+
%w[
|
|
91
|
+
text-lg
|
|
92
|
+
px-4
|
|
93
|
+
py-3
|
|
94
|
+
space-x-2
|
|
95
|
+
[&>svg]:pl-[-10px]
|
|
96
|
+
[&>svg]:flex-shrink-0
|
|
97
|
+
[&>svg]:w-6
|
|
98
|
+
[&>svg]:h-6
|
|
99
|
+
]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def classes
|
|
106
|
+
[
|
|
107
|
+
css,
|
|
108
|
+
style(variant:, disabled:, full:, size:)
|
|
109
|
+
].join(" ")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
erb_template <<~ERB
|
|
113
|
+
<% if href %>
|
|
114
|
+
<%= link_to(href, method:, data: merged_data, class: classes, target:) do %>
|
|
115
|
+
<%= lucide_icon(icon, class: "flex-shrink-0 icon") if icon %>
|
|
116
|
+
<%= ui("spinner", size: :sm, variant: :white, css: "spinner hidden") %>
|
|
117
|
+
<% if label %><span class="truncate flex-shrink"><%= label %></span><% end %>
|
|
118
|
+
<% end %>
|
|
119
|
+
<% else %>
|
|
120
|
+
<%= content_tag(as, data: merged_data, type: type || "button", class: classes) do %>
|
|
121
|
+
<%= lucide_icon(icon, class: "flex-shrink-0 icon") if icon %>
|
|
122
|
+
<%= ui("spinner", size: :sm, variant: :white, css: "spinner hidden") %>
|
|
123
|
+
<% if label %><span class="truncate flex-shrink"><%= label %></span><% end %>
|
|
124
|
+
<% end %>
|
|
125
|
+
<% end %>
|
|
126
|
+
ERB
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<div class="<%= classes %>">
|
|
2
|
+
<div>
|
|
3
|
+
<%= ui("button",
|
|
4
|
+
label: label,
|
|
5
|
+
icon: "chevron-down",
|
|
6
|
+
variant: button_variant,
|
|
7
|
+
size: button_size,
|
|
8
|
+
data: { "dropdown-menu-toggle": "" }) %>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="hidden origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-10"
|
|
12
|
+
role="menu"
|
|
13
|
+
aria-orientation="vertical">
|
|
14
|
+
<div class="py-1">
|
|
15
|
+
<% if groups.any? %>
|
|
16
|
+
<% groups.each do |group| %>
|
|
17
|
+
<%= group %>
|
|
18
|
+
<% end %>
|
|
19
|
+
<% else %>
|
|
20
|
+
<% options.each do |option| %>
|
|
21
|
+
<%= option %>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% end %>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Aeros::Dropdown
|
|
2
|
+
class Component < ::Aeros::ApplicationViewComponent
|
|
3
|
+
option(:css, optional: true)
|
|
4
|
+
option(:label, optional: true)
|
|
5
|
+
option(:button_variant, default: proc { :white })
|
|
6
|
+
option(:button_size, default: proc { :small })
|
|
7
|
+
|
|
8
|
+
renders_many :options, "OptionComponent"
|
|
9
|
+
renders_many :groups, "GroupComponent"
|
|
10
|
+
|
|
11
|
+
class OptionComponent < ApplicationViewComponent
|
|
12
|
+
option(:value)
|
|
13
|
+
option(:label)
|
|
14
|
+
option(:href, optional: true)
|
|
15
|
+
option(:selected, default: proc { false })
|
|
16
|
+
option(:icon, optional: true)
|
|
17
|
+
|
|
18
|
+
erb_template <<~ERB
|
|
19
|
+
<% if href %>
|
|
20
|
+
<a href="<%= href %>"
|
|
21
|
+
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 <%= 'bg-gray-50' if selected %>"
|
|
22
|
+
role="menuitem">
|
|
23
|
+
<div class="flex items-center space-x-2">
|
|
24
|
+
<%= lucide_icon(icon, class: "w-4 h-4") if icon %>
|
|
25
|
+
<span><%= label %></span>
|
|
26
|
+
</div>
|
|
27
|
+
</a>
|
|
28
|
+
<% else %>
|
|
29
|
+
<button type="button"
|
|
30
|
+
data-value="<%= value %>"
|
|
31
|
+
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 <%= 'bg-gray-50' if selected %>"
|
|
32
|
+
role="menuitem">
|
|
33
|
+
<div class="flex items-center space-x-2">
|
|
34
|
+
<%= lucide_icon(icon, class: "w-4 h-4") if icon %>
|
|
35
|
+
<span><%= label %></span>
|
|
36
|
+
</div>
|
|
37
|
+
</button>
|
|
38
|
+
<% end %>
|
|
39
|
+
ERB
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class GroupComponent < ApplicationViewComponent
|
|
43
|
+
option(:label)
|
|
44
|
+
|
|
45
|
+
renders_many :items, "Aeros::Dropdown::Component::OptionComponent"
|
|
46
|
+
|
|
47
|
+
erb_template <<~ERB
|
|
48
|
+
<div class="py-1">
|
|
49
|
+
<div class="px-4 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
|
50
|
+
<%= label %>
|
|
51
|
+
</div>
|
|
52
|
+
<% items.each do |item| %>
|
|
53
|
+
<%= item %>
|
|
54
|
+
<% end %>
|
|
55
|
+
</div>
|
|
56
|
+
ERB
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def classes
|
|
60
|
+
[
|
|
61
|
+
"relative inline-block text-left",
|
|
62
|
+
css
|
|
63
|
+
].join(" ")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="p-6">
|
|
2
|
+
<div class="flex items-center justify-center flex-col border border-dashed border-stone-300 rounded-lg p-6 min-h-[300px]">
|
|
3
|
+
<div class="flex items-center justify-center flex-col space-y-4">
|
|
4
|
+
<% if title %>
|
|
5
|
+
<div class="font-bold text-xl"><%= title %></div>
|
|
6
|
+
<% end %>
|
|
7
|
+
<div>
|
|
8
|
+
<%= content %>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Aeros::FormBuilder < ActionView::Helpers::FormBuilder
|
|
4
|
+
class BaseComponent < Aeros::ApplicationViewComponent
|
|
5
|
+
INPUT_BASE_CLASSES = %w[
|
|
6
|
+
block
|
|
7
|
+
w-full
|
|
8
|
+
rounded-md
|
|
9
|
+
bg-white
|
|
10
|
+
px-3
|
|
11
|
+
py-2
|
|
12
|
+
text-sm
|
|
13
|
+
text-gray-900
|
|
14
|
+
placeholder:text-gray-400
|
|
15
|
+
outline-none
|
|
16
|
+
border
|
|
17
|
+
border-gray-300
|
|
18
|
+
focus:border-indigo-500
|
|
19
|
+
focus:ring-1
|
|
20
|
+
focus:ring-indigo-500
|
|
21
|
+
shadow-sm
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
option(:id, optional: true)
|
|
25
|
+
option(:name)
|
|
26
|
+
option(:value, optional: true)
|
|
27
|
+
option(:disabled, default: proc { false })
|
|
28
|
+
option(:label, optional: true)
|
|
29
|
+
option(:type, optional: true)
|
|
30
|
+
option(:helper_text, optional: true)
|
|
31
|
+
option(:error_text, optional: true)
|
|
32
|
+
option(:placeholder, optional: true)
|
|
33
|
+
option(:required, default: proc { false })
|
|
34
|
+
option(:data, default: proc { {} })
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def field(field_type, name, options = {}, &block)
|
|
38
|
+
klass = "Aeros::Input#{field_type.to_s.classify}::Component".constantize
|
|
39
|
+
resolved_name = resolve_name(name)
|
|
40
|
+
error_text = @object&.errors&.[](name)&.first
|
|
41
|
+
|
|
42
|
+
value = options[:value] || @object&.send(name)
|
|
43
|
+
|
|
44
|
+
merged_options = options.merge(
|
|
45
|
+
id: options[:id] || "#{@object_name}_#{name}".parameterize(separator: "_"),
|
|
46
|
+
name: resolved_name,
|
|
47
|
+
error_text:,
|
|
48
|
+
value:
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@template.render(klass.new(**merged_options), &block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def text_field(name, options = {})
|
|
55
|
+
field(:text, name, options)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def password_field(name, options = {})
|
|
59
|
+
field(:password, name, options.merge(type: "password"))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def select_field(name, options = {}, &block)
|
|
63
|
+
field(:select, name, options, &block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def resolve_name(name)
|
|
69
|
+
@object_name ? "#{@object_name}[#{name}]" : name
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<%= ui("input_wrapper",
|
|
2
|
+
label: label,
|
|
3
|
+
helper_text: helper_text,
|
|
4
|
+
error_text: error_text,
|
|
5
|
+
name: name,
|
|
6
|
+
id: id,
|
|
7
|
+
disabled: disabled,
|
|
8
|
+
required: required) do %>
|
|
9
|
+
<div class="relative" data-controller="<%= controller_name %>">
|
|
10
|
+
<input
|
|
11
|
+
type="password"
|
|
12
|
+
id="<%= id %>"
|
|
13
|
+
name="<%= name %>"
|
|
14
|
+
value="<%= value %>"
|
|
15
|
+
placeholder="<%= placeholder %>"
|
|
16
|
+
autocomplete="<%= autocomplete %>"
|
|
17
|
+
class="<%= Ui::FormBuilder::BaseComponent::INPUT_BASE_CLASSES.join(' ') %> <%= show_toggle ? 'pr-10' : '' %>"
|
|
18
|
+
data-<%= controller_name %>-target="input"
|
|
19
|
+
<%= 'disabled' if disabled %>
|
|
20
|
+
<%= 'required' if required %>
|
|
21
|
+
<% if data.any? %>
|
|
22
|
+
<% data.each do |key, val| %>
|
|
23
|
+
data-<%= key.to_s.dasherize %>="<%= val %>"
|
|
24
|
+
<% end %>
|
|
25
|
+
<% end %>
|
|
26
|
+
/>
|
|
27
|
+
<% if show_toggle %>
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
class="absolute inset-y-0 right-0 pr-3 flex items-center"
|
|
31
|
+
data-action="click-><%= controller_name %>#toggle"
|
|
32
|
+
tabindex="-1"
|
|
33
|
+
>
|
|
34
|
+
<span data-<%= controller_name %>-target="showIcon">
|
|
35
|
+
<%= lucide_icon("eye", class: "h-4 w-4 text-gray-400 hover:text-gray-600") %>
|
|
36
|
+
</span>
|
|
37
|
+
<span data-<%= controller_name %>-target="hideIcon" class="hidden">
|
|
38
|
+
<%= lucide_icon("eye-off", class: "h-4 w-4 text-gray-400 hover:text-gray-600") %>
|
|
39
|
+
</span>
|
|
40
|
+
</button>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
<% end %>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["input", "showIcon", "hideIcon"];
|
|
5
|
+
|
|
6
|
+
toggle() {
|
|
7
|
+
if (this.inputTarget.type === "password") {
|
|
8
|
+
this.inputTarget.type = "text";
|
|
9
|
+
this.showIconTarget.classList.add("hidden");
|
|
10
|
+
this.hideIconTarget.classList.remove("hidden");
|
|
11
|
+
} else {
|
|
12
|
+
this.inputTarget.type = "password";
|
|
13
|
+
this.showIconTarget.classList.remove("hidden");
|
|
14
|
+
this.hideIconTarget.classList.add("hidden");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<%= ui("input_wrapper",
|
|
2
|
+
label: label,
|
|
3
|
+
helper_text: helper_text,
|
|
4
|
+
error_text: error_text,
|
|
5
|
+
name: name,
|
|
6
|
+
id: id,
|
|
7
|
+
disabled: disabled,
|
|
8
|
+
required: required) do %>
|
|
9
|
+
<select
|
|
10
|
+
id="<%= id %>"
|
|
11
|
+
name="<%= name %>"
|
|
12
|
+
class="<%= Ui::FormBuilder::BaseComponent::INPUT_BASE_CLASSES.join(' ') %>"
|
|
13
|
+
<%= 'disabled' if disabled %>
|
|
14
|
+
<%= 'required' if required %>
|
|
15
|
+
<% if data.any? %>
|
|
16
|
+
<% data.each do |key, val| %>
|
|
17
|
+
data-<%= key.to_s.dasherize %>="<%= val %>"
|
|
18
|
+
<% end %>
|
|
19
|
+
<% end %>
|
|
20
|
+
>
|
|
21
|
+
<% if prompt %>
|
|
22
|
+
<option value=""><%= prompt %></option>
|
|
23
|
+
<% end %>
|
|
24
|
+
|
|
25
|
+
<% if collection && value_method && label_method %>
|
|
26
|
+
<% collection.each do |item| %>
|
|
27
|
+
<option value="<%= item.send(value_method) %>" <%= 'selected' if value == item.send(value_method).to_s %>>
|
|
28
|
+
<%= item.send(label_method) %>
|
|
29
|
+
</option>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% elsif options.any? %>
|
|
32
|
+
<% options.each do |option_value, option_label| %>
|
|
33
|
+
<option value="<%= option_value %>" <%= 'selected' if value == option_value.to_s %>>
|
|
34
|
+
<%= option_label %>
|
|
35
|
+
</option>
|
|
36
|
+
<% end %>
|
|
37
|
+
<% else %>
|
|
38
|
+
<% select_options.each do |option| %>
|
|
39
|
+
<%= option %>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|
|
42
|
+
</select>
|
|
43
|
+
<% end %>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Aeros::InputSelect
|
|
2
|
+
class Component < ::Aeros::FormBuilder::BaseComponent
|
|
3
|
+
option(:prompt, optional: true)
|
|
4
|
+
option(:options, default: proc { [] })
|
|
5
|
+
option(:collection, optional: true)
|
|
6
|
+
option(:value_method, optional: true)
|
|
7
|
+
option(:label_method, optional: true)
|
|
8
|
+
|
|
9
|
+
renders_many :select_options, "OptionComponent"
|
|
10
|
+
|
|
11
|
+
class OptionComponent < ApplicationViewComponent
|
|
12
|
+
option(:value)
|
|
13
|
+
option(:label)
|
|
14
|
+
option(:selected, default: proc { false })
|
|
15
|
+
option(:disabled, default: proc { false })
|
|
16
|
+
|
|
17
|
+
erb_template <<~ERB
|
|
18
|
+
<option value="<%= value %>" <%= 'selected' if selected %> <%= 'disabled' if disabled %>>
|
|
19
|
+
<%= label %>
|
|
20
|
+
</option>
|
|
21
|
+
ERB
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<%= ui("input_wrapper",
|
|
2
|
+
label: label,
|
|
3
|
+
helper_text: helper_text,
|
|
4
|
+
error_text: error_text,
|
|
5
|
+
name: name,
|
|
6
|
+
id: id,
|
|
7
|
+
disabled: disabled,
|
|
8
|
+
required: required) do %>
|
|
9
|
+
<input
|
|
10
|
+
type="<%= type || 'text' %>"
|
|
11
|
+
id="<%= id %>"
|
|
12
|
+
name="<%= name %>"
|
|
13
|
+
value="<%= value %>"
|
|
14
|
+
placeholder="<%= placeholder %>"
|
|
15
|
+
autocomplete="<%= autocomplete %>"
|
|
16
|
+
class="<%= Ui::FormBuilder::BaseComponent::INPUT_BASE_CLASSES.join(' ') %>"
|
|
17
|
+
<%= 'disabled' if disabled %>
|
|
18
|
+
<%= 'required' if required %>
|
|
19
|
+
<% if data.any? %>
|
|
20
|
+
<% data.each do |key, val| %>
|
|
21
|
+
data-<%= key.to_s.dasherize %>="<%= val %>"
|
|
22
|
+
<% end %>
|
|
23
|
+
<% end %>
|
|
24
|
+
/>
|
|
25
|
+
<% end %>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<div class="<%= 'opacity-50 pointer-events-none' if disabled %>" data-<%= data.to_json if data.any? %>>
|
|
2
|
+
<% if label %>
|
|
3
|
+
<label for="<%= id || name %>" class="block text-sm font-medium text-gray-700 mb-1">
|
|
4
|
+
<%= label %>
|
|
5
|
+
<% if required %>
|
|
6
|
+
<span class="text-red-500">*</span>
|
|
7
|
+
<% end %>
|
|
8
|
+
</label>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<div class="relative">
|
|
12
|
+
<%= content %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<% if error_text %>
|
|
16
|
+
<p class="mt-1 text-sm text-red-600"><%= error_text %></p>
|
|
17
|
+
<% elsif helper_text %>
|
|
18
|
+
<p class="mt-1 text-sm text-gray-500"><%= helper_text %></p>
|
|
19
|
+
<% end %>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Aeros::InputWrapper
|
|
2
|
+
class Component < ::Aeros::ApplicationViewComponent
|
|
3
|
+
option(:label, optional: true)
|
|
4
|
+
option(:name)
|
|
5
|
+
option(:id, optional: true)
|
|
6
|
+
option(:helper_text, optional: true)
|
|
7
|
+
option(:error_text, optional: true)
|
|
8
|
+
option(:disabled, default: proc { false })
|
|
9
|
+
option(:required, default: proc { false })
|
|
10
|
+
option(:data, default: proc { {} })
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<div class="flex flex-col w-full">
|
|
2
|
+
<div class="flex items-center justify-between py-6 space-x-6">
|
|
3
|
+
<div class="w-full min-w-0 truncate">
|
|
4
|
+
<h1 class="flex items-center space-x-2 font-semibold text-3xl tracking-tight">
|
|
5
|
+
<span class="flex-shrink truncate min-w-0 max-w-2/3"><%= title %></span>
|
|
6
|
+
<% if subtitle %>
|
|
7
|
+
<span class="text-gray-500/70 flex-shrink-0"><%= subtitle %></span>
|
|
8
|
+
<% end %>
|
|
9
|
+
</h1>
|
|
10
|
+
<% if description %>
|
|
11
|
+
<p class="mt-2 text-gray-600"><%= description %></p>
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
14
|
+
<% if actions_area %>
|
|
15
|
+
<div class="flex-shrink-0">
|
|
16
|
+
<%= actions_area %>
|
|
17
|
+
</div>
|
|
18
|
+
<% end %>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="pb-6 w-full">
|
|
22
|
+
<%= content %>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|