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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +230 -0
- data/Rakefile +8 -0
- data/app/assets/stylesheets/aeno/application.css +1 -0
- data/app/assets/stylesheets/aeno/base.css +43 -0
- data/app/assets/stylesheets/aeno/reset.css +397 -0
- data/app/assets/stylesheets/aeno/source.css +15 -0
- data/app/assets/stylesheets/aeno/theme.css +6 -0
- data/app/assets/stylesheets/aeno/themes/slate.css +163 -0
- data/app/assets/stylesheets/aeno/themes/zinc.css +163 -0
- data/app/assets/stylesheets/aeno/utilities.css +23 -0
- data/app/components/aeno/application_view_component.rb +219 -0
- data/app/components/aeno/blocks/component_preview/component.html.erb +7 -0
- data/app/components/aeno/blocks/component_preview/component.rb +10 -0
- data/app/components/aeno/blocks/component_preview/styles.css +10 -0
- data/app/components/aeno/form_builder.rb +87 -0
- data/app/components/aeno/pages/showcase/index/component.html.erb +53 -0
- data/app/components/aeno/pages/showcase/index/component.rb +7 -0
- data/app/components/aeno/pages/showcase/index/styles.css +27 -0
- data/app/components/aeno/pages/showcase/placeholder/component.html.erb +7 -0
- data/app/components/aeno/pages/showcase/placeholder/component.rb +10 -0
- data/app/components/aeno/pages/showcase/show/component.html.erb +38 -0
- data/app/components/aeno/pages/showcase/show/component.rb +48 -0
- data/app/components/aeno/primitives/button/component.rb +66 -0
- data/app/components/aeno/primitives/button/controller.js +7 -0
- data/app/components/aeno/primitives/button/styles.css +153 -0
- data/app/components/aeno/primitives/card/component.html.erb +3 -0
- data/app/components/aeno/primitives/card/component.rb +42 -0
- data/app/components/aeno/primitives/card/styles.css +28 -0
- data/app/components/aeno/primitives/conversation/component.html.erb +28 -0
- data/app/components/aeno/primitives/conversation/component.rb +15 -0
- data/app/components/aeno/primitives/conversation/controller.js +18 -0
- data/app/components/aeno/primitives/conversation/message/component.html.erb +24 -0
- data/app/components/aeno/primitives/conversation/message/component.rb +35 -0
- data/app/components/aeno/primitives/conversation/streaming_indicator/component.html.erb +21 -0
- data/app/components/aeno/primitives/conversation/streaming_indicator/component.rb +18 -0
- data/app/components/aeno/primitives/conversation/styles.css +221 -0
- data/app/components/aeno/primitives/conversation/user_message_box/component.html.erb +1 -0
- data/app/components/aeno/primitives/conversation/user_message_box/component.rb +4 -0
- data/app/components/aeno/primitives/drawer/component.html.erb +43 -0
- data/app/components/aeno/primitives/drawer/component.rb +33 -0
- data/app/components/aeno/primitives/drawer/controller.js +104 -0
- data/app/components/aeno/primitives/drawer/styles.css +90 -0
- data/app/components/aeno/primitives/dropdown/checkbox.rb +22 -0
- data/app/components/aeno/primitives/dropdown/component.html.erb +38 -0
- data/app/components/aeno/primitives/dropdown/component.rb +53 -0
- data/app/components/aeno/primitives/dropdown/controller.js +153 -0
- data/app/components/aeno/primitives/dropdown/item.rb +29 -0
- data/app/components/aeno/primitives/dropdown/label.rb +7 -0
- data/app/components/aeno/primitives/dropdown/radio_group.rb +16 -0
- data/app/components/aeno/primitives/dropdown/radio_item.rb +24 -0
- data/app/components/aeno/primitives/dropdown/separator.rb +7 -0
- data/app/components/aeno/primitives/dropdown/styles.css +155 -0
- data/app/components/aeno/primitives/empty/component.html.erb +15 -0
- data/app/components/aeno/primitives/empty/component.rb +18 -0
- data/app/components/aeno/primitives/empty/styles.css +40 -0
- data/app/components/aeno/primitives/input_attachments/component.html.erb +60 -0
- data/app/components/aeno/primitives/input_attachments/component.rb +52 -0
- data/app/components/aeno/primitives/input_attachments/controller.js +357 -0
- data/app/components/aeno/primitives/input_attachments/styles.css +102 -0
- data/app/components/aeno/primitives/input_color/component.html.erb +24 -0
- data/app/components/aeno/primitives/input_color/component.rb +42 -0
- data/app/components/aeno/primitives/input_color/styles.css +64 -0
- data/app/components/aeno/primitives/input_password/component.html.erb +43 -0
- data/app/components/aeno/primitives/input_password/component.rb +20 -0
- data/app/components/aeno/primitives/input_password/controller.js +17 -0
- data/app/components/aeno/primitives/input_password/styles.css +61 -0
- data/app/components/aeno/primitives/input_select/component.html.erb +43 -0
- data/app/components/aeno/primitives/input_select/component.rb +21 -0
- data/app/components/aeno/primitives/input_select/option.rb +14 -0
- data/app/components/aeno/primitives/input_select/styles.css +30 -0
- data/app/components/aeno/primitives/input_slider/component.html.erb +33 -0
- data/app/components/aeno/primitives/input_slider/component.rb +35 -0
- data/app/components/aeno/primitives/input_slider/styles.css +74 -0
- data/app/components/aeno/primitives/input_tagging/component.html.erb +73 -0
- data/app/components/aeno/primitives/input_tagging/component.rb +40 -0
- data/app/components/aeno/primitives/input_tagging/controller.js +326 -0
- data/app/components/aeno/primitives/input_tagging/styles.css +148 -0
- data/app/components/aeno/primitives/input_text/component.html.erb +25 -0
- data/app/components/aeno/primitives/input_text/component.rb +20 -0
- data/app/components/aeno/primitives/input_text/styles.css +38 -0
- data/app/components/aeno/primitives/input_text_area/component.html.erb +23 -0
- data/app/components/aeno/primitives/input_text_area/component.rb +19 -0
- data/app/components/aeno/primitives/input_text_area/styles.css +30 -0
- data/app/components/aeno/primitives/input_text_area_ai/component.html.erb +51 -0
- data/app/components/aeno/primitives/input_text_area_ai/component.rb +47 -0
- data/app/components/aeno/primitives/input_text_area_ai/controller.js +198 -0
- data/app/components/aeno/primitives/input_text_area_ai/styles.css +91 -0
- data/app/components/aeno/primitives/input_wrapper/component.html.erb +20 -0
- data/app/components/aeno/primitives/input_wrapper/component.rb +31 -0
- data/app/components/aeno/primitives/input_wrapper/styles.css +72 -0
- data/app/components/aeno/primitives/layouts/agentic/component.html.erb +4 -0
- data/app/components/aeno/primitives/layouts/agentic/component.rb +9 -0
- data/app/components/aeno/primitives/layouts/agentic/styles.css +23 -0
- data/app/components/aeno/primitives/layouts/app/aside.rb +9 -0
- data/app/components/aeno/primitives/layouts/app/component.html.erb +14 -0
- data/app/components/aeno/primitives/layouts/app/component.rb +11 -0
- data/app/components/aeno/primitives/layouts/app/sidebar.rb +9 -0
- data/app/components/aeno/primitives/layouts/app/styles.css +46 -0
- data/app/components/aeno/primitives/page/component.html.erb +24 -0
- data/app/components/aeno/primitives/page/component.rb +23 -0
- data/app/components/aeno/primitives/page/styles.css +55 -0
- data/app/components/aeno/primitives/sidebar/component.html.erb +25 -0
- data/app/components/aeno/primitives/sidebar/component.rb +14 -0
- data/app/components/aeno/primitives/sidebar/footer.rb +7 -0
- data/app/components/aeno/primitives/sidebar/group.rb +18 -0
- data/app/components/aeno/primitives/sidebar/header.rb +7 -0
- data/app/components/aeno/primitives/sidebar/item.rb +19 -0
- data/app/components/aeno/primitives/sidebar/styles.css +95 -0
- data/app/components/aeno/primitives/spinner/component.rb +36 -0
- data/app/components/aeno/primitives/spinner/styles.css +81 -0
- data/app/components/aeno/primitives/table/cell.rb +7 -0
- data/app/components/aeno/primitives/table/column.rb +7 -0
- data/app/components/aeno/primitives/table/component.html.erb +8 -0
- data/app/components/aeno/primitives/table/component.rb +14 -0
- data/app/components/aeno/primitives/table/header.rb +13 -0
- data/app/components/aeno/primitives/table/row.rb +11 -0
- data/app/components/aeno/primitives/table/styles.css +39 -0
- data/app/controllers/aeno/application_controller.rb +15 -0
- data/app/controllers/aeno/showcase_controller.rb +40 -0
- data/app/controllers/aeno/theme_controller.rb +10 -0
- data/app/helpers/aeno/application_helper.rb +28 -0
- data/app/javascript/aeno/application.js +3 -0
- data/app/javascript/aeno/controllers/application.js +5 -0
- data/app/javascript/aeno/controllers/index.js +5 -0
- data/app/javascript/aeno/controllers/loader.js +62 -0
- data/app/jobs/aeno/application_job.rb +4 -0
- data/app/models/aeno/application_record.rb +5 -0
- data/app/views/layouts/aeno/application.html.erb +55 -0
- data/config/importmap.rb +20 -0
- data/config/routes.rb +5 -0
- data/lib/aeno/configuration.rb +56 -0
- data/lib/aeno/engine.rb +43 -0
- data/lib/aeno/engine_helpers.rb +44 -0
- data/lib/aeno/theme.rb +326 -0
- data/lib/aeno/version.rb +3 -0
- data/lib/aeno.rb +11 -0
- data/lib/tasks/aeno_tasks.rake +39 -0
- 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,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,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,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,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 };
|