aeno 0.0.3

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 (140) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +230 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/stylesheets/aeno/application.css +1 -0
  6. data/app/assets/stylesheets/aeno/base.css +43 -0
  7. data/app/assets/stylesheets/aeno/reset.css +397 -0
  8. data/app/assets/stylesheets/aeno/source.css +15 -0
  9. data/app/assets/stylesheets/aeno/theme.css +6 -0
  10. data/app/assets/stylesheets/aeno/themes/slate.css +163 -0
  11. data/app/assets/stylesheets/aeno/themes/zinc.css +163 -0
  12. data/app/assets/stylesheets/aeno/utilities.css +23 -0
  13. data/app/components/aeno/application_view_component.rb +219 -0
  14. data/app/components/aeno/blocks/component_preview/component.html.erb +7 -0
  15. data/app/components/aeno/blocks/component_preview/component.rb +10 -0
  16. data/app/components/aeno/blocks/component_preview/styles.css +10 -0
  17. data/app/components/aeno/form_builder.rb +87 -0
  18. data/app/components/aeno/pages/showcase/index/component.html.erb +53 -0
  19. data/app/components/aeno/pages/showcase/index/component.rb +7 -0
  20. data/app/components/aeno/pages/showcase/index/styles.css +27 -0
  21. data/app/components/aeno/pages/showcase/placeholder/component.html.erb +7 -0
  22. data/app/components/aeno/pages/showcase/placeholder/component.rb +10 -0
  23. data/app/components/aeno/pages/showcase/show/component.html.erb +38 -0
  24. data/app/components/aeno/pages/showcase/show/component.rb +48 -0
  25. data/app/components/aeno/primitives/button/component.rb +66 -0
  26. data/app/components/aeno/primitives/button/controller.js +7 -0
  27. data/app/components/aeno/primitives/button/styles.css +153 -0
  28. data/app/components/aeno/primitives/card/component.html.erb +3 -0
  29. data/app/components/aeno/primitives/card/component.rb +42 -0
  30. data/app/components/aeno/primitives/card/styles.css +28 -0
  31. data/app/components/aeno/primitives/conversation/component.html.erb +28 -0
  32. data/app/components/aeno/primitives/conversation/component.rb +15 -0
  33. data/app/components/aeno/primitives/conversation/controller.js +18 -0
  34. data/app/components/aeno/primitives/conversation/message/component.html.erb +24 -0
  35. data/app/components/aeno/primitives/conversation/message/component.rb +35 -0
  36. data/app/components/aeno/primitives/conversation/streaming_indicator/component.html.erb +21 -0
  37. data/app/components/aeno/primitives/conversation/streaming_indicator/component.rb +18 -0
  38. data/app/components/aeno/primitives/conversation/styles.css +221 -0
  39. data/app/components/aeno/primitives/conversation/user_message_box/component.html.erb +1 -0
  40. data/app/components/aeno/primitives/conversation/user_message_box/component.rb +4 -0
  41. data/app/components/aeno/primitives/drawer/component.html.erb +43 -0
  42. data/app/components/aeno/primitives/drawer/component.rb +33 -0
  43. data/app/components/aeno/primitives/drawer/controller.js +104 -0
  44. data/app/components/aeno/primitives/drawer/styles.css +90 -0
  45. data/app/components/aeno/primitives/dropdown/checkbox.rb +22 -0
  46. data/app/components/aeno/primitives/dropdown/component.html.erb +38 -0
  47. data/app/components/aeno/primitives/dropdown/component.rb +53 -0
  48. data/app/components/aeno/primitives/dropdown/controller.js +153 -0
  49. data/app/components/aeno/primitives/dropdown/item.rb +29 -0
  50. data/app/components/aeno/primitives/dropdown/label.rb +7 -0
  51. data/app/components/aeno/primitives/dropdown/radio_group.rb +16 -0
  52. data/app/components/aeno/primitives/dropdown/radio_item.rb +24 -0
  53. data/app/components/aeno/primitives/dropdown/separator.rb +7 -0
  54. data/app/components/aeno/primitives/dropdown/styles.css +155 -0
  55. data/app/components/aeno/primitives/empty/component.html.erb +15 -0
  56. data/app/components/aeno/primitives/empty/component.rb +18 -0
  57. data/app/components/aeno/primitives/empty/styles.css +40 -0
  58. data/app/components/aeno/primitives/input_attachments/component.html.erb +60 -0
  59. data/app/components/aeno/primitives/input_attachments/component.rb +52 -0
  60. data/app/components/aeno/primitives/input_attachments/controller.js +357 -0
  61. data/app/components/aeno/primitives/input_attachments/styles.css +102 -0
  62. data/app/components/aeno/primitives/input_color/component.html.erb +24 -0
  63. data/app/components/aeno/primitives/input_color/component.rb +42 -0
  64. data/app/components/aeno/primitives/input_color/styles.css +64 -0
  65. data/app/components/aeno/primitives/input_password/component.html.erb +43 -0
  66. data/app/components/aeno/primitives/input_password/component.rb +20 -0
  67. data/app/components/aeno/primitives/input_password/controller.js +17 -0
  68. data/app/components/aeno/primitives/input_password/styles.css +61 -0
  69. data/app/components/aeno/primitives/input_select/component.html.erb +43 -0
  70. data/app/components/aeno/primitives/input_select/component.rb +21 -0
  71. data/app/components/aeno/primitives/input_select/option.rb +14 -0
  72. data/app/components/aeno/primitives/input_select/styles.css +30 -0
  73. data/app/components/aeno/primitives/input_slider/component.html.erb +33 -0
  74. data/app/components/aeno/primitives/input_slider/component.rb +35 -0
  75. data/app/components/aeno/primitives/input_slider/styles.css +74 -0
  76. data/app/components/aeno/primitives/input_tagging/component.html.erb +73 -0
  77. data/app/components/aeno/primitives/input_tagging/component.rb +40 -0
  78. data/app/components/aeno/primitives/input_tagging/controller.js +326 -0
  79. data/app/components/aeno/primitives/input_tagging/styles.css +148 -0
  80. data/app/components/aeno/primitives/input_text/component.html.erb +25 -0
  81. data/app/components/aeno/primitives/input_text/component.rb +20 -0
  82. data/app/components/aeno/primitives/input_text/styles.css +38 -0
  83. data/app/components/aeno/primitives/input_text_area/component.html.erb +23 -0
  84. data/app/components/aeno/primitives/input_text_area/component.rb +19 -0
  85. data/app/components/aeno/primitives/input_text_area/styles.css +30 -0
  86. data/app/components/aeno/primitives/input_text_area_ai/component.html.erb +51 -0
  87. data/app/components/aeno/primitives/input_text_area_ai/component.rb +47 -0
  88. data/app/components/aeno/primitives/input_text_area_ai/controller.js +198 -0
  89. data/app/components/aeno/primitives/input_text_area_ai/styles.css +91 -0
  90. data/app/components/aeno/primitives/input_wrapper/component.html.erb +20 -0
  91. data/app/components/aeno/primitives/input_wrapper/component.rb +31 -0
  92. data/app/components/aeno/primitives/input_wrapper/styles.css +72 -0
  93. data/app/components/aeno/primitives/layouts/agentic/component.html.erb +4 -0
  94. data/app/components/aeno/primitives/layouts/agentic/component.rb +9 -0
  95. data/app/components/aeno/primitives/layouts/agentic/styles.css +23 -0
  96. data/app/components/aeno/primitives/layouts/app/aside.rb +9 -0
  97. data/app/components/aeno/primitives/layouts/app/component.html.erb +14 -0
  98. data/app/components/aeno/primitives/layouts/app/component.rb +11 -0
  99. data/app/components/aeno/primitives/layouts/app/sidebar.rb +9 -0
  100. data/app/components/aeno/primitives/layouts/app/styles.css +46 -0
  101. data/app/components/aeno/primitives/page/component.html.erb +24 -0
  102. data/app/components/aeno/primitives/page/component.rb +23 -0
  103. data/app/components/aeno/primitives/page/styles.css +55 -0
  104. data/app/components/aeno/primitives/sidebar/component.html.erb +25 -0
  105. data/app/components/aeno/primitives/sidebar/component.rb +14 -0
  106. data/app/components/aeno/primitives/sidebar/footer.rb +7 -0
  107. data/app/components/aeno/primitives/sidebar/group.rb +18 -0
  108. data/app/components/aeno/primitives/sidebar/header.rb +7 -0
  109. data/app/components/aeno/primitives/sidebar/item.rb +19 -0
  110. data/app/components/aeno/primitives/sidebar/styles.css +95 -0
  111. data/app/components/aeno/primitives/spinner/component.rb +36 -0
  112. data/app/components/aeno/primitives/spinner/styles.css +81 -0
  113. data/app/components/aeno/primitives/table/cell.rb +7 -0
  114. data/app/components/aeno/primitives/table/column.rb +7 -0
  115. data/app/components/aeno/primitives/table/component.html.erb +8 -0
  116. data/app/components/aeno/primitives/table/component.rb +14 -0
  117. data/app/components/aeno/primitives/table/header.rb +13 -0
  118. data/app/components/aeno/primitives/table/row.rb +11 -0
  119. data/app/components/aeno/primitives/table/styles.css +39 -0
  120. data/app/controllers/aeno/application_controller.rb +15 -0
  121. data/app/controllers/aeno/showcase_controller.rb +40 -0
  122. data/app/controllers/aeno/theme_controller.rb +10 -0
  123. data/app/helpers/aeno/application_helper.rb +28 -0
  124. data/app/javascript/aeno/application.js +3 -0
  125. data/app/javascript/aeno/controllers/application.js +5 -0
  126. data/app/javascript/aeno/controllers/index.js +5 -0
  127. data/app/javascript/aeno/controllers/loader.js +62 -0
  128. data/app/jobs/aeno/application_job.rb +4 -0
  129. data/app/models/aeno/application_record.rb +5 -0
  130. data/app/views/layouts/aeno/application.html.erb +55 -0
  131. data/config/importmap.rb +20 -0
  132. data/config/routes.rb +5 -0
  133. data/lib/aeno/configuration.rb +56 -0
  134. data/lib/aeno/engine.rb +43 -0
  135. data/lib/aeno/engine_helpers.rb +44 -0
  136. data/lib/aeno/theme.rb +326 -0
  137. data/lib/aeno/version.rb +3 -0
  138. data/lib/aeno.rb +11 -0
  139. data/lib/tasks/aeno_tasks.rake +39 -0
  140. metadata +310 -0
