turbo_turbo 0.2.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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/settings.local.json +14 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +96 -0
  5. data/CHANGELOG.md +50 -0
  6. data/LICENSE +21 -0
  7. data/README.md +498 -0
  8. data/Rakefile +12 -0
  9. data/app/assets/stylesheets/turbo_turbo/alerts.css +7 -0
  10. data/app/assets/stylesheets/turbo_turbo/base.css +4 -0
  11. data/app/assets/stylesheets/turbo_turbo/button.css +24 -0
  12. data/app/assets/stylesheets/turbo_turbo/modal.css +81 -0
  13. data/app/components/turbo_turbo/alerts/alert_component.html.erb +36 -0
  14. data/app/components/turbo_turbo/alerts/alert_component.rb +12 -0
  15. data/app/components/turbo_turbo/alerts/error_component.html.erb +36 -0
  16. data/app/components/turbo_turbo/alerts/error_component.rb +12 -0
  17. data/app/components/turbo_turbo/alerts/info_component.html.erb +36 -0
  18. data/app/components/turbo_turbo/alerts/info_component.rb +12 -0
  19. data/app/components/turbo_turbo/alerts/success_component.html.erb +47 -0
  20. data/app/components/turbo_turbo/alerts/success_component.rb +12 -0
  21. data/app/components/turbo_turbo/alerts/warning_component.html.erb +36 -0
  22. data/app/components/turbo_turbo/alerts/warning_component.rb +12 -0
  23. data/app/components/turbo_turbo/modal_component.html.erb +20 -0
  24. data/app/components/turbo_turbo/modal_component.rb +9 -0
  25. data/app/components/turbo_turbo/modal_footer_component.html.erb +6 -0
  26. data/app/components/turbo_turbo/modal_footer_component.rb +10 -0
  27. data/config/locales/en.yml +15 -0
  28. data/config/routes/turbo_turbo_routes.rb +20 -0
  29. data/lib/generators/turbo_turbo/install_generator.rb +351 -0
  30. data/lib/generators/turbo_turbo/layout_generator.rb +221 -0
  31. data/lib/generators/turbo_turbo/templates/config/routes/turbo_turbo_routes.rb +20 -0
  32. data/lib/generators/turbo_turbo/templates/turbo_turbo/_error_message.html.erb +15 -0
  33. data/lib/generators/turbo_turbo/templates/turbo_turbo/_flashes.html.erb +8 -0
  34. data/lib/generators/turbo_turbo/templates/turbo_turbo/_modal_background.html.erb +2 -0
  35. data/lib/generators/turbo_turbo/templates/turbo_turbo/flash_controller.js +28 -0
  36. data/lib/generators/turbo_turbo/templates/turbo_turbo/modal_controller.js +114 -0
  37. data/lib/generators/turbo_turbo/views_generator.rb +57 -0
  38. data/lib/turbo_turbo/controller_helpers.rb +157 -0
  39. data/lib/turbo_turbo/engine.rb +19 -0
  40. data/lib/turbo_turbo/form_helper.rb +135 -0
  41. data/lib/turbo_turbo/parameter_sanitizer.rb +69 -0
  42. data/lib/turbo_turbo/standard_actions.rb +96 -0
  43. data/lib/turbo_turbo/test_helpers.rb +64 -0
  44. data/lib/turbo_turbo/version.rb +5 -0
  45. data/lib/turbo_turbo.rb +15 -0
  46. data/sig/turbo_turbo.rbs +4 -0
  47. data/turbo_turbo.gemspec +42 -0
  48. metadata +136 -0
