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,326 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = [
5
+ "search",
6
+ "dropdown",
7
+ "selectedContainer",
8
+ "hiddenInputs",
9
+ "optionsContainer",
10
+ "createOption",
11
+ "createLabel",
12
+ "empty",
13
+ "loading",
14
+ "tag"
15
+ ];
16
+
17
+ static values = {
18
+ tagsUrl: String,
19
+ createUrl: String,
20
+ allowCreate: { type: Boolean, default: true },
21
+ maxTags: Number,
22
+ selected: { type: Array, default: [] }
23
+ };
24
+
25
+ connect() {
26
+ this.closeDropdown = this.closeDropdown.bind(this);
27
+ this.availableTags = [];
28
+ this.highlightedIndex = -1;
29
+
30
+ if (this.tagsUrlValue) {
31
+ this.loadTags();
32
+ }
33
+ }
34
+
35
+ disconnect() {
36
+ document.removeEventListener("click", this.closeDropdown);
37
+ }
38
+
39
+ async loadTags() {
40
+ if (!this.tagsUrlValue) return;
41
+
42
+ this.showLoading();
43
+
44
+ try {
45
+ const response = await fetch(this.tagsUrlValue, {
46
+ headers: { "Accept": "application/json" }
47
+ });
48
+ const data = await response.json();
49
+ this.availableTags = Array.isArray(data) ? data : (data.tags || []);
50
+ } catch (error) {
51
+ console.error("Failed to load tags:", error);
52
+ this.availableTags = [];
53
+ }
54
+
55
+ this.hideLoading();
56
+ }
57
+
58
+ onFocus() {
59
+ this.openDropdown();
60
+ this.filterOptions();
61
+ }
62
+
63
+ onSearchInput() {
64
+ this.openDropdown();
65
+ this.filterOptions();
66
+ }
67
+
68
+ onKeydown(event) {
69
+ switch (event.key) {
70
+ case "ArrowDown":
71
+ event.preventDefault();
72
+ this.highlightNext();
73
+ break;
74
+ case "ArrowUp":
75
+ event.preventDefault();
76
+ this.highlightPrevious();
77
+ break;
78
+ case "Enter":
79
+ event.preventDefault();
80
+ this.selectHighlighted();
81
+ break;
82
+ case "Escape":
83
+ this.closeDropdown();
84
+ this.searchTarget.blur();
85
+ break;
86
+ case "Backspace":
87
+ if (this.searchTarget.value === "" && this.selectedValue.length > 0) {
88
+ this.removeLastTag();
89
+ }
90
+ break;
91
+ case "Tab":
92
+ this.closeDropdown();
93
+ break;
94
+ }
95
+ }
96
+
97
+ filterOptions() {
98
+ const query = this.searchTarget.value.toLowerCase().trim();
99
+ const filtered = this.availableTags.filter(tag => {
100
+ const tagName = typeof tag === "string" ? tag : tag.name;
101
+ const isNotSelected = !this.selectedValue.includes(tagName);
102
+ const matchesQuery = query === "" || tagName.toLowerCase().includes(query);
103
+ return isNotSelected && matchesQuery;
104
+ });
105
+
106
+ this.renderOptions(filtered);
107
+ this.updateCreateOption(query, filtered);
108
+ this.updateEmptyState(filtered, query);
109
+ this.highlightedIndex = -1;
110
+ }
111
+
112
+ renderOptions(tags) {
113
+ const html = tags.map((tag, index) => {
114
+ const tagName = typeof tag === "string" ? tag : tag.name;
115
+ return `
116
+ <button type="button"
117
+ class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 focus:bg-gray-100 focus:outline-none"
118
+ data-action="click->${this.identifier}#selectOption"
119
+ data-tag-name="${this.escapeHtml(tagName)}"
120
+ data-index="${index}">
121
+ ${this.escapeHtml(tagName)}
122
+ </button>
123
+ `;
124
+ }).join("");
125
+
126
+ this.optionsContainerTarget.innerHTML = html;
127
+ }
128
+
129
+ updateCreateOption(query, filteredTags) {
130
+ if (!this.allowCreateValue || !this.hasCreateOptionTarget) return;
131
+
132
+ const exactMatch = filteredTags.some(tag => {
133
+ const tagName = typeof tag === "string" ? tag : tag.name;
134
+ return tagName.toLowerCase() === query.toLowerCase();
135
+ });
136
+
137
+ const alreadySelected = this.selectedValue.some(
138
+ tag => tag.toLowerCase() === query.toLowerCase()
139
+ );
140
+
141
+ const showCreate = query.length > 0 && !exactMatch && !alreadySelected;
142
+
143
+ this.createOptionTarget.classList.toggle("hidden", !showCreate);
144
+ if (showCreate && this.hasCreateLabelTarget) {
145
+ this.createLabelTarget.textContent = query;
146
+ }
147
+ }
148
+
149
+ updateEmptyState(filtered, query) {
150
+ if (!this.hasEmptyTarget) return;
151
+
152
+ const hasCreateOption = this.allowCreateValue && query.length > 0;
153
+ const showEmpty = filtered.length === 0 && !hasCreateOption;
154
+ this.emptyTarget.classList.toggle("hidden", !showEmpty);
155
+ }
156
+
157
+ openDropdown() {
158
+ this.dropdownTarget.classList.remove("hidden");
159
+ document.addEventListener("click", this.closeDropdown);
160
+ }
161
+
162
+ closeDropdown(event) {
163
+ if (event && this.element.contains(event.target)) return;
164
+ this.dropdownTarget.classList.add("hidden");
165
+ document.removeEventListener("click", this.closeDropdown);
166
+ this.highlightedIndex = -1;
167
+ }
168
+
169
+ selectOption(event) {
170
+ const tagName = event.currentTarget.dataset.tagName;
171
+ this.addTag(tagName);
172
+ this.searchTarget.value = "";
173
+ this.filterOptions();
174
+ this.searchTarget.focus();
175
+ }
176
+
177
+ async createTag() {
178
+ const tagName = this.searchTarget.value.trim();
179
+ if (!tagName) return;
180
+
181
+ if (this.createUrlValue) {
182
+ try {
183
+ const response = await fetch(this.createUrlValue, {
184
+ method: "POST",
185
+ headers: {
186
+ "Content-Type": "application/json",
187
+ "Accept": "application/json",
188
+ "X-CSRF-Token": this.csrfToken()
189
+ },
190
+ body: JSON.stringify({ tag: { name: tagName } })
191
+ });
192
+
193
+ if (response.ok) {
194
+ const newTag = await response.json();
195
+ const newTagName = typeof newTag === "string" ? newTag : (newTag.name || tagName);
196
+ this.availableTags.push(newTagName);
197
+ }
198
+ } catch (error) {
199
+ console.error("Failed to create tag:", error);
200
+ }
201
+ }
202
+
203
+ this.addTag(tagName);
204
+ this.searchTarget.value = "";
205
+ this.filterOptions();
206
+ this.searchTarget.focus();
207
+ }
208
+
209
+ addTag(tagName) {
210
+ if (this.selectedValue.includes(tagName)) return;
211
+ if (this.maxTagsValue && this.selectedValue.length >= this.maxTagsValue) return;
212
+
213
+ this.selectedValue = [...this.selectedValue, tagName];
214
+ this.renderSelectedTags();
215
+ this.renderHiddenInputs();
216
+ this.dispatch("change", { detail: { tags: this.selectedValue } });
217
+ }
218
+
219
+ removeTag(event) {
220
+ const tagElement = event.currentTarget.closest("[data-tag-name]");
221
+ const tagName = tagElement.dataset.tagName;
222
+
223
+ this.selectedValue = this.selectedValue.filter(t => t !== tagName);
224
+ this.renderSelectedTags();
225
+ this.renderHiddenInputs();
226
+ this.filterOptions();
227
+ this.dispatch("change", { detail: { tags: this.selectedValue } });
228
+ }
229
+
230
+ removeLastTag() {
231
+ if (this.selectedValue.length === 0) return;
232
+ this.selectedValue = this.selectedValue.slice(0, -1);
233
+ this.renderSelectedTags();
234
+ this.renderHiddenInputs();
235
+ this.filterOptions();
236
+ this.dispatch("change", { detail: { tags: this.selectedValue } });
237
+ }
238
+
239
+ renderSelectedTags() {
240
+ const html = this.selectedValue.map(tag => `
241
+ <span class="inline-flex items-center gap-x-1 rounded-md bg-indigo-50 px-2 py-1 text-xs font-medium text-indigo-700 ring-1 ring-inset ring-indigo-700/10"
242
+ data-${this.identifier}-target="tag"
243
+ data-tag-name="${this.escapeHtml(tag)}">
244
+ ${this.escapeHtml(tag)}
245
+ <button type="button"
246
+ class="group relative -mr-1 h-3.5 w-3.5 rounded-sm hover:bg-indigo-600/20"
247
+ data-action="click->${this.identifier}#removeTag">
248
+ <svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
249
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
250
+ </svg>
251
+ </button>
252
+ </span>
253
+ `).join("");
254
+
255
+ this.selectedContainerTarget.innerHTML = html;
256
+ }
257
+
258
+ renderHiddenInputs() {
259
+ const baseName = this.element.querySelector("input[type='hidden']")?.name ||
260
+ this.searchTarget.name?.replace(/\[\]$/, "") + "[]";
261
+ const inputName = baseName.endsWith("[]") ? baseName : baseName + "[]";
262
+
263
+ const html = this.selectedValue.map(tag =>
264
+ `<input type="hidden" name="${inputName}" value="${this.escapeHtml(tag)}">`
265
+ ).join("");
266
+
267
+ this.hiddenInputsTarget.innerHTML = html;
268
+ }
269
+
270
+ highlightNext() {
271
+ const options = this.optionsContainerTarget.querySelectorAll("button");
272
+ if (options.length === 0) return;
273
+
274
+ this.highlightedIndex = Math.min(this.highlightedIndex + 1, options.length - 1);
275
+ this.updateHighlight(options);
276
+ }
277
+
278
+ highlightPrevious() {
279
+ const options = this.optionsContainerTarget.querySelectorAll("button");
280
+ if (options.length === 0) return;
281
+
282
+ this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
283
+ this.updateHighlight(options);
284
+ }
285
+
286
+ updateHighlight(options) {
287
+ options.forEach((opt, idx) => {
288
+ opt.classList.toggle("bg-gray-100", idx === this.highlightedIndex);
289
+ });
290
+
291
+ if (this.highlightedIndex >= 0 && options[this.highlightedIndex]) {
292
+ options[this.highlightedIndex].scrollIntoView({ block: "nearest" });
293
+ }
294
+ }
295
+
296
+ selectHighlighted() {
297
+ const options = this.optionsContainerTarget.querySelectorAll("button");
298
+ if (this.highlightedIndex >= 0 && this.highlightedIndex < options.length) {
299
+ options[this.highlightedIndex].click();
300
+ } else if (this.allowCreateValue && this.searchTarget.value.trim()) {
301
+ this.createTag();
302
+ }
303
+ }
304
+
305
+ showLoading() {
306
+ if (this.hasLoadingTarget) {
307
+ this.loadingTarget.classList.remove("hidden");
308
+ }
309
+ }
310
+
311
+ hideLoading() {
312
+ if (this.hasLoadingTarget) {
313
+ this.loadingTarget.classList.add("hidden");
314
+ }
315
+ }
316
+
317
+ escapeHtml(text) {
318
+ const div = document.createElement("div");
319
+ div.textContent = text;
320
+ return div.innerHTML;
321
+ }
322
+
323
+ csrfToken() {
324
+ return document.querySelector('meta[name="csrf-token"]')?.content || "";
325
+ }
326
+ }
@@ -0,0 +1,148 @@
1
+ /* Input Tagging */
2
+ .cp-input-tagging {
3
+ }
4
+
5
+ .cp-input-tagging__container {
6
+ position: relative;
7
+ }
8
+
9
+ .cp-input-tagging__tags {
10
+ display: flex;
11
+ flex-wrap: wrap;
12
+ gap: 0.375rem;
13
+ margin-bottom: 0.5rem;
14
+ min-height: 1.75rem;
15
+ }
16
+
17
+ .cp-input-tagging__tag {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ gap: 0.25rem;
21
+ padding: 0.125rem 0.5rem;
22
+ font-size: 0.75rem;
23
+ font-weight: 500;
24
+ background: var(--ui-accent);
25
+ color: var(--ui-accent-foreground);
26
+ border-radius: var(--ui-radius-sm);
27
+ }
28
+
29
+ .cp-input-tagging__tag-remove {
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ padding: 0;
34
+ margin-left: 0.125rem;
35
+ background: none;
36
+ border: none;
37
+ cursor: pointer;
38
+ color: inherit;
39
+ opacity: 0.7;
40
+ border-radius: 2px;
41
+ }
42
+
43
+ .cp-input-tagging__tag-remove:hover {
44
+ opacity: 1;
45
+ background: rgba(0, 0, 0, 0.1);
46
+ }
47
+
48
+ .cp-input-tagging__tag-icon {
49
+ width: 0.75rem;
50
+ height: 0.75rem;
51
+ }
52
+
53
+ .cp-input-tagging__search {
54
+ position: relative;
55
+ }
56
+
57
+ .cp-input-tagging__input {
58
+ display: block;
59
+ width: 100%;
60
+ padding: 0.5rem 0.75rem;
61
+ font-size: 0.875rem;
62
+ color: var(--ui-input-fg);
63
+ background: var(--ui-input-bg);
64
+ border: 1px solid var(--ui-input-border);
65
+ border-radius: var(--ui-input-radius, 0.375rem);
66
+ outline: none;
67
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
68
+ }
69
+
70
+ .cp-input-tagging__input:focus {
71
+ border-color: var(--ui-input-border-focus);
72
+ box-shadow: 0 0 0 1px var(--ui-input-ring);
73
+ }
74
+
75
+ .cp-input-tagging__dropdown {
76
+ position: absolute;
77
+ z-index: 10;
78
+ margin-top: 0.25rem;
79
+ width: 100%;
80
+ max-height: 15rem;
81
+ overflow: auto;
82
+ background: var(--ui-popover);
83
+ border: 1px solid var(--ui-border);
84
+ border-radius: var(--ui-radius);
85
+ box-shadow: var(--ui-shadow-lg);
86
+ }
87
+
88
+ .cp-input-tagging__dropdown--hidden {
89
+ display: none;
90
+ }
91
+
92
+ .cp-input-tagging__loading {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 0.5rem;
96
+ padding: 0.5rem 1rem;
97
+ font-size: 0.875rem;
98
+ color: var(--ui-muted-foreground);
99
+ }
100
+
101
+ .cp-input-tagging__loading--hidden {
102
+ display: none;
103
+ }
104
+
105
+ .cp-input-tagging__options {
106
+ padding: 0.25rem 0;
107
+ }
108
+
109
+ .cp-input-tagging__create {
110
+ border-top: 1px solid var(--ui-border);
111
+ }
112
+
113
+ .cp-input-tagging__create--hidden {
114
+ display: none;
115
+ }
116
+
117
+ .cp-input-tagging__create-btn {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 0.5rem;
121
+ width: 100%;
122
+ padding: 0.5rem 1rem;
123
+ font-size: 0.875rem;
124
+ text-align: left;
125
+ color: var(--ui-foreground);
126
+ background: none;
127
+ border: none;
128
+ cursor: pointer;
129
+ }
130
+
131
+ .cp-input-tagging__create-btn:hover {
132
+ background: var(--ui-accent);
133
+ }
134
+
135
+ .cp-input-tagging__create-icon {
136
+ width: 1rem;
137
+ height: 1rem;
138
+ }
139
+
140
+ .cp-input-tagging__empty {
141
+ padding: 0.5rem 1rem;
142
+ font-size: 0.875rem;
143
+ color: var(--ui-muted-foreground);
144
+ }
145
+
146
+ .cp-input-tagging__empty--hidden {
147
+ display: none;
148
+ }
@@ -0,0 +1,25 @@
1
+ <div class="cp-input-text">
2
+ <%= render Aeros::Primitives::InputWrapper::Component.new(
3
+ label: label,
4
+ helper_text: helper_text,
5
+ error_text: error_text,
6
+ name: name,
7
+ id: id,
8
+ disabled: disabled,
9
+ required: required) do %>
10
+ <input
11
+ type="<%= type || 'text' %>"
12
+ id="<%= id %>"
13
+ name="<%= name %>"
14
+ value="<%= value %>"
15
+ placeholder="<%= placeholder %>"
16
+ autocomplete="<%= autocomplete %>"
17
+ class="cp-input-text__input <%= 'cp-input-text__input--error' if error_text %>"
18
+ <%= 'disabled' if disabled %>
19
+ <%= 'required' if required %>
20
+ <% data.each do |key, val| %>
21
+ data-<%= key.to_s.dasherize %>="<%= val %>"
22
+ <% end %>
23
+ />
24
+ <% end %>
25
+ </div>
@@ -0,0 +1,20 @@
1
+ module Aeros::Primitives::InputText
2
+ class Component < ::Aeros::FormBuilder::BaseComponent
3
+ prop :autocomplete, description: "Autocomplete attribute", optional: true
4
+
5
+ examples("Input Text", description: "Single-line text input") do |b|
6
+ b.example(:default, title: "Default") do |e|
7
+ e.preview name: "username"
8
+ end
9
+
10
+ b.example(:with_label, title: "With Label") do |e|
11
+ e.preview name: "email", label: "Email", placeholder: "you@example.com"
12
+ end
13
+
14
+ b.example(:states, title: "States") do |e|
15
+ e.preview name: "disabled_field", label: "Disabled", disabled: true
16
+ e.preview name: "required_field", label: "Required", required: true
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,38 @@
1
+ /* Input Text */
2
+ .cp-input-text {
3
+ }
4
+
5
+ .cp-input-text__input {
6
+ display: block;
7
+ width: 100%;
8
+ padding: 0.5rem 0.75rem;
9
+ font-size: 0.875rem;
10
+ color: var(--ui-input-fg);
11
+ background: var(--ui-input-bg);
12
+ border: 1px solid var(--ui-input-border);
13
+ border-radius: var(--ui-input-radius, 0.375rem);
14
+ outline: none;
15
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
16
+ }
17
+
18
+ .cp-input-text__input::placeholder {
19
+ color: var(--ui-input-placeholder);
20
+ }
21
+
22
+ .cp-input-text__input:focus {
23
+ border-color: var(--ui-input-border-focus);
24
+ box-shadow: 0 0 0 1px var(--ui-input-ring);
25
+ }
26
+
27
+ .cp-input-text__input:disabled {
28
+ opacity: 0.5;
29
+ cursor: not-allowed;
30
+ }
31
+
32
+ .cp-input-text__input--error {
33
+ border-color: var(--ui-destructive);
34
+ }
35
+
36
+ .cp-input-text__input--error:focus {
37
+ box-shadow: 0 0 0 1px var(--ui-destructive);
38
+ }
@@ -0,0 +1,23 @@
1
+ <div class="cp-input-text-area">
2
+ <%= render Aeros::Primitives::InputWrapper::Component.new(
3
+ label: label,
4
+ helper_text: helper_text,
5
+ error_text: error_text,
6
+ name: name,
7
+ id: id,
8
+ disabled: disabled,
9
+ required: required) do %>
10
+ <textarea
11
+ id="<%= id %>"
12
+ name="<%= name %>"
13
+ placeholder="<%= placeholder %>"
14
+ rows="<%= rows %>"
15
+ class="cp-input-text-area__textarea"
16
+ <%= 'disabled' if disabled %>
17
+ <%= 'required' if required %>
18
+ <% data.each do |key, val| %>
19
+ data-<%= key.to_s.dasherize %>="<%= val %>"
20
+ <% end %>
21
+ ><%= value %></textarea>
22
+ <% end %>
23
+ </div>
@@ -0,0 +1,19 @@
1
+ module Aeros::Primitives::InputTextArea
2
+ class Component < ::Aeros::FormBuilder::BaseComponent
3
+ prop :rows, description: "Number of visible rows", default: -> { 4 }
4
+
5
+ examples("Input Text Area", description: "Multi-line text input") do |b|
6
+ b.example(:default, title: "Default") do |e|
7
+ e.preview name: "description"
8
+ end
9
+
10
+ b.example(:with_label, title: "With Label") do |e|
11
+ e.preview name: "bio", label: "Bio", placeholder: "Tell us about yourself"
12
+ end
13
+
14
+ b.example(:rows, title: "Custom Rows") do |e|
15
+ e.preview name: "content", rows: 8
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ /* Input Text Area */
2
+ .cp-input-text-area {
3
+ }
4
+
5
+ .cp-input-text-area__textarea {
6
+ display: block;
7
+ width: 100%;
8
+ padding: 0.5rem 0.75rem;
9
+ font-size: 0.875rem;
10
+ color: var(--ui-input-fg);
11
+ background: var(--ui-input-bg);
12
+ border: 1px solid var(--ui-input-border);
13
+ border-radius: var(--ui-input-radius, 0.375rem);
14
+ outline: none;
15
+ resize: vertical;
16
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
17
+ }
18
+
19
+ .cp-input-text-area__textarea::placeholder {
20
+ color: var(--ui-input-placeholder);
21
+ }
22
+
23
+ .cp-input-text-area__textarea:focus {
24
+ border-color: var(--ui-input-border-focus);
25
+ box-shadow: 0 0 0 1px var(--ui-input-ring);
26
+ }
27
+
28
+ .cp-input-text-area__textarea--error {
29
+ border-color: var(--ui-destructive);
30
+ }
@@ -0,0 +1,51 @@
1
+ <div class="cp-input-text-area-ai">
2
+ <%= render Aeros::Primitives::InputWrapper::Component.new(
3
+ label: label,
4
+ helper_text: helper_text,
5
+ error_text: error_text,
6
+ name: name,
7
+ id: id,
8
+ disabled: disabled,
9
+ required: required) do %>
10
+ <div class="cp-input-text-area-ai__container"
11
+ data-controller="<%= controller_name %>"
12
+ <%= stimulus_values.map { |k, v| "data-#{k.dasherize}=\"#{ERB::Util.html_escape(v)}\"" }.join(" ").html_safe %>>
13
+
14
+ <textarea
15
+ id="<%= id %>"
16
+ name="<%= name %>"
17
+ placeholder="<%= placeholder %>"
18
+ rows="<%= rows %>"
19
+ class="cp-input-text-area-ai__textarea"
20
+ data-<%= controller_name %>-target="textarea"
21
+ <%= 'disabled' if disabled %>
22
+ <%= 'required' if required %>
23
+ <% data.each do |key, val| %>
24
+ data-<%= key.to_s.dasherize %>="<%= val %>"
25
+ <% end %>
26
+ ><%= value %></textarea>
27
+
28
+ <div class="cp-input-text-area-ai__actions">
29
+ <button type="button"
30
+ class="cp-input-text-area-ai__ai-btn"
31
+ data-<%= controller_name %>-target="aiButton"
32
+ data-action="click-><%= controller_name %>#generateAi"
33
+ <%= 'disabled' if disabled %>>
34
+ <span data-<%= controller_name %>-target="buttonIcon">
35
+ <%= lucide_icon("sparkles", class: "cp-input-text-area-ai__ai-icon") %>
36
+ </span>
37
+ <span data-<%= controller_name %>-target="buttonSpinner" class="cp-input-text-area-ai__spinner--hidden">
38
+ <%= ui("spinner", size: :xs) %>
39
+ </span>
40
+ <span data-<%= controller_name %>-target="buttonLabel"><%= ai_button_label %></span>
41
+ </button>
42
+ </div>
43
+
44
+ <div class="cp-input-text-area-ai__streaming cp-input-text-area-ai__streaming--hidden"
45
+ data-<%= controller_name %>-target="streamingIndicator">
46
+ <%= ui("spinner", size: :xs) %>
47
+ <span>Generating...</span>
48
+ </div>
49
+ </div>
50
+ <% end %>
51
+ </div>