aeros 0.0.1 → 0.0.2
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/Rakefile +1 -1
- data/app/assets/stylesheets/aeros/application.css +1 -15
- data/app/assets/stylesheets/aeros/base.css +43 -0
- data/app/assets/stylesheets/aeros/reset.css +397 -0
- data/app/assets/stylesheets/aeros/source.css +15 -0
- data/app/assets/stylesheets/aeros/theme.css +6 -0
- data/app/assets/stylesheets/aeros/themes/slate.css +163 -0
- data/app/assets/stylesheets/aeros/themes/zinc.css +163 -0
- data/app/assets/stylesheets/aeros/utilities.css +23 -0
- data/app/components/aeros/application_view_component.rb +149 -5
- data/app/components/aeros/blocks/component_preview/component.html.erb +7 -0
- data/app/components/aeros/blocks/component_preview/component.rb +10 -0
- data/app/components/aeros/blocks/component_preview/styles.css +10 -0
- data/app/components/aeros/form_builder.rb +24 -8
- data/app/components/aeros/pages/showcase/index/component.html.erb +53 -0
- data/app/components/aeros/pages/showcase/index/component.rb +7 -0
- data/app/components/aeros/pages/showcase/index/styles.css +27 -0
- data/app/components/aeros/pages/showcase/placeholder/component.html.erb +7 -0
- data/app/components/aeros/pages/showcase/placeholder/component.rb +10 -0
- data/app/components/aeros/pages/showcase/show/component.html.erb +38 -0
- data/app/components/aeros/pages/showcase/show/component.rb +48 -0
- data/app/components/aeros/primitives/button/component.rb +66 -0
- data/app/components/aeros/primitives/button/styles.css +153 -0
- data/app/components/aeros/primitives/card/component.html.erb +3 -0
- data/app/components/aeros/primitives/card/component.rb +42 -0
- data/app/components/aeros/primitives/card/styles.css +28 -0
- data/app/components/aeros/primitives/conversation/component.html.erb +28 -0
- data/app/components/aeros/primitives/conversation/component.rb +15 -0
- data/app/components/aeros/primitives/conversation/controller.js +18 -0
- data/app/components/aeros/primitives/conversation/message/component.html.erb +24 -0
- data/app/components/aeros/primitives/conversation/message/component.rb +35 -0
- data/app/components/aeros/primitives/conversation/streaming_indicator/component.html.erb +21 -0
- data/app/components/aeros/primitives/conversation/streaming_indicator/component.rb +18 -0
- data/app/components/aeros/primitives/conversation/styles.css +221 -0
- data/app/components/aeros/primitives/conversation/user_message_box/component.html.erb +1 -0
- data/app/components/aeros/{empty → primitives/conversation/user_message_box}/component.rb +1 -2
- data/app/components/aeros/primitives/drawer/component.html.erb +43 -0
- data/app/components/aeros/primitives/drawer/component.rb +33 -0
- data/app/components/aeros/primitives/drawer/controller.js +104 -0
- data/app/components/aeros/primitives/drawer/styles.css +90 -0
- data/app/components/aeros/primitives/dropdown/checkbox.rb +22 -0
- data/app/components/aeros/primitives/dropdown/component.html.erb +38 -0
- data/app/components/aeros/primitives/dropdown/component.rb +53 -0
- data/app/components/aeros/primitives/dropdown/controller.js +153 -0
- data/app/components/aeros/primitives/dropdown/item.rb +29 -0
- data/app/components/aeros/primitives/dropdown/label.rb +7 -0
- data/app/components/aeros/primitives/dropdown/radio_group.rb +16 -0
- data/app/components/aeros/primitives/dropdown/radio_item.rb +24 -0
- data/app/components/aeros/primitives/dropdown/separator.rb +7 -0
- data/app/components/aeros/primitives/dropdown/styles.css +155 -0
- data/app/components/aeros/primitives/empty/component.html.erb +15 -0
- data/app/components/aeros/primitives/empty/component.rb +18 -0
- data/app/components/aeros/primitives/empty/styles.css +40 -0
- data/app/components/aeros/primitives/input_attachments/component.html.erb +60 -0
- data/app/components/aeros/primitives/input_attachments/component.rb +52 -0
- data/app/components/aeros/primitives/input_attachments/controller.js +357 -0
- data/app/components/aeros/primitives/input_attachments/styles.css +102 -0
- data/app/components/aeros/primitives/input_color/component.html.erb +24 -0
- data/app/components/aeros/primitives/input_color/component.rb +42 -0
- data/app/components/aeros/primitives/input_color/styles.css +64 -0
- data/app/components/aeros/primitives/input_password/component.html.erb +43 -0
- data/app/components/aeros/primitives/input_password/component.rb +20 -0
- data/app/components/aeros/primitives/input_password/styles.css +61 -0
- data/app/components/aeros/{input_select → primitives/input_select}/component.html.erb +19 -19
- data/app/components/aeros/primitives/input_select/component.rb +21 -0
- data/app/components/aeros/primitives/input_select/option.rb +14 -0
- data/app/components/aeros/primitives/input_select/styles.css +30 -0
- data/app/components/aeros/primitives/input_slider/component.html.erb +33 -0
- data/app/components/aeros/primitives/input_slider/component.rb +35 -0
- data/app/components/aeros/primitives/input_slider/styles.css +74 -0
- data/app/components/aeros/primitives/input_tagging/component.html.erb +73 -0
- data/app/components/aeros/primitives/input_tagging/component.rb +40 -0
- data/app/components/aeros/primitives/input_tagging/controller.js +326 -0
- data/app/components/aeros/primitives/input_tagging/styles.css +148 -0
- data/app/components/aeros/primitives/input_text/component.html.erb +25 -0
- data/app/components/aeros/primitives/input_text/component.rb +20 -0
- data/app/components/aeros/primitives/input_text/styles.css +38 -0
- data/app/components/aeros/primitives/input_text_area/component.html.erb +23 -0
- data/app/components/aeros/primitives/input_text_area/component.rb +19 -0
- data/app/components/aeros/primitives/input_text_area/styles.css +30 -0
- data/app/components/aeros/primitives/input_text_area_ai/component.html.erb +51 -0
- data/app/components/aeros/primitives/input_text_area_ai/component.rb +47 -0
- data/app/components/aeros/primitives/input_text_area_ai/controller.js +198 -0
- data/app/components/aeros/primitives/input_text_area_ai/styles.css +91 -0
- data/app/components/aeros/primitives/input_wrapper/component.html.erb +20 -0
- data/app/components/aeros/primitives/input_wrapper/component.rb +31 -0
- data/app/components/aeros/primitives/input_wrapper/styles.css +72 -0
- data/app/components/aeros/primitives/layouts/agentic/component.html.erb +4 -0
- data/app/components/aeros/primitives/layouts/agentic/component.rb +23 -0
- data/app/components/aeros/primitives/layouts/app/aside.rb +9 -0
- data/app/components/aeros/primitives/layouts/app/component.html.erb +14 -0
- data/app/components/aeros/primitives/layouts/app/component.rb +11 -0
- data/app/components/aeros/primitives/layouts/app/sidebar.rb +9 -0
- data/app/components/aeros/primitives/layouts/app/styles.css +46 -0
- data/app/components/aeros/primitives/page/component.html.erb +24 -0
- data/app/components/aeros/primitives/page/component.rb +23 -0
- data/app/components/aeros/primitives/page/styles.css +55 -0
- data/app/components/aeros/primitives/sidebar/component.html.erb +25 -0
- data/app/components/aeros/primitives/sidebar/component.rb +14 -0
- data/app/components/aeros/primitives/sidebar/footer.rb +7 -0
- data/app/components/aeros/primitives/sidebar/group.rb +18 -0
- data/app/components/aeros/primitives/sidebar/header.rb +7 -0
- data/app/components/aeros/primitives/sidebar/item.rb +19 -0
- data/app/components/aeros/primitives/sidebar/styles.css +95 -0
- data/app/components/aeros/primitives/spinner/component.rb +36 -0
- data/app/components/aeros/primitives/spinner/styles.css +81 -0
- data/app/components/aeros/primitives/table/cell.rb +7 -0
- data/app/components/aeros/primitives/table/column.rb +7 -0
- data/app/components/aeros/primitives/table/component.html.erb +8 -0
- data/app/components/aeros/primitives/table/component.rb +14 -0
- data/app/components/aeros/primitives/table/header.rb +13 -0
- data/app/components/aeros/primitives/table/row.rb +11 -0
- data/app/components/aeros/primitives/table/styles.css +39 -0
- data/app/controllers/aeros/application_controller.rb +11 -0
- data/app/controllers/aeros/showcase_controller.rb +37 -1
- data/app/controllers/aeros/theme_controller.rb +10 -0
- data/app/helpers/aeros/application_helper.rb +19 -7
- data/app/views/layouts/aeros/application.html.erb +49 -14
- data/config/importmap.rb +6 -1
- data/config/routes.rb +2 -0
- data/lib/aeros/configuration.rb +56 -0
- data/lib/aeros/engine.rb +7 -1
- data/lib/aeros/theme.rb +326 -0
- data/lib/aeros/version.rb +1 -1
- data/lib/aeros.rb +2 -0
- data/lib/tasks/aeros_tasks.rake +25 -7
- metadata +127 -38
- data/app/assets/stylesheets/aeros/application.tailwind.css +0 -7
- data/app/assets/stylesheets/aeros/tailwind.css +0 -1100
- data/app/components/aeros/button/component.rb +0 -128
- data/app/components/aeros/card/component.html.erb +0 -3
- data/app/components/aeros/card/component.rb +0 -7
- data/app/components/aeros/dropdown/component.html.erb +0 -26
- data/app/components/aeros/dropdown/component.rb +0 -66
- data/app/components/aeros/empty/component.html.erb +0 -12
- data/app/components/aeros/input_password/component.html.erb +0 -43
- data/app/components/aeros/input_password/component.rb +0 -6
- data/app/components/aeros/input_select/component.rb +0 -24
- data/app/components/aeros/input_text/component.html.erb +0 -25
- data/app/components/aeros/input_text/component.rb +0 -5
- data/app/components/aeros/input_wrapper/component.html.erb +0 -20
- data/app/components/aeros/input_wrapper/component.rb +0 -12
- data/app/components/aeros/page/component.html.erb +0 -24
- data/app/components/aeros/page/component.rb +0 -9
- data/app/components/aeros/spinner/component.rb +0 -55
- data/app/components/aeros/table/component.html.erb +0 -10
- data/app/components/aeros/table/component.rb +0 -64
- data/app/views/aeros/showcase/index.html.erb +0 -1
- /data/app/components/aeros/{button → primitives/button}/controller.js +0 -0
- /data/app/components/aeros/{input_password → primitives/input_password}/controller.js +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.cg-showcase {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: 1.5rem;
|
|
5
|
+
max-width: 900px;
|
|
6
|
+
|
|
7
|
+
& h1 {
|
|
8
|
+
font-size: 2rem;
|
|
9
|
+
font-weight: 700;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
& h2 {
|
|
13
|
+
font-size: 0.875rem;
|
|
14
|
+
font-weight: 600;
|
|
15
|
+
color: var(--ui-muted-color);
|
|
16
|
+
margin-bottom: 1rem;
|
|
17
|
+
text-transform: uppercase;
|
|
18
|
+
letter-spacing: 0.05em;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&__row {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-wrap: wrap;
|
|
24
|
+
gap: 0.5rem;
|
|
25
|
+
align-items: center;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<div class="cg-showcase">
|
|
2
|
+
<h1><%= title %></h1>
|
|
3
|
+
<% if description %>
|
|
4
|
+
<p class="text-muted"><%= description %></p>
|
|
5
|
+
<% end %>
|
|
6
|
+
|
|
7
|
+
<% examples.each do |example| %>
|
|
8
|
+
<%= block("component_preview", title: example.title, description: example.description) do %>
|
|
9
|
+
<% example.previews.each do |preview| %>
|
|
10
|
+
<%= render_component(**preview.props) %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<%= ui("card") do %>
|
|
16
|
+
<h2>Props</h2>
|
|
17
|
+
<%= ui("card") do %>
|
|
18
|
+
<%= ui("table") do |t| %>
|
|
19
|
+
<% t.with_header do |h| %>
|
|
20
|
+
<% h.with_column do %>Name<% end %>
|
|
21
|
+
<% h.with_column do %>Description<% end %>
|
|
22
|
+
<% h.with_column do %>Values<% end %>
|
|
23
|
+
<% end %>
|
|
24
|
+
<% props.each do |name, meta| %>
|
|
25
|
+
<% t.with_row do |r| %>
|
|
26
|
+
<% r.with_cell do %><code><%= name %></code><% end %>
|
|
27
|
+
<% r.with_cell do %><%= meta[:description] %><% end %>
|
|
28
|
+
<% r.with_cell do %>
|
|
29
|
+
<% if meta[:values] %>
|
|
30
|
+
<% meta[:values].each do |v| %><code><%= v %></code> <% end %>
|
|
31
|
+
<% end %>
|
|
32
|
+
<% end %>
|
|
33
|
+
<% end %>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% end %>
|
|
36
|
+
<% end %>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Aeros::Pages::Showcase::Show
|
|
2
|
+
class Component < ::Aeros::ApplicationViewComponent
|
|
3
|
+
option :component_class
|
|
4
|
+
|
|
5
|
+
def title
|
|
6
|
+
component_class.examples_title || component_name
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def description
|
|
10
|
+
component_class.examples_description
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def examples
|
|
14
|
+
component_class.examples_list
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def props
|
|
18
|
+
component_class.props
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns :primitive or :block
|
|
22
|
+
def component_type
|
|
23
|
+
if component_class.name.include?("::Primitives::")
|
|
24
|
+
:primitive
|
|
25
|
+
elsif component_class.name.include?("::Blocks::")
|
|
26
|
+
:block
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# e.g., "button" from Aeros::Primitives::Button::Component
|
|
31
|
+
def component_name
|
|
32
|
+
component_class.name
|
|
33
|
+
.sub(/^Aeros::(Primitives|Blocks)::/, "")
|
|
34
|
+
.sub(/::Component$/, "")
|
|
35
|
+
.underscore
|
|
36
|
+
.tr("_", "-")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def render_component(**props)
|
|
40
|
+
case component_type
|
|
41
|
+
when :primitive
|
|
42
|
+
helpers.ui(component_name, **props)
|
|
43
|
+
when :block
|
|
44
|
+
helpers.block(component_name, **props)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Aeros::Primitives::Button
|
|
2
|
+
class Component < ::Aeros::ApplicationViewComponent
|
|
3
|
+
prop :variant, description: "Button style variant",
|
|
4
|
+
values: [:default, :secondary, :destructive, :ghost, :outline, :link],
|
|
5
|
+
default: -> { :default }
|
|
6
|
+
prop :size, description: "Button size",
|
|
7
|
+
values: [:xsmall, :small, :large, :icon],
|
|
8
|
+
optional: true
|
|
9
|
+
prop :label, description: "Button text label", optional: true
|
|
10
|
+
prop :icon, description: "Lucide icon name", optional: true
|
|
11
|
+
prop :href, description: "Link URL (renders as <a>)", optional: true
|
|
12
|
+
prop :disabled, description: "Disable the button", default: -> { false }
|
|
13
|
+
prop :full, description: "Full width button", default: -> { false }
|
|
14
|
+
|
|
15
|
+
option(:type, optional: true)
|
|
16
|
+
option(:method, optional: true)
|
|
17
|
+
option(:data, default: -> { {} })
|
|
18
|
+
option(:target, optional: true)
|
|
19
|
+
|
|
20
|
+
examples("Button", description: "Clickable actions and navigation links") do |b|
|
|
21
|
+
b.example(:default, title: "Default") do |e|
|
|
22
|
+
e.preview label: "Click me"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
b.example(:variants, title: "Variants", description: "Available style variants") do |e|
|
|
26
|
+
e.preview variant: :default, label: "Default"
|
|
27
|
+
e.preview variant: :secondary, label: "Secondary"
|
|
28
|
+
e.preview variant: :destructive, label: "Destructive"
|
|
29
|
+
e.preview variant: :outline, label: "Outline"
|
|
30
|
+
e.preview variant: :ghost, label: "Ghost"
|
|
31
|
+
e.preview variant: :link, label: "Link"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
b.example(:sizes, title: "Sizes", description: "Available sizes") do |e|
|
|
35
|
+
e.preview size: :xsmall, label: "XSmall"
|
|
36
|
+
e.preview size: :small, label: "Small"
|
|
37
|
+
e.preview label: "Default"
|
|
38
|
+
e.preview size: :large, label: "Large"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
b.example(:icons, title: "With Icons", description: "Buttons with icons") do |e|
|
|
42
|
+
e.preview icon: "settings", label: "Settings"
|
|
43
|
+
e.preview icon: "download", label: "Download", variant: :secondary
|
|
44
|
+
e.preview icon: "trash-2", label: "Delete", variant: :destructive
|
|
45
|
+
e.preview icon: "settings", size: :icon
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
b.example(:states, title: "States") do |e|
|
|
49
|
+
e.preview label: "Disabled", disabled: true
|
|
50
|
+
e.preview label: "Full Width", full: true
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def button_classes
|
|
55
|
+
classes(variant:, size:, disabled:, full:)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def call
|
|
59
|
+
action_tag(href: href, method: method, data: merged_data, class: button_classes, target: target, disabled: disabled) do
|
|
60
|
+
(icon ? lucide_icon(icon, class: "flex-shrink-0 icon") : "".html_safe) +
|
|
61
|
+
ui("spinner", size: :sm, variant: :white, css: "spinner hidden") +
|
|
62
|
+
(label ? content_tag(:span, label, class: "truncate flex-shrink") : "".html_safe)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/* Button component styles */
|
|
2
|
+
.cp-button {
|
|
3
|
+
border-radius: var(--ui-rounded-button, 0.375rem);
|
|
4
|
+
padding: 0.625rem 0.875rem;
|
|
5
|
+
font-weight: 600;
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
gap: 0.25rem;
|
|
9
|
+
cursor: pointer;
|
|
10
|
+
transition:
|
|
11
|
+
color 0.15s ease,
|
|
12
|
+
background-color 0.15s ease,
|
|
13
|
+
border-color 0.15s ease;
|
|
14
|
+
|
|
15
|
+
&:focus-visible {
|
|
16
|
+
outline: 2px solid var(--ui-ring, #3b82f6);
|
|
17
|
+
outline-offset: 2px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
& > span {
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
text-overflow: ellipsis;
|
|
23
|
+
white-space: nowrap;
|
|
24
|
+
flex-shrink: 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
& > svg {
|
|
28
|
+
flex-shrink: 0;
|
|
29
|
+
width: 1rem;
|
|
30
|
+
height: 1rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
& .spinner {
|
|
34
|
+
display: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&.loading {
|
|
38
|
+
opacity: 0.5;
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
|
|
41
|
+
& .icon {
|
|
42
|
+
display: none;
|
|
43
|
+
}
|
|
44
|
+
& .spinner {
|
|
45
|
+
display: flex;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Variants */
|
|
50
|
+
&--default {
|
|
51
|
+
background-color: var(--ui-button-bg, #475569);
|
|
52
|
+
color: var(--ui-button-foreground, #ffffff);
|
|
53
|
+
|
|
54
|
+
&:hover {
|
|
55
|
+
background-color: var(--ui-button-hover, #334155);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&--secondary {
|
|
60
|
+
background-color: var(--ui-button-secondary-bg, #e2e8f0);
|
|
61
|
+
color: var(--ui-button-secondary-foreground, #1e293b);
|
|
62
|
+
|
|
63
|
+
&:hover {
|
|
64
|
+
background-color: var(--ui-button-secondary-hover, #cbd5e1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&--destructive {
|
|
69
|
+
background-color: var(--ui-button-destructive-bg, #ef4444);
|
|
70
|
+
color: var(--ui-button-destructive-foreground, #ffffff);
|
|
71
|
+
|
|
72
|
+
&:hover {
|
|
73
|
+
background-color: var(--ui-button-destructive-hover, #dc2626);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&--outline {
|
|
78
|
+
border: 1px solid var(--ui-button-border, #e2e8f0);
|
|
79
|
+
background-color: var(--ui-background, #ffffff);
|
|
80
|
+
color: var(--ui-button-outline-foreground, #1e293b);
|
|
81
|
+
|
|
82
|
+
&:hover {
|
|
83
|
+
background-color: var(--ui-button-outline-hover, #f1f5f9);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&--ghost {
|
|
88
|
+
background-color: transparent;
|
|
89
|
+
color: var(--ui-button-ghost-foreground, #1e293b);
|
|
90
|
+
|
|
91
|
+
&:hover {
|
|
92
|
+
background-color: var(--ui-button-ghost-hover, #f1f5f9);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
&--link {
|
|
97
|
+
background-color: transparent;
|
|
98
|
+
color: var(--ui-link, #3b82f6);
|
|
99
|
+
text-underline-offset: 4px;
|
|
100
|
+
|
|
101
|
+
&:hover {
|
|
102
|
+
text-decoration: underline;
|
|
103
|
+
color: var(--ui-link-hover, #2563eb);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Sizes */
|
|
108
|
+
&--xsmall {
|
|
109
|
+
padding: 0.25rem 0.625rem;
|
|
110
|
+
font-size: 0.75rem;
|
|
111
|
+
border-radius: var(--ui-rounded-button-sm, 0.25rem);
|
|
112
|
+
gap: 0.25rem;
|
|
113
|
+
|
|
114
|
+
& > svg {
|
|
115
|
+
width: 0.75rem;
|
|
116
|
+
height: 0.75rem;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
&--small {
|
|
121
|
+
padding: 0.375rem 0.75rem;
|
|
122
|
+
font-size: 0.875rem;
|
|
123
|
+
border-radius: var(--ui-rounded-button-sm, 0.25rem);
|
|
124
|
+
|
|
125
|
+
& > svg {
|
|
126
|
+
width: 1rem;
|
|
127
|
+
height: 1rem;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
&--large {
|
|
132
|
+
padding: 0.75rem 1.5rem;
|
|
133
|
+
font-size: 1.125rem;
|
|
134
|
+
border-radius: var(--ui-rounded-button-lg, 0.5rem);
|
|
135
|
+
gap: 0.5rem;
|
|
136
|
+
|
|
137
|
+
& > svg {
|
|
138
|
+
width: 1.25rem;
|
|
139
|
+
height: 1.25rem;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* States */
|
|
144
|
+
&--disabled {
|
|
145
|
+
pointer-events: none;
|
|
146
|
+
opacity: 0.5;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
&--full {
|
|
150
|
+
width: 100%;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Aeros::Primitives::Card
|
|
2
|
+
class Component < ::Aeros::ApplicationViewComponent
|
|
3
|
+
prop :variant, description: "Card style variant",
|
|
4
|
+
values: [:default, :ghost, :elevated],
|
|
5
|
+
default: -> { :default }
|
|
6
|
+
prop :pad, description: "Padding level (1-4, each = 1rem)", values: [1, 2, 3, 4], optional: true
|
|
7
|
+
prop :centered, description: "Center content with flexbox", default: -> { false }
|
|
8
|
+
|
|
9
|
+
examples("Card", description: "Container for grouping related content") do |b|
|
|
10
|
+
b.example(:default, title: "Default") do |e|
|
|
11
|
+
e.preview
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
b.example(:variants, title: "Variants", description: "Available style variants") do |e|
|
|
15
|
+
e.preview variant: :default
|
|
16
|
+
e.preview variant: :ghost
|
|
17
|
+
e.preview variant: :elevated
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
b.example(:padding, title: "Padding", description: "Padding levels") do |e|
|
|
21
|
+
e.preview pad: 1
|
|
22
|
+
e.preview pad: 2
|
|
23
|
+
e.preview pad: 3
|
|
24
|
+
e.preview pad: 4
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
b.example(:centered, title: "Centered", description: "Centered content") do |e|
|
|
28
|
+
e.preview centered: true
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def card_classes
|
|
33
|
+
[
|
|
34
|
+
class_for,
|
|
35
|
+
(class_for(:centered) if centered),
|
|
36
|
+
(class_for(variant) if variant != :default),
|
|
37
|
+
("pad-#{pad}" if pad),
|
|
38
|
+
css
|
|
39
|
+
].compact.join(" ")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* Card component styles */
|
|
2
|
+
.cp-card {
|
|
3
|
+
background-color: var(--ui-card-bg);
|
|
4
|
+
color: var(--ui-card-fg);
|
|
5
|
+
border: 1px solid var(--ui-card-border);
|
|
6
|
+
border-radius: var(--ui-card-radius);
|
|
7
|
+
box-shadow: var(--ui-card-shadow);
|
|
8
|
+
|
|
9
|
+
/* Layout */
|
|
10
|
+
&--centered {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Variants */
|
|
17
|
+
&--ghost {
|
|
18
|
+
border-color: transparent;
|
|
19
|
+
box-shadow: none;
|
|
20
|
+
background-color: transparent;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&--elevated {
|
|
24
|
+
box-shadow:
|
|
25
|
+
0 10px 15px -3px rgb(0 0 0 / 0.1),
|
|
26
|
+
0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div class="cp-conversation" data-controller="<%= controller_name %>">
|
|
2
|
+
<div class="cp-conversation__header">
|
|
3
|
+
<h2 class="cp-conversation__title"><%= title || "Chat" %></h2>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="cp-conversation__messages" data-<%= controller_name %>-target="messagesContainer" id="messages">
|
|
7
|
+
<% if messages.any? %>
|
|
8
|
+
<% messages.each do |message| %>
|
|
9
|
+
<%= message %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% else %>
|
|
12
|
+
<div class="cp-conversation__empty">
|
|
13
|
+
<div class="cp-conversation__empty-content">
|
|
14
|
+
<p class="cp-conversation__empty-title">Start a conversation</p>
|
|
15
|
+
<p class="cp-conversation__empty-subtitle">Ask me anything!</p>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<% end %>
|
|
19
|
+
|
|
20
|
+
<div id="streaming-indicator" class="cp-conversation__streaming-hidden">
|
|
21
|
+
<%= ui("conversation/streaming-indicator") %>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div data-<%= controller_name %>-target="scrollAnchor"></div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<%= user_message_box %>
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Aeros::Primitives::Conversation
|
|
2
|
+
class Component < ::Aeros::ApplicationViewComponent
|
|
3
|
+
prop :title, description: "Chat title", optional: true
|
|
4
|
+
prop :streaming, description: "Show streaming indicator", default: -> { false }
|
|
5
|
+
|
|
6
|
+
renders_many :messages, Message::Component
|
|
7
|
+
renders_one :user_message_box, UserMessageBox::Component
|
|
8
|
+
|
|
9
|
+
examples("Conversation", description: "Chat conversation interface") do |b|
|
|
10
|
+
b.example(:default, title: "Default") do |e|
|
|
11
|
+
e.preview title: "Chat"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["messagesContainer", "scrollAnchor"];
|
|
5
|
+
|
|
6
|
+
connect() {
|
|
7
|
+
console.log("conversation connected");
|
|
8
|
+
|
|
9
|
+
// Auto-scroll to bottom on initial load
|
|
10
|
+
this.scrollToBottom();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
scrollToBottom() {
|
|
14
|
+
if (this.hasScrollAnchorTarget) {
|
|
15
|
+
this.scrollAnchorTarget.scrollIntoView({ behavior: "smooth" });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<div class="cp-conversation-message <%= role_class %>">
|
|
2
|
+
<div class="cp-conversation-message__container">
|
|
3
|
+
<div class="cp-conversation-message__row">
|
|
4
|
+
<div class="cp-conversation-message__avatar-wrapper">
|
|
5
|
+
<div class="cp-conversation-message__avatar <%= avatar_class %>">
|
|
6
|
+
<%= avatar_label %>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="cp-conversation-message__content">
|
|
10
|
+
<div class="cp-conversation-message__role">
|
|
11
|
+
<%= role_label %>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="cp-conversation-message__body">
|
|
14
|
+
<%= content %>
|
|
15
|
+
</div>
|
|
16
|
+
<% if timestamp %>
|
|
17
|
+
<div class="cp-conversation-message__timestamp">
|
|
18
|
+
<%= timestamp %>
|
|
19
|
+
</div>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Aeros::Primitives::Conversation::Message
|
|
2
|
+
class Component < ::Aeros::ApplicationViewComponent
|
|
3
|
+
prop :role, description: "Message role", values: [:user, :assistant, :error, :system], default: -> { "assistant" }
|
|
4
|
+
prop :content, description: "Message content", default: -> { "" }
|
|
5
|
+
prop :timestamp, description: "Message timestamp", optional: true
|
|
6
|
+
|
|
7
|
+
def role_class
|
|
8
|
+
"cp-conversation-message--#{role}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def avatar_class
|
|
12
|
+
"cp-conversation-message__avatar--#{role}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def role_label
|
|
16
|
+
case role.to_s
|
|
17
|
+
when "user" then "You"
|
|
18
|
+
when "assistant" then "Assistant"
|
|
19
|
+
when "error" then "Error"
|
|
20
|
+
when "system" then "System"
|
|
21
|
+
else role.to_s.capitalize
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def avatar_label
|
|
26
|
+
case role.to_s
|
|
27
|
+
when "user" then "U"
|
|
28
|
+
when "assistant" then "A"
|
|
29
|
+
when "error" then "!"
|
|
30
|
+
when "system" then "S"
|
|
31
|
+
else "?"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<div class="cp-conversation-streaming">
|
|
2
|
+
<div class="cp-conversation-streaming__container">
|
|
3
|
+
<div class="cp-conversation-streaming__row">
|
|
4
|
+
<div class="cp-conversation-streaming__avatar-wrapper">
|
|
5
|
+
<div class="cp-conversation-streaming__avatar <%= avatar_class %>">
|
|
6
|
+
<%= avatar_label %>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="cp-conversation-streaming__content">
|
|
10
|
+
<div class="cp-conversation-streaming__label">
|
|
11
|
+
<%= label %>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="cp-conversation-streaming__dots">
|
|
14
|
+
<div class="cp-conversation-streaming__dot" style="animation-delay: 0ms;"></div>
|
|
15
|
+
<div class="cp-conversation-streaming__dot" style="animation-delay: 150ms;"></div>
|
|
16
|
+
<div class="cp-conversation-streaming__dot" style="animation-delay: 300ms;"></div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Aeros::Primitives::Conversation::StreamingIndicator
|
|
2
|
+
class Component < ::Aeros::ApplicationViewComponent
|
|
3
|
+
prop :role, description: "Role being streamed", default: -> { "assistant" }
|
|
4
|
+
prop :label, description: "Label text", default: -> { "Assistant" }
|
|
5
|
+
|
|
6
|
+
def avatar_class
|
|
7
|
+
"cp-conversation-streaming__avatar--#{role}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def avatar_label
|
|
11
|
+
case role.to_s
|
|
12
|
+
when "user" then "U"
|
|
13
|
+
when "assistant" then "A"
|
|
14
|
+
else role.to_s[0].upcase
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|