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.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/app/assets/stylesheets/aeros/application.css +1 -15
  4. data/app/assets/stylesheets/aeros/base.css +43 -0
  5. data/app/assets/stylesheets/aeros/reset.css +397 -0
  6. data/app/assets/stylesheets/aeros/source.css +15 -0
  7. data/app/assets/stylesheets/aeros/theme.css +6 -0
  8. data/app/assets/stylesheets/aeros/themes/slate.css +163 -0
  9. data/app/assets/stylesheets/aeros/themes/zinc.css +163 -0
  10. data/app/assets/stylesheets/aeros/utilities.css +23 -0
  11. data/app/components/aeros/application_view_component.rb +149 -5
  12. data/app/components/aeros/blocks/component_preview/component.html.erb +7 -0
  13. data/app/components/aeros/blocks/component_preview/component.rb +10 -0
  14. data/app/components/aeros/blocks/component_preview/styles.css +10 -0
  15. data/app/components/aeros/form_builder.rb +24 -8
  16. data/app/components/aeros/pages/showcase/index/component.html.erb +53 -0
  17. data/app/components/aeros/pages/showcase/index/component.rb +7 -0
  18. data/app/components/aeros/pages/showcase/index/styles.css +27 -0
  19. data/app/components/aeros/pages/showcase/placeholder/component.html.erb +7 -0
  20. data/app/components/aeros/pages/showcase/placeholder/component.rb +10 -0
  21. data/app/components/aeros/pages/showcase/show/component.html.erb +38 -0
  22. data/app/components/aeros/pages/showcase/show/component.rb +48 -0
  23. data/app/components/aeros/primitives/button/component.rb +66 -0
  24. data/app/components/aeros/primitives/button/styles.css +153 -0
  25. data/app/components/aeros/primitives/card/component.html.erb +3 -0
  26. data/app/components/aeros/primitives/card/component.rb +42 -0
  27. data/app/components/aeros/primitives/card/styles.css +28 -0
  28. data/app/components/aeros/primitives/conversation/component.html.erb +28 -0
  29. data/app/components/aeros/primitives/conversation/component.rb +15 -0
  30. data/app/components/aeros/primitives/conversation/controller.js +18 -0
  31. data/app/components/aeros/primitives/conversation/message/component.html.erb +24 -0
  32. data/app/components/aeros/primitives/conversation/message/component.rb +35 -0
  33. data/app/components/aeros/primitives/conversation/streaming_indicator/component.html.erb +21 -0
  34. data/app/components/aeros/primitives/conversation/streaming_indicator/component.rb +18 -0
  35. data/app/components/aeros/primitives/conversation/styles.css +221 -0
  36. data/app/components/aeros/primitives/conversation/user_message_box/component.html.erb +1 -0
  37. data/app/components/aeros/{empty → primitives/conversation/user_message_box}/component.rb +1 -2
  38. data/app/components/aeros/primitives/drawer/component.html.erb +43 -0
  39. data/app/components/aeros/primitives/drawer/component.rb +33 -0
  40. data/app/components/aeros/primitives/drawer/controller.js +104 -0
  41. data/app/components/aeros/primitives/drawer/styles.css +90 -0
  42. data/app/components/aeros/primitives/dropdown/checkbox.rb +22 -0
  43. data/app/components/aeros/primitives/dropdown/component.html.erb +38 -0
  44. data/app/components/aeros/primitives/dropdown/component.rb +53 -0
  45. data/app/components/aeros/primitives/dropdown/controller.js +153 -0
  46. data/app/components/aeros/primitives/dropdown/item.rb +29 -0
  47. data/app/components/aeros/primitives/dropdown/label.rb +7 -0
  48. data/app/components/aeros/primitives/dropdown/radio_group.rb +16 -0
  49. data/app/components/aeros/primitives/dropdown/radio_item.rb +24 -0
  50. data/app/components/aeros/primitives/dropdown/separator.rb +7 -0
  51. data/app/components/aeros/primitives/dropdown/styles.css +155 -0
  52. data/app/components/aeros/primitives/empty/component.html.erb +15 -0
  53. data/app/components/aeros/primitives/empty/component.rb +18 -0
  54. data/app/components/aeros/primitives/empty/styles.css +40 -0
  55. data/app/components/aeros/primitives/input_attachments/component.html.erb +60 -0
  56. data/app/components/aeros/primitives/input_attachments/component.rb +52 -0
  57. data/app/components/aeros/primitives/input_attachments/controller.js +357 -0
  58. data/app/components/aeros/primitives/input_attachments/styles.css +102 -0
  59. data/app/components/aeros/primitives/input_color/component.html.erb +24 -0
  60. data/app/components/aeros/primitives/input_color/component.rb +42 -0
  61. data/app/components/aeros/primitives/input_color/styles.css +64 -0
  62. data/app/components/aeros/primitives/input_password/component.html.erb +43 -0
  63. data/app/components/aeros/primitives/input_password/component.rb +20 -0
  64. data/app/components/aeros/primitives/input_password/styles.css +61 -0
  65. data/app/components/aeros/{input_select → primitives/input_select}/component.html.erb +19 -19
  66. data/app/components/aeros/primitives/input_select/component.rb +21 -0
  67. data/app/components/aeros/primitives/input_select/option.rb +14 -0
  68. data/app/components/aeros/primitives/input_select/styles.css +30 -0
  69. data/app/components/aeros/primitives/input_slider/component.html.erb +33 -0
  70. data/app/components/aeros/primitives/input_slider/component.rb +35 -0
  71. data/app/components/aeros/primitives/input_slider/styles.css +74 -0
  72. data/app/components/aeros/primitives/input_tagging/component.html.erb +73 -0
  73. data/app/components/aeros/primitives/input_tagging/component.rb +40 -0
  74. data/app/components/aeros/primitives/input_tagging/controller.js +326 -0
  75. data/app/components/aeros/primitives/input_tagging/styles.css +148 -0
  76. data/app/components/aeros/primitives/input_text/component.html.erb +25 -0
  77. data/app/components/aeros/primitives/input_text/component.rb +20 -0
  78. data/app/components/aeros/primitives/input_text/styles.css +38 -0
  79. data/app/components/aeros/primitives/input_text_area/component.html.erb +23 -0
  80. data/app/components/aeros/primitives/input_text_area/component.rb +19 -0
  81. data/app/components/aeros/primitives/input_text_area/styles.css +30 -0
  82. data/app/components/aeros/primitives/input_text_area_ai/component.html.erb +51 -0
  83. data/app/components/aeros/primitives/input_text_area_ai/component.rb +47 -0
  84. data/app/components/aeros/primitives/input_text_area_ai/controller.js +198 -0
  85. data/app/components/aeros/primitives/input_text_area_ai/styles.css +91 -0
  86. data/app/components/aeros/primitives/input_wrapper/component.html.erb +20 -0
  87. data/app/components/aeros/primitives/input_wrapper/component.rb +31 -0
  88. data/app/components/aeros/primitives/input_wrapper/styles.css +72 -0
  89. data/app/components/aeros/primitives/layouts/agentic/component.html.erb +4 -0
  90. data/app/components/aeros/primitives/layouts/agentic/component.rb +23 -0
  91. data/app/components/aeros/primitives/layouts/app/aside.rb +9 -0
  92. data/app/components/aeros/primitives/layouts/app/component.html.erb +14 -0
  93. data/app/components/aeros/primitives/layouts/app/component.rb +11 -0
  94. data/app/components/aeros/primitives/layouts/app/sidebar.rb +9 -0
  95. data/app/components/aeros/primitives/layouts/app/styles.css +46 -0
  96. data/app/components/aeros/primitives/page/component.html.erb +24 -0
  97. data/app/components/aeros/primitives/page/component.rb +23 -0
  98. data/app/components/aeros/primitives/page/styles.css +55 -0
  99. data/app/components/aeros/primitives/sidebar/component.html.erb +25 -0
  100. data/app/components/aeros/primitives/sidebar/component.rb +14 -0
  101. data/app/components/aeros/primitives/sidebar/footer.rb +7 -0
  102. data/app/components/aeros/primitives/sidebar/group.rb +18 -0
  103. data/app/components/aeros/primitives/sidebar/header.rb +7 -0
  104. data/app/components/aeros/primitives/sidebar/item.rb +19 -0
  105. data/app/components/aeros/primitives/sidebar/styles.css +95 -0
  106. data/app/components/aeros/primitives/spinner/component.rb +36 -0
  107. data/app/components/aeros/primitives/spinner/styles.css +81 -0
  108. data/app/components/aeros/primitives/table/cell.rb +7 -0
  109. data/app/components/aeros/primitives/table/column.rb +7 -0
  110. data/app/components/aeros/primitives/table/component.html.erb +8 -0
  111. data/app/components/aeros/primitives/table/component.rb +14 -0
  112. data/app/components/aeros/primitives/table/header.rb +13 -0
  113. data/app/components/aeros/primitives/table/row.rb +11 -0
  114. data/app/components/aeros/primitives/table/styles.css +39 -0
  115. data/app/controllers/aeros/application_controller.rb +11 -0
  116. data/app/controllers/aeros/showcase_controller.rb +37 -1
  117. data/app/controllers/aeros/theme_controller.rb +10 -0
  118. data/app/helpers/aeros/application_helper.rb +19 -7
  119. data/app/views/layouts/aeros/application.html.erb +49 -14
  120. data/config/importmap.rb +6 -1
  121. data/config/routes.rb +2 -0
  122. data/lib/aeros/configuration.rb +56 -0
  123. data/lib/aeros/engine.rb +7 -1
  124. data/lib/aeros/theme.rb +326 -0
  125. data/lib/aeros/version.rb +1 -1
  126. data/lib/aeros.rb +2 -0
  127. data/lib/tasks/aeros_tasks.rake +25 -7
  128. metadata +127 -38
  129. data/app/assets/stylesheets/aeros/application.tailwind.css +0 -7
  130. data/app/assets/stylesheets/aeros/tailwind.css +0 -1100
  131. data/app/components/aeros/button/component.rb +0 -128
  132. data/app/components/aeros/card/component.html.erb +0 -3
  133. data/app/components/aeros/card/component.rb +0 -7
  134. data/app/components/aeros/dropdown/component.html.erb +0 -26
  135. data/app/components/aeros/dropdown/component.rb +0 -66
  136. data/app/components/aeros/empty/component.html.erb +0 -12
  137. data/app/components/aeros/input_password/component.html.erb +0 -43
  138. data/app/components/aeros/input_password/component.rb +0 -6
  139. data/app/components/aeros/input_select/component.rb +0 -24
  140. data/app/components/aeros/input_text/component.html.erb +0 -25
  141. data/app/components/aeros/input_text/component.rb +0 -5
  142. data/app/components/aeros/input_wrapper/component.html.erb +0 -20
  143. data/app/components/aeros/input_wrapper/component.rb +0 -12
  144. data/app/components/aeros/page/component.html.erb +0 -24
  145. data/app/components/aeros/page/component.rb +0 -9
  146. data/app/components/aeros/spinner/component.rb +0 -55
  147. data/app/components/aeros/table/component.html.erb +0 -10
  148. data/app/components/aeros/table/component.rb +0 -64
  149. data/app/views/aeros/showcase/index.html.erb +0 -1
  150. /data/app/components/aeros/{button → primitives/button}/controller.js +0 -0
  151. /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,7 @@
1
+ <div class="cg-showcase cg-showcase--not-ready">
2
+ <div class="cg-showcase__empty">
3
+ <%= lucide_icon("construction", class: "cg-showcase__empty-icon") %>
4
+ <h1><%= component_name %></h1>
5
+ <p class="text-muted">Documentation not ready yet.</p>
6
+ </div>
7
+ </div>
@@ -0,0 +1,10 @@
1
+ module Aeros::Pages::Showcase::Placeholder
2
+ class Component < ::Aeros::ApplicationViewComponent
3
+ option :namespace
4
+ option :id
5
+
6
+ def component_name
7
+ id.to_s.titleize
8
+ end
9
+ end
10
+ end
@@ -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,3 @@
1
+ <%= tag.div class: card_classes, style: merged_style do %>
2
+ <%= content %>
3
+ <% end %>
@@ -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