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,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
+ }
@@ -0,0 +1,25 @@
1
+ <div class="cp-sidebar">
2
+ <% if header? %>
3
+ <div class="cp-sidebar__header"><%= header %></div>
4
+ <% end %>
5
+
6
+ <% if items.any? || groups.any? %>
7
+ <nav class="cp-sidebar__nav">
8
+ <% if items.any? %>
9
+ <ul class="cp-sidebar__menu">
10
+ <% items.each do |item| %>
11
+ <li><%= item %></li>
12
+ <% end %>
13
+ </ul>
14
+ <% end %>
15
+
16
+ <% groups.each do |group| %>
17
+ <%= group %>
18
+ <% end %>
19
+ </nav>
20
+ <% end %>
21
+
22
+ <% if footer? %>
23
+ <div class="cp-sidebar__footer"><%= footer %></div>
24
+ <% end %>
25
+ </div>
@@ -0,0 +1,14 @@
1
+ module Aeno::Primitives::Sidebar
2
+ class Component < Aeno::ApplicationViewComponent
3
+ renders_many(:items, Aeno::Primitives::Sidebar::Item)
4
+ renders_many(:groups, Aeno::Primitives::Sidebar::Group)
5
+ renders_one(:header, Aeno::Primitives::Sidebar::Header)
6
+ renders_one(:footer, Aeno::Primitives::Sidebar::Footer)
7
+
8
+ examples("Sidebar", description: "Navigation sidebar with groups and items") do |b|
9
+ b.example(:default, title: "Default") do |e|
10
+ e.preview
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ module Aeno::Primitives::Sidebar
2
+ class Footer < Aeno::ApplicationViewComponent
3
+ erb_template <<~ERB
4
+ <div><%= content %></div>
5
+ ERB
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ module Aeno::Primitives::Sidebar
2
+ class Group < Aeno::ApplicationViewComponent
3
+ option :label
4
+
5
+ renders_many :items, Item
6
+
7
+ erb_template <<~ERB
8
+ <div class="cp-sidebar__group">
9
+ <div class="cp-sidebar__group-label"><%= label %></div>
10
+ <ul class="cp-sidebar__group-menu">
11
+ <% items.each do |item| %>
12
+ <li><%= item %></li>
13
+ <% end %>
14
+ </ul>
15
+ </div>
16
+ ERB
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module Aeno::Primitives::Sidebar
2
+ class Header < Aeno::ApplicationViewComponent
3
+ erb_template <<~ERB
4
+ <div><%= content %></div>
5
+ ERB
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module Aeno::Primitives::Sidebar
2
+ class Item < Aeno::ApplicationViewComponent
3
+ option :label
4
+ option :href
5
+ option :icon, optional: true
6
+ option :active, default: proc { false }
7
+
8
+ def item_classes
9
+ ["cp-sidebar__item", active ? "cp-sidebar__item--active" : nil, css].compact.join(" ")
10
+ end
11
+
12
+ erb_template <<~ERB
13
+ <%= link_to(href, class: item_classes) do %>
14
+ <%= lucide_icon(icon) if icon %>
15
+ <span><%= label %></span>
16
+ <% end %>
17
+ ERB
18
+ end
19
+ end
@@ -0,0 +1,95 @@
1
+ /* Sidebar component styles */
2
+ .cp-sidebar {
3
+ width: 100%;
4
+ height: 100vh;
5
+ display: flex;
6
+ flex-direction: column;
7
+ overflow: hidden;
8
+ background-color: var(--ui-sidebar-bg, #fafafa);
9
+ color: var(--ui-sidebar-fg, #1a1a1a);
10
+ border-right: 1px solid var(--ui-sidebar-border, #e5e5e5);
11
+
12
+ &__header {
13
+ flex-shrink: 0;
14
+ }
15
+
16
+ &__nav {
17
+ flex: 1;
18
+ overflow-y: auto;
19
+ padding: 0 0.5rem;
20
+ }
21
+
22
+ &__footer {
23
+ flex-shrink: 0;
24
+ padding: 1rem;
25
+ border-top: 1px solid var(--ui-sidebar-border, #e5e5e5);
26
+ font-size: 0.75rem;
27
+ color: var(--ui-muted-color, #737373);
28
+ }
29
+
30
+ &__menu {
31
+ display: flex;
32
+ width: 100%;
33
+ min-width: 0;
34
+ flex-direction: column;
35
+ gap: 0.25rem;
36
+ }
37
+
38
+ &__group {
39
+ display: flex;
40
+ width: 100%;
41
+ min-width: 0;
42
+ flex-direction: column;
43
+ padding: 0.25rem 0;
44
+ }
45
+
46
+ &__group-label {
47
+ color: var(--ui-muted-color, #737373);
48
+ height: 2rem;
49
+ display: flex;
50
+ align-items: center;
51
+ padding: 0 0.5rem;
52
+ font-size: 0.75rem;
53
+ font-weight: 500;
54
+ text-transform: uppercase;
55
+ letter-spacing: 0.05em;
56
+ }
57
+
58
+ &__group-menu {
59
+ display: flex;
60
+ width: 100%;
61
+ min-width: 0;
62
+ flex-direction: column;
63
+ gap: 0.25rem;
64
+ }
65
+
66
+ &__item {
67
+ display: flex;
68
+ width: 100%;
69
+ align-items: center;
70
+ gap: 0.5rem;
71
+ border-radius: var(--ui-input-radius, 0.375rem);
72
+ padding: 0.5rem;
73
+ text-align: left;
74
+ font-size: 0.875rem;
75
+ color: var(--ui-sidebar-fg, #1a1a1a);
76
+ transition:
77
+ background-color 0.15s ease,
78
+ color 0.15s ease;
79
+
80
+ &:hover {
81
+ background-color: var(--ui-sidebar-hover, #f0f0f0);
82
+ }
83
+
84
+ & svg {
85
+ width: 1rem;
86
+ height: 1rem;
87
+ flex-shrink: 0;
88
+ }
89
+
90
+ &--active {
91
+ background-color: var(--ui-sidebar-active, #e5e5e5);
92
+ font-weight: 500;
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,36 @@
1
+ module Aeno::Primitives::Spinner
2
+ class Component < ::Aeno::ApplicationViewComponent
3
+ prop :size, description: "Spinner size", values: [:sm, :default, :lg], default: -> { :default }
4
+ prop :variant, description: "Spinner color variant", values: [:default, :white], default: -> { :default }
5
+
6
+ examples("Spinner", description: "Loading indicator") do |b|
7
+ b.example(:default, title: "Default") do |e|
8
+ e.preview
9
+ end
10
+
11
+ b.example(:sizes, title: "Sizes") do |e|
12
+ e.preview size: :sm
13
+ e.preview size: :default
14
+ e.preview size: :lg
15
+ end
16
+
17
+ b.example(:variants, title: "Variants") do |e|
18
+ e.preview variant: :default
19
+ e.preview variant: :white
20
+ end
21
+ end
22
+
23
+ def classes
24
+ [
25
+ class_for("base"),
26
+ class_for(size.to_s),
27
+ class_for(variant.to_s),
28
+ css
29
+ ].compact.join(" ")
30
+ end
31
+
32
+ erb_template <<~ERB
33
+ <span class="<%= classes %>"></span>
34
+ ERB
35
+ end
36
+ end
@@ -0,0 +1,81 @@
1
+ /* Spinner component styles */
2
+ .cp-spinner {
3
+ position: relative;
4
+ inset: 0;
5
+ padding-left: 0.5rem;
6
+ margin-right: 0;
7
+ width: 1.25rem;
8
+ height: 1.25rem;
9
+
10
+ &::before {
11
+ content: "";
12
+ box-sizing: border-box;
13
+ position: absolute;
14
+ top: 50%;
15
+ left: 50%;
16
+ width: 1.25rem;
17
+ height: 1.25rem;
18
+ margin-top: -0.625rem;
19
+ margin-left: -0.625rem;
20
+ border-radius: 9999px;
21
+ border-width: 2px;
22
+ border-style: solid;
23
+ animation: spin 1s linear infinite;
24
+ }
25
+
26
+ /* Sizes */
27
+ &--xs::before {
28
+ width: 0.75rem;
29
+ height: 0.75rem;
30
+ margin-top: -0.375rem;
31
+ margin-left: -0.375rem;
32
+ }
33
+
34
+ &--sm::before {
35
+ width: 1rem;
36
+ height: 1rem;
37
+ margin-top: -0.5rem;
38
+ margin-left: -0.5rem;
39
+ }
40
+
41
+ &--lg::before {
42
+ width: 1.5rem;
43
+ height: 1.5rem;
44
+ margin-top: -0.75rem;
45
+ margin-left: -0.75rem;
46
+ }
47
+
48
+ &--xl::before {
49
+ width: 2rem;
50
+ height: 2rem;
51
+ margin-top: -1rem;
52
+ margin-left: -1rem;
53
+ }
54
+
55
+ /* Variants */
56
+ &--white::before {
57
+ border-color: rgb(255 255 255 / 0.5);
58
+ border-top-color: white;
59
+ }
60
+
61
+ &--default::before {
62
+ border-color: rgb(107 114 128 / 0.5);
63
+ border-top-color: rgb(107 114 128);
64
+ }
65
+
66
+ &--primary::before {
67
+ border-color: rgb(from var(--ui-button-bg, #475569) r g b / 0.5);
68
+ border-top-color: var(--ui-button-bg, #475569);
69
+ }
70
+
71
+ &--black::before {
72
+ border-color: rgb(0 0 0 / 0.5);
73
+ border-top-color: black;
74
+ }
75
+ }
76
+
77
+ @keyframes spin {
78
+ to {
79
+ transform: rotate(360deg);
80
+ }
81
+ }
@@ -0,0 +1,7 @@
1
+ module Aeno::Primitives::Table
2
+ class Cell < Aeno::ApplicationViewComponent
3
+ erb_template <<~ERB
4
+ <td class="cp-table__td <%= css %>"><%= content %></td>
5
+ ERB
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Aeno::Primitives::Table
2
+ class Column < Aeno::ApplicationViewComponent
3
+ erb_template <<~ERB
4
+ <th scope="col" class="cp-table__th <%= css %>"><%= content %></th>
5
+ ERB
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ <div class="cp-table">
2
+ <table<%= " id=\"#{id}\"" if id %>>
3
+ <%= header if header? %>
4
+ <tbody>
5
+ <% rows.each do |row| %><%= row %><% end %>
6
+ </tbody>
7
+ </table>
8
+ </div>
@@ -0,0 +1,14 @@
1
+ module Aeno::Primitives::Table
2
+ class Component < ::Aeno::ApplicationViewComponent
3
+ prop :id, description: "HTML id attribute", optional: true
4
+
5
+ renders_one :header, Aeno::Primitives::Table::Header
6
+ renders_many :rows, Aeno::Primitives::Table::Row
7
+
8
+ examples("Table", description: "Data table with header and rows") do |b|
9
+ b.example(:default, title: "Default") do |e|
10
+ e.preview
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Aeno::Primitives::Table
2
+ class Header < Aeno::ApplicationViewComponent
3
+ renders_many :columns, Aeno::Primitives::Table::Column
4
+
5
+ erb_template <<~ERB
6
+ <thead class="cp-table__head">
7
+ <tr>
8
+ <% columns.each do |column| %><%= column %><% end %>
9
+ </tr>
10
+ </thead>
11
+ ERB
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Aeno::Primitives::Table
2
+ class Row < Aeno::ApplicationViewComponent
3
+ renders_many :cells, Aeno::Primitives::Table::Cell
4
+
5
+ erb_template <<~ERB
6
+ <tr class="cp-table__tr <%= css %>">
7
+ <% cells.each do |cell| %><%= cell %><% end %>
8
+ </tr>
9
+ ERB
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ .cp-table {
2
+ overflow-x: auto;
3
+
4
+ & table {
5
+ width: 100%;
6
+ border-collapse: collapse;
7
+ }
8
+
9
+ &__head {
10
+ background-color: var(--ui-area-bg);
11
+ }
12
+
13
+ &__th {
14
+ padding: 0.75rem 1rem;
15
+ text-align: left;
16
+ font-size: 0.75rem;
17
+ font-weight: 500;
18
+ color: var(--ui-muted-color);
19
+ text-transform: uppercase;
20
+ letter-spacing: 0.05em;
21
+ }
22
+
23
+ &__tr {
24
+ border-bottom: 1px solid var(--ui-card-border);
25
+ }
26
+
27
+ &__td {
28
+ padding: 0.75rem 1rem;
29
+ font-size: 0.875rem;
30
+ color: var(--ui-foreground);
31
+
32
+ & code {
33
+ background: var(--ui-area-bg);
34
+ padding: 0.125rem 0.375rem;
35
+ border-radius: 0.25rem;
36
+ font-size: 0.8125rem;
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,15 @@
1
+ module Aeno
2
+ class ApplicationController < ActionController::Base
3
+ include Aeno::ApplicationHelper
4
+
5
+ helper_method :current_theme, :current_mode
6
+
7
+ def current_theme
8
+ session[:theme] || "slate"
9
+ end
10
+
11
+ def current_mode
12
+ session[:mode] || "light"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,40 @@
1
+ module Aeno
2
+ class ShowcaseController < ApplicationController
3
+ helper_method :primitives, :blocks
4
+
5
+ def index
6
+ page("showcase/index")
7
+ end
8
+
9
+ def show
10
+ component_class = resolve_component(params[:namespace], params[:id])
11
+ if component_class
12
+ page("showcase/show", component_class: component_class)
13
+ else
14
+ page("showcase/placeholder", namespace: params[:namespace], id: params[:id])
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def resolve_component(namespace, id)
21
+ ns = namespace.to_s.camelize
22
+ name = id.to_s.camelize
23
+ "Aeno::#{ns}::#{name}::Component".constantize
24
+ rescue NameError, NoMethodError
25
+ nil
26
+ end
27
+
28
+ def components_path
29
+ Aeno::Engine.root.join("app/components/aeno")
30
+ end
31
+
32
+ def primitives
33
+ @primitives ||= Dir.glob(components_path.join("primitives/*/")).map { |p| File.basename(p) }.sort
34
+ end
35
+
36
+ def blocks
37
+ @blocks ||= Dir.glob(components_path.join("blocks/*/")).map { |p| File.basename(p) }.sort
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,10 @@
1
+ module Aeno
2
+ class ThemeController < ApplicationController
3
+ def update
4
+ session[:theme] = params[:theme] if %w[slate zinc].include?(params[:theme])
5
+ session[:mode] = params[:mode] if %w[light dark].include?(params[:mode])
6
+
7
+ redirect_back(fallback_location: root_path, status: :see_other)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ module Aeno
2
+ module ApplicationHelper
3
+ def page(name, *args, **kwargs, &block)
4
+ component = "Aeno::Pages::#{name.split("/").map(&:camelize).join("::")}::Component".constantize
5
+ render(component.new(*args, **kwargs), &block)
6
+ end
7
+
8
+ def ui(name, *args, **kwargs, &block)
9
+ class_name = name.to_s.tr("-", "_").camelize
10
+ component = "Aeno::Primitives::#{class_name}::Component".constantize
11
+ render(component.new(*args, **kwargs), &block)
12
+ end
13
+
14
+ def block(name, *args, **kwargs, &blk)
15
+ class_name = name.to_s.tr("-", "_").camelize
16
+ component = "Aeno::Blocks::#{class_name}::Component".constantize
17
+ render(component.new(*args, **kwargs), &blk)
18
+ end
19
+
20
+ # Output theme configuration as inline CSS in the aeno-config layer
21
+ def aeno_theme_tag
22
+ css = Aeno.configuration.theme_css
23
+ return "".html_safe if css.empty?
24
+
25
+ "<style>#{css}</style>".html_safe
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2
+ import "@hotwired/turbo-rails";
3
+ import "aeno/controllers";
@@ -0,0 +1,5 @@
1
+ import { Application } from "@hotwired/stimulus";
2
+ const application = Application.start();
3
+ application.debug = false;
4
+ window.Stimulus = application;
5
+ export { application };
@@ -0,0 +1,5 @@
1
+ import { application } from "aeno/controllers/application";
2
+ import { eagerLoadEngineControllersFrom } from "aeno/controllers/loader";
3
+
4
+ // Load component controllers
5
+ eagerLoadEngineControllersFrom("aeno/components", application);
@@ -0,0 +1,62 @@
1
+ // Copied from @hotwired/stimulus-loading
2
+ function parseImportmapJson() {
3
+ return JSON.parse(document.querySelector("script[type=importmap]").text)
4
+ .imports;
5
+ }
6
+
7
+ function registerControllerFromPath(path, under, application) {
8
+ // Build Stimulus identifier from import path
9
+ // Extract namespace from the path (e.g., "aeno" from "aeno/components", "sqema" from "sqema/controllers")
10
+ const namespace = under.split("/")[0];
11
+ const pathType = under.split("/")[1]; // "components" or "controllers"
12
+
13
+ const withoutPrefix = path.replace(new RegExp(`^${under}/`), "");
14
+
15
+ let base;
16
+ if (pathType === "components") {
17
+ // Components: drop the last path segment ("controller" or "*_controller")
18
+ // - "aeno/components/button/controller" -> "aeno/button" -> "aeno--button"
19
+ // - "sqema/components/views/static/index/controller" -> "sqema/views/static/index" -> "sqema--views--static--index"
20
+ if (withoutPrefix.endsWith("/controller")) {
21
+ base = namespace + "/" + withoutPrefix.slice(0, -"/controller".length);
22
+ } else if (/\/[^/]+_controller$/.test(withoutPrefix)) {
23
+ base = namespace + "/" + withoutPrefix.replace(/\/[^/]+_controller$/, "");
24
+ } else if (/_controller$/.test(withoutPrefix)) {
25
+ // Fallback for flat files
26
+ base = namespace + "/" + withoutPrefix.replace(/_controller$/, "");
27
+ } else {
28
+ // Not a controller path we recognize
29
+ return;
30
+ }
31
+ } else if (pathType === "controllers") {
32
+ // Standard controllers: namespace--(controllername)
33
+ // - "sqema/controllers/hello_controller" -> "sqema/hello" -> "sqema--hello"
34
+ base = namespace + "/" + withoutPrefix.replace(/_controller$/, "");
35
+ } else {
36
+ return;
37
+ }
38
+
39
+ const name = base.replace(/\//g, "--").replace(/_/g, "-");
40
+ import(path)
41
+ .then((module) => {
42
+ if (module.default) {
43
+ application.register(name, module.default);
44
+ }
45
+ })
46
+ .catch((error) => {
47
+ console.error(`Failed to register controller: ${name} (${path})`, error);
48
+ });
49
+ }
50
+
51
+ function eagerLoadEngineControllersFrom(under, application) {
52
+ const paths = Object.keys(parseImportmapJson()).filter((path) =>
53
+ path.match(new RegExp(`^${under}/.+`)),
54
+ );
55
+ paths.forEach((path) => {
56
+ if (path.endsWith("_controller") || path.endsWith("/controller")) {
57
+ registerControllerFromPath(path, under, application);
58
+ }
59
+ });
60
+ }
61
+
62
+ export { eagerLoadEngineControllersFrom };
@@ -0,0 +1,4 @@
1
+ module Aeno
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module Aeno
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end