infotable 0.1.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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +1043 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/javascripts/infotable/controllers/drawer_controller.js +1412 -0
  6. data/app/assets/javascripts/infotable/controllers/table_controller.js +921 -0
  7. data/app/assets/javascripts/infotable/index.js +14 -0
  8. data/app/controllers/infotable/application_controller.rb +4 -0
  9. data/app/controllers/infotable/tables_controller.rb +299 -0
  10. data/app/helpers/infotable/application_helper.rb +5 -0
  11. data/app/helpers/infotable/tables_helper.rb +354 -0
  12. data/app/jobs/infotable/application_job.rb +4 -0
  13. data/app/mailers/infotable/application_mailer.rb +6 -0
  14. data/app/models/infotable/application_record.rb +5 -0
  15. data/app/views/infotable/tables/_column_visibility.html.erb +100 -0
  16. data/app/views/infotable/tables/_filter_drawer.html.erb +65 -0
  17. data/app/views/infotable/tables/_next_page_frame.html.erb +39 -0
  18. data/app/views/infotable/tables/_pagination.html.erb +101 -0
  19. data/app/views/infotable/tables/_side_panel.html.erb +63 -0
  20. data/app/views/infotable/tables/_table.html.erb +135 -0
  21. data/app/views/infotable/tables/_table_rows.html.erb +38 -0
  22. data/app/views/infotable/tables/filters/_boolean.html.erb +22 -0
  23. data/app/views/infotable/tables/filters/_date.html.erb +30 -0
  24. data/app/views/infotable/tables/filters/_number.html.erb +32 -0
  25. data/app/views/infotable/tables/filters/_select.html.erb +68 -0
  26. data/app/views/infotable/tables/filters/_text.html.erb +38 -0
  27. data/app/views/infotable/tables/show.html.erb +113 -0
  28. data/app/views/layouts/infotable/application.html.erb +15 -0
  29. data/config/importmap.rb +6 -0
  30. data/config/locales/en.yml +88 -0
  31. data/config/locales/pl.yml +88 -0
  32. data/config/routes.rb +6 -0
  33. data/lib/generators/infotable/install/install_generator.rb +565 -0
  34. data/lib/generators/infotable/install/templates/TAILWIND_CONFIG.md +131 -0
  35. data/lib/generators/infotable/install/templates/initializer.rb +26 -0
  36. data/lib/generators/infotable/install/templates/tailwind.config.js +284 -0
  37. data/lib/generators/infotable/table/table_generator.rb +95 -0
  38. data/lib/generators/infotable/table/templates/table.rb.tt +26 -0
  39. data/lib/infotable/base.rb +463 -0
  40. data/lib/infotable/batch_actions.rb +34 -0
  41. data/lib/infotable/configuration.rb +53 -0
  42. data/lib/infotable/engine.rb +51 -0
  43. data/lib/infotable/filter_processor.rb +326 -0
  44. data/lib/infotable/formatters/action_formatter.rb +141 -0
  45. data/lib/infotable/formatters/avatar_formatter.rb +49 -0
  46. data/lib/infotable/formatters/badge_formatter.rb +80 -0
  47. data/lib/infotable/formatters/base_formatter.rb +47 -0
  48. data/lib/infotable/formatters/boolean_formatter.rb +38 -0
  49. data/lib/infotable/formatters/currency_formatter.rb +22 -0
  50. data/lib/infotable/formatters/date_formatter.rb +58 -0
  51. data/lib/infotable/formatters/link_formatter.rb +48 -0
  52. data/lib/infotable/formatters/percent_formatter.rb +27 -0
  53. data/lib/infotable/search_processor.rb +42 -0
  54. data/lib/infotable/table_resolver.rb +45 -0
  55. data/lib/infotable/version.rb +3 -0
  56. data/lib/infotable.rb +54 -0
  57. data/lib/tasks/infotable.rake +55 -0
  58. metadata +216 -0
