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,463 @@
1
+ module Infotable
2
+ class Base
3
+ class << self
4
+ attr_accessor :table_columns, :table_query, :table_query_sql, :pagination_settings, :sort_settings, :height_settings, :infinite_scroll_settings, :row_link_settings, :slim_rows_settings, :row_size_settings, :filters_button_settings, :display_settings, :nil_value_settings, :empty_value_settings, :table_authorize, :checkbox_settings, :batch_action_list, :countless_setting, :url_state_setting
5
+
6
+ def inherited(subclass)
7
+ super
8
+ subclass.table_columns = []
9
+ subclass.pagination_settings = {
10
+ per_page: Infotable.configuration.default_per_page,
11
+ per_page_options: Infotable.configuration.per_page_options
12
+ }
13
+ subclass.sort_settings = {}
14
+ subclass.height_settings = {
15
+ min_height: nil, # nil means use global config
16
+ max_height: nil # nil means use global config
17
+ }
18
+ subclass.infinite_scroll_settings = {
19
+ enabled: false
20
+ }
21
+ subclass.row_link_settings = {
22
+ enabled: false,
23
+ url: nil
24
+ }
25
+ subclass.slim_rows_settings = {
26
+ enabled: false
27
+ }
28
+ subclass.row_size_settings = {
29
+ size: :default # :slim, :mid, :default
30
+ }
31
+ subclass.filters_button_settings = {
32
+ visible: true
33
+ }
34
+ subclass.display_settings = {
35
+ title: nil,
36
+ subtitle: nil
37
+ }
38
+ subclass.nil_value_settings = {
39
+ value: nil # nil means use global config
40
+ }
41
+ subclass.empty_value_settings = {
42
+ value: nil # nil means use global config
43
+ }
44
+ subclass.checkbox_settings = { enabled: false, id_method: :id }
45
+ subclass.batch_action_list = []
46
+ subclass.countless_setting = false
47
+ subclass.url_state_setting = nil
48
+
49
+ inherit_from_parent(subclass)
50
+ end
51
+
52
+ # Copy parent table definition so a subclass can extend it
53
+ def inherit_from_parent(subclass)
54
+ parent = subclass.superclass
55
+ return if parent == Infotable::Base || parent.table_columns.nil?
56
+
57
+ subclass.table_columns = parent.table_columns.map(&:dup)
58
+ subclass.table_query = parent.table_query
59
+ subclass.table_query_sql = parent.table_query_sql
60
+ subclass.table_authorize = parent.table_authorize
61
+ subclass.checkbox_settings = parent.checkbox_settings.dup
62
+ subclass.batch_action_list = parent.batch_action_list.map(&:dup)
63
+ subclass.countless_setting = parent.countless_setting
64
+ subclass.url_state_setting = parent.url_state_setting
65
+ %i[pagination_settings sort_settings height_settings infinite_scroll_settings row_link_settings
66
+ slim_rows_settings row_size_settings filters_button_settings display_settings
67
+ nil_value_settings empty_value_settings].each do |attr|
68
+ subclass.public_send("#{attr}=", parent.public_send(attr).dup)
69
+ end
70
+ end
71
+
72
+ # Define data source using ActiveRecord.
73
+ # The block receives the controller (current_user, params, ...) for per-request scoping:
74
+ # query { |controller| Post.where(account: controller.current_account) }
75
+ def query(&block)
76
+ @table_query = block
77
+ end
78
+
79
+ # Skip SELECT COUNT(*) (slow on big tables): no total, no page numbers, only prev/next.
80
+ def countless(enabled = true)
81
+ @countless_setting = enabled
82
+ end
83
+
84
+ # Keep search, filters, sort and page in the page's address bar (shareable links, working Back
85
+ # button). Default: Infotable.configuration.url_state. Turn it off for tables in modals/tabs
86
+ # or when several unrelated tables should not fight over the URL.
87
+ def url_state(enabled = true)
88
+ @url_state_setting = enabled
89
+ end
90
+
91
+ def url_state?
92
+ @url_state_setting.nil? ? Infotable.configuration.url_state : @url_state_setting ? true : false
93
+ end
94
+
95
+ def countless?
96
+ @countless_setting ? true : false
97
+ end
98
+
99
+ # Row checkboxes without bulk actions (e.g. to read the selection from your own JS)
100
+ def checkbox_column(enabled: true, id_method: :id)
101
+ @checkbox_settings = { enabled: enabled, id_method: id_method }
102
+ end
103
+
104
+ # Bulk actions; defining any turns the checkbox column on. See Infotable::BatchActions.
105
+ def batch_actions(&block)
106
+ builder = Infotable::BatchActions::Builder.new
107
+ builder.instance_eval(&block)
108
+ @batch_action_list = builder.actions
109
+ end
110
+
111
+ def checkbox_enabled?
112
+ @checkbox_settings[:enabled] || @batch_action_list.any?
113
+ end
114
+
115
+ def checkbox_value(record)
116
+ record.public_send(@checkbox_settings[:id_method])
117
+ end
118
+
119
+ # Per-table access check. Block receives the controller; falsy => 403.
120
+ # authorize { |controller| controller.current_user&.admin? }
121
+ def authorize(&block)
122
+ @table_authorize = block
123
+ end
124
+
125
+ def authorized?(context = nil)
126
+ @table_authorize.nil? || @table_authorize.call(context) ? true : false
127
+ end
128
+
129
+ # Define data source using raw SQL
130
+ # Raw SQL data source. The SQL becomes a sub-select aliased as the model's table, so
131
+ # filters, search, sorting, pagination and export work like with `query`:
132
+ # query_sql "SELECT u.*, COUNT(p.id) AS posts_n FROM users u LEFT JOIN posts p ON ... GROUP BY u.id", model: User
133
+ # The SQL is developer-defined (never interpolate request input into it).
134
+ def query_sql(sql, model:)
135
+ raise ArgumentError, "query_sql requires a model: (an ActiveRecord class used for types and the table alias)" unless model.respond_to?(:table_name)
136
+
137
+ @table_query_sql = { sql: sql.to_s, model: model }
138
+ end
139
+
140
+ # Define a column
141
+ def column(name, **options)
142
+ @table_columns ||= []
143
+ type = options[:type] || :string
144
+
145
+ # Determine width - use custom if provided and not blank, otherwise use default
146
+ width = if options[:width].present?
147
+ options[:width]
148
+ else
149
+ default_column_width(type)
150
+ end
151
+
152
+ column_definition = {
153
+ name: name,
154
+ label: options[:label] || name.to_s.tr("_", " "),
155
+ type: type,
156
+ sortable: options.fetch(:sortable, false),
157
+ searchable: options.fetch(:searchable, false),
158
+ filterable: options.fetch(:filterable, false),
159
+ visible: options.fetch(:visible, true),
160
+ pinned: options[:pinned], # :left, :right, or nil
161
+ virtual: options.fetch(:virtual, false), # Virtual/computed columns (aggregates, subqueries)
162
+ virtual_sql: options[:virtual_sql], # SQL expression for aggregate columns (used in HAVING clause)
163
+ sql: options[:sql], # Row-level SQL expression for filtering/sorting (WHERE), e.g. "authors.name"
164
+ sort_sql: options[:sort_sql], # Overrides :sql for ORDER BY only, e.g. filter by id, sort by name
165
+ value: options[:value], # ->(record) { ... } reads the cell value when it is not a record attribute
166
+ includes: options[:includes], # association(s) to preload for this column (avoids N+1), e.g. :author
167
+ formatter: options[:formatter],
168
+ formatter_options: options[:formatter_options],
169
+ actions: options[:actions], # For action columns (deprecated - use formatter_options)
170
+ filter_type: options[:filter_type],
171
+ filter_options: options[:filter_options],
172
+ filter_options_cache: options[:filter_options_cache], # e.g. 5.minutes: cache a Proc's result (not for per-user options)
173
+ filter_options_limit: options[:filter_options_limit], # max <option>s rendered; default Infotable.configuration.max_filter_options
174
+ filter_multi_select: options.fetch(:filter_multi_select, false),
175
+ width: width
176
+ }
177
+ @table_columns << column_definition
178
+ end
179
+
180
+ # Get default width based on column type
181
+ def default_column_width(type)
182
+ case type
183
+ when :boolean
184
+ "80px"
185
+ when :date
186
+ "120px"
187
+ when :datetime
188
+ "180px"
189
+ when :integer
190
+ "100px"
191
+ when :decimal, :float, :currency
192
+ "120px"
193
+ when :badge, :status
194
+ "120px"
195
+ when :action, :actions
196
+ "100px"
197
+ when :text
198
+ "300px"
199
+ when :string
200
+ "200px"
201
+ else
202
+ "200px"
203
+ end
204
+ end
205
+
206
+ # Set pagination per_page
207
+ def per_page(value)
208
+ @pagination_settings[:per_page] = value
209
+ end
210
+
211
+ # Set per_page options
212
+ def per_page_options(values)
213
+ @pagination_settings[:per_page_options] = values
214
+ end
215
+
216
+ # Set default sort
217
+ def default_sort(column, direction = :asc)
218
+ @sort_settings = { column: column, direction: direction }
219
+ end
220
+
221
+ # Set table minimum height
222
+ def table_min_height(value)
223
+ @height_settings[:min_height] = value
224
+ end
225
+
226
+ # Set table maximum height
227
+ def table_max_height(value)
228
+ @height_settings[:max_height] = value
229
+ end
230
+
231
+ # Enable/disable infinite scroll
232
+ def infinite_scroll(enabled)
233
+ @infinite_scroll_settings[:enabled] = enabled
234
+ end
235
+
236
+ # Check if infinite scroll is enabled
237
+ def infinite_scroll_enabled?
238
+ @infinite_scroll_settings[:enabled]
239
+ end
240
+
241
+ # Configure row link
242
+ # @param url [Proc, String] - URL generator (Proc receives record) or static URL
243
+ # @example
244
+ # row_link ->(record) { "/comments/#{record.id}" }
245
+ # row_link "/posts/show"
246
+ def row_link(url)
247
+ @row_link_settings[:enabled] = true
248
+ @row_link_settings[:url] = url
249
+ end
250
+
251
+ # Check if row link is enabled
252
+ def row_link_enabled?
253
+ @row_link_settings[:enabled]
254
+ end
255
+
256
+ # Get row link URL for a record
257
+ def row_link_url(record)
258
+ return nil unless row_link_enabled?
259
+
260
+ url_generator = @row_link_settings[:url]
261
+ if url_generator.is_a?(Proc)
262
+ url_generator.call(record)
263
+ else
264
+ url_generator
265
+ end
266
+ end
267
+
268
+ # Set row size (padding and text size)
269
+ # @param size [Symbol] - Row size (:slim, :mid, :default)
270
+ # @example
271
+ # row_size :slim
272
+ # row_size :mid
273
+ # row_size :default
274
+ def row_size(size)
275
+ valid_sizes = [ :slim, :mid, :default ]
276
+ unless valid_sizes.include?(size)
277
+ raise ArgumentError, "Invalid row size: #{size}. Must be one of #{valid_sizes.join(', ')}"
278
+ end
279
+ @row_size_settings ||= { size: :default }
280
+ @row_size_settings[:size] = size
281
+
282
+ # Also set slim_rows for backward compatibility
283
+ @slim_rows_settings ||= { enabled: false }
284
+ @slim_rows_settings[:enabled] = (size == :slim)
285
+ end
286
+
287
+ # Get current row size
288
+ def row_size_value
289
+ @row_size_settings ||= { size: :default }
290
+
291
+ # Check slim_rows for backward compatibility
292
+ if @slim_rows_settings && @slim_rows_settings[:enabled]
293
+ return :slim
294
+ end
295
+
296
+ @row_size_settings[:size]
297
+ end
298
+
299
+ # Enable slim rows (compact padding) - DEPRECATED: Use row_size(:slim) instead
300
+ # @example
301
+ # slim_rows true
302
+ def slim_rows(enabled = true)
303
+ @slim_rows_settings ||= { enabled: false }
304
+ @slim_rows_settings[:enabled] = enabled
305
+
306
+ # Also update row_size_settings for consistency
307
+ @row_size_settings ||= { size: :default }
308
+ @row_size_settings[:size] = enabled ? :slim : :default
309
+ end
310
+
311
+ # Check if slim rows is enabled - DEPRECATED: Use row_size_value instead
312
+ def slim_rows_enabled?
313
+ row_size_value == :slim
314
+ end
315
+
316
+ # Show/hide filters button
317
+ # @param visible [Boolean] - Whether to show the filters button
318
+ # @example
319
+ # show_filters_button false
320
+ def show_filters_button(visible = true)
321
+ @filters_button_settings ||= { visible: true }
322
+ @filters_button_settings[:visible] = visible
323
+ end
324
+
325
+ # Check if filters button is visible
326
+ def filters_button_visible?
327
+ @filters_button_settings ||= { visible: true }
328
+ @filters_button_settings[:visible]
329
+ end
330
+
331
+ # Set table title
332
+ # @param text [String] - The title to display above the table
333
+ # @example
334
+ # table_title "User Management"
335
+ def table_title(text)
336
+ @display_settings ||= { title: nil, subtitle: nil }
337
+ @display_settings[:title] = text
338
+ end
339
+
340
+ # Set table subtitle
341
+ # @param text [String] - The subtitle to display below the title
342
+ # @example
343
+ # table_subtitle "Manage all users in the system"
344
+ def table_subtitle(text)
345
+ @display_settings ||= { title: nil, subtitle: nil }
346
+ @display_settings[:subtitle] = text
347
+ end
348
+
349
+ # Get table title
350
+ def title
351
+ @display_settings ||= { title: nil, subtitle: nil }
352
+ @display_settings[:title]
353
+ end
354
+
355
+ # Get table subtitle
356
+ def subtitle
357
+ @display_settings ||= { title: nil, subtitle: nil }
358
+ @display_settings[:subtitle]
359
+ end
360
+
361
+ # Set nil value placeholder
362
+ # @param text [String] - The text to display for nil values
363
+ # @example
364
+ # nil_value "N/A"
365
+ # nil_value ""
366
+ def nil_value(text)
367
+ @nil_value_settings ||= { value: nil }
368
+ @nil_value_settings[:value] = text
369
+ end
370
+
371
+ # Get nil value placeholder (table-specific or global)
372
+ def nil_value_placeholder
373
+ @nil_value_settings ||= { value: nil }
374
+ @nil_value_settings[:value].nil? ? Infotable.configuration.nil_value : @nil_value_settings[:value]
375
+ end
376
+
377
+ # Set empty value placeholder
378
+ # @param text [String] - The text to display for empty strings
379
+ # @example
380
+ # empty_value "N/A"
381
+ # empty_value ""
382
+ def empty_value(text)
383
+ @empty_value_settings ||= { value: nil }
384
+ @empty_value_settings[:value] = text
385
+ end
386
+
387
+ # Get empty value placeholder (table-specific or global)
388
+ def empty_value_placeholder
389
+ @empty_value_settings ||= { value: nil }
390
+ @empty_value_settings[:value].nil? ? Infotable.configuration.empty_value : @empty_value_settings[:value]
391
+ end
392
+
393
+ # Get minimum height (table-specific or global)
394
+ def min_height
395
+ @height_settings[:min_height] || Infotable.configuration.table_min_height
396
+ end
397
+
398
+ # Get maximum height (table-specific or global)
399
+ def max_height
400
+ @height_settings[:max_height] || Infotable.configuration.table_max_height
401
+ end
402
+
403
+ # Get the base query
404
+ def base_query(context = nil)
405
+ if @table_query
406
+ @table_query.call(context)
407
+ elsif @table_query_sql
408
+ model = @table_query_sql[:model]
409
+ model.from(Arel.sql("(#{@table_query_sql[:sql]}) AS #{model.connection.quote_table_name(model.table_name)}"))
410
+ else
411
+ raise Infotable::Error, "No query or query_sql defined for #{name}"
412
+ end
413
+ end
414
+
415
+ # Raw cell value of a record for a column (value: proc, attribute or association)
416
+ def cell_value(record, column)
417
+ return column[:value].call(record) if column[:value]
418
+
419
+ record.try(column[:name]) || record[column[:name]]
420
+ end
421
+
422
+ # Associations to preload for the table's columns (deduplicated)
423
+ def preloads
424
+ columns.flat_map { |col| Array.wrap(col[:includes]) }.uniq
425
+ end
426
+
427
+ # Get all columns
428
+ def columns
429
+ @table_columns || []
430
+ end
431
+
432
+ # Get virtual (computed/aggregated) columns
433
+ def virtual_columns
434
+ columns.select { |col| col[:virtual] }
435
+ end
436
+
437
+ # Get non-virtual (regular database) columns
438
+ def regular_columns
439
+ columns.reject { |col| col[:virtual] }
440
+ end
441
+
442
+ # Get visible columns
443
+ def visible_columns
444
+ columns.select { |col| col[:visible] }
445
+ end
446
+
447
+ # Get sortable columns
448
+ def sortable_columns
449
+ columns.select { |col| col[:sortable] }
450
+ end
451
+
452
+ # Get filterable columns
453
+ def filterable_columns
454
+ columns.select { |col| col[:filterable] }
455
+ end
456
+
457
+ # Get searchable columns
458
+ def searchable_columns
459
+ columns.select { |col| col[:searchable] }
460
+ end
461
+ end
462
+ end
463
+ end
@@ -0,0 +1,34 @@
1
+ module Infotable
2
+ # Row selection + bulk actions. The gem renders the checkboxes and the action bar;
3
+ # the host app implements the endpoints and receives the ids as `ids[]`.
4
+ #
5
+ # class PostsTable < Infotable::Base
6
+ # batch_actions do
7
+ # action :archive, label: "Archive", url: "/posts/archive", method: :patch
8
+ # action :delete, label: "Delete", url: "/posts/delete", method: :delete,
9
+ # confirm: "Delete %{count} posts?", color: :danger
10
+ # end
11
+ # end
12
+ module BatchActions
13
+ METHODS = %i[get post put patch delete].freeze
14
+ COLORS = %i[default primary success warning danger].freeze
15
+
16
+ class Builder
17
+ attr_reader :actions
18
+
19
+ def initialize
20
+ @actions = []
21
+ end
22
+
23
+ def action(name, url:, label: nil, method: :post, confirm: nil, color: :default)
24
+ method = method.to_s.downcase.to_sym
25
+ color = color.to_s.to_sym
26
+ raise ArgumentError, "batch action :#{name}: url must be a String" unless url.is_a?(String) && url.present?
27
+ raise ArgumentError, "batch action :#{name}: method must be one of #{METHODS.inspect}" unless METHODS.include?(method)
28
+ raise ArgumentError, "batch action :#{name}: color must be one of #{COLORS.inspect}" unless COLORS.include?(color)
29
+
30
+ @actions << { name: name.to_sym, label: label || name.to_s.humanize, url: url, method: method, confirm: confirm, color: color }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,53 @@
1
+ module Infotable
2
+ class Configuration
3
+ attr_accessor :default_per_page,
4
+ :per_page_options,
5
+ :drawer_position,
6
+ :drawer_width,
7
+ :theme,
8
+ :turbo_enabled,
9
+ :pagination_gem,
10
+ :table_min_height,
11
+ :table_max_height,
12
+ :autofilter,
13
+ :nil_value,
14
+ :empty_value,
15
+ :parent_controller,
16
+ :max_filter_options,
17
+ :url_state
18
+
19
+ def initialize
20
+ @default_per_page = 25
21
+ @per_page_options = [ 10, 25, 50, 100 ]
22
+ @drawer_position = :right # :left or :right
23
+ @drawer_width = "26rem"
24
+ @theme = :default # no effect yet (kept so existing initializers keep working)
25
+ @turbo_enabled = true # no effect yet
26
+ @pagination_gem = :pagy # no effect yet; Pagy is the only backend
27
+ @table_min_height = "400px"
28
+ @table_max_height = "600px"
29
+ @autofilter = true # true = auto-apply filters (debounced), false = manual apply button
30
+ @nil_value = "-" # Default placeholder for nil values
31
+ @empty_value = "-" # Default placeholder for empty strings
32
+ @url_state = true # mirror table state in the address bar
33
+ @max_filter_options = 500 # select filters render at most this many <option>s (nil = no limit)
34
+ @parent_controller = "ActionController::Base" # Set to "ApplicationController" to inherit host auth/filters
35
+ end
36
+ end
37
+
38
+ class << self
39
+ attr_writer :configuration
40
+
41
+ def configuration
42
+ @configuration ||= Configuration.new
43
+ end
44
+
45
+ def configure
46
+ yield(configuration)
47
+ end
48
+
49
+ def reset_configuration!
50
+ @configuration = Configuration.new
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,51 @@
1
+ require "turbo-rails"
2
+ require "stimulus-rails"
3
+ require "pagy"
4
+
5
+ module Infotable
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace Infotable
8
+
9
+ # Configure engine
10
+ config.infotable = ActiveSupport::OrderedOptions.new
11
+
12
+ # Default configuration values
13
+ config.infotable.table_min_height = "400px"
14
+ config.infotable.table_max_height = "600px"
15
+
16
+ # Rails already adds the engine's app/assets/* directories to the asset path;
17
+ # only the precompile list is needed. Skipped for apps without an asset pipeline.
18
+ initializer "infotable.assets" do |app|
19
+ if app.config.respond_to?(:assets)
20
+ app.config.assets.precompile += %w[infotable/controllers/*.js]
21
+ end
22
+ end
23
+
24
+ # Setup Stimulus controllers for importmap (only runs if importmap-rails is installed)
25
+ initializer "infotable.importmap", before: "importmap" do |app|
26
+ if app.config.respond_to?(:importmap)
27
+ app.config.importmap.paths << root.join("config/importmap.rb") if root.join("config/importmap.rb").exist?
28
+ end
29
+ end
30
+
31
+ # Add helpers to host application
32
+ initializer "infotable.helpers" do
33
+ ActiveSupport.on_load(:action_controller_base) do
34
+ helper Infotable::Engine.helpers
35
+ end
36
+ end
37
+
38
+ # Load engine i18n locale files
39
+ initializer "infotable.i18n" do |app|
40
+ config.i18n.load_path += Dir[root.join("config/locales/**/*.yml")]
41
+ end
42
+
43
+ # Expose gem paths via ENV variables for Tailwind CSS v4 @source directives
44
+ # This allows Tailwind to scan the gem's views/formatters at build time
45
+ # Works in development, production, Docker, CI - everywhere!
46
+ initializer "infotable.tailwind", before: :load_config_initializers 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
+ end
51
+ end