keystone_ui 0.6.0

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 (131) hide show
  1. checksums.yaml +7 -0
  2. data/app/assets/javascripts/keystone_ui/accordion_controller.js +21 -0
  3. data/app/assets/javascripts/keystone_ui/auto_submit_controller.js +7 -0
  4. data/app/assets/javascripts/keystone_ui/chartjs.js +24 -0
  5. data/app/assets/javascripts/keystone_ui/clipboard_controller.js +25 -0
  6. data/app/assets/javascripts/keystone_ui/color_picker_controller.js +158 -0
  7. data/app/assets/javascripts/keystone_ui/column_picker_controller.js +47 -0
  8. data/app/assets/javascripts/keystone_ui/dismiss_controller.js +7 -0
  9. data/app/assets/javascripts/keystone_ui/dropdown_controller.js +16 -0
  10. data/app/assets/javascripts/keystone_ui/file_upload_controller.js +61 -0
  11. data/app/assets/javascripts/keystone_ui/index.js +33 -0
  12. data/app/assets/javascripts/keystone_ui/line_chart_controller.js +38 -0
  13. data/app/assets/javascripts/keystone_ui/modal_controller.js +19 -0
  14. data/app/assets/javascripts/keystone_ui/multi_select_controller.js +35 -0
  15. data/app/assets/javascripts/keystone_ui/stat_card_info_controller.js +9 -0
  16. data/app/assets/javascripts/keystone_ui/swipe_deck_controller.js +123 -0
  17. data/app/assets/javascripts/keystone_ui/tab_switcher_controller.js +19 -0
  18. data/app/assets/tailwind/keystone_ui_engine/color_picker.css +31 -0
  19. data/app/assets/tailwind/keystone_ui_engine/engine.css +7 -0
  20. data/app/assets/tailwind/keystone_ui_engine/nav.css +175 -0
  21. data/app/assets/tailwind/keystone_ui_engine/theme.css +32 -0
  22. data/app/components/keystone/ui/accordion_component.html.erb +19 -0
  23. data/app/components/keystone/ui/accordion_component.rb +51 -0
  24. data/app/components/keystone/ui/alert_component.html.erb +15 -0
  25. data/app/components/keystone/ui/alert_component.rb +58 -0
  26. data/app/components/keystone/ui/badge_component.html.erb +1 -0
  27. data/app/components/keystone/ui/badge_component.rb +28 -0
  28. data/app/components/keystone/ui/bottom_nav_component.html.erb +3 -0
  29. data/app/components/keystone/ui/bottom_nav_component.rb +13 -0
  30. data/app/components/keystone/ui/bottom_nav_item_component.html.erb +4 -0
  31. data/app/components/keystone/ui/bottom_nav_item_component.rb +24 -0
  32. data/app/components/keystone/ui/button_component.html.erb +1 -0
  33. data/app/components/keystone/ui/button_component.rb +57 -0
  34. data/app/components/keystone/ui/card_component.html.erb +9 -0
  35. data/app/components/keystone/ui/card_component.rb +33 -0
  36. data/app/components/keystone/ui/card_link_component.html.erb +3 -0
  37. data/app/components/keystone/ui/card_link_component.rb +25 -0
  38. data/app/components/keystone/ui/chart_card_component.html.erb +6 -0
  39. data/app/components/keystone/ui/chart_card_component.rb +35 -0
  40. data/app/components/keystone/ui/code_component.html.erb +6 -0
  41. data/app/components/keystone/ui/code_component.rb +40 -0
  42. data/app/components/keystone/ui/color_picker_component.html.erb +37 -0
  43. data/app/components/keystone/ui/color_picker_component.rb +31 -0
  44. data/app/components/keystone/ui/column.rb +21 -0
  45. data/app/components/keystone/ui/column_picker_component.html.erb +20 -0
  46. data/app/components/keystone/ui/column_picker_component.rb +35 -0
  47. data/app/components/keystone/ui/copy_button_component.html.erb +11 -0
  48. data/app/components/keystone/ui/copy_button_component.rb +39 -0
  49. data/app/components/keystone/ui/cta_banner_component.html.erb +9 -0
  50. data/app/components/keystone/ui/cta_banner_component.rb +39 -0
  51. data/app/components/keystone/ui/data_table_component.html.erb +54 -0
  52. data/app/components/keystone/ui/data_table_component.rb +173 -0
  53. data/app/components/keystone/ui/disclosure_component.html.erb +9 -0
  54. data/app/components/keystone/ui/disclosure_component.rb +46 -0
  55. data/app/components/keystone/ui/feature_grid_component.html.erb +17 -0
  56. data/app/components/keystone/ui/feature_grid_component.rb +55 -0
  57. data/app/components/keystone/ui/file_upload_component.html.erb +18 -0
  58. data/app/components/keystone/ui/file_upload_component.rb +85 -0
  59. data/app/components/keystone/ui/form_component.html.erb +3 -0
  60. data/app/components/keystone/ui/form_component.rb +27 -0
  61. data/app/components/keystone/ui/form_field_component.html.erb +36 -0
  62. data/app/components/keystone/ui/form_field_component.rb +83 -0
  63. data/app/components/keystone/ui/form_page_component.html.erb +10 -0
  64. data/app/components/keystone/ui/form_page_component.rb +21 -0
  65. data/app/components/keystone/ui/funnel_component.html.erb +14 -0
  66. data/app/components/keystone/ui/funnel_component.rb +85 -0
  67. data/app/components/keystone/ui/grid_component.html.erb +3 -0
  68. data/app/components/keystone/ui/grid_component.rb +38 -0
  69. data/app/components/keystone/ui/hero_component.html.erb +27 -0
  70. data/app/components/keystone/ui/hero_component.rb +74 -0
  71. data/app/components/keystone/ui/input_component.html.erb +1 -0
  72. data/app/components/keystone/ui/input_component.rb +57 -0
  73. data/app/components/keystone/ui/line_chart_component.html.erb +3 -0
  74. data/app/components/keystone/ui/line_chart_component.rb +48 -0
  75. data/app/components/keystone/ui/mobile_actions_component.html.erb +11 -0
  76. data/app/components/keystone/ui/mobile_actions_component.rb +21 -0
  77. data/app/components/keystone/ui/mobile_header_component.html.erb +11 -0
  78. data/app/components/keystone/ui/mobile_header_component.rb +38 -0
  79. data/app/components/keystone/ui/modal_component.html.erb +13 -0
  80. data/app/components/keystone/ui/modal_component.rb +68 -0
  81. data/app/components/keystone/ui/multi_select_component.html.erb +21 -0
  82. data/app/components/keystone/ui/multi_select_component.rb +40 -0
  83. data/app/components/keystone/ui/nav_dropdown_component.html.erb +11 -0
  84. data/app/components/keystone/ui/nav_dropdown_component.rb +33 -0
  85. data/app/components/keystone/ui/nav_item_component.html.erb +1 -0
  86. data/app/components/keystone/ui/nav_item_component.rb +21 -0
  87. data/app/components/keystone/ui/navbar_component.html.erb +34 -0
  88. data/app/components/keystone/ui/navbar_component.rb +32 -0
  89. data/app/components/keystone/ui/option_card_component.html.erb +9 -0
  90. data/app/components/keystone/ui/option_card_component.rb +28 -0
  91. data/app/components/keystone/ui/page_component.html.erb +3 -0
  92. data/app/components/keystone/ui/page_component.rb +40 -0
  93. data/app/components/keystone/ui/page_header_component.html.erb +17 -0
  94. data/app/components/keystone/ui/page_header_component.rb +42 -0
  95. data/app/components/keystone/ui/panel_component.html.erb +3 -0
  96. data/app/components/keystone/ui/panel_component.rb +25 -0
  97. data/app/components/keystone/ui/pipeline_component.html.erb +34 -0
  98. data/app/components/keystone/ui/pipeline_component.rb +48 -0
  99. data/app/components/keystone/ui/progress_component.html.erb +8 -0
  100. data/app/components/keystone/ui/progress_component.rb +35 -0
  101. data/app/components/keystone/ui/radio_card_component.html.erb +9 -0
  102. data/app/components/keystone/ui/radio_card_component.rb +42 -0
  103. data/app/components/keystone/ui/section_component.html.erb +18 -0
  104. data/app/components/keystone/ui/section_component.rb +32 -0
  105. data/app/components/keystone/ui/select_component.html.erb +8 -0
  106. data/app/components/keystone/ui/select_component.rb +38 -0
  107. data/app/components/keystone/ui/settings_link_component.html.erb +4 -0
  108. data/app/components/keystone/ui/settings_link_component.rb +26 -0
  109. data/app/components/keystone/ui/show_page_component.html.erb +4 -0
  110. data/app/components/keystone/ui/show_page_component.rb +21 -0
  111. data/app/components/keystone/ui/stat_card_component.html.erb +22 -0
  112. data/app/components/keystone/ui/stat_card_component.rb +92 -0
  113. data/app/components/keystone/ui/swipe_deck_component.html.erb +39 -0
  114. data/app/components/keystone/ui/swipe_deck_component.rb +52 -0
  115. data/app/components/keystone/ui/tab_switcher_component.html.erb +14 -0
  116. data/app/components/keystone/ui/tab_switcher_component.rb +33 -0
  117. data/app/components/keystone/ui/textarea_component.html.erb +1 -0
  118. data/app/components/keystone/ui/textarea_component.rb +38 -0
  119. data/app/helpers/keystone_ui_helper.rb +195 -0
  120. data/config/importmap.rb +19 -0
  121. data/lib/generators/keystone/install_generator.rb +90 -0
  122. data/lib/keystone_ui/configuration.rb +24 -0
  123. data/lib/keystone_ui/engine.rb +47 -0
  124. data/lib/keystone_ui/safelist.rb +106 -0
  125. data/lib/keystone_ui/version.rb +5 -0
  126. data/lib/keystone_ui.rb +9 -0
  127. data/the_local/agents/keystone_ui-develop.md +297 -0
  128. data/the_local/agents/keystone_ui-info.md +81 -0
  129. data/the_local/agents/keystone_ui-install.md +137 -0
  130. data/the_local/interface.yml +110 -0
  131. metadata +203 -0
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeystoneUiHelper
4
+ def ui_accordion(**args)
5
+ render Keystone::Ui::AccordionComponent.new(**args)
6
+ end
7
+
8
+ def ui_tab_switcher(**args, &block)
9
+ render Keystone::Ui::TabSwitcherComponent.new(**args), &block
10
+ end
11
+
12
+ def ui_stat_card(**args)
13
+ render Keystone::Ui::StatCardComponent.new(**args)
14
+ end
15
+
16
+ def ui_chart_card(**args, &block)
17
+ render Keystone::Ui::ChartCardComponent.new(**args), &block
18
+ end
19
+
20
+ def ui_line_chart(**args)
21
+ render Keystone::Ui::LineChartComponent.new(**args)
22
+ end
23
+
24
+ def ui_cta_banner(**args, &block)
25
+ render Keystone::Ui::CtaBannerComponent.new(**args), &block
26
+ end
27
+
28
+ def ui_feature_grid(**args)
29
+ render Keystone::Ui::FeatureGridComponent.new(**args)
30
+ end
31
+
32
+ def ui_hero(**args, &block)
33
+ render Keystone::Ui::HeroComponent.new(**args), &block
34
+ end
35
+
36
+ def ui_modal(**args, &block)
37
+ render Keystone::Ui::ModalComponent.new(**args), &block
38
+ end
39
+
40
+ def ui_badge(**args)
41
+ render Keystone::Ui::BadgeComponent.new(**args)
42
+ end
43
+
44
+ def ui_copy_button(**args)
45
+ render Keystone::Ui::CopyButtonComponent.new(**args)
46
+ end
47
+
48
+ def ui_card(**args)
49
+ render Keystone::Ui::CardComponent.new(**args)
50
+ end
51
+
52
+ def ui_button(**args)
53
+ render Keystone::Ui::ButtonComponent.new(**args)
54
+ end
55
+
56
+ def ui_data_table(**args, &block)
57
+ render Keystone::Ui::DataTableComponent.new(**args), &block
58
+ end
59
+
60
+ def ui_page(**args, &block)
61
+ render Keystone::Ui::PageComponent.new(**args), &block
62
+ end
63
+
64
+ def ui_section(**args, &block)
65
+ render Keystone::Ui::SectionComponent.new(**args), &block
66
+ end
67
+
68
+ def ui_grid(**args, &block)
69
+ render Keystone::Ui::GridComponent.new(**args), &block
70
+ end
71
+
72
+ def ui_panel(**args, &block)
73
+ render Keystone::Ui::PanelComponent.new(**args), &block
74
+ end
75
+
76
+ def ui_card_link(**args, &block)
77
+ render Keystone::Ui::CardLinkComponent.new(**args), &block
78
+ end
79
+
80
+ def ui_input(**args)
81
+ render Keystone::Ui::InputComponent.new(**args)
82
+ end
83
+
84
+ def ui_textarea(**args)
85
+ render Keystone::Ui::TextareaComponent.new(**args)
86
+ end
87
+
88
+ def ui_select(**args)
89
+ render Keystone::Ui::SelectComponent.new(**args)
90
+ end
91
+
92
+ def ui_form_field(**args)
93
+ render Keystone::Ui::FormFieldComponent.new(**args)
94
+ end
95
+
96
+ def ui_page_header(**args, &block)
97
+ render Keystone::Ui::PageHeaderComponent.new(**args), &block
98
+ end
99
+
100
+ def ui_alert(**args)
101
+ render Keystone::Ui::AlertComponent.new(**args)
102
+ end
103
+
104
+ def ui_form_page(**args)
105
+ render Keystone::Ui::FormPageComponent.new(**args)
106
+ end
107
+
108
+ def ui_show_page(**args)
109
+ render Keystone::Ui::ShowPageComponent.new(**args)
110
+ end
111
+
112
+ def ui_mobile_header(**args)
113
+ render Keystone::Ui::MobileHeaderComponent.new(**args)
114
+ end
115
+
116
+ def ui_mobile_actions(**args, &block)
117
+ render Keystone::Ui::MobileActionsComponent.new(**args), &block
118
+ end
119
+
120
+ def ui_navbar(**args, &block)
121
+ render Keystone::Ui::NavbarComponent.new(**args), &block
122
+ end
123
+
124
+ def ui_nav_item(**args)
125
+ render Keystone::Ui::NavItemComponent.new(**args)
126
+ end
127
+
128
+ def ui_nav_dropdown(**args, &block)
129
+ render Keystone::Ui::NavDropdownComponent.new(**args), &block
130
+ end
131
+
132
+ def ui_bottom_nav(**args, &block)
133
+ render Keystone::Ui::BottomNavComponent.new(**args), &block
134
+ end
135
+
136
+ def ui_bottom_nav_item(**args)
137
+ render Keystone::Ui::BottomNavItemComponent.new(**args)
138
+ end
139
+
140
+ def ui_option_card(**args, &block)
141
+ render Keystone::Ui::OptionCardComponent.new(**args), &block
142
+ end
143
+
144
+ def ui_radio_card(**args)
145
+ render Keystone::Ui::RadioCardComponent.new(**args)
146
+ end
147
+
148
+ def ui_progress(**args)
149
+ render Keystone::Ui::ProgressComponent.new(**args)
150
+ end
151
+
152
+ def ui_settings_link(**args)
153
+ render Keystone::Ui::SettingsLinkComponent.new(**args)
154
+ end
155
+
156
+ def ui_color_picker(**args)
157
+ render Keystone::Ui::ColorPickerComponent.new(**args)
158
+ end
159
+
160
+ def ui_multi_select(**args)
161
+ render Keystone::Ui::MultiSelectComponent.new(**args)
162
+ end
163
+
164
+ def ui_swipe_deck(**args, &block)
165
+ render Keystone::Ui::SwipeDeckComponent.new(**args), &block
166
+ end
167
+
168
+ def ui_column_picker(**args)
169
+ render Keystone::Ui::ColumnPickerComponent.new(**args)
170
+ end
171
+
172
+ def ui_form(**args, &block)
173
+ render Keystone::Ui::FormComponent.new(**args), &block
174
+ end
175
+
176
+ def ui_file_upload(**args)
177
+ render Keystone::Ui::FileUploadComponent.new(**args)
178
+ end
179
+
180
+ def ui_funnel(**args)
181
+ render Keystone::Ui::FunnelComponent.new(**args)
182
+ end
183
+
184
+ def ui_pipeline(**args)
185
+ render Keystone::Ui::PipelineComponent.new(**args)
186
+ end
187
+
188
+ def ui_code(**args, &block)
189
+ render Keystone::Ui::CodeComponent.new(**args), &block
190
+ end
191
+
192
+ def ui_disclosure(**args, &block)
193
+ render Keystone::Ui::DisclosureComponent.new(**args), &block
194
+ end
195
+ end
@@ -0,0 +1,19 @@
1
+ pin "keystone_ui/index", to: "keystone_ui/index.js"
2
+ pin "keystone_ui/color_picker_controller", to: "keystone_ui/color_picker_controller.js"
3
+ pin "keystone_ui/multi_select_controller", to: "keystone_ui/multi_select_controller.js"
4
+ pin "keystone_ui/swipe_deck_controller", to: "keystone_ui/swipe_deck_controller.js"
5
+ pin "keystone_ui/column_picker_controller", to: "keystone_ui/column_picker_controller.js"
6
+ pin "keystone_ui/file_upload_controller", to: "keystone_ui/file_upload_controller.js"
7
+ pin "keystone_ui/dropdown_controller", to: "keystone_ui/dropdown_controller.js"
8
+ pin "keystone_ui/dismiss_controller", to: "keystone_ui/dismiss_controller.js"
9
+ pin "keystone_ui/modal_controller", to: "keystone_ui/modal_controller.js"
10
+ pin "keystone_ui/clipboard_controller", to: "keystone_ui/clipboard_controller.js"
11
+ pin "keystone_ui/tab_switcher_controller", to: "keystone_ui/tab_switcher_controller.js"
12
+ pin "keystone_ui/accordion_controller", to: "keystone_ui/accordion_controller.js"
13
+ pin "keystone_ui/stat_card_info_controller", to: "keystone_ui/stat_card_info_controller.js"
14
+ pin "keystone_ui/auto_submit_controller", to: "keystone_ui/auto_submit_controller.js"
15
+ pin "keystone_ui/line_chart_controller", to: "keystone_ui/line_chart_controller.js"
16
+
17
+ # Chart.js for the line-chart controller, so host apps don't pin it themselves.
18
+ # Vendored as a single self-contained bundle (deps inlined, no external imports).
19
+ pin "chart.js", to: "keystone_ui/chartjs.js"
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keystone
4
+ class InstallGenerator < Rails::Generators::Base
5
+ desc "Set up Keystone UI in your Rails application"
6
+
7
+ TAILWIND_IMPORT = '@import "tailwindcss";'
8
+ KEYSTONE_IMPORT = '@import "./keystone_source.css";'
9
+
10
+ JS_CONTROLLERS_PATH = "app/javascript/controllers/index.js"
11
+ JS_IMPORT = 'import { registerControllers } from "keystone_ui/index"'
12
+ JS_REGISTER = "registerControllers(application)"
13
+
14
+ def setup_tailwind
15
+ say ""
16
+ say "Keystone UI — setup", :green
17
+ say "=" * 40
18
+ say ""
19
+
20
+ css_path = Rails.root.join("app/assets/tailwind/application.css")
21
+ unless css_path.exist?
22
+ FileUtils.mkdir_p(css_path.dirname)
23
+ File.write(css_path, "#{TAILWIND_IMPORT}\n#{KEYSTONE_IMPORT}\n")
24
+ say " ✔ Created application.css with Tailwind and Keystone imports", :green
25
+ say ""
26
+ say "Done! See the README for component usage.", :green
27
+ return
28
+ end
29
+
30
+ content = css_path.read
31
+ changed = false
32
+
33
+ # Remove legacy @import for engine CSS (no longer needed)
34
+ legacy_import = '@import "../builds/tailwind/keystone_components_engine";' # also handles pre-rename installs
35
+ if content.include?(legacy_import)
36
+ gsub_file css_path, /#{Regexp.escape(legacy_import)}\n?/, ""
37
+ say " ✔ Removed legacy engine CSS import", :green
38
+ changed = true
39
+ end
40
+
41
+ # Remove legacy inline safelist (replaced by @source path injection)
42
+ legacy_safelist = "/* keystone:safelist */"
43
+ if content.include?(legacy_safelist)
44
+ gsub_file css_path, /#{Regexp.escape(legacy_safelist)}.*\n?/, ""
45
+ say " ✔ Removed legacy inline safelist", :green
46
+ changed = true
47
+ end
48
+
49
+ # Remove legacy marker comment (replaced by @import for keystone_source.css)
50
+ legacy_marker = "/* keystone:source */"
51
+ content = css_path.read # re-read after removals
52
+ if content.include?(legacy_marker)
53
+ gsub_file css_path, /#{Regexp.escape(legacy_marker)}.*\n?/, ""
54
+ say " ✔ Removed legacy source marker", :green
55
+ changed = true
56
+ end
57
+
58
+ # Inject @import for keystone_source.css (written at boot by engine initializer)
59
+ content = css_path.read # re-read after removals
60
+ unless content.include?(KEYSTONE_IMPORT)
61
+ inject_into_file css_path, "#{KEYSTONE_IMPORT}\n", after: /#{Regexp.escape(TAILWIND_IMPORT)}\n/
62
+ say " ✔ Added Keystone source import", :green
63
+ changed = true
64
+ end
65
+
66
+ say " ✔ application.css already up to date", :green unless changed
67
+ say ""
68
+ say "Done! See the README for component usage.", :green
69
+ end
70
+
71
+ def setup_javascript
72
+ say ""
73
+ say "Wiring Keystone UI Stimulus controllers...", :green
74
+
75
+ js_path = Rails.root.join(JS_CONTROLLERS_PATH)
76
+ unless js_path.exist?
77
+ say " ⚠ #{JS_CONTROLLERS_PATH} not found — add `#{JS_REGISTER}` to your Stimulus setup manually.", :yellow
78
+ return
79
+ end
80
+
81
+ if js_path.read.include?(JS_REGISTER)
82
+ say " ✔ Keystone UI controllers already registered", :green
83
+ return
84
+ end
85
+
86
+ append_to_file js_path, "\n#{JS_IMPORT}\n#{JS_REGISTER}\n"
87
+ say " ✔ Registered Keystone UI controllers", :green
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeystoneUi
4
+ class Configuration
5
+ attr_accessor :accent, :surface
6
+
7
+ def initialize
8
+ @accent = :blue
9
+ @surface = :zinc
10
+ end
11
+ end
12
+
13
+ def self.configuration
14
+ @configuration ||= Configuration.new
15
+ end
16
+
17
+ def self.configure
18
+ yield(configuration)
19
+ end
20
+
21
+ def self.reset_configuration!
22
+ @configuration = Configuration.new
23
+ end
24
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeystoneUi
4
+ class Engine < ::Rails::Engine
5
+ config.autoload_paths << root.join("app/components")
6
+
7
+ # Pin JavaScript controllers for importmap-based host apps.
8
+ initializer "keystone_ui.importmap", before: "importmap" do |app|
9
+ if app.config.respond_to?(:importmap)
10
+ app.config.importmap.paths << root.join("config/importmap.rb")
11
+ end
12
+ end
13
+
14
+ # Write a separate keystone_source.css with the gem's @source directive
15
+ # so Tailwind can scan component files during asset compilation.
16
+ #
17
+ # Uses after_initialize so host app config/initializers (where
18
+ # KeystoneUi.configure is called) have already run. Dependent engines
19
+ # that also need the palette should use config.after_initialize too —
20
+ # Rails runs these in engine dependency order, so KeystoneUi's block
21
+ # executes before any engine that depends on it.
22
+ config.after_initialize do
23
+ tailwind_dir = Rails.root.join("app/assets/tailwind")
24
+ css_path = tailwind_dir.join("application.css")
25
+ next unless css_path.exist?
26
+
27
+ keystone_import = '@import "./keystone_source.css";'
28
+ next unless css_path.read.include?(keystone_import)
29
+
30
+ source_css = tailwind_dir.join("keystone_source.css")
31
+ lines = [ %(@source "#{root}/app/components/**/*.{erb,rb}";) ]
32
+
33
+ # Import theme CSS (accent + surface custom property defaults)
34
+ theme_css = root.join("app/assets/tailwind/keystone_ui_engine/theme.css")
35
+ lines << %(@import "#{theme_css}";) if theme_css.exist?
36
+
37
+ # Import component CSS files shipped with the gem
38
+ nav_css = root.join("app/assets/tailwind/keystone_ui_engine/nav.css")
39
+ lines << %(@import "#{nav_css}";) if nav_css.exist?
40
+
41
+ color_picker_css = root.join("app/assets/tailwind/keystone_ui_engine/color_picker.css")
42
+ lines << %(@import "#{color_picker_css}";) if color_picker_css.exist?
43
+
44
+ source_css.write(lines.join("\n") + "\n")
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keystone
4
+ module Safelist
5
+ # All component classes to scan for Tailwind CSS classes.
6
+ # The safelist_spec verifies this list matches Keystone::Ui components.
7
+ COMPONENTS = [
8
+ Keystone::Ui::AccordionComponent,
9
+ Keystone::Ui::TabSwitcherComponent,
10
+ Keystone::Ui::StatCardComponent,
11
+ Keystone::Ui::ChartCardComponent,
12
+ Keystone::Ui::LineChartComponent,
13
+ Keystone::Ui::CtaBannerComponent,
14
+ Keystone::Ui::FeatureGridComponent,
15
+ Keystone::Ui::HeroComponent,
16
+ Keystone::Ui::ModalComponent,
17
+ Keystone::Ui::SelectComponent,
18
+ Keystone::Ui::BadgeComponent,
19
+ Keystone::Ui::CopyButtonComponent,
20
+ Keystone::Ui::CardComponent,
21
+ Keystone::Ui::ButtonComponent,
22
+ Keystone::Ui::DataTableComponent,
23
+ Keystone::Ui::PageComponent,
24
+ Keystone::Ui::SectionComponent,
25
+ Keystone::Ui::GridComponent,
26
+ Keystone::Ui::PanelComponent,
27
+ Keystone::Ui::CardLinkComponent,
28
+ Keystone::Ui::InputComponent,
29
+ Keystone::Ui::TextareaComponent,
30
+ Keystone::Ui::FormFieldComponent,
31
+ Keystone::Ui::PageHeaderComponent,
32
+ Keystone::Ui::AlertComponent,
33
+ Keystone::Ui::NavDropdownComponent,
34
+ Keystone::Ui::NavItemComponent,
35
+ Keystone::Ui::BottomNavItemComponent,
36
+ Keystone::Ui::NavbarComponent,
37
+ Keystone::Ui::BottomNavComponent,
38
+ Keystone::Ui::SettingsLinkComponent,
39
+ Keystone::Ui::FormPageComponent,
40
+ Keystone::Ui::MobileActionsComponent,
41
+ Keystone::Ui::MobileHeaderComponent,
42
+ Keystone::Ui::ShowPageComponent,
43
+ Keystone::Ui::OptionCardComponent,
44
+ Keystone::Ui::RadioCardComponent,
45
+ Keystone::Ui::ProgressComponent,
46
+ Keystone::Ui::ColorPickerComponent,
47
+ Keystone::Ui::MultiSelectComponent,
48
+ Keystone::Ui::SwipeDeckComponent,
49
+ Keystone::Ui::ColumnPickerComponent,
50
+ Keystone::Ui::FormComponent,
51
+ Keystone::Ui::FileUploadComponent,
52
+ Keystone::Ui::FunnelComponent,
53
+ Keystone::Ui::PipelineComponent,
54
+ Keystone::Ui::CodeComponent,
55
+ Keystone::Ui::DisclosureComponent
56
+ ].freeze
57
+
58
+ # Constants that hold non-CSS values (e.g. HTML input type maps)
59
+ SKIP_CONSTANTS = %i[TYPE_MAP ELLIPSIS_ICON BACK_ICON CARET_ICON CLOSE_ICON COPY_ICON SORT_ASC_ICON SORT_DESC_ICON SORT_NEUTRAL_ICON COLUMNS_ICON UPLOAD_ICON].freeze
60
+
61
+ # Classes used in Ruby methods or ERB templates, not in frozen constants.
62
+ # These cannot be auto-extracted and must be listed manually.
63
+ # The safelist_spec ensures every entry here appears in SAFELIST.
64
+ NON_CONSTANT_CLASSES = %w[
65
+ grid
66
+ mx-auto
67
+ overflow-hidden rounded-lg border border-gray-200 shadow-sm
68
+ dark:border-zinc-700 dark:shadow-none
69
+ relative min-w-full divide-y divide-gray-300 dark:divide-white/15
70
+ bg-gray-50 dark:bg-gray-800/75
71
+ divide-gray-200 bg-white dark:divide-white/10 dark:bg-gray-800/50
72
+ px-3 py-4 text-sm text-gray-500 dark:text-gray-400
73
+ ].freeze
74
+
75
+ def self.extract_classes_from_hash(hash, classes)
76
+ hash.each_value do |v|
77
+ case v
78
+ when String then classes.concat(v.split)
79
+ when Hash then extract_classes_from_hash(v, classes)
80
+ end
81
+ end
82
+ end
83
+
84
+ def self.generate
85
+ classes = []
86
+
87
+ COMPONENTS.each do |klass|
88
+ klass.constants(false).each do |const_name|
89
+ next if SKIP_CONSTANTS.include?(const_name)
90
+
91
+ value = klass.const_get(const_name)
92
+ case value
93
+ when String then classes.concat(value.split)
94
+ when Hash then extract_classes_from_hash(value, classes)
95
+ end
96
+ end
97
+ end
98
+
99
+ classes.concat(NON_CONSTANT_CLASSES)
100
+
101
+ classes.uniq.sort.join(" ")
102
+ end
103
+ end
104
+
105
+ SAFELIST = Safelist.generate
106
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeystoneUi
4
+ VERSION = "0.6.0"
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "view_component"
4
+ require "keystone_ui/configuration"
5
+ require "keystone_ui/engine"
6
+ require "keystone_ui/version"
7
+
8
+ module KeystoneUi
9
+ end