@@ -0,0 +1,565 @@
1
+ require "json"
2
+
3
+ module Infotable
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Install InfoTable into your application"
9
+
10
+ def check_tailwindcss_rails_gem
11
+ begin
12
+ gem_spec = Bundler.load.specs.find { |s| s.name == "tailwindcss-rails" }
13
+
14
+ if gem_spec.nil?
15
+ say "\n" + "=" * 60, :red
16
+ say "❌ ERROR: tailwindcss-rails gem not found", :red
17
+ say "=" * 60, :red
18
+ say "\nInfoTable requires the tailwindcss-rails gem to be installed.", :yellow
19
+ say "This gem provides Tailwind CSS v4 support for Rails applications.\n", :yellow
20
+ say "To install, add to your Gemfile:", :cyan
21
+ say " gem 'tailwindcss-rails'", :green
22
+ say "\nThen run:", :cyan
23
+ say " bundle install", :green
24
+ say " rails tailwindcss:install", :green
25
+ say "\nAfter installing tailwindcss-rails, run this installer again:", :cyan
26
+ say " rails generate infotable:install", :green
27
+ say "\n" + "=" * 60, :red
28
+ exit 1
29
+ end
30
+
31
+ say "✅ Found tailwindcss-rails gem (version #{gem_spec.version})", :green
32
+ rescue => e
33
+ say "⚠️ Could not verify tailwindcss-rails gem: #{e.message}", :yellow
34
+ say " Continuing with installation...", :yellow
35
+ end
36
+ end
37
+
38
+ def detect_js_bundler
39
+ @js_bundler = if File.exist?("config/importmap.rb")
40
+ :importmap
41
+ elsif File.exist?("esbuild.config.js") || package_json_has?("esbuild")
42
+ :esbuild
43
+ elsif File.exist?("vite.config.ts") || package_json_has?("vite")
44
+ :vite
45
+ elsif File.exist?("webpack.config.js") || package_json_has?("webpack")
46
+ :webpack
47
+ else
48
+ :unknown
49
+ end
50
+
51
+ say "Detected JavaScript bundler: #{@js_bundler}", :green
52
+ end
53
+
54
+ def add_routes
55
+ route 'mount Infotable::Engine => "/infotable"'
56
+ end
57
+
58
+ def create_infotable_directory
59
+ empty_directory "app/infotable"
60
+ create_file "app/infotable/.keep"
61
+ end
62
+
63
+ def setup_javascript
64
+ case @js_bundler
65
+ when :importmap
66
+ setup_importmap
67
+ when :esbuild, :webpack, :vite
68
+ setup_bundler
69
+ else
70
+ say "⚠️ Could not detect JavaScript bundler. Please see manual setup instructions.", :yellow
71
+ end
72
+ end
73
+
74
+ def setup_tailwind
75
+ # Check for Tailwind CSS file from tailwindcss-rails gem
76
+ if File.exist?("app/assets/stylesheets/application.tailwind.css") ||
77
+ File.exist?("app/assets/tailwind/application.css")
78
+ say "✅ Detected Tailwind CSS configuration", :green
79
+ append_tailwind_v4_sources
80
+ else
81
+ say "\n" + "=" * 60, :yellow
82
+ say "⚠️ Tailwind CSS file not found", :yellow
83
+ say "=" * 60, :yellow
84
+ say "\nInfoTable requires tailwindcss-rails to be installed and configured.", :yellow
85
+ say "\nPlease run:", :cyan
86
+ say " rails tailwindcss:install", :green
87
+ say "\nThis will create the required Tailwind CSS configuration.", :cyan
88
+ say "After that, run this installer again:", :cyan
89
+ say " rails generate infotable:install", :green
90
+ say "\n" + "=" * 60, :yellow
91
+ exit 1
92
+ end
93
+ end
94
+
95
+ def create_initializer
96
+ template "initializer.rb", "config/initializers/infotable.rb"
97
+ end
98
+
99
+ def add_to_gitignore
100
+ gitignore_entry = "# InfoTable - Generated from ERB template, do not commit\napp/assets/stylesheets/infotable.css\napp/assets/tailwind/infotable.css\n"
101
+
102
+ if File.exist?(".gitignore")
103
+ content = File.read(".gitignore")
104
+ unless content.include?("infotable.css")
105
+ append_to_file ".gitignore", "\n#{gitignore_entry}"
106
+ say " ✅ Added generated infotable.css to .gitignore", :green
107
+ else
108
+ say " ℹ️ infotable.css already in .gitignore", :blue
109
+ end
110
+ else
111
+ create_file ".gitignore", gitignore_entry
112
+ say " ✅ Created .gitignore with infotable.css", :green
113
+ end
114
+
115
+ say " 📝 Commit infotable.css.erb (the template), not infotable.css (generated)", :cyan
116
+ end
117
+
118
+ def show_post_install_message
119
+ say "\n" + "=" * 60, :green
120
+ say "✅ InfoTable installation complete!", :green
121
+ say "=" * 60, :green
122
+ say "\nNext steps:", :cyan
123
+ say "1. Rebuild your assets:", :yellow
124
+
125
+ if @js_bundler == :esbuild
126
+ say " npm run build", :green
127
+ elsif @js_bundler == :webpack
128
+ say " npm run build", :green
129
+ elsif @js_bundler == :vite
130
+ say " npm run build", :green
131
+ end
132
+
133
+ say " rails tailwindcss:build", :green
134
+
135
+ say "\n2. Verify Tailwind CSS is loaded in your layout:", :yellow
136
+ say " Ensure your application layout includes:", :cyan
137
+ say " <%= stylesheet_link_tag \"tailwind\", \"data-turbo-track\": \"reload\" %>", :green
138
+
139
+ say "\n3. Restart your Rails server", :yellow
140
+ say "4. Create your first table definition in app/infotable/", :yellow
141
+ say "5. Use <%= infotable :your_table_name %> in your views", :yellow
142
+
143
+ say "\n" + "=" * 60, :cyan
144
+ say "💡 Important Notes:", :cyan
145
+ say "=" * 60, :cyan
146
+ say "• InfoTable requires the tailwindcss-rails gem", :green
147
+ say "• infotable.css.erb is processed by Rake task before Tailwind builds", :green
148
+ say "• Gem paths resolve dynamically for each environment (dev/production)", :green
149
+ say "• Commit infotable.css.erb (template), gitignore infotable.css (generated)", :cyan
150
+
151
+ say "\n📖 Documentation: https://github.com/jcieslar/infotable", :cyan
152
+ end
153
+
154
+ private
155
+
156
+ def setup_importmap
157
+ say "Configuring for importmap...", :green
158
+ # Importmap pins are handled by the engine's importmap.rb
159
+ end
160
+
161
+ def setup_bundler
162
+ say "Configuring for #{@js_bundler}...", :green
163
+
164
+ # Find the infotable gem path
165
+ gem_path = find_gem_path
166
+
167
+ case @js_bundler
168
+ when :esbuild
169
+ setup_esbuild(gem_path)
170
+ when :webpack
171
+ setup_webpack(gem_path)
172
+ when :vite
173
+ setup_vite(gem_path)
174
+ else
175
+ say "⚠️ Unknown bundler. Please configure manually.", :yellow
176
+ end
177
+ end
178
+
179
+ def setup_esbuild(gem_path)
180
+ # Check for required peer dependencies
181
+ ensure_npm_dependencies
182
+
183
+ # Add import for InfoTable controllers from asset paths
184
+ if File.exist?("app/javascript/controllers/index.js")
185
+ content = File.read("app/javascript/controllers/index.js")
186
+
187
+ # Check if InfoTable controllers are already registered
188
+ unless content.include?("infotable")
189
+ # Check if application is already imported
190
+ unless content.match?(/from\s+['"]\.\/application['"]/)
191
+ inject_into_file "app/javascript/controllers/index.js", after: /import.*\n/ do
192
+ "import { application } from \"./application\"\n"
193
+ end
194
+ end
195
+
196
+ js_content = <<~JS
197
+
198
+ // InfoTable Stimulus controllers (resolved from Rails asset paths)
199
+ import InfoTableTableController from "infotable/controllers/table_controller"
200
+ import InfoTableDrawerController from "infotable/controllers/drawer_controller"
201
+ application.register("infotable--table", InfoTableTableController)
202
+ application.register("infotable--drawer", InfoTableDrawerController)
203
+ JS
204
+
205
+ append_to_file "app/javascript/controllers/index.js", js_content
206
+ say " ✅ Added InfoTable controllers to controllers/index.js", :green
207
+ else
208
+ say " ℹ️ InfoTable controllers already registered in controllers/index.js", :blue
209
+ end
210
+ elsif File.exist?("app/javascript/application.js")
211
+ content = File.read("app/javascript/application.js")
212
+
213
+ unless content.include?("infotable")
214
+ # Find the application instance in application.js
215
+ application_var = if content.include?("const application")
216
+ "application"
217
+ elsif content.include?("export { application }")
218
+ "application"
219
+ else
220
+ "Stimulus.Application.start()"
221
+ end
222
+
223
+ js_content = <<~JS
224
+
225
+ // InfoTable Stimulus controllers (resolved from Rails asset paths)
226
+ import InfoTableTableController from "infotable/controllers/table_controller"
227
+ import InfoTableDrawerController from "infotable/controllers/drawer_controller"
228
+ #{application_var}.register("infotable--table", InfoTableTableController)
229
+ #{application_var}.register("infotable--drawer", InfoTableDrawerController)
230
+ JS
231
+
232
+ append_to_file "app/javascript/application.js", js_content
233
+ say " ✅ Added InfoTable controllers to application.js", :green
234
+ else
235
+ say " ℹ️ InfoTable controllers already registered in application.js", :blue
236
+ end
237
+ else
238
+ say " ⚠️ Could not find controllers/index.js or application.js", :yellow
239
+ say " Please manually import InfoTable controllers", :yellow
240
+ end
241
+
242
+ say "✅ esbuild configured successfully", :green
243
+ end
244
+
245
+ def setup_webpack(gem_path)
246
+ # Check for required peer dependencies
247
+ ensure_npm_dependencies
248
+
249
+ # Add import for InfoTable controllers from asset paths
250
+ if File.exist?("app/javascript/controllers/index.js")
251
+ content = File.read("app/javascript/controllers/index.js")
252
+
253
+ unless content.include?("infotable")
254
+ # Check if application is already imported
255
+ unless content.match?(/from\s+['"]\.\/application['"]/)
256
+ inject_into_file "app/javascript/controllers/index.js", after: /import.*\n/ do
257
+ "import { application } from \"./application\"\n"
258
+ end
259
+ end
260
+
261
+ js_content = <<~JS
262
+
263
+ // InfoTable Stimulus controllers (resolved from Rails asset paths)
264
+ import InfoTableTableController from "infotable/controllers/table_controller"
265
+ import InfoTableDrawerController from "infotable/controllers/drawer_controller"
266
+ application.register("infotable--table", InfoTableTableController)
267
+ application.register("infotable--drawer", InfoTableDrawerController)
268
+ JS
269
+
270
+ append_to_file "app/javascript/controllers/index.js", js_content
271
+ say " ✅ Added InfoTable controllers to controllers/index.js", :green
272
+ else
273
+ say " ℹ️ InfoTable controllers already registered", :blue
274
+ end
275
+ elsif File.exist?("app/javascript/application.js")
276
+ content = File.read("app/javascript/application.js")
277
+
278
+ unless content.include?("infotable")
279
+ # Find the application instance in application.js
280
+ application_var = if content.include?("const application")
281
+ "application"
282
+ elsif content.include?("export { application }")
283
+ "application"
284
+ else
285
+ "Stimulus.Application.start()"
286
+ end
287
+
288
+ js_content = <<~JS
289
+
290
+ // InfoTable Stimulus controllers (resolved from Rails asset paths)
291
+ import InfoTableTableController from "infotable/controllers/table_controller"
292
+ import InfoTableDrawerController from "infotable/controllers/drawer_controller"
293
+ #{application_var}.register("infotable--table", InfoTableTableController)
294
+ #{application_var}.register("infotable--drawer", InfoTableDrawerController)
295
+ JS
296
+
297
+ append_to_file "app/javascript/application.js", js_content
298
+ say " ✅ Added InfoTable controllers to application.js", :green
299
+ else
300
+ say " ℹ️ InfoTable controllers already registered", :blue
301
+ end
302
+ end
303
+
304
+ say "✅ webpack configured successfully", :green
305
+ end
306
+
307
+ def setup_vite(gem_path)
308
+ # Check for required peer dependencies
309
+ ensure_npm_dependencies
310
+
311
+ # Add import for InfoTable controllers from asset paths
312
+ if File.exist?("app/javascript/controllers/index.js")
313
+ content = File.read("app/javascript/controllers/index.js")
314
+
315
+ unless content.include?("infotable")
316
+ # Check if application is already imported
317
+ unless content.match?(/from\s+['"]\.\/application['"]/)
318
+ inject_into_file "app/javascript/controllers/index.js", after: /import.*\n/ do
319
+ "import { application } from \"./application\"\n"
320
+ end
321
+ end
322
+
323
+ js_content = <<~JS
324
+
325
+ // InfoTable Stimulus controllers (resolved from Rails asset paths)
326
+ import InfoTableTableController from "infotable/controllers/table_controller"
327
+ import InfoTableDrawerController from "infotable/controllers/drawer_controller"
328
+ application.register("infotable--table", InfoTableTableController)
329
+ application.register("infotable--drawer", InfoTableDrawerController)
330
+ JS
331
+
332
+ append_to_file "app/javascript/controllers/index.js", js_content
333
+ say " ✅ Added InfoTable controllers to controllers/index.js", :green
334
+ else
335
+ say " ℹ️ InfoTable controllers already registered", :blue
336
+ end
337
+ elsif File.exist?("app/javascript/application.js")
338
+ content = File.read("app/javascript/application.js")
339
+
340
+ unless content.include?("infotable")
341
+ # Find the application instance in application.js
342
+ application_var = if content.include?("const application")
343
+ "application"
344
+ elsif content.include?("export { application }")
345
+ "application"
346
+ else
347
+ "Stimulus.Application.start()"
348
+ end
349
+
350
+ js_content = <<~JS
351
+
352
+ // InfoTable Stimulus controllers (resolved from Rails asset paths)
353
+ import InfoTableTableController from "infotable/controllers/table_controller"
354
+ import InfoTableDrawerController from "infotable/controllers/drawer_controller"
355
+ #{application_var}.register("infotable--table", InfoTableTableController)
356
+ #{application_var}.register("infotable--drawer", InfoTableDrawerController)
357
+ JS
358
+
359
+ append_to_file "app/javascript/application.js", js_content
360
+ say " ✅ Added InfoTable controllers to application.js", :green
361
+ else
362
+ say " ℹ️ InfoTable controllers already registered", :blue
363
+ end
364
+ end
365
+
366
+ say "✅ vite configured successfully", :green
367
+ end
368
+
369
+ def create_vite_config(gem_path)
370
+ return if File.exist?("vite.config.ts") || File.exist?("vite.config.js")
371
+
372
+ vite_config = <<~JS
373
+ import { defineConfig } from 'vite'
374
+ import RubyPlugin from 'vite-plugin-ruby'
375
+
376
+ export default defineConfig({
377
+ plugins: [RubyPlugin()],
378
+ resolve: {
379
+ alias: {
380
+ 'infotable': '#{gem_path}/app/assets/javascripts/infotable'
381
+ }
382
+ }
383
+ })
384
+ JS
385
+
386
+ create_file "vite.config.ts", vite_config
387
+ say " Created vite.config.ts with infotable alias", :green
388
+ end
389
+
390
+ def should_create_vite_config?
391
+ !File.exist?("vite.config.ts") && !File.exist?("vite.config.js")
392
+ end
393
+
394
+ def ensure_npm_dependencies
395
+ # Only check for bundlers that require npm packages
396
+ return unless [ :esbuild, :webpack, :vite ].include?(@js_bundler)
397
+
398
+ unless File.exist?("package.json")
399
+ say " ⚠️ No package.json found", :yellow
400
+ say " 📝 You'll need to install npm dependencies manually", :cyan
401
+ return
402
+ end
403
+
404
+ package_json = JSON.parse(File.read("package.json"))
405
+ package_json["dependencies"] ||= {}
406
+ package_json["devDependencies"] ||= {}
407
+
408
+ missing_deps = []
409
+ missing_tailwind_deps = []
410
+
411
+ # Check for required peer dependencies
412
+ unless package_json["dependencies"]["@hotwired/stimulus"] || package_json["devDependencies"]["@hotwired/stimulus"]
413
+ missing_deps << "@hotwired/stimulus"
414
+ end
415
+
416
+ unless package_json["dependencies"]["sortablejs"] || package_json["devDependencies"]["sortablejs"]
417
+ missing_deps << "sortablejs"
418
+ end
419
+
420
+ # Check for Tailwind plugin (recommended, not required)
421
+ unless package_json["dependencies"]["@tailwindcss/forms"] || package_json["devDependencies"]["@tailwindcss/forms"]
422
+ missing_tailwind_deps << "@tailwindcss/forms"
423
+ end
424
+
425
+ if missing_deps.any?
426
+ say "\n" + "=" * 60, :red
427
+ say "❌ ERROR: Missing required npm dependencies", :red
428
+ say "=" * 60, :red
429
+ say "\nInfoTable requires the following npm packages:", :yellow
430
+ missing_deps.each do |dep|
431
+ say " • #{dep}", :yellow
432
+ end
433
+ say "\nThese are required for esbuild/webpack/vite to bundle the controllers.", :cyan
434
+ say "\nInstall them by running:", :cyan
435
+
436
+ package_manager = if File.exist?("yarn.lock")
437
+ "yarn add @hotwired/stimulus sortablejs"
438
+ elsif File.exist?("pnpm-lock.yaml")
439
+ "pnpm add @hotwired/stimulus sortablejs"
440
+ else
441
+ "npm install @hotwired/stimulus sortablejs"
442
+ end
443
+
444
+ say " #{package_manager}", :green
445
+ say "\nAfter installing, restart your dev server.", :cyan
446
+ say "=" * 60, :red
447
+ exit 1
448
+ else
449
+ say " ✅ All required npm dependencies are installed", :green
450
+ end
451
+
452
+ if missing_tailwind_deps.any?
453
+ say " ℹ️ Recommended Tailwind plugins:", :cyan
454
+ missing_tailwind_deps.each do |dep|
455
+ say " - #{dep}", :cyan
456
+ end
457
+ say " Install with: npm install -D @tailwindcss/forms", :cyan
458
+ end
459
+
460
+ rescue JSON::ParserError => e
461
+ say "⚠️ Could not parse package.json: #{e.message}", :yellow
462
+ end
463
+
464
+ def add_controller_registration
465
+ # This method is no longer needed with npm package approach
466
+ end
467
+
468
+ def find_gem_path
469
+ # Use Bundler to find the gem path - works with path:, git:, and rubygems installs
470
+ if defined?(Bundler)
471
+ begin
472
+ spec = Bundler.load.specs.find { |s| s.name == "infotable" }
473
+ return spec.full_gem_path if spec
474
+ rescue => e
475
+ say " ⚠️ Could not find gem via Bundler: #{e.message}", :yellow
476
+ end
477
+ end
478
+
479
+ # Fallback to Gem::Specification
480
+ begin
481
+ spec = Gem::Specification.find_by_name("infotable")
482
+ spec.gem_dir
483
+ rescue Gem::MissingSpecError
484
+ # Last resort: use relative path from generator location
485
+ File.expand_path("../../..", __dir__)
486
+ end
487
+ end
488
+
489
+ def append_tailwind_v4_sources
490
+ tailwind_file = if File.exist?("app/assets/stylesheets/application.tailwind.css")
491
+ "app/assets/stylesheets/application.tailwind.css"
492
+ elsif File.exist?("app/assets/tailwind/application.css")
493
+ "app/assets/tailwind/application.css"
494
+ else
495
+ say " ⚠️ Tailwind CSS file not found", :yellow
496
+ return
497
+ end
498
+
499
+ # Create separate infotable.css file with resolved gem paths
500
+ create_infotable_css_file
501
+
502
+ # Tailwind only scans the gem's views if the main CSS imports the generated file
503
+ content = File.read(tailwind_file)
504
+ if content.match?(/@import\s+["']\.?\/?infotable["']/)
505
+ say " ℹ️ InfoTable import already exists in #{File.basename(tailwind_file)}", :blue
506
+ else
507
+ if content.include?("@import \"tailwindcss\"")
508
+ inject_into_file tailwind_file, after: /@import\s+["']tailwindcss["'];?\s*\n/ do
509
+ "\n/* InfoTable - Requires tailwindcss-rails gem */\n@import './infotable';\n"
510
+ end
511
+ else
512
+ prepend_to_file tailwind_file, "/* InfoTable - Requires tailwindcss-rails gem */\n@import './infotable';\n\n"
513
+ end
514
+
515
+ say " ✅ Added @import './infotable' to #{File.basename(tailwind_file)}", :green
516
+ end
517
+
518
+ say " 📝 Run: rails tailwindcss:build", :cyan
519
+ end
520
+
521
+ def create_infotable_css_file
522
+ # Determine the stylesheets directory
523
+ stylesheets_dir = if File.exist?("app/assets/stylesheets")
524
+ "app/assets/stylesheets"
525
+ elsif File.exist?("app/assets/tailwind")
526
+ "app/assets/tailwind"
527
+ else
528
+ "app/assets/stylesheets"
529
+ end
530
+
531
+ infotable_css_path = File.join(stylesheets_dir, "infotable.css.erb")
532
+
533
+ # Create ERB file that dynamically resolves gem paths
534
+ css_content = <<~CSS
535
+ /* InfoTable Tailwind Sources - ERB Template */
536
+ /* Requires: tailwindcss-rails gem (https://github.com/rails/tailwindcss-rails) */
537
+ /* Paths are resolved dynamically via ERB from Infotable module */
538
+
539
+ /* Local table definitions in your Rails app */
540
+ @source "../../app/infotable";
541
+
542
+ /* InfoTable gem paths (resolved via ERB at build time) */
543
+ @source "<%= Infotable.views_path %>";
544
+ @source "<%= Infotable.formatters_path %>";
545
+ CSS
546
+
547
+ create_file infotable_css_path, css_content
548
+
549
+ say " ✅ Created #{infotable_css_path} with ERB templates", :green
550
+ say " 📝 Gem paths resolve dynamically via Infotable module", :cyan
551
+ say " 📝 Works in all environments automatically", :green
552
+ end
553
+
554
+ def package_json_has?(package)
555
+ return false unless File.exist?("package.json")
556
+
557
+ package_json = JSON.parse(File.read("package.json"))
558
+ (package_json["dependencies"]&.key?(package) ||
559
+ package_json["devDependencies"]&.key?(package))
560
+ rescue JSON::ParserError
561
+ false
562
+ end
563
+ end
564
+ end
565
+ end
@@ -0,0 +1,131 @@
1
+ # Tailwind Configuration for InfoTable
2
+
3
+ InfoTable supports both Tailwind CSS v4 (recommended) and v3.
4
+
5
+ ## Tailwind v4 (CSS-based Config) - RECOMMENDED
6
+
7
+ Tailwind v4 uses CSS-based configuration with `@source` directives. The installer creates a separate CSS file for InfoTable.
8
+
9
+ ### What the Installer Does
10
+
11
+ When you run `rails generate infotable:install`, it:
12
+
13
+ 1. **Creates `app/assets/stylesheets/infotable.css`** with ENV variable references:
14
+ ```css
15
+ /* InfoTable Tailwind Sources */
16
+
17
+ /* Local table definitions in your Rails app */
18
+ @source "../../app/infotable";
19
+
20
+ /* InfoTable gem paths (resolved dynamically via ENV variables) */
21
+ @source "$INFOTABLE_VIEWS_PATH";
22
+ @source "$INFOTABLE_FORMATTERS_PATH";
23
+ ```
24
+
25
+ 2. **Adds import to `application.tailwind.css`**:
26
+ ```css
27
+ @import "tailwindcss";
28
+
29
+ /* InfoTable gem styles */
30
+ @import './infotable';
31
+ ```
32
+
33
+ ### Why This Approach?
34
+
35
+ - **Always correct**: Rails engine knows the exact gem location
36
+ - **Environment-agnostic**: Works in dev, staging, production, Docker, CI
37
+ - **Safe to commit**: No environment-specific paths
38
+ - **Zero maintenance**: Install once, works everywhere
39
+ - **Simple**: No glob patterns or complex path logic
40
+
41
+ ### ENV Variable Strategy
42
+
43
+ The InfoTable engine sets ENV variables at Rails boot time:
44
+ ```ruby
45
+ # lib/infotable/engine.rb
46
+ initializer "infotable.tailwind" do
47
+ ENV["INFOTABLE_VIEWS_PATH"] = root.join("app/views/infotable").to_s
48
+ ENV["INFOTABLE_FORMATTERS_PATH"] = root.join("lib/infotable/formatters").to_s
49
+ end
50
+ ```
51
+
52
+ Tailwind v4 expands these ENV variables when building:
53
+ ```css
54
+ @source "$INFOTABLE_VIEWS_PATH";
55
+ ```
56
+ → Expands to the actual gem path at build time
57
+
58
+ ### Works Everywhere Automatically
59
+
60
+ The same CSS file works in all environments:
61
+ - **Development**: ENV variables point to your local gem location
62
+ - **Production**: ENV variables point to production gem location
63
+ - **CI/CD**: ENV variables point to CI gem location
64
+ - **Docker**: ENV variables point to container gem location
65
+
66
+ Rails `Infotable::Engine.root` always returns the correct path!
67
+
68
+ You can safely commit `infotable.css` to version control!
69
+
70
+ ## Tailwind v3 (JS-based Config) - FALLBACK
71
+
72
+ If using Tailwind v3, the installer adds dynamic path resolution to your `tailwind.config.js`:
73
+
74
+ ```javascript
75
+ const { execSync } = require('child_process');
76
+ const path = require('path');
77
+
78
+ // Helper to resolve gem path dynamically
79
+ function resolveGemPath(gemName) {
80
+ try {
81
+ const bundlerCmd = `bundle show ${gemName}`;
82
+ const gemPath = execSync(bundlerCmd, { encoding: 'utf-8' }).trim();
83
+ return gemPath;
84
+ } catch (error) {
85
+ console.warn(`Warning: Could not resolve ${gemName} gem path:`, error.message);
86
+ return null;
87
+ }
88
+ }
89
+
90
+ const infotableGemPath = resolveGemPath('infotable');
91
+
92
+ module.exports = {
93
+ content: [
94
+ './app/infotable/**/*.rb',
95
+ ...(infotableGemPath ? [
96
+ path.join(infotableGemPath, 'app/views/infotable/**/*.{erb,rb}'),
97
+ path.join(infotableGemPath, 'app/assets/javascripts/infotable/**/*.js'),
98
+ path.join(infotableGemPath, 'lib/infotable/formatters/**/*.rb'),
99
+ path.join(infotableGemPath, 'app/components/**/*.{erb,rb}'),
100
+ ] : []),
101
+ ],
102
+ // ... rest of config
103
+ }
104
+ ```
105
+
106
+ This approach dynamically resolves the gem path at **build time** (every time Tailwind builds).
107
+
108
+ ## Troubleshooting
109
+
110
+ ### Styles Not Appearing
111
+
112
+ 1. Check your Tailwind CSS file includes InfoTable @source directives
113
+ 2. Verify gem path is correct: `bundle show infotable`
114
+ 3. Update paths if needed: `rake infotable:update_tailwind_paths`
115
+ 4. Rebuild: `rails tailwindcss:build`
116
+
117
+ ### After Changing Gem Installation Method
118
+
119
+ If you switch from `path:` to `git:` or vice versa:
120
+
121
+ ```bash
122
+ rake infotable:update_tailwind_paths
123
+ rails tailwindcss:build
124
+ ```
125
+
126
+ ## Why Different Approaches?
127
+
128
+ - **Tailwind v4**: @source doesn't support variables, so paths must be static and resolved at install time
129
+ - **Tailwind v3**: JavaScript config supports dynamic resolution via `execSync`, so paths are resolved at build time
130
+
131
+ Both approaches work reliably, but v4 is recommended for better Rails integration and performance.