@@ -0,0 +1,61 @@
1
+ /* Input Password */
2
+ .cp-input-password {
3
+ }
4
+
5
+ .cp-input-password__field {
6
+ position: relative;
7
+ }
8
+
9
+ .cp-input-password__input {
10
+ display: block;
11
+ width: 100%;
12
+ padding: 0.5rem 2.5rem 0.5rem 0.75rem;
13
+ font-size: 0.875rem;
14
+ color: var(--ui-input-fg);
15
+ background: var(--ui-input-bg);
16
+ border: 1px solid var(--ui-input-border);
17
+ border-radius: var(--ui-input-radius, 0.375rem);
18
+ outline: none;
19
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
20
+ }
21
+
22
+ .cp-input-password__input::placeholder {
23
+ color: var(--ui-input-placeholder);
24
+ }
25
+
26
+ .cp-input-password__input:focus {
27
+ border-color: var(--ui-input-border-focus);
28
+ box-shadow: 0 0 0 1px var(--ui-input-ring);
29
+ }
30
+
31
+ .cp-input-password__input:disabled {
32
+ opacity: 0.5;
33
+ cursor: not-allowed;
34
+ }
35
+
36
+ .cp-input-password__toggle {
37
+ position: absolute;
38
+ right: 0.5rem;
39
+ top: 50%;
40
+ transform: translateY(-50%);
41
+ padding: 0.25rem;
42
+ color: var(--ui-input-placeholder);
43
+ background: transparent;
44
+ border: none;
45
+ cursor: pointer;
46
+ border-radius: 0.25rem;
47
+ }
48
+
49
+ .cp-input-password__toggle:hover {
50
+ color: var(--ui-input-fg);
51
+ }
52
+
53
+ .cp-input-password__toggle-icon {
54
+ width: 1rem;
55
+ height: 1rem;
56
+ display: block;
57
+ }
58
+
59
+ .cp-input-password__toggle-icon--hidden {
60
+ display: none;
61
+ }
@@ -0,0 +1,43 @@
1
+ <div class="cp-input-select">
2
+ <%= render Aeno::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
+ <select
11
+ id="<%= id %>"
12
+ name="<%= name %>"
13
+ class="cp-input-select__select"
14
+ <%= 'disabled' if disabled %>
15
+ <%= 'required' if required %>
16
+ <% data.each do |key, val| %>
17
+ data-<%= key.to_s.dasherize %>="<%= val %>"
18
+ <% end %>
19
+ >
20
+ <% if prompt %>
21
+ <option value=""><%= prompt %></option>
22
+ <% end %>
23
+
24
+ <% if collection && value_method && label_method %>
25
+ <% collection.each do |item| %>
26
+ <option value="<%= item.send(value_method) %>" <%= 'selected' if value == item.send(value_method).to_s %>>
27
+ <%= item.send(label_method) %>
28
+ </option>
29
+ <% end %>
30
+ <% elsif options.any? %>
31
+ <% options.each do |option_value, option_label| %>
32
+ <option value="<%= option_value %>" <%= 'selected' if value == option_value.to_s %>>
33
+ <%= option_label %>
34
+ </option>
35
+ <% end %>
36
+ <% else %>
37
+ <% select_options.each do |option| %>
38
+ <%= option %>
39
+ <% end %>
40
+ <% end %>
41
+ </select>
42
+ <% end %>
43
+ </div>
@@ -0,0 +1,21 @@
1
+ module Aeno::Primitives::InputSelect
2
+ class Component < ::Aeno::FormBuilder::BaseComponent
3
+ prop :prompt, description: "Placeholder option", optional: true
4
+ prop :options, description: "Array of [label, value] pairs", default: -> { [] }
5
+ prop :collection, description: "ActiveRecord collection", optional: true
6
+ prop :value_method, description: "Method for option value", optional: true
7
+ prop :label_method, description: "Method for option label", optional: true
8
+
9
+ renders_many :select_options, Aeno::Primitives::InputSelect::Option
10
+
11
+ examples("Input Select", description: "Dropdown select input") do |b|
12
+ b.example(:default, title: "Default") do |e|
13
+ e.preview name: "country", options: [["USA", "us"], ["Canada", "ca"]]
14
+ end
15
+
16
+ b.example(:with_prompt, title: "With Prompt") do |e|
17
+ e.preview name: "status", prompt: "Select status...", options: [["Active", "active"], ["Inactive", "inactive"]]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Aeno::Primitives::InputSelect
2
+ class Option < ::Aeno::ApplicationViewComponent
3
+ option(:value)
4
+ option(:label)
5
+ option(:selected, default: proc { false })
6
+ option(:disabled, default: proc { false })
7
+
8
+ erb_template <<~ERB
9
+ <option value="<%= value %>" <%= 'selected' if selected %> <%= 'disabled' if disabled %>>
10
+ <%= label %>
11
+ </option>
12
+ ERB
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ /* Input Select */
2
+ .cp-input-select {
3
+ }
4
+
5
+ .cp-input-select__select {
6
+ display: block;
7
+ width: 100%;
8
+ padding: 0.5rem 2rem 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
+ appearance: none;
16
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
17
+ background-position: right 0.5rem center;
18
+ background-repeat: no-repeat;
19
+ background-size: 1.5em 1.5em;
20
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
21
+ }
22
+
23
+ .cp-input-select__select: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-select__select--error {
29
+ border-color: var(--ui-destructive);
30
+ }
@@ -0,0 +1,33 @@
1
+ <div class="cp-input-slider <%= 'cp-input-slider--disabled' if disabled %>">
2
+ <% if label || show_value %>
3
+ <div class="cp-input-slider__header">
4
+ <% if label %>
5
+ <label for="<%= input_id %>" class="cp-input-slider__label">
6
+ <%= label %>
7
+ </label>
8
+ <% end %>
9
+ <% if show_value %>
10
+ <span class="cp-input-slider__value" data-slider-value><%= value %></span>
11
+ <% end %>
12
+ </div>
13
+ <% end %>
14
+
15
+ <input
16
+ type="range"
17
+ id="<%= input_id %>"
18
+ name="<%= name %>"
19
+ value="<%= value %>"
20
+ min="<%= min %>"
21
+ max="<%= max %>"
22
+ step="<%= step %>"
23
+ class="cp-input-slider__input"
24
+ <%= 'disabled' if disabled %>
25
+ <% data.each do |key, val| %>
26
+ data-<%= key.to_s.dasherize %>="<%= val %>"
27
+ <% end %>
28
+ />
29
+
30
+ <% if helper_text %>
31
+ <p class="cp-input-slider__helper"><%= helper_text %></p>
32
+ <% end %>
33
+ </div>
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aeno::Primitives::InputSlider
4
+ class Component < ::Aeno::ApplicationViewComponent
5
+ prop :name, description: "Input name attribute"
6
+ prop :id, description: "Input id", optional: true
7
+ prop :value, description: "Current value", default: -> { 50 }
8
+ prop :min, description: "Minimum value", default: -> { 0 }
9
+ prop :max, description: "Maximum value", default: -> { 100 }
10
+ prop :step, description: "Step increment", default: -> { 1 }
11
+ prop :label, description: "Label text", optional: true
12
+ prop :show_value, description: "Show current value", default: -> { true }
13
+ prop :helper_text, description: "Helper text", optional: true
14
+ prop :disabled, description: "Disabled state", default: -> { false }
15
+ prop :data, description: "Data attributes", default: -> { {} }
16
+
17
+ examples("Input Slider", description: "Range slider input") do |b|
18
+ b.example(:default, title: "Default") do |e|
19
+ e.preview name: "volume"
20
+ end
21
+
22
+ b.example(:with_label, title: "With Label") do |e|
23
+ e.preview name: "brightness", label: "Brightness", value: 75
24
+ end
25
+
26
+ b.example(:custom_range, title: "Custom Range") do |e|
27
+ e.preview name: "temperature", min: -10, max: 40, value: 20, label: "Temperature"
28
+ end
29
+ end
30
+
31
+ def input_id
32
+ id || name
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,74 @@
1
+ /* Input Slider */
2
+ .cp-input-slider {
3
+ }
4
+
5
+ .cp-input-slider--disabled {
6
+ opacity: 0.5;
7
+ pointer-events: none;
8
+ }
9
+
10
+ .cp-input-slider__header {
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: space-between;
14
+ margin-bottom: 0.5rem;
15
+ }
16
+
17
+ .cp-input-slider__label {
18
+ font-size: 0.875rem;
19
+ font-weight: 500;
20
+ color: var(--ui-foreground);
21
+ }
22
+
23
+ .cp-input-slider__value {
24
+ font-size: 0.875rem;
25
+ font-family: monospace;
26
+ color: var(--ui-muted-foreground);
27
+ }
28
+
29
+ .cp-input-slider__input {
30
+ width: 100%;
31
+ height: 0.5rem;
32
+ appearance: none;
33
+ background: var(--ui-accent);
34
+ border-radius: 9999px;
35
+ cursor: pointer;
36
+ outline: none;
37
+ }
38
+
39
+ .cp-input-slider__input::-webkit-slider-thumb {
40
+ appearance: none;
41
+ width: 1rem;
42
+ height: 1rem;
43
+ background: var(--ui-primary);
44
+ border-radius: 50%;
45
+ cursor: pointer;
46
+ transition: transform 0.15s ease;
47
+ }
48
+
49
+ .cp-input-slider__input::-webkit-slider-thumb:hover {
50
+ transform: scale(1.1);
51
+ }
52
+
53
+ .cp-input-slider__input::-moz-range-thumb {
54
+ width: 1rem;
55
+ height: 1rem;
56
+ background: var(--ui-primary);
57
+ border: none;
58
+ border-radius: 50%;
59
+ cursor: pointer;
60
+ }
61
+
62
+ .cp-input-slider__input:focus {
63
+ outline: none;
64
+ }
65
+
66
+ .cp-input-slider__input:focus-visible::-webkit-slider-thumb {
67
+ box-shadow: 0 0 0 2px var(--ui-background), 0 0 0 4px var(--ui-ring);
68
+ }
69
+
70
+ .cp-input-slider__helper {
71
+ margin-top: 0.25rem;
72
+ font-size: 0.875rem;
73
+ color: var(--ui-muted-foreground);
74
+ }
@@ -0,0 +1,73 @@
1
+ <div class="cp-input-tagging">
2
+ <%= render Aeno::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-tagging__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
+ <div class="cp-input-tagging__tags" data-<%= controller_name %>-target="selectedContainer">
15
+ <% selected_tags.each do |tag| %>
16
+ <span class="cp-input-tagging__tag"
17
+ data-<%= controller_name %>-target="tag"
18
+ data-tag-name="<%= tag %>">
19
+ <%= tag %>
20
+ <button type="button"
21
+ class="cp-input-tagging__tag-remove"
22
+ data-action="click-><%= controller_name %>#removeTag">
23
+ <%= lucide_icon("x", class: "cp-input-tagging__tag-icon") %>
24
+ </button>
25
+ </span>
26
+ <% end %>
27
+ </div>
28
+
29
+ <div data-<%= controller_name %>-target="hiddenInputs">
30
+ <% selected_tags.each do |tag| %>
31
+ <input type="hidden" name="<%= input_name %>" value="<%= tag %>">
32
+ <% end %>
33
+ </div>
34
+
35
+ <div class="cp-input-tagging__search">
36
+ <input type="text"
37
+ class="cp-input-tagging__input"
38
+ placeholder="<%= placeholder || 'Search or add tags...' %>"
39
+ autocomplete="off"
40
+ data-<%= controller_name %>-target="search"
41
+ data-action="input-><%= controller_name %>#onSearchInput focus-><%= controller_name %>#onFocus keydown-><%= controller_name %>#onKeydown"
42
+ <%= 'disabled' if disabled %>>
43
+
44
+ <div class="cp-input-tagging__dropdown cp-input-tagging__dropdown--hidden"
45
+ data-<%= controller_name %>-target="dropdown">
46
+
47
+ <div class="cp-input-tagging__loading cp-input-tagging__loading--hidden" data-<%= controller_name %>-target="loading">
48
+ <%= ui("spinner", size: :sm) %>
49
+ <span>Loading...</span>
50
+ </div>
51
+
52
+ <div class="cp-input-tagging__options" data-<%= controller_name %>-target="optionsContainer">
53
+ </div>
54
+
55
+ <% if allow_create %>
56
+ <div class="cp-input-tagging__create cp-input-tagging__create--hidden" data-<%= controller_name %>-target="createOption">
57
+ <button type="button"
58
+ class="cp-input-tagging__create-btn"
59
+ data-action="click-><%= controller_name %>#createTag">
60
+ <%= lucide_icon("plus", class: "cp-input-tagging__create-icon") %>
61
+ <span>Create "<span data-<%= controller_name %>-target="createLabel"></span>"</span>
62
+ </button>
63
+ </div>
64
+ <% end %>
65
+
66
+ <div class="cp-input-tagging__empty cp-input-tagging__empty--hidden" data-<%= controller_name %>-target="empty">
67
+ No tags found.
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ <% end %>
73
+ </div>
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aeno::Primitives::InputTagging
4
+ class Component < ::Aeno::FormBuilder::BaseComponent
5
+ prop :tags_url, description: "URL to fetch tags", optional: true
6
+ prop :create_url, description: "URL to create new tags", optional: true
7
+ prop :selected_tags, description: "Pre-selected tags", default: -> { [] }
8
+ prop :allow_create, description: "Allow creating new tags", default: -> { true }
9
+ prop :max_tags, description: "Maximum number of tags", optional: true
10
+
11
+ examples("Input Tagging", description: "Tag input with autocomplete") do |b|
12
+ b.example(:default, title: "Default") do |e|
13
+ e.preview name: "tags"
14
+ end
15
+
16
+ b.example(:with_selected, title: "With Selected Tags") do |e|
17
+ e.preview name: "tags", selected_tags: [{ id: 1, name: "Ruby" }, { id: 2, name: "Rails" }]
18
+ end
19
+
20
+ b.example(:limited, title: "Limited Tags") do |e|
21
+ e.preview name: "tags", max_tags: 3
22
+ end
23
+ end
24
+
25
+ def stimulus_values
26
+ values = {
27
+ "#{controller_name}-selected-value" => selected_tags.to_json,
28
+ "#{controller_name}-allow-create-value" => allow_create
29
+ }
30
+ values["#{controller_name}-tags-url-value"] = tags_url if tags_url
31
+ values["#{controller_name}-create-url-value"] = create_url if create_url
32
+ values["#{controller_name}-max-tags-value"] = max_tags if max_tags
33
+ values
34
+ end
35
+
36
+ def input_name
37
+ name.to_s.end_with?("[]") ? name : "#{name}[]"
38
+ end
39
+ end
40
+ end