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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +230 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/stylesheets/aeros/application.css +15 -0
  6. data/app/assets/stylesheets/aeros/application.tailwind.css +7 -0
  7. data/app/assets/stylesheets/aeros/tailwind.css +1100 -0
  8. data/app/components/aeros/application_view_component.rb +75 -0
  9. data/app/components/aeros/button/component.rb +128 -0
  10. data/app/components/aeros/button/controller.js +7 -0
  11. data/app/components/aeros/card/component.html.erb +3 -0
  12. data/app/components/aeros/card/component.rb +7 -0
  13. data/app/components/aeros/dropdown/component.html.erb +26 -0
  14. data/app/components/aeros/dropdown/component.rb +66 -0
  15. data/app/components/aeros/empty/component.html.erb +12 -0
  16. data/app/components/aeros/empty/component.rb +5 -0
  17. data/app/components/aeros/form_builder.rb +71 -0
  18. data/app/components/aeros/input_password/component.html.erb +43 -0
  19. data/app/components/aeros/input_password/component.rb +6 -0
  20. data/app/components/aeros/input_password/controller.js +17 -0
  21. data/app/components/aeros/input_select/component.html.erb +43 -0
  22. data/app/components/aeros/input_select/component.rb +24 -0
  23. data/app/components/aeros/input_text/component.html.erb +25 -0
  24. data/app/components/aeros/input_text/component.rb +5 -0
  25. data/app/components/aeros/input_wrapper/component.html.erb +20 -0
  26. data/app/components/aeros/input_wrapper/component.rb +12 -0
  27. data/app/components/aeros/page/component.html.erb +24 -0
  28. data/app/components/aeros/page/component.rb +9 -0
  29. data/app/components/aeros/spinner/component.rb +55 -0
  30. data/app/components/aeros/table/component.html.erb +10 -0
  31. data/app/components/aeros/table/component.rb +64 -0
  32. data/app/controllers/aeros/application_controller.rb +4 -0
  33. data/app/controllers/aeros/showcase_controller.rb +4 -0
  34. data/app/helpers/aeros/application_helper.rb +16 -0
  35. data/app/javascript/aeros/application.js +3 -0
  36. data/app/javascript/aeros/controllers/application.js +5 -0
  37. data/app/javascript/aeros/controllers/index.js +5 -0
  38. data/app/javascript/aeros/controllers/loader.js +62 -0
  39. data/app/jobs/aeros/application_job.rb +4 -0
  40. data/app/models/aeros/application_record.rb +5 -0
  41. data/app/views/aeros/showcase/index.html.erb +1 -0
  42. data/app/views/layouts/aeros/application.html.erb +20 -0
  43. data/config/importmap.rb +15 -0
  44. data/config/routes.rb +3 -0
  45. data/lib/aeros/engine.rb +37 -0
  46. data/lib/aeros/engine_helpers.rb +44 -0
  47. data/lib/aeros/version.rb +3 -0
  48. data/lib/aeros.rb +9 -0
  49. data/lib/tasks/aeros_tasks.rake +21 -0
  50. 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,7 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ console.log("button connected");
6
+ }
7
+ }
@@ -0,0 +1,3 @@
1
+ <div class="<%= default_styles %>">
2
+ <%= content %>
3
+ </div>
@@ -0,0 +1,7 @@
1
+ module Aeros::Card
2
+ class Component < ::Aeros::ApplicationViewComponent
3
+ style do
4
+ base { "shadow-lg ring ring-stone-400/20 rounded-lg" }
5
+ end
6
+ end
7
+ 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,5 @@
1
+ module Aeros::Empty
2
+ class Component < ::Aeros::ApplicationViewComponent
3
+ renders_one(:title)
4
+ end
5
+ end
@@ -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,6 @@
1
+ module Aeros::InputPassword
2
+ class Component < ::Aeros::FormBuilder::BaseComponent
3
+ option(:autocomplete, default: proc { "current-password" })
4
+ option(:show_toggle, default: proc { true })
5
+ end
6
+ 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,5 @@
1
+ module Aeros::InputText
2
+ class Component < ::Aeros::FormBuilder::BaseComponent
3
+ option(:autocomplete, optional: true)
4
+ end
5
+ 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>
@@ -0,0 +1,9 @@
1
+ module Aeros::Page
2
+ class Component < Aeros::ApplicationViewComponent
3
+ option(:title)
4
+ option(:subtitle, optional: true)
5
+ option(:description, optional: true)
6
+
7
+ renders_one(:actions_area)
8
+ end
9
+ end