@@ -0,0 +1,351 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+ require "fileutils"
5
+
6
+ module TurboTurbo
7
+ module Generators
8
+ class InstallGenerator < Rails::Generators::Base
9
+ desc "Install TurboTurbo files"
10
+ source_root File.expand_path("templates", __dir__)
11
+
12
+ def install_javascript_controllers
13
+ empty_directory "app/javascript/controllers/turbo_turbo"
14
+ copy_file "turbo_turbo/modal_controller.js", "app/javascript/controllers/turbo_turbo/modal_controller.js"
15
+ copy_file "turbo_turbo/flash_controller.js", "app/javascript/controllers/turbo_turbo/flash_controller.js"
16
+ end
17
+
18
+ def register_stimulus_controllers
19
+ index_file = "app/javascript/controllers/index.js"
20
+
21
+ unless File.exist?(index_file)
22
+ say "Warning: Could not find #{index_file}", :yellow
23
+ return
24
+ end
25
+
26
+ content = File.read(index_file)
27
+
28
+ # Check if TurboTurbo controllers are already registered
29
+ if content.include?("turbo-turbo--modal") && content.include?("turbo-turbo--flash")
30
+ say "TurboTurbo controllers already registered in #{index_file}", :blue
31
+ return
32
+ end
33
+
34
+ # Add import statements and registrations
35
+ turbo_turbo_imports = <<~JS
36
+
37
+ // TurboTurbo controllers
38
+ import TurboTurboModalController from "./turbo_turbo/modal_controller"
39
+ import TurboTurboFlashController from "./turbo_turbo/flash_controller"
40
+
41
+ application.register("turbo-turbo--modal", TurboTurboModalController)
42
+ application.register("turbo-turbo--flash", TurboTurboFlashController)
43
+ JS
44
+
45
+ # Insert before the last line (usually eagerLoadControllersFrom)
46
+ lines = content.split("\n")
47
+ insert_index = lines.rindex { |line| line.strip.length.positive? && !line.strip.start_with?("//") }
48
+
49
+ if insert_index
50
+ lines.insert(insert_index + 1, turbo_turbo_imports)
51
+ File.write(index_file, lines.join("\n"))
52
+ say "Added TurboTurbo controller registrations to #{index_file}", :green
53
+ else
54
+ say "Warning: Could not determine where to insert TurboTurbo registrations in #{index_file}", :yellow
55
+ end
56
+ end
57
+
58
+ def install_layout_files
59
+ empty_directory "app/views/turbo_turbo"
60
+ copy_file "turbo_turbo/_flashes.html.erb", "app/views/turbo_turbo/_flashes.html.erb"
61
+ copy_file "turbo_turbo/_modal_background.html.erb", "app/views/turbo_turbo/_modal_background.html.erb"
62
+ copy_file "turbo_turbo/_error_message.html.erb", "app/views/turbo_turbo/_error_message.html.erb"
63
+ end
64
+
65
+ def modify_application_controller
66
+ controller_file = "app/controllers/application_controller.rb"
67
+
68
+ unless File.exist?(controller_file)
69
+ say "Warning: Could not find #{controller_file}", :yellow
70
+ return
71
+ end
72
+
73
+ content = File.read(controller_file)
74
+ original_content = content.dup
75
+
76
+ # Add include TurboTurbo::ControllerHelpers if not present
77
+ unless content.include?("include TurboTurbo::ControllerHelpers")
78
+ content = content.gsub(/(class ApplicationController < ActionController::Base\s*\n)/) do
79
+ "#{::Regexp.last_match(1)} include TurboTurbo::ControllerHelpers\n\n"
80
+ end
81
+ end
82
+
83
+ # Add flash types if not present
84
+ unless content.include?("add_flash_types") || content.match?(/add_flash_types\s*:success.*:error.*:warning.*:info/)
85
+ content = content.gsub(/(include TurboTurbo::ControllerHelpers\s*\n)/) do
86
+ "#{::Regexp.last_match(1)} add_flash_types :success, :error, :warning, :info\n"
87
+ end
88
+ end
89
+
90
+ if content == original_content
91
+ say "#{controller_file} already includes TurboTurbo configuration", :blue
92
+ else
93
+ File.write(controller_file, content)
94
+ say "Modified #{controller_file} to include TurboTurbo helpers and flash types", :green
95
+ end
96
+ end
97
+
98
+ def add_css_import
99
+ copy_css_file_to_app
100
+ add_css_import_to_main_file
101
+ end
102
+
103
+ private
104
+
105
+ def copy_css_file_to_app
106
+ # Copy the TurboTurbo CSS files from the gem to the app
107
+ gem_css_dir = File.join(gem_root, "app/assets/stylesheets")
108
+ app_css_dir = "app/assets/stylesheets"
109
+
110
+ # CSS subdirectory (now contains base.css and component CSS files)
111
+ gem_css_subdir = File.join(gem_css_dir, "turbo_turbo")
112
+ app_css_subdir = File.join(app_css_dir, "turbo_turbo")
113
+
114
+ unless File.exist?(gem_css_subdir)
115
+ say "Warning: Could not find turbo_turbo CSS directory in the gem", :red
116
+ return
117
+ end
118
+
119
+ if File.exist?(app_css_subdir)
120
+ say "TurboTurbo CSS directory already exists in #{app_css_dir}", :blue
121
+ return
122
+ end
123
+
124
+ # Copy CSS subdirectory (contains base.css and component files)
125
+ FileUtils.cp_r(gem_css_subdir, app_css_subdir)
126
+
127
+ say "Copied TurboTurbo CSS files to #{app_css_dir}/turbo_turbo", :green
128
+ end
129
+
130
+ def add_css_import_to_main_file
131
+ css_files = [
132
+ "app/assets/stylesheets/application.tailwind.css",
133
+ "app/assets/stylesheets/application.css"
134
+ ]
135
+
136
+ css_file = css_files.find { |file| File.exist?(file) }
137
+
138
+ unless css_file
139
+ say "Warning: Could not find application.tailwind.css or application.css", :red
140
+ say "Please manually add '@import \"./turbo_turbo.css\";' to your main CSS file", :red
141
+ return
142
+ end
143
+
144
+ content = File.read(css_file)
145
+
146
+ # Check if import already exists (check for both old and new import formats)
147
+ if content.include?('@import "turbo_turbo"') || content.include?('@import "./turbo_turbo"') || content.include?('@import "./turbo_turbo.css"') || content.include?('@import "./turbo_turbo/base"')
148
+ say "TurboTurbo CSS import already exists in #{css_file}", :blue
149
+ return
150
+ end
151
+
152
+ # Add import at the top of the file (using relative path to base.css)
153
+ content = "@import \"./turbo_turbo/base\";\n\n#{content}"
154
+ File.write(css_file, content)
155
+ say "Added TurboTurbo CSS import to #{css_file}", :green
156
+ end
157
+
158
+ def gem_root
159
+ @gem_root ||= File.expand_path("../../..", __dir__)
160
+ end
161
+
162
+ def modify_layout_file
163
+ layout_files = [
164
+ "app/views/layouts/application.html.erb",
165
+ "app/views/layouts/application.html.slim"
166
+ ]
167
+
168
+ layout_file = layout_files.find { |file| File.exist?(file) }
169
+
170
+ unless layout_file
171
+ say "Warning: Could not find application layout file", :yellow
172
+ return
173
+ end
174
+
175
+ content = File.read(layout_file)
176
+ original_content = content.dup
177
+
178
+ content = if layout_file.end_with?(".slim")
179
+ modify_slim_layout(content)
180
+ else
181
+ modify_erb_layout(content)
182
+ end
183
+
184
+ if content == original_content
185
+ say "#{layout_file} already includes TurboTurbo components", :blue
186
+ else
187
+ File.write(layout_file, content)
188
+ say "Modified #{layout_file} to include TurboTurbo components", :green
189
+ end
190
+ end
191
+
192
+ def install_routes
193
+ # Copy routes file to config/routes/
194
+ empty_directory "config/routes"
195
+ copy_file "config/routes/turbo_turbo_routes.rb", "config/routes/turbo_turbo_routes.rb"
196
+
197
+ # Add draw(:turbo_turbo_routes) to main routes.rb
198
+ routes_file = "config/routes.rb"
199
+
200
+ unless File.exist?(routes_file)
201
+ say "Warning: Could not find #{routes_file}", :yellow
202
+ return
203
+ end
204
+
205
+ content = File.read(routes_file)
206
+
207
+ # Check if draw(:turbo_turbo_routes) already exists
208
+ if content.include?("draw(:turbo_turbo_routes)") || content.include?('draw("turbo_turbo_routes")')
209
+ say "TurboTurbo routes already included in #{routes_file}", :blue
210
+ return
211
+ end
212
+
213
+ # Add draw(:turbo_turbo_routes) before the final 'end'
214
+ content = content.gsub(/(\s*end\s*)$/) do
215
+ " draw(:turbo_turbo_routes)\n#{::Regexp.last_match(1)}"
216
+ end
217
+
218
+ File.write(routes_file, content)
219
+ say "Added draw(:turbo_turbo_routes) to #{routes_file}", :green
220
+ end
221
+
222
+ def display_instructions
223
+ say "\n\nTurboTurbo has been installed!", :green
224
+ say "\nWhat was installed:"
225
+ say "✅ JavaScript controllers copied to app/javascript/controllers/turbo_turbo/"
226
+ say "✅ Stimulus controllers registered in index.js"
227
+ say "✅ ApplicationController configured with TurboTurbo::ControllerHelpers"
228
+ say "✅ Flash types configured (:success, :error, :warning, :info)"
229
+ say "✅ CSS files copied to app/assets/stylesheets/turbo_turbo/"
230
+ say "✅ CSS import added to main stylesheet"
231
+ say "✅ Body tag configured with turbo-turbo--modal controller"
232
+ say "✅ Flash messages render added"
233
+ say "✅ Modal background render added"
234
+ say "✅ TurboTurbo::ModalComponent render added"
235
+ say "✅ TurboTurbo routes template copied to config/routes/turbo_turbo_routes.rb"
236
+ say "✅ draw(:turbo_turbo_routes) added to config/routes.rb"
237
+ say "\nNext steps:"
238
+ say "🚀 You're ready to use TurboTurbo! Start by adding turbo_actions to your controllers."
239
+ say "📝 Add your modal routes to config/routes/turbo_turbo_routes.rb"
240
+ say "\nOptional:"
241
+ say "• Run 'rails generate turbo_turbo:views' to copy ViewComponents for customization"
242
+ say "• Run 'rails generate turbo_turbo:layout [LAYOUT_NAME]' for custom layouts"
243
+ say "\nFor usage examples, see: https://github.com/lordofthedanse/turbo_turbo"
244
+ end
245
+
246
+ def modify_erb_layout(content)
247
+ # Add turbo-turbo--modal to data-controller if not present
248
+ if content.match?(/data-controller\s*=\s*["'][^"']*["']/)
249
+ # data-controller exists, check if turbo-turbo--modal is present
250
+ unless content.match?(/data-controller\s*=\s*["'][^"']*\bturbo-turbo--modal\b[^"']*["']/)
251
+ content = content.gsub(/(data-controller\s*=\s*["'])([^"']*)(['"])/) do
252
+ prefix = ::Regexp.last_match(1)
253
+ controller_list = ::Regexp.last_match(2)
254
+ suffix = ::Regexp.last_match(3)
255
+ controllers = controller_list.strip.split(/\s+/)
256
+ controllers << "turbo-turbo--modal" unless controllers.include?("turbo-turbo--modal")
257
+ "#{prefix}#{controllers.join(' ')}#{suffix}"
258
+ end
259
+ end
260
+ else
261
+ # Add data-controller with turbo-turbo--modal to body tag
262
+ content = content.gsub(/(<body[^>]*?)>/) do
263
+ body_tag = ::Regexp.last_match(1)
264
+ "#{body_tag} data-controller=\"turbo-turbo--modal\">"
265
+ end
266
+ end
267
+
268
+ # Add layout renders after body tag if not present
269
+ unless content.include?("render 'turbo_turbo/flashes'") || content.include?('render "turbo_turbo/flashes"')
270
+ content = content.gsub(/(<body[^>]*>\s*)/) do
271
+ "#{::Regexp.last_match(1)} <%= render 'turbo_turbo/flashes' %>\n "
272
+ end
273
+ end
274
+
275
+ unless content.include?("render 'turbo_turbo/modal_background'") || content.include?('render "turbo_turbo/modal_background"')
276
+ content = content.gsub(%r{(.*render ['"]turbo_turbo/flashes['"].*\n\s*)}) do
277
+ "#{::Regexp.last_match(1)} <%= render 'turbo_turbo/modal_background' %>\n "
278
+ end
279
+ end
280
+
281
+ # Add ModalComponent before closing body tag if not present
282
+ unless content.include?("TurboTurbo::ModalComponent")
283
+ content = content.gsub(%r{(\s*)</body>}) do
284
+ "#{::Regexp.last_match(1)} <%= render TurboTurbo::ModalComponent.new %>\n#{::Regexp.last_match(1)}</body>"
285
+ end
286
+ end
287
+
288
+ content
289
+ end
290
+
291
+ def modify_slim_layout(content)
292
+ # Add turbo-turbo--modal to data-controller if not present
293
+ if content.match?(/data-controller\s*=\s*["'][^"']*["']/)
294
+ # data-controller exists, check if turbo-turbo--modal is present
295
+ unless content.match?(/data-controller\s*=\s*["'][^"']*\bturbo-turbo--modal\b[^"']*["']/)
296
+ content = content.gsub(/(data-controller\s*=\s*["'])([^"']*)(['"])/) do
297
+ prefix = ::Regexp.last_match(1)
298
+ controller_list = ::Regexp.last_match(2)
299
+ suffix = ::Regexp.last_match(3)
300
+ controllers = controller_list.strip.split(/\s+/)
301
+ controllers << "turbo-turbo--modal" unless controllers.include?("turbo-turbo--modal")
302
+ "#{prefix}#{controllers.join(' ')}#{suffix}"
303
+ end
304
+ end
305
+ else
306
+ # Add data-controller with turbo-turbo--modal to body tag
307
+ content = content.gsub(/(^ *body(?:\.[a-zA-Z0-9_-]+)*)\s*$/m) do
308
+ body_line = ::Regexp.last_match(1)
309
+ "#{body_line} data-controller=\"turbo-turbo--modal\""
310
+ end
311
+ end
312
+
313
+ # Add layout renders after body tag if not present
314
+ unless content.match?(%r{render\s+["']turbo_turbo/flashes["']}) || content.match?(%r{render\s+"turbo_turbo/flashes"})
315
+ content = content.gsub(/(^ *body(?:\.[a-zA-Z0-9_-]+)*.*\n)/m) do
316
+ "#{::Regexp.last_match(1)} = render \"turbo_turbo/flashes\"\n"
317
+ end
318
+ end
319
+
320
+ unless content.match?(%r{render\s+["']turbo_turbo/modal_background["']}) || content.match?(%r{render\s+"turbo_turbo/modal_background"})
321
+ content = content.gsub(%r{(.*render\s+["']turbo_turbo/flashes["'].*\n)}) do
322
+ "#{::Regexp.last_match(1)} = render \"turbo_turbo/modal_background\"\n"
323
+ end
324
+ end
325
+
326
+ # Add TurboTurbo::ModalComponent at end of body if not present
327
+ unless content.include?("TurboTurbo::ModalComponent")
328
+ # Find the last line with content before implicit body closing
329
+ lines = content.split("\n")
330
+ body_found = false
331
+ insert_index = -1
332
+
333
+ lines.each_with_index do |line, index|
334
+ if line.match?(/^\s*body/)
335
+ body_found = true
336
+ elsif body_found && line.strip.length.positive? && !line.match?(%r{^\s*/}) # not a comment
337
+ insert_index = index
338
+ end
339
+ end
340
+
341
+ if insert_index >= 0
342
+ lines.insert(insert_index + 1, " = render TurboTurbo::ModalComponent.new")
343
+ content = lines.join("\n")
344
+ end
345
+ end
346
+
347
+ content
348
+ end
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module TurboTurbo
6
+ module Generators
7
+ class LayoutGenerator < Rails::Generators::Base
8
+ desc "Install TurboTurbo components to a custom layout file"
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ argument :layout_name, type: :string, default: "application",
12
+ desc: "The layout file name (without extension)"
13
+
14
+ class_option :skip_flash, type: :boolean, default: false,
15
+ desc: "Skip adding flash-related components (flashes render)"
16
+
17
+ class_option :skip_modal, type: :boolean, default: false,
18
+ desc: "Skip adding modal-related components (data controller, modal background, ModalComponent)"
19
+
20
+ def check_if_everything_skipped
21
+ return unless options[:skip_flash] && options[:skip_modal]
22
+
23
+ say "You skipped everything, so we did nothing!", :yellow
24
+ exit(0)
25
+ end
26
+
27
+ def ensure_layout_partials_exist
28
+ empty_directory "app/views/turbo_turbo"
29
+
30
+ if !options[:skip_flash] && !File.exist?("app/views/turbo_turbo/_flashes.html.erb")
31
+ copy_file "turbo_turbo/_flashes.html.erb", "app/views/turbo_turbo/_flashes.html.erb"
32
+ end
33
+
34
+ return if options[:skip_modal]
35
+ return if File.exist?("app/views/turbo_turbo/_modal_background.html.erb")
36
+
37
+ copy_file "turbo_turbo/_modal_background.html.erb", "app/views/turbo_turbo/_modal_background.html.erb"
38
+ end
39
+
40
+ def modify_custom_layout_file
41
+ layout_files = [
42
+ "app/views/layouts/#{layout_name}.html.erb",
43
+ "app/views/layouts/#{layout_name}.html.slim"
44
+ ]
45
+
46
+ layout_file = layout_files.find { |file| File.exist?(file) }
47
+
48
+ unless layout_file
49
+ say "Error: Could not find layout file for '#{layout_name}'", :red
50
+ say "Looking for: #{layout_files.join(' or ')}", :yellow
51
+ return
52
+ end
53
+
54
+ content = File.read(layout_file)
55
+ original_content = content.dup
56
+
57
+ content = if layout_file.end_with?(".slim")
58
+ modify_slim_layout(content)
59
+ else
60
+ modify_erb_layout(content)
61
+ end
62
+
63
+ if content == original_content
64
+ say "#{layout_file} already includes TurboTurbo components", :blue
65
+ else
66
+ File.write(layout_file, content)
67
+ say "Modified #{layout_file} to include TurboTurbo components", :green
68
+ end
69
+ end
70
+
71
+ def display_completion_message
72
+ say "\nTurboTurbo components added to #{layout_name} layout!", :green
73
+ say "\nComponents added:"
74
+
75
+ say "✅ turbo_turbo/flashes render" unless options[:skip_flash]
76
+
77
+ unless options[:skip_modal]
78
+ say "✅ turbo-turbo--modal data controller"
79
+ say "✅ turbo_turbo/modal_background render"
80
+ say "✅ TurboTurbo::ModalComponent render"
81
+ end
82
+
83
+ return unless options[:skip_flash] || options[:skip_modal]
84
+
85
+ say "\nSkipped components:"
86
+ say "⏭️ Flash-related: turbo_turbo/flashes render" if options[:skip_flash]
87
+ return unless options[:skip_modal]
88
+
89
+ say "⏭️ Modal-related: data controller, modal background, ModalComponent"
90
+ end
91
+
92
+ private
93
+
94
+ def modify_erb_layout(content)
95
+ # Add turbo-turbo--modal to data-controller if not present and not skipped
96
+ unless options[:skip_modal]
97
+ if content.match?(/data-controller\s*=\s*["'][^"']*["']/)
98
+ # data-controller exists, check if turbo-turbo--modal is present
99
+ unless content.match?(/data-controller\s*=\s*["'][^"']*\bturbo-turbo--modal\b[^"']*["']/)
100
+ content = content.gsub(/(data-controller\s*=\s*["'])([^"']*)(['"])/) do
101
+ prefix = ::Regexp.last_match(1)
102
+ controller_list = ::Regexp.last_match(2)
103
+ suffix = ::Regexp.last_match(3)
104
+ controllers = controller_list.strip.split(/\s+/)
105
+ controllers << "turbo-turbo--modal" unless controllers.include?("turbo-turbo--modal")
106
+ "#{prefix}#{controllers.join(' ')}#{suffix}"
107
+ end
108
+ end
109
+ else
110
+ # Add data-controller with turbo-turbo--modal to body tag
111
+ content = content.gsub(/(<body[^>]*?)>/) do
112
+ body_tag = ::Regexp.last_match(1)
113
+ "#{body_tag} data-controller=\"turbo-turbo--modal\">"
114
+ end
115
+ end
116
+ end
117
+
118
+ # Add flashes render after body tag if not present and not skipped
119
+ if !options[:skip_flash] && !(content.include?("render 'turbo_turbo/flashes'") || content.include?('render "turbo_turbo/flashes"'))
120
+ content = content.gsub(/(<body[^>]*>\s*)/) do
121
+ "#{::Regexp.last_match(1)} <%= render 'turbo_turbo/flashes' %>\n "
122
+ end
123
+ end
124
+
125
+ # Add modal background render if not present and not skipped
126
+ if !options[:skip_modal] && !(content.include?("render 'turbo_turbo/modal_background'") || content.include?('render "turbo_turbo/modal_background"'))
127
+ content = if content.include?("render 'turbo_turbo/flashes'") || content.include?('render "turbo_turbo/flashes"')
128
+ # Insert after flashes
129
+ content.gsub(%r{(.*render ['"]turbo_turbo/flashes['"].*\n\s*)}) do
130
+ "#{::Regexp.last_match(1)} <%= render 'turbo_turbo/modal_background' %>\n "
131
+ end
132
+ else
133
+ # Insert after body tag
134
+ content.gsub(/(<body[^>]*>\s*)/) do
135
+ "#{::Regexp.last_match(1)} <%= render 'turbo_turbo/modal_background' %>\n "
136
+ end
137
+ end
138
+ end
139
+
140
+ # Add ModalComponent before closing body tag if not present and not skipped
141
+ if !options[:skip_modal] && !content.include?("TurboTurbo::ModalComponent")
142
+ content = content.gsub(%r{(\s*)</body>}) do
143
+ "#{::Regexp.last_match(1)} <%= render TurboTurbo::ModalComponent.new %>\n#{::Regexp.last_match(1)}</body>"
144
+ end
145
+ end
146
+
147
+ content
148
+ end
149
+
150
+ def modify_slim_layout(content)
151
+ # Add turbo-turbo--modal to data-controller if not present and not skipped
152
+ unless options[:skip_modal]
153
+ if content.match?(/data-controller\s*=\s*["'][^"']*["']/)
154
+ # data-controller exists, check if turbo-turbo--modal is present
155
+ unless content.match?(/data-controller\s*=\s*["'][^"']*\bturbo-turbo--modal\b[^"']*["']/)
156
+ content = content.gsub(/(data-controller\s*=\s*["'])([^"']*)(['"])/) do
157
+ prefix = ::Regexp.last_match(1)
158
+ controller_list = ::Regexp.last_match(2)
159
+ suffix = ::Regexp.last_match(3)
160
+ controllers = controller_list.strip.split(/\s+/)
161
+ controllers << "turbo-turbo--modal" unless controllers.include?("turbo-turbo--modal")
162
+ "#{prefix}#{controllers.join(' ')}#{suffix}"
163
+ end
164
+ end
165
+ else
166
+ # Add data-controller with turbo-turbo--modal to body tag
167
+ content = content.gsub(/(^ *body(?:\.[a-zA-Z0-9_-]+)*)\s*$/m) do
168
+ body_line = ::Regexp.last_match(1)
169
+ "#{body_line} data-controller=\"turbo-turbo--modal\""
170
+ end
171
+ end
172
+ end
173
+
174
+ # Add flashes render after body tag if not present and not skipped
175
+ if !options[:skip_flash] && !(content.match?(%r{render\s+["']turbo_turbo/flashes["']}) || content.match?(%r{render\s+"turbo_turbo/flashes"}))
176
+ content = content.gsub(/(^ *body(?:\.[a-zA-Z0-9_-]+)*.*\n)/m) do
177
+ "#{::Regexp.last_match(1)} = render \"turbo_turbo/flashes\"\n"
178
+ end
179
+ end
180
+
181
+ # Add modal background render if not present and not skipped
182
+ if !options[:skip_modal] && !(content.match?(%r{render\s+["']turbo_turbo/modal_background["']}) || content.match?(%r{render\s+"turbo_turbo/modal_background"}))
183
+ content = if content.match?(%r{render\s+["']turbo_turbo/flashes["']})
184
+ # Insert after flashes
185
+ content.gsub(%r{(.*render\s+["']turbo_turbo/flashes["'].*\n)}) do
186
+ "#{::Regexp.last_match(1)} = render \"turbo_turbo/modal_background\"\n"
187
+ end
188
+ else
189
+ # Insert after body tag
190
+ content.gsub(/(^ *body(?:\.[a-zA-Z0-9_-]+)*.*\n)/m) do
191
+ "#{::Regexp.last_match(1)} = render \"turbo_turbo/modal_background\"\n"
192
+ end
193
+ end
194
+ end
195
+
196
+ # Add TurboTurbo::ModalComponent at end of body if not present and not skipped
197
+ if !options[:skip_modal] && !content.include?("TurboTurbo::ModalComponent")
198
+ # Find the last line with content before implicit body closing
199
+ lines = content.split("\n")
200
+ body_found = false
201
+ insert_index = -1
202
+
203
+ lines.each_with_index do |line, index|
204
+ if line.match?(/^\s*body/)
205
+ body_found = true
206
+ elsif body_found && line.strip.length.positive? && !line.match?(%r{^\s*/}) # not a comment
207
+ insert_index = index
208
+ end
209
+ end
210
+
211
+ if insert_index >= 0
212
+ lines.insert(insert_index + 1, " = render TurboTurbo::ModalComponent.new")
213
+ content = lines.join("\n")
214
+ end
215
+ end
216
+
217
+ content
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TurboTurbo routes for modal-based CRUD operations
4
+ # Add `draw(:turbo_turbo_routes)` to your main routes.rb file to include these routes
5
+
6
+ namespace :turbo_turbo do
7
+ # Admin namespace for administrative modal operations
8
+ namespace :admin do
9
+ # Add your admin resources here
10
+ # Example: resources :pathways, only: [:new, :create, :show, :edit, :update, :destroy]
11
+ end
12
+
13
+ # Provider namespace for provider modal operations
14
+ namespace :provider do
15
+ # Add your provider resources here
16
+ # Example: resources :pathways, only: [:new, :create, :show, :edit, :update, :destroy]
17
+ end
18
+
19
+ # Add other namespaces as needed
20
+ end
@@ -0,0 +1,15 @@
1
+ <div class="rounded-md bg-red-50 p-4 mb-4">
2
+ <div class="flex">
3
+ <div class="flex-shrink-0">
4
+ <svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
5
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
6
+ </svg>
7
+ </div>
8
+ <div class="ml-3">
9
+ <h3 class="text-sm font-medium text-red-800">There were errors with your submission</h3>
10
+ <div class="mt-2 text-sm text-red-700">
11
+ <%= message.html_safe %>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </div>
@@ -0,0 +1,8 @@
1
+ <div id="flashes_turbo_turbo">
2
+ <% flash.each do |key, value| %>
3
+ <% key = convert_flash_keys(key) %>
4
+ <% next unless ApplicationController._flash_types.include?(key.to_sym) %>
5
+ <% value = { message: value } if value.is_a?(String) %>
6
+ <%= render("TurboTurbo::Alerts::#{key.titleize}Component".constantize.new(value)) %>
7
+ <% end %>
8
+ </div>
@@ -0,0 +1,2 @@
1
+ <div class="ModalBackground-turbo-turbo" role="dialog" aria-modal="true" data-turbo-turbo--modal-target="background" id="modalBackground" data-action="click->turbo-turbo--modal#closeModal">
2
+ </div>
@@ -0,0 +1,28 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Connects to data-controller="turbo-turbo--flash"
4
+ export default class extends Controller {
5
+ connect() {
6
+ setTimeout(() => this.fadeOut(this.element), (5000))
7
+ }
8
+
9
+ fadeOut(element){
10
+ var intervalID = setInterval(() => {
11
+ if (!element.style.opacity) {
12
+ element.style.opacity = 1;
13
+ }
14
+
15
+ if (element.style.opacity > 0) {
16
+ element.style.opacity -= 0.05;
17
+ } else {
18
+ clearInterval(intervalID);
19
+ element.classList.add("hidden")
20
+ }
21
+ }, 75);
22
+ }
23
+
24
+ dismiss(){
25
+ event.preventDefault();
26
+ this.element.classList.add("hidden");
27
+ }
28
+ }