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
+ <!DOCTYPE html>
2
+ <html data-theme="<%= current_theme %>" data-mode="<%= current_mode %>">
3
+ <head>
4
+ <title>Aeno</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+ <%= yield :head %>
8
+ <%= stylesheet_link_tag "aeno/application", media: "all" %>
9
+ <%= javascript_importmap_tags "aeno/application", importmap: Aeno.importmap %>
10
+ </head>
11
+ <body>
12
+ <%= ui("layouts/app") do |layout| %>
13
+ <% layout.with_sidebar(style: { width: "240px" }) do %>
14
+ <%= ui("sidebar") do |s| %>
15
+ <% s.with_header do %>
16
+ <a href="#" class="cp-layout-app__logo">Aeno</a>
17
+ <% end %>
18
+
19
+ <% s.with_group(label: "Primitives") do |g| %>
20
+ <% primitives.each do |name| %>
21
+ <% g.with_item(label: name.titleize, href: showcase_component_path("primitives", name)) %>
22
+ <% end %>
23
+ <% end %>
24
+
25
+ <% s.with_group(label: "Blocks") do |g| %>
26
+ <% blocks.each do |name| %>
27
+ <% g.with_item(label: name.titleize, href: showcase_component_path("blocks", name)) %>
28
+ <% end %>
29
+ <% end %>
30
+
31
+ <% s.with_footer do %>
32
+ <span>v0.1.0</span>
33
+ <% end %>
34
+ <% end %>
35
+ <% end %>
36
+
37
+ <%= yield %>
38
+ <% end %>
39
+
40
+ <div class="cg-theme-toggle">
41
+ <%= ui("dropdown", placement: :top, strategy: :fixed) do |d| %>
42
+ <% d.with_trigger do %>
43
+ <%= ui("button", icon: "palette", label: "#{current_theme.titleize} (#{current_mode.titleize})", variant: :outline, css: "text-xs") %>
44
+ <% end %>
45
+ <% d.with_label do %>Palette<% end %>
46
+ <% d.with_item(href: theme_path(theme: "slate", mode: current_mode), method: :patch, form_data: { turbo: false }) do %>Slate<% end %>
47
+ <% d.with_item(href: theme_path(theme: "zinc", mode: current_mode), method: :patch, form_data: { turbo: false }) do %>Zinc<% end %>
48
+ <% d.with_separator %>
49
+ <% d.with_label do %>Mode<% end %>
50
+ <% d.with_item(href: theme_path(theme: current_theme, mode: "light"), method: :patch, form_data: { turbo: false }) do %>Light<% end %>
51
+ <% d.with_item(href: theme_path(theme: current_theme, mode: "dark"), method: :patch, form_data: { turbo: false }) do %>Dark<% end %>
52
+ <% end %>
53
+ </div>
54
+ </body>
55
+ </html>
@@ -0,0 +1,20 @@
1
+ pin "@hotwired/turbo-rails", to: "turbo.min.js"
2
+ pin "aeno/application"
3
+ pin "@hotwired/stimulus", to: "stimulus.min.js"
4
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
5
+
6
+ # Floating UI
7
+ pin "@floating-ui/core", to: "https://cdn.jsdelivr.net/npm/@floating-ui/core@1.6.9/+esm"
8
+ pin "@floating-ui/utils", to: "https://cdn.jsdelivr.net/npm/@floating-ui/utils@0.2.9/+esm"
9
+ pin "@floating-ui/dom", to: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.13/+esm"
10
+
11
+ pin_all_from(
12
+ Aeno::Engine.root.join("app/javascript/aeno/controllers"),
13
+ under: "aeno/controllers",
14
+ )
15
+
16
+ pin_all_from(
17
+ Aeno::Engine.root.join("app/components/aeno"),
18
+ under: "aeno/components",
19
+ to: "aeno"
20
+ )
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ Aeno::Engine.routes.draw do
2
+ get("/showcase/:namespace/:id", to: "showcase#show", as: :showcase_component)
3
+ patch("/theme", to: "theme#update", as: :theme)
4
+ root(to: "showcase#index")
5
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aeno
4
+ class Configuration
5
+ attr_reader :theme_overrides
6
+
7
+ def initialize
8
+ @theme_overrides = {}
9
+ end
10
+
11
+ def theme
12
+ yield ThemeBuilder.new(@theme_overrides) if block_given?
13
+ end
14
+
15
+ # Generate CSS for the aeno-config layer
16
+ def theme_css
17
+ return "" if @theme_overrides.empty?
18
+
19
+ vars = @theme_overrides.map { |k, v| "#{k}:#{v}" }.join(";")
20
+ "@layer aeno-config{:root{#{vars}}}"
21
+ end
22
+
23
+ class ThemeBuilder
24
+ def initialize(overrides)
25
+ @overrides = overrides
26
+ end
27
+
28
+ # Allow setting any --ui-* variable
29
+ def method_missing(name, value = nil)
30
+ if value
31
+ # Remove trailing = from setter method name
32
+ var_name = "--ui-#{name.to_s.chomp('=').tr('_', '-')}"
33
+ @overrides[var_name] = value
34
+ end
35
+ end
36
+
37
+ def respond_to_missing?(name, include_private = false)
38
+ true
39
+ end
40
+ end
41
+ end
42
+
43
+ class << self
44
+ def configuration
45
+ @configuration ||= Configuration.new
46
+ end
47
+
48
+ def configure
49
+ yield(configuration)
50
+ end
51
+
52
+ def reset_configuration!
53
+ @configuration = Configuration.new
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,43 @@
1
+ require "importmap-rails"
2
+ require "view_component-contrib"
3
+ require "dry-effects"
4
+ require "lucide-rails"
5
+
6
+ module Aeno
7
+ class << self
8
+ attr_accessor :importmap
9
+ end
10
+
11
+ class Engine < ::Rails::Engine
12
+ isolate_namespace Aeno
13
+
14
+ # Support both Propshaft and Sprockets
15
+ initializer "aeno.assets" do |app|
16
+ if app.config.respond_to?(:assets)
17
+ app.config.assets.paths << root.join("app/javascript")
18
+ app.config.assets.paths << root.join("app/components")
19
+ end
20
+ end
21
+
22
+ initializer "aeno.helpers" do
23
+ ActiveSupport.on_load(:action_controller) do
24
+ helper Aeno::ApplicationHelper
25
+ end
26
+ end
27
+
28
+ initializer "aeno.importmap", before: "importmap" do |app|
29
+ Aeno.importmap = Importmap::Map.new
30
+ Aeno.importmap.draw(app.root.join("config/importmap.rb"))
31
+ Aeno.importmap.draw(root.join("config/importmap.rb"))
32
+ Aeno.importmap.cache_sweeper(watches: root.join("app/components"))
33
+
34
+ if app.config.respond_to?(:importmap)
35
+ app.config.importmap.paths << root.join("config/importmap.rb")
36
+ end
37
+
38
+ ActiveSupport.on_load(:action_controller_base) do
39
+ before_action { Aeno.importmap.cache_sweeper.execute_if_updated }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ module Aeno
2
+ module EngineHelpers
3
+ # Sets up importmap for a Rails engine with Aeno gem integration
4
+ #
5
+ # Usage in your engine.rb:
6
+ # module YourEngine
7
+ # class << self
8
+ # attr_accessor :importmap
9
+ # end
10
+ #
11
+ # class Engine < ::Rails::Engine
12
+ # Aeno::EngineHelpers.setup_importmap(self, namespace: YourEngine)
13
+ # end
14
+ # end
15
+ def self.setup_importmap(engine_class, namespace:)
16
+ engine_class.initializer "#{namespace.name.underscore}.importmap", before: "importmap" do |app|
17
+ namespace.importmap = Importmap::Map.new
18
+ namespace.importmap.draw(app.root.join("config/importmap.rb"))
19
+ namespace.importmap.draw(engine_class.root.join("config/importmap.rb"))
20
+ namespace.importmap.draw(Aeno::Engine.root.join("config/importmap.rb"))
21
+ namespace.importmap.cache_sweeper(watches: engine_class.root.join("app/javascript"))
22
+ namespace.importmap.cache_sweeper(watches: engine_class.root.join("app/components"))
23
+
24
+ if app.config.respond_to?(:importmap)
25
+ app.config.importmap.paths << engine_class.root.join("config/importmap.rb")
26
+ end
27
+
28
+ ActiveSupport.on_load(:action_controller_base) do
29
+ before_action { namespace.importmap.cache_sweeper.execute_if_updated }
30
+ end
31
+ end
32
+ end
33
+
34
+ # Sets up asset paths for a Rails engine
35
+ def self.setup_assets(engine_class, namespace:)
36
+ engine_class.initializer "#{namespace.name.underscore}.assets" do |app|
37
+ if app.config.respond_to?(:assets)
38
+ app.config.assets.paths << engine_class.root.join("app/javascript")
39
+ app.config.assets.paths << engine_class.root.join("app/components")
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/aeno/theme.rb ADDED
@@ -0,0 +1,326 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aeno
4
+ class Theme
5
+ # ═══════════════════════════════════════════════════════════════════════════
6
+ # PROPERTY TYPES
7
+ # Define how each type is rendered in the configurator
8
+ # ═══════════════════════════════════════════════════════════════════════════
9
+ PROPERTY_TYPES = {
10
+ color: {
11
+ widget: :color_picker,
12
+ },
13
+ length: {
14
+ widget: :slider,
15
+ unit: "rem",
16
+ min: 0,
17
+ max: 2,
18
+ step: 0.0625,
19
+ },
20
+ multiplier: {
21
+ widget: :slider,
22
+ prefix: "×",
23
+ min: 0,
24
+ max: 3,
25
+ step: 0.1,
26
+ },
27
+ shadow: {
28
+ widget: :button_group,
29
+ options: {
30
+ none: "none",
31
+ sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
32
+ md: "0 4px 6px -1px rgb(0 0 0 / 0.1)",
33
+ lg: "0 10px 15px -3px rgb(0 0 0 / 0.1)",
34
+ xl: "0 20px 25px -5px rgb(0 0 0 / 0.1)"
35
+ }
36
+ },
37
+ font_family: {
38
+ widget: :select,
39
+ options: {
40
+ sans: "ui-sans-serif, system-ui, sans-serif",
41
+ serif: "ui-serif, Georgia, serif",
42
+ mono: "ui-monospace, monospace"
43
+ }
44
+ },
45
+ font_weight: {
46
+ widget: :button_group,
47
+ options: {
48
+ normal: "400",
49
+ medium: "500",
50
+ semibold: "600",
51
+ bold: "700"
52
+ }
53
+ },
54
+ }.freeze
55
+
56
+ # ═══════════════════════════════════════════════════════════════════════════
57
+ # PRIMITIVES
58
+ # The foundational values. Change these → everything derives.
59
+ # ═══════════════════════════════════════════════════════════════════════════
60
+ PRIMITIVES = {
61
+ # Colors
62
+ primary: "#475569",
63
+ primary_hover: "#334155",
64
+ primary_fg: "#ffffff",
65
+
66
+ secondary: "#f5f5f5",
67
+ secondary_hover: "#e5e5e5",
68
+ secondary_fg: "#1a1a1a",
69
+
70
+ destructive: "#dc2626",
71
+ destructive_hover: "#b91c1c",
72
+ destructive_fg: "#ffffff",
73
+
74
+ background: "#ffffff",
75
+ foreground: "#1a1a1a",
76
+
77
+ muted: "#f5f5f5",
78
+ muted_fg: "#737373",
79
+
80
+ border: "#e5e5e5",
81
+ ring: "#475569",
82
+
83
+ overlay: "#00000080",
84
+
85
+ # Lengths
86
+ radius_sm: "0.25rem",
87
+ radius_md: "0.375rem",
88
+ radius_lg: "0.5rem",
89
+ radius_xl: "0.75rem",
90
+
91
+ spacing_xs: "0.25rem",
92
+ spacing_sm: "0.5rem",
93
+ spacing_md: "0.75rem",
94
+ spacing_lg: "1rem",
95
+ spacing_xl: "1.5rem",
96
+ spacing_2xl: "2rem",
97
+
98
+ # Shadows
99
+ shadow_none: "none",
100
+ shadow_sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
101
+ shadow_md: "0 4px 6px -1px rgb(0 0 0 / 0.1)",
102
+ shadow_lg: "0 10px 15px -3px rgb(0 0 0 / 0.1)",
103
+ shadow_xl: "0 20px 25px -5px rgb(0 0 0 / 0.1)",
104
+
105
+ # Fonts
106
+ font_sans: "ui-sans-serif, system-ui, sans-serif",
107
+ font_mono: "ui-monospace, monospace",
108
+
109
+ weight_normal: "400",
110
+ weight_medium: "500",
111
+ weight_semibold: "600",
112
+ weight_bold: "700",
113
+ }.freeze
114
+
115
+ # ═══════════════════════════════════════════════════════════════════════════
116
+ # CORNERSTONES
117
+ # Component-level tokens, defaults reference primitives
118
+ # ═══════════════════════════════════════════════════════════════════════════
119
+ CORNERSTONES = {
120
+ button: {
121
+ label: "Button",
122
+ properties: {
123
+ bg: { var: "--ui-button-bg", type: :color, default: :primary },
124
+ bg_hover: { var: "--ui-button-bg-hover", type: :color, default: :primary_hover },
125
+ fg: { var: "--ui-button-fg", type: :color, default: :primary_fg },
126
+ border: { var: "--ui-button-border", type: :color, default: :primary },
127
+ radius: { var: "--ui-button-radius", type: :length, default: :radius_md, max: 1 },
128
+ padding_x: { var: "--ui-button-px", type: :length, default: :spacing_md, max: 2 },
129
+ padding_y: { var: "--ui-button-py", type: :length, default: :spacing_sm, max: 1.5 },
130
+ font_weight: { var: "--ui-button-weight", type: :font_weight, default: :weight_semibold },
131
+ }
132
+ },
133
+
134
+ input: {
135
+ label: "Input",
136
+ properties: {
137
+ bg: { var: "--ui-input-bg", type: :color, default: :background },
138
+ fg: { var: "--ui-input-fg", type: :color, default: :foreground },
139
+ border: { var: "--ui-input-border", type: :color, default: :border },
140
+ border_focus: { var: "--ui-input-border-focus", type: :color, default: :primary },
141
+ ring: { var: "--ui-input-ring", type: :color, default: :ring },
142
+ placeholder: { var: "--ui-input-placeholder", type: :color, default: :muted_fg },
143
+ radius: { var: "--ui-input-radius", type: :length, default: :radius_md, max: 1 },
144
+ padding_x: { var: "--ui-input-px", type: :length, default: :spacing_md, max: 1.5 },
145
+ padding_y: { var: "--ui-input-py", type: :length, default: :spacing_sm, max: 1 },
146
+ }
147
+ },
148
+
149
+ card: {
150
+ label: "Card",
151
+ properties: {
152
+ bg: { var: "--ui-card-bg", type: :color, default: :background },
153
+ fg: { var: "--ui-card-fg", type: :color, default: :foreground },
154
+ border: { var: "--ui-card-border", type: :color, default: :border },
155
+ radius: { var: "--ui-card-radius", type: :length, default: :radius_lg, max: 1.5 },
156
+ padding: { var: "--ui-card-padding", type: :length, default: :spacing_xl, max: 3 },
157
+ shadow: { var: "--ui-card-shadow", type: :shadow, default: :shadow_sm },
158
+ }
159
+ },
160
+
161
+ sheet: {
162
+ label: "Sheet",
163
+ properties: {
164
+ bg: { var: "--ui-sheet-bg", type: :color, default: :background },
165
+ fg: { var: "--ui-sheet-fg", type: :color, default: :foreground },
166
+ border: { var: "--ui-sheet-border", type: :color, default: :border },
167
+ overlay: { var: "--ui-sheet-overlay", type: :color, default: :overlay },
168
+ radius: { var: "--ui-sheet-radius", type: :length, default: :radius_xl, max: 2 },
169
+ padding: { var: "--ui-sheet-padding", type: :length, default: :spacing_xl, max: 3 },
170
+ shadow: { var: "--ui-sheet-shadow", type: :shadow, default: :shadow_xl },
171
+ }
172
+ },
173
+
174
+ area: {
175
+ label: "Area",
176
+ properties: {
177
+ bg: { var: "--ui-area-bg", type: :color, default: :muted },
178
+ fg: { var: "--ui-area-fg", type: :color, default: :foreground },
179
+ border: { var: "--ui-area-border", type: :color, default: :border },
180
+ radius: { var: "--ui-area-radius", type: :length, default: :radius_xl, max: 2 },
181
+ padding: { var: "--ui-area-padding", type: :length, default: :spacing_2xl, max: 4 },
182
+ }
183
+ },
184
+
185
+ table: {
186
+ label: "Table",
187
+ properties: {
188
+ bg: { var: "--ui-table-bg", type: :color, default: :background },
189
+ fg: { var: "--ui-table-fg", type: :color, default: :foreground },
190
+ border: { var: "--ui-table-border", type: :color, default: :border },
191
+ header_bg: { var: "--ui-table-header-bg", type: :color, default: :muted },
192
+ header_fg: { var: "--ui-table-header-fg", type: :color, default: :muted_fg },
193
+ row_hover: { var: "--ui-table-row-hover", type: :color, default: :muted },
194
+ row_stripe: { var: "--ui-table-row-stripe", type: :color, default: :muted },
195
+ radius: { var: "--ui-table-radius", type: :length, default: :radius_lg, max: 1 },
196
+ cell_padding: { var: "--ui-table-cell-padding", type: :length, default: :spacing_md, max: 1.5 },
197
+ }
198
+ },
199
+
200
+ typography: {
201
+ label: "Typography",
202
+ properties: {
203
+ heading: { var: "--ui-heading-color", type: :color, default: :foreground },
204
+ body: { var: "--ui-body-color", type: :color, default: :foreground },
205
+ muted: { var: "--ui-muted-color", type: :color, default: :muted_fg },
206
+ link: { var: "--ui-link-color", type: :color, default: :primary },
207
+ link_hover: { var: "--ui-link-hover", type: :color, default: :primary_hover },
208
+ code_bg: { var: "--ui-code-bg", type: :color, default: :muted },
209
+ code_fg: { var: "--ui-code-fg", type: :color, default: :foreground },
210
+ font_sans: { var: "--ui-font-sans", type: :font_family, default: :font_sans },
211
+ font_mono: { var: "--ui-font-mono", type: :font_family, default: :font_mono },
212
+ heading_weight: { var: "--ui-heading-weight", type: :font_weight, default: :weight_semibold },
213
+ }
214
+ },
215
+
216
+ menu: {
217
+ label: "Menu",
218
+ properties: {
219
+ bg: { var: "--ui-menu-bg", type: :color, default: :background },
220
+ fg: { var: "--ui-menu-fg", type: :color, default: :foreground },
221
+ border: { var: "--ui-menu-border", type: :color, default: :border },
222
+ radius: { var: "--ui-menu-radius", type: :length, default: :radius_md },
223
+ shadow: { var: "--ui-menu-shadow", type: :shadow, default: :shadow_lg },
224
+ padding: { var: "--ui-menu-padding", type: :length, default: :spacing_xs },
225
+ item_radius: { var: "--ui-menu-item-radius", type: :length, default: :radius_sm },
226
+ }
227
+ },
228
+ }.freeze
229
+
230
+ # ═══════════════════════════════════════════════════════════════════════════
231
+ # API METHODS
232
+ # ═══════════════════════════════════════════════════════════════════════════
233
+
234
+ class << self
235
+ # All CSS variable names (for JS)
236
+ def css_variables
237
+ vars = []
238
+ CORNERSTONES.each_value do |cornerstone|
239
+ cornerstone[:properties].each_value do |prop|
240
+ vars << prop[:var]
241
+ end
242
+ end
243
+ vars
244
+ end
245
+
246
+ # For JS: array of var names
247
+ def to_js_array
248
+ css_variables.map { |v| %("#{v}") }.join(", ")
249
+ end
250
+
251
+ # Resolve a default value (symbol → primitive value)
252
+ def resolve_default(default_key)
253
+ return default_key unless default_key.is_a?(Symbol)
254
+ PRIMITIVES[default_key] || default_key.to_s
255
+ end
256
+
257
+ # Get property config with resolved default
258
+ def property_config(cornerstone, property)
259
+ prop = CORNERSTONES.dig(cornerstone, :properties, property)
260
+ return nil unless prop
261
+
262
+ type_config = PROPERTY_TYPES[prop[:type]] || {}
263
+ resolved = type_config.merge(prop)
264
+ resolved[:resolved_default] = resolve_default(prop[:default])
265
+ resolved
266
+ end
267
+
268
+ # Full config for JS configurator
269
+ def to_js_config
270
+ config = {}
271
+ CORNERSTONES.each do |key, cornerstone|
272
+ config[key] = {
273
+ label: cornerstone[:label],
274
+ properties: {}
275
+ }
276
+ cornerstone[:properties].each do |prop_key, prop|
277
+ type_config = PROPERTY_TYPES[prop[:type]] || {}
278
+ config[key][:properties][prop_key] = type_config
279
+ .merge(prop)
280
+ .merge(resolved_default: resolve_default(prop[:default]))
281
+ end
282
+ end
283
+ config.to_json
284
+ end
285
+
286
+ # Generate CSS with defaults
287
+ def to_default_css
288
+ lines = [":root {"]
289
+ CORNERSTONES.each_value do |cornerstone|
290
+ cornerstone[:properties].each_value do |prop|
291
+ value = resolve_default(prop[:default])
292
+ lines << " #{prop[:var]}: #{value};"
293
+ end
294
+ end
295
+ lines << "}"
296
+ lines.join("\n")
297
+ end
298
+ end
299
+
300
+ # ═══════════════════════════════════════════════════════════════════════════
301
+ # INSTANCE METHODS (for runtime theming)
302
+ # ═══════════════════════════════════════════════════════════════════════════
303
+
304
+ def initialize
305
+ @values = {}
306
+ end
307
+
308
+ def set(var_name, value)
309
+ @values[var_name] = value
310
+ end
311
+
312
+ def to_css
313
+ return "" if @values.empty?
314
+
315
+ vars = @values.map { |k, v| " #{k}: #{v};" }.join("\n")
316
+ ":root {\n#{vars}\n}"
317
+ end
318
+
319
+ def to_style_tag
320
+ css = to_css
321
+ return "" if css.empty?
322
+
323
+ "<style>#{css}</style>".html_safe
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,3 @@
1
+ module Aeno
2
+ VERSION = "0.0.3"
3
+ end
data/lib/aeno.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "aeno/version"
2
+ require "aeno/engine"
3
+ require "aeno/engine_helpers"
4
+ require "aeno/theme"
5
+ require "aeno/configuration"
6
+
7
+ module Aeno
8
+ class << self
9
+ attr_accessor :importmap
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ # desc "Explaining what the task does"
2
+ namespace :aeno do
3
+ desc "build tailwind css once"
4
+ task tailwind_build: :environment do
5
+ require "tailwindcss-rails"
6
+
7
+ command = [
8
+ Tailwindcss::Commands.compile_command.first,
9
+ "-i", Aeno::Engine.root.join("app/assets/stylesheets/aeno/application.tailwind.css").to_s,
10
+ "-o", Aeno::Engine.root.join("app/assets/stylesheets/aeno/tailwind.css").to_s
11
+ ]
12
+
13
+ puts "Building Tailwind CSS..."
14
+ puts "Input: #{Aeno::Engine.root.join("app/assets/stylesheets/aeno/application.tailwind.css")}"
15
+ puts "Output: #{Aeno::Engine.root.join("app/assets/stylesheets/aeno/tailwind.css")}"
16
+ puts "Command: #{command.join(' ')}"
17
+ puts ""
18
+
19
+ system(*command)
20
+
21
+ puts ""
22
+ puts "Build complete!"
23
+ end
24
+
25
+ desc "run tailwind in watch mode"
26
+ task tailwind_engine_watch: :environment do
27
+ require "tailwindcss-rails"
28
+
29
+ command = [
30
+ Tailwindcss::Commands.compile_command.first,
31
+ "-i", Aeno::Engine.root.join("app/assets/stylesheets/aeno/application.tailwind.css").to_s,
32
+ "-o", Aeno::Engine.root.join("app/assets/stylesheets/aeno/tailwind.css").to_s,
33
+ "-w"
34
+ ]
35
+
36
+ p command
37
+ system(*command)
38
+ end
39
+ end