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,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aeros::Primitives::InputTextAreaAi
4
+ class Component < ::Aeros::FormBuilder::BaseComponent
5
+ prop :rows, description: "Number of visible rows", default: -> { 6 }
6
+ prop :ai_url, description: "AI endpoint URL"
7
+ prop :ai_prompt, description: "AI prompt template", optional: true
8
+ prop :system_prompts, description: "System prompts array", default: -> { [] }
9
+ prop :model, description: "AI model to use", optional: true
10
+ prop :ai_button_label, description: "AI button label", default: -> { "AI Assist" }
11
+ prop :ai_button_position, description: "AI button position", default: -> { :bottom_right }
12
+
13
+ examples("Input Text Area AI", description: "Text area with AI assistance") do |b|
14
+ b.example(:default, title: "Default") do |e|
15
+ e.preview name: "content", ai_url: "/api/ai/complete"
16
+ end
17
+
18
+ b.example(:custom_label, title: "Custom Button") do |e|
19
+ e.preview name: "description", ai_url: "/api/ai", ai_button_label: "Generate"
20
+ end
21
+ end
22
+
23
+ def stimulus_values
24
+ values = {
25
+ "#{controller_name}-ai-url-value" => ai_url,
26
+ "#{controller_name}-ai-button-label-value" => ai_button_label
27
+ }
28
+ values["#{controller_name}-ai-prompt-value"] = ai_prompt if ai_prompt.present?
29
+ values["#{controller_name}-system-prompts-value"] = system_prompts.to_json if system_prompts.any?
30
+ values["#{controller_name}-model-value"] = model if model.present?
31
+ values
32
+ end
33
+
34
+ def button_position_classes
35
+ case ai_button_position.to_sym
36
+ when :top_right
37
+ "top-2 right-2"
38
+ when :top_left
39
+ "top-2 left-2"
40
+ when :bottom_left
41
+ "bottom-2 left-2"
42
+ else
43
+ "bottom-2 right-2"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,198 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = [
5
+ "textarea",
6
+ "aiButton",
7
+ "buttonIcon",
8
+ "buttonSpinner",
9
+ "buttonLabel",
10
+ "streamingIndicator"
11
+ ];
12
+
13
+ static values = {
14
+ aiUrl: String,
15
+ aiPrompt: String,
16
+ systemPrompts: { type: Array, default: [] },
17
+ model: String,
18
+ aiButtonLabel: { type: String, default: "AI Assist" },
19
+ generating: { type: Boolean, default: false }
20
+ };
21
+
22
+ connect() {
23
+ this.abortController = null;
24
+ }
25
+
26
+ disconnect() {
27
+ this.cancelGeneration();
28
+ }
29
+
30
+ async generateAi() {
31
+ if (this.generatingValue) {
32
+ this.cancelGeneration();
33
+ return;
34
+ }
35
+
36
+ const currentText = this.textareaTarget.value;
37
+
38
+ const prompt = this.aiPromptValue
39
+ ? this.aiPromptValue.replace("{{content}}", currentText)
40
+ : currentText;
41
+
42
+ if (!prompt.trim()) {
43
+ this.textareaTarget.focus();
44
+ return;
45
+ }
46
+
47
+ this.startGenerating();
48
+
49
+ try {
50
+ this.abortController = new AbortController();
51
+
52
+ const body = {
53
+ prompt: prompt
54
+ };
55
+
56
+ if (this.systemPromptsValue.length > 0) {
57
+ body.system_prompts = this.systemPromptsValue;
58
+ }
59
+
60
+ if (this.modelValue) {
61
+ body.model = this.modelValue;
62
+ }
63
+
64
+ const response = await fetch(this.aiUrlValue, {
65
+ method: "POST",
66
+ headers: {
67
+ "Content-Type": "application/json",
68
+ "Accept": "text/event-stream",
69
+ "X-CSRF-Token": this.csrfToken()
70
+ },
71
+ body: JSON.stringify(body),
72
+ signal: this.abortController.signal
73
+ });
74
+
75
+ if (!response.ok) {
76
+ throw new Error(`HTTP error! status: ${response.status}`);
77
+ }
78
+
79
+ const contentType = response.headers.get("content-type");
80
+
81
+ if (contentType?.includes("text/event-stream") || contentType?.includes("application/x-ndjson")) {
82
+ await this.handleStreamingResponse(response);
83
+ } else {
84
+ await this.handleJsonResponse(response);
85
+ }
86
+
87
+ } catch (error) {
88
+ if (error.name === "AbortError") {
89
+ console.log("Generation cancelled");
90
+ } else {
91
+ console.error("AI generation failed:", error);
92
+ this.dispatch("error", { detail: { message: error.message } });
93
+ }
94
+ } finally {
95
+ this.stopGenerating();
96
+ }
97
+ }
98
+
99
+ async handleStreamingResponse(response) {
100
+ const reader = response.body.getReader();
101
+ const decoder = new TextDecoder();
102
+
103
+ const originalContent = this.textareaTarget.value;
104
+ const separator = originalContent.trim() ? "\n\n" : "";
105
+ let generatedContent = "";
106
+
107
+ try {
108
+ while (true) {
109
+ const { done, value } = await reader.read();
110
+ if (done) break;
111
+
112
+ const chunk = decoder.decode(value, { stream: true });
113
+ const lines = chunk.split("\n");
114
+
115
+ for (const line of lines) {
116
+ if (line.startsWith("data: ")) {
117
+ const data = line.slice(6);
118
+
119
+ if (data === "[DONE]") {
120
+ continue;
121
+ }
122
+
123
+ try {
124
+ const parsed = JSON.parse(data);
125
+ const content = parsed.content || parsed.text || parsed.delta?.content || "";
126
+
127
+ if (content) {
128
+ generatedContent += content;
129
+ this.textareaTarget.value = originalContent + separator + generatedContent;
130
+ this.scrollTextareaToBottom();
131
+ }
132
+ } catch {
133
+ if (data.trim() && data !== "[DONE]") {
134
+ generatedContent += data;
135
+ this.textareaTarget.value = originalContent + separator + generatedContent;
136
+ this.scrollTextareaToBottom();
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+ } catch (error) {
143
+ if (error.name !== "AbortError") {
144
+ throw error;
145
+ }
146
+ }
147
+
148
+ this.dispatch("complete", { detail: { content: generatedContent } });
149
+ }
150
+
151
+ async handleJsonResponse(response) {
152
+ const data = await response.json();
153
+ const content = data.content || data.text || data.response || "";
154
+
155
+ if (content) {
156
+ const originalContent = this.textareaTarget.value;
157
+ const separator = originalContent.trim() ? "\n\n" : "";
158
+ this.textareaTarget.value = originalContent + separator + content;
159
+ this.dispatch("complete", { detail: { content } });
160
+ }
161
+ }
162
+
163
+ cancelGeneration() {
164
+ if (this.abortController) {
165
+ this.abortController.abort();
166
+ this.abortController = null;
167
+ }
168
+ }
169
+
170
+ startGenerating() {
171
+ this.generatingValue = true;
172
+ this.aiButtonTarget.disabled = false;
173
+ this.buttonIconTarget.classList.add("hidden");
174
+ this.buttonSpinnerTarget.classList.remove("hidden");
175
+ this.buttonLabelTarget.textContent = "Cancel";
176
+ this.streamingIndicatorTarget.classList.remove("hidden");
177
+ this.textareaTarget.readOnly = true;
178
+ }
179
+
180
+ stopGenerating() {
181
+ this.generatingValue = false;
182
+ this.aiButtonTarget.disabled = false;
183
+ this.buttonIconTarget.classList.remove("hidden");
184
+ this.buttonSpinnerTarget.classList.add("hidden");
185
+ this.buttonLabelTarget.textContent = this.aiButtonLabelValue;
186
+ this.streamingIndicatorTarget.classList.add("hidden");
187
+ this.textareaTarget.readOnly = false;
188
+ this.abortController = null;
189
+ }
190
+
191
+ scrollTextareaToBottom() {
192
+ this.textareaTarget.scrollTop = this.textareaTarget.scrollHeight;
193
+ }
194
+
195
+ csrfToken() {
196
+ return document.querySelector('meta[name="csrf-token"]')?.content || "";
197
+ }
198
+ }
@@ -0,0 +1,91 @@
1
+ /* Input Text Area AI */
2
+ .cp-input-text-area-ai {
3
+ }
4
+
5
+ .cp-input-text-area-ai__container {
6
+ position: relative;
7
+ }
8
+
9
+ .cp-input-text-area-ai__textarea {
10
+ display: block;
11
+ width: 100%;
12
+ padding: 0.5rem 0.75rem;
13
+ padding-right: 6rem;
14
+ font-size: 0.875rem;
15
+ color: var(--ui-input-fg);
16
+ background: var(--ui-input-bg);
17
+ border: 1px solid var(--ui-input-border);
18
+ border-radius: var(--ui-input-radius, 0.375rem);
19
+ outline: none;
20
+ resize: vertical;
21
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
22
+ }
23
+
24
+ .cp-input-text-area-ai__textarea::placeholder {
25
+ color: var(--ui-input-placeholder);
26
+ }
27
+
28
+ .cp-input-text-area-ai__textarea:focus {
29
+ border-color: var(--ui-input-border-focus);
30
+ box-shadow: 0 0 0 1px var(--ui-input-ring);
31
+ }
32
+
33
+ .cp-input-text-area-ai__textarea:disabled {
34
+ opacity: 0.5;
35
+ cursor: not-allowed;
36
+ }
37
+
38
+ .cp-input-text-area-ai__actions {
39
+ position: absolute;
40
+ bottom: 0.5rem;
41
+ right: 0.5rem;
42
+ }
43
+
44
+ .cp-input-text-area-ai__ai-btn {
45
+ display: inline-flex;
46
+ align-items: center;
47
+ gap: 0.375rem;
48
+ padding: 0.375rem 0.75rem;
49
+ font-size: 0.75rem;
50
+ font-weight: 500;
51
+ color: var(--ui-primary);
52
+ background: var(--ui-accent);
53
+ border: none;
54
+ border-radius: var(--ui-radius-sm);
55
+ cursor: pointer;
56
+ transition: background-color 0.15s ease;
57
+ }
58
+
59
+ .cp-input-text-area-ai__ai-btn:hover {
60
+ background: var(--ui-accent-foreground);
61
+ color: var(--ui-accent);
62
+ }
63
+
64
+ .cp-input-text-area-ai__ai-btn:disabled {
65
+ opacity: 0.5;
66
+ cursor: not-allowed;
67
+ }
68
+
69
+ .cp-input-text-area-ai__ai-icon {
70
+ width: 1rem;
71
+ height: 1rem;
72
+ }
73
+
74
+ .cp-input-text-area-ai__spinner--hidden {
75
+ display: none;
76
+ }
77
+
78
+ .cp-input-text-area-ai__streaming {
79
+ position: absolute;
80
+ bottom: 0.5rem;
81
+ left: 0.5rem;
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 0.25rem;
85
+ font-size: 0.75rem;
86
+ color: var(--ui-muted-foreground);
87
+ }
88
+
89
+ .cp-input-text-area-ai__streaming--hidden {
90
+ display: none;
91
+ }
@@ -0,0 +1,20 @@
1
+ <div class="cp-input-wrapper <%= 'cp-input-wrapper--disabled' if disabled %>">
2
+ <% if label %>
3
+ <label for="<%= id || name %>" class="cp-input-wrapper__label">
4
+ <%= label %>
5
+ <% if required %>
6
+ <span class="cp-input-wrapper__required">*</span>
7
+ <% end %>
8
+ </label>
9
+ <% end %>
10
+
11
+ <div class="cp-input-wrapper__field">
12
+ <%= content %>
13
+ </div>
14
+
15
+ <% if error_text %>
16
+ <p class="cp-input-wrapper__error"><%= error_text %></p>
17
+ <% elsif helper_text %>
18
+ <p class="cp-input-wrapper__helper"><%= helper_text %></p>
19
+ <% end %>
20
+ </div>
@@ -0,0 +1,31 @@
1
+ module Aeros::Primitives::InputWrapper
2
+ class Component < ::Aeros::ApplicationViewComponent
3
+ prop :label, description: "Input label", optional: true
4
+ prop :name, description: "Input name attribute"
5
+ prop :id, description: "Input id attribute", optional: true
6
+ prop :helper_text, description: "Helper text below input", optional: true
7
+ prop :error_text, description: "Error message", optional: true
8
+ prop :disabled, description: "Disabled state", default: -> { false }
9
+ prop :required, description: "Required field", default: -> { false }
10
+
11
+ option(:data, default: proc { {} })
12
+
13
+ examples("Input Wrapper", description: "Wrapper for form inputs with label and messages") do |b|
14
+ b.example(:default, title: "Default") do |e|
15
+ e.preview name: "field"
16
+ end
17
+
18
+ b.example(:with_label, title: "With Label") do |e|
19
+ e.preview name: "email", label: "Email Address"
20
+ end
21
+
22
+ b.example(:with_helper, title: "With Helper Text") do |e|
23
+ e.preview name: "username", label: "Username", helper_text: "Choose a unique username"
24
+ end
25
+
26
+ b.example(:with_error, title: "With Error") do |e|
27
+ e.preview name: "email", label: "Email", error_text: "Email is invalid"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,72 @@
1
+ /* Input Wrapper */
2
+ .cp-input-wrapper {
3
+ }
4
+
5
+ .cp-input-wrapper--disabled {
6
+ opacity: 0.5;
7
+ pointer-events: none;
8
+ }
9
+
10
+ .cp-input-wrapper__label {
11
+ display: block;
12
+ font-size: 0.875rem;
13
+ font-weight: 500;
14
+ color: var(--ui-foreground);
15
+ margin-bottom: 0.25rem;
16
+ }
17
+
18
+ .cp-input-wrapper__required {
19
+ color: var(--ui-destructive);
20
+ }
21
+
22
+ .cp-input-wrapper__field {
23
+ position: relative;
24
+ }
25
+
26
+ .cp-input-wrapper__helper {
27
+ margin-top: 0.25rem;
28
+ font-size: 0.875rem;
29
+ color: var(--ui-muted-foreground);
30
+ }
31
+
32
+ .cp-input-wrapper__error {
33
+ margin-top: 0.25rem;
34
+ font-size: 0.875rem;
35
+ color: var(--ui-destructive);
36
+ }
37
+
38
+ /* Shared input styles */
39
+ .cp-input {
40
+ display: block;
41
+ width: 100%;
42
+ padding: 0.5rem 0.75rem;
43
+ font-size: 0.875rem;
44
+ color: var(--ui-input-fg);
45
+ background: var(--ui-input-bg);
46
+ border: 1px solid var(--ui-input-border);
47
+ border-radius: var(--ui-input-radius, 0.375rem);
48
+ outline: none;
49
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
50
+ }
51
+
52
+ .cp-input::placeholder {
53
+ color: var(--ui-input-placeholder);
54
+ }
55
+
56
+ .cp-input:focus {
57
+ border-color: var(--ui-input-border-focus);
58
+ box-shadow: 0 0 0 1px var(--ui-input-ring);
59
+ }
60
+
61
+ .cp-input:disabled {
62
+ opacity: 0.5;
63
+ cursor: not-allowed;
64
+ }
65
+
66
+ .cp-input--error {
67
+ border-color: var(--ui-destructive);
68
+ }
69
+
70
+ .cp-input--error:focus {
71
+ box-shadow: 0 0 0 1px var(--ui-destructive);
72
+ }
@@ -0,0 +1,4 @@
1
+ <div class="<%= default_styles %>">
2
+ <div class="h-screen min-w-0 overflow-hidden"><%= sidebar %></div>
3
+ <div class="h-screen min-w-0"><%= content %>
4
+ </div>
@@ -0,0 +1,23 @@
1
+ module Aeros::Primitives::Layouts::Agentic
2
+ class Component < Aeros::ApplicationViewComponent
3
+ renders_one :sidebar, Aeros::Primitives::Sidebar::Component
4
+
5
+ style do
6
+ base do
7
+ [
8
+ "fixed",
9
+ "top-0",
10
+ "left-0",
11
+ "right-0",
12
+ "bottom-0",
13
+ "w-screen",
14
+ "h-screen",
15
+ "grid",
16
+ "grid-cols-[1fr_5fr]",
17
+ "divide-x",
18
+ "divide-slate-200"
19
+ ]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module Aeros::Primitives::Layouts::App
2
+ class Aside < Aeros::ApplicationViewComponent
3
+ erb_template <<~ERB
4
+ <aside class="cp-layout-app__aside" style="<%= merged_style %>">
5
+ <%= content %>
6
+ </aside>
7
+ ERB
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ <div class="cp-layout-app<%= " cp-layout-app--with-aside" if aside? %>">
2
+ <%= sidebar %>
3
+ <main class="cp-layout-app__main">
4
+ <% if header? %>
5
+ <header class="cp-layout-app__header">
6
+ <%= header %>
7
+ </header>
8
+ <% end %>
9
+ <div class="cp-layout-app__body">
10
+ <%= content %>
11
+ </div>
12
+ </main>
13
+ <%= aside if aside? %>
14
+ </div>
@@ -0,0 +1,11 @@
1
+ module Aeros::Primitives::Layouts::App
2
+ class Component < Aeros::ApplicationViewComponent
3
+ renders_one :sidebar, ->(style: nil, &block) {
4
+ Aeros::Primitives::Layouts::App::Sidebar.new(style: style, &block)
5
+ }
6
+ renders_one :header
7
+ renders_one :aside, ->(style: nil, &block) {
8
+ Aeros::Primitives::Layouts::App::Aside.new(style: style, &block)
9
+ }
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Aeros::Primitives::Layouts::App
2
+ class Sidebar < Aeros::ApplicationViewComponent
3
+ erb_template <<~ERB
4
+ <aside class="cp-layout-app__sidebar" style="<%= merged_style %>">
5
+ <%= content %>
6
+ </aside>
7
+ ERB
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ /* App Layout */
2
+ .cp-layout-app {
3
+ position: fixed;
4
+ inset: 0;
5
+ display: grid;
6
+ grid-template-columns: auto 1fr;
7
+ width: 100vw;
8
+ height: 100vh;
9
+
10
+ &--with-aside {
11
+ grid-template-columns: auto 1fr auto;
12
+ }
13
+
14
+ &__sidebar {
15
+ height: 100vh;
16
+ flex-shrink: 0;
17
+ overflow: hidden;
18
+ }
19
+
20
+ &__main {
21
+ display: flex;
22
+ flex-direction: column;
23
+ height: 100vh;
24
+ min-width: 0;
25
+ overflow: hidden;
26
+ }
27
+
28
+ &__header {
29
+ flex-shrink: 0;
30
+ border-bottom: 1px solid var(--ui-sidebar-border, #e5e5e5);
31
+ padding: 1rem 2rem;
32
+ }
33
+
34
+ &__body {
35
+ flex: 1;
36
+ overflow-y: auto;
37
+ padding: 2rem;
38
+ }
39
+
40
+ &__aside {
41
+ height: 100vh;
42
+ flex-shrink: 0;
43
+ overflow: hidden;
44
+ border-left: 1px solid var(--ui-sidebar-border, #e5e5e5);
45
+ }
46
+ }
@@ -0,0 +1,24 @@
1
+ <div class="cp-page">
2
+ <div class="cp-page__header">
3
+ <div class="cp-page__title-area">
4
+ <h1 class="cp-page__title">
5
+ <span class="cp-page__title-text"><%= title %></span>
6
+ <% if subtitle %>
7
+ <span class="cp-page__subtitle"><%= subtitle %></span>
8
+ <% end %>
9
+ </h1>
10
+ <% if description %>
11
+ <p class="cp-page__description"><%= description %></p>
12
+ <% end %>
13
+ </div>
14
+ <% if actions_area %>
15
+ <div class="cp-page__actions">
16
+ <%= actions_area %>
17
+ </div>
18
+ <% end %>
19
+ </div>
20
+
21
+ <div class="cp-page__content">
22
+ <%= content %>
23
+ </div>
24
+ </div>
@@ -0,0 +1,23 @@
1
+ module Aeros::Primitives::Page
2
+ class Component < Aeros::ApplicationViewComponent
3
+ prop :title, description: "Page title"
4
+ prop :subtitle, description: "Optional subtitle", optional: true
5
+ prop :description, description: "Page description", optional: true
6
+
7
+ renders_one(:actions_area)
8
+
9
+ examples("Page", description: "Page layout with title and content") do |b|
10
+ b.example(:default, title: "Default") do |e|
11
+ e.preview title: "Dashboard"
12
+ end
13
+
14
+ b.example(:with_subtitle, title: "With Subtitle") do |e|
15
+ e.preview title: "Users", subtitle: "(123)"
16
+ end
17
+
18
+ b.example(:with_description, title: "With Description") do |e|
19
+ e.preview title: "Settings", description: "Manage your account settings"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,55 @@
1
+ /* Page */
2
+ .cp-page {
3
+ display: flex;
4
+ flex-direction: column;
5
+ width: 100%;
6
+ }
7
+
8
+ .cp-page__header {
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: space-between;
12
+ padding: var(--ui-spacing-lg) 0;
13
+ gap: var(--ui-spacing-lg);
14
+ }
15
+
16
+ .cp-page__title-area {
17
+ flex: 1;
18
+ min-width: 0;
19
+ }
20
+
21
+ .cp-page__title {
22
+ display: flex;
23
+ align-items: center;
24
+ gap: var(--ui-spacing-sm);
25
+ font-weight: 600;
26
+ font-size: 1.875rem;
27
+ letter-spacing: -0.025em;
28
+ color: var(--ui-foreground);
29
+ }
30
+
31
+ .cp-page__title-text {
32
+ overflow: hidden;
33
+ text-overflow: ellipsis;
34
+ white-space: nowrap;
35
+ min-width: 0;
36
+ }
37
+
38
+ .cp-page__subtitle {
39
+ color: var(--ui-muted-foreground);
40
+ flex-shrink: 0;
41
+ }
42
+
43
+ .cp-page__description {
44
+ margin-top: var(--ui-spacing-sm);
45
+ color: var(--ui-muted-foreground);
46
+ }
47
+
48
+ .cp-page__actions {
49
+ flex-shrink: 0;
50
+ }
51
+
52
+ .cp-page__content {
53
+ padding-bottom: var(--ui-spacing-lg);
54
+ width: 100%;
55
+ }