turbo_overlay 0.3.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +436 -0
- data/LICENSE.txt +21 -0
- data/README.md +330 -0
- data/Rakefile +35 -0
- data/app/assets/stylesheets/turbo_overlay.css +234 -0
- data/app/javascript/turbo_overlay/dialog_utils.js +46 -0
- data/app/javascript/turbo_overlay/hint.js +670 -0
- data/app/javascript/turbo_overlay/history.js +184 -0
- data/app/javascript/turbo_overlay/index.js +53 -0
- data/app/javascript/turbo_overlay/options.js +152 -0
- data/app/javascript/turbo_overlay/overlay_controller.js +882 -0
- data/app/javascript/turbo_overlay/popover_position.js +64 -0
- data/app/javascript/turbo_overlay/setup.js +885 -0
- data/app/javascript/turbo_overlay/stack_controller.js +131 -0
- data/app/javascript/turbo_overlay/submit_close.js +49 -0
- data/app/javascript/turbo_overlay/visit.js +52 -0
- data/app/views/layouts/turbo_overlay/drawer.html.erb +5 -0
- data/app/views/layouts/turbo_overlay/hint.html.erb +10 -0
- data/app/views/layouts/turbo_overlay/modal.html.erb +5 -0
- data/app/views/layouts/turbo_overlay/popover.html.erb +5 -0
- data/app/views/turbo_overlay/_drawer.html.erb +49 -0
- data/app/views/turbo_overlay/_hint.html.erb +6 -0
- data/app/views/turbo_overlay/_loading.html.erb +12 -0
- data/app/views/turbo_overlay/_modal.html.erb +46 -0
- data/app/views/turbo_overlay/_popover.html.erb +54 -0
- data/config/importmap.rb +11 -0
- data/lib/generators/turbo_overlay/eject_generator.rb +115 -0
- data/lib/generators/turbo_overlay/install_generator.rb +443 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_confirm.html.erb +13 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_drawer.html.erb +50 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_hint.html.erb +9 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_loading.html.erb +9 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_modal.html.erb +49 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_popover.html.erb +54 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_confirm.html.erb +13 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_drawer.html.erb +55 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_hint.html.erb +9 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_loading.html.erb +9 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_modal.html.erb +58 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_popover.html.erb +53 -0
- data/lib/generators/turbo_overlay/templates/chrome/plain/_confirm.html.erb +14 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_confirm.html.erb +17 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_drawer.html.erb +55 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_hint.html.erb +6 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_loading.html.erb +9 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_modal.html.erb +46 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_popover.html.erb +54 -0
- data/lib/generators/turbo_overlay/templates/initializer.rb.tt +67 -0
- data/lib/turbo_overlay/configuration.rb +226 -0
- data/lib/turbo_overlay/controller.rb +405 -0
- data/lib/turbo_overlay/engine.rb +52 -0
- data/lib/turbo_overlay/helpers/stream_helper.rb +77 -0
- data/lib/turbo_overlay/helpers/view_helper.rb +651 -0
- data/lib/turbo_overlay/version.rb +3 -0
- data/lib/turbo_overlay.rb +20 -0
- metadata +161 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
require "rails/generators/base"
|
|
2
|
+
|
|
3
|
+
module TurboOverlay
|
|
4
|
+
module Generators
|
|
5
|
+
# Wires the host app to use turbo_overlay. Detects the host's JS
|
|
6
|
+
# and CSS toolchain, then either injects the required one-liners
|
|
7
|
+
# or prints the snippets the user needs to paste. Also copies the
|
|
8
|
+
# chosen chrome partials into `app/views/turbo_overlay/` so the
|
|
9
|
+
# app owns its modal/drawer appearance from day one (and so
|
|
10
|
+
# Tailwind / similar scanners can see the markup).
|
|
11
|
+
#
|
|
12
|
+
# bin/rails g turbo_overlay:install
|
|
13
|
+
# bin/rails g turbo_overlay:install --theme tailwind
|
|
14
|
+
#
|
|
15
|
+
# Re-running is idempotent: existing files and already-injected
|
|
16
|
+
# wiring are detected and skipped. Pass `--force` to overwrite
|
|
17
|
+
# files when upgrading.
|
|
18
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
19
|
+
source_root File.expand_path("templates", __dir__)
|
|
20
|
+
|
|
21
|
+
GEM_ROOT = File.expand_path("../../..", __dir__)
|
|
22
|
+
|
|
23
|
+
THEMES = %w[plain tailwind bootstrap5 bootstrap3].freeze
|
|
24
|
+
|
|
25
|
+
# Chrome + loading partials the gem ships fallbacks for under
|
|
26
|
+
# `app/views/turbo_overlay/`. For the `plain` theme the install
|
|
27
|
+
# generator copies those fallbacks directly so we don't have two
|
|
28
|
+
# canonical copies of the same markup. Themed (`tailwind`,
|
|
29
|
+
# `bootstrap5`, `bootstrap3`) installs source from the
|
|
30
|
+
# per-theme template directory.
|
|
31
|
+
#
|
|
32
|
+
# `_loading.html.erb` is a single shared partial — the same body
|
|
33
|
+
# renders inside modal, drawer, popover, and hint chromes, with
|
|
34
|
+
# chrome-context CSS handling the size differences. Apps that
|
|
35
|
+
# want chrome-specific loading markup can add
|
|
36
|
+
# `_loading.html+<variant>.erb` and the existing lookup will
|
|
37
|
+
# prefer it over the shared file.
|
|
38
|
+
FALLBACK_PARTIALS = %w[
|
|
39
|
+
_modal.html.erb
|
|
40
|
+
_drawer.html.erb
|
|
41
|
+
_popover.html.erb
|
|
42
|
+
_hint.html.erb
|
|
43
|
+
_loading.html.erb
|
|
44
|
+
].freeze
|
|
45
|
+
|
|
46
|
+
# Confirm partials have no gem-side fallback — they're optional
|
|
47
|
+
# (only used when `register(application, confirm: true)`) and
|
|
48
|
+
# require per-theme markup. Always source from the theme directory.
|
|
49
|
+
# A single `_confirm.html.erb` serves both modal- and popover-style
|
|
50
|
+
# confirms; add `_confirm.html+<variant>.erb` for chrome-specific tuning.
|
|
51
|
+
THEME_ONLY_PARTIALS = %w[
|
|
52
|
+
_confirm.html.erb
|
|
53
|
+
].freeze
|
|
54
|
+
|
|
55
|
+
class_option :theme,
|
|
56
|
+
type: :string,
|
|
57
|
+
default: "plain",
|
|
58
|
+
desc: "Chrome theme to scaffold. One of: #{THEMES.join(", ")}."
|
|
59
|
+
|
|
60
|
+
class_option :skip_layout_inject,
|
|
61
|
+
type: :boolean,
|
|
62
|
+
default: false,
|
|
63
|
+
desc: "Skip injecting `<%= overlay_stack_tag %>` into application.html.erb"
|
|
64
|
+
|
|
65
|
+
class_option :skip_javascript,
|
|
66
|
+
type: :boolean,
|
|
67
|
+
default: false,
|
|
68
|
+
desc: "Skip wiring the Stimulus registration"
|
|
69
|
+
|
|
70
|
+
class_option :skip_stylesheet,
|
|
71
|
+
type: :boolean,
|
|
72
|
+
default: false,
|
|
73
|
+
desc: "Skip wiring the stylesheet import"
|
|
74
|
+
|
|
75
|
+
class_option :skip_chrome,
|
|
76
|
+
type: :boolean,
|
|
77
|
+
default: false,
|
|
78
|
+
desc: "Skip copying the modal/drawer chrome partials into app/views/turbo_overlay/"
|
|
79
|
+
|
|
80
|
+
def validate_theme
|
|
81
|
+
@theme = options[:theme].to_s
|
|
82
|
+
unless THEMES.include?(@theme)
|
|
83
|
+
raise Thor::Error,
|
|
84
|
+
"Theme '#{@theme}' not recognized. Choose from: #{THEMES.join(", ")}."
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def copy_initializer
|
|
89
|
+
template "initializer.rb.tt", "config/initializers/turbo_overlay.rb"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def copy_chrome_partials
|
|
93
|
+
return if options[:skip_chrome]
|
|
94
|
+
|
|
95
|
+
FALLBACK_PARTIALS.each do |filename|
|
|
96
|
+
dest = "app/views/turbo_overlay/#{filename}"
|
|
97
|
+
if @theme == "plain"
|
|
98
|
+
copy_gem_fallback_partial(filename, dest)
|
|
99
|
+
else
|
|
100
|
+
copy_file chrome_source_path(filename), dest
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
THEME_ONLY_PARTIALS.each do |filename|
|
|
105
|
+
copy_file chrome_source_path(filename),
|
|
106
|
+
"app/views/turbo_overlay/#{filename}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def inject_stack_tag
|
|
111
|
+
if options[:skip_layout_inject]
|
|
112
|
+
@layout_instructions_only = true
|
|
113
|
+
return
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
layout_path = locate_application_layout
|
|
117
|
+
unless layout_path
|
|
118
|
+
@layout_instructions_only = true
|
|
119
|
+
say_status :skip, "no application.html.erb found; see post-install instructions for `overlay_stack_tag`", :yellow
|
|
120
|
+
return
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
contents = File.read(File.join(destination_root, layout_path))
|
|
124
|
+
if contents.include?("overlay_stack_tag") || contents.include?("overlay_frame_tags")
|
|
125
|
+
say_status :identical, layout_path, :blue
|
|
126
|
+
return
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
inject_into_file layout_path, before: %r{</body>} do
|
|
130
|
+
" <%= overlay_stack_tag %>\n "
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def wire_javascript
|
|
135
|
+
if options[:skip_javascript]
|
|
136
|
+
@js_instructions_only = true
|
|
137
|
+
return
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
@js_setup = detect_js_setup
|
|
141
|
+
case @js_setup
|
|
142
|
+
when :importmap
|
|
143
|
+
wire_importmap_stimulus_entry
|
|
144
|
+
when :jsbundling
|
|
145
|
+
@js_instructions_only = true
|
|
146
|
+
else
|
|
147
|
+
@js_instructions_only = true
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def wire_stylesheet
|
|
152
|
+
if options[:skip_stylesheet]
|
|
153
|
+
@css_instructions_only = true
|
|
154
|
+
return
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
@css_setup = detect_css_setup
|
|
158
|
+
case @css_setup
|
|
159
|
+
when :sprockets
|
|
160
|
+
wire_sprockets_stylesheet
|
|
161
|
+
when :propshaft
|
|
162
|
+
wire_propshaft_stylesheet
|
|
163
|
+
when :cssbundling
|
|
164
|
+
@css_instructions_only = true
|
|
165
|
+
else
|
|
166
|
+
@css_instructions_only = true
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def show_post_install_message
|
|
171
|
+
say "\nTurbo Overlay installed with the #{@theme} theme.\n"
|
|
172
|
+
say "\nSetup checklist:\n"
|
|
173
|
+
|
|
174
|
+
print_controller_step
|
|
175
|
+
print_layout_step
|
|
176
|
+
print_js_step
|
|
177
|
+
print_css_step
|
|
178
|
+
|
|
179
|
+
say "\nUsage:\n"
|
|
180
|
+
say <<~MSG
|
|
181
|
+
Open views as overlays:
|
|
182
|
+
|
|
183
|
+
<%= modal_link_to "New", new_thing_path %>
|
|
184
|
+
<%= drawer_link_to "Filters", filters_path %>
|
|
185
|
+
<%= popover_link_to "Edit", edit_thing_path(@thing) %>
|
|
186
|
+
|
|
187
|
+
Add hover-hint previews with `hint: true` (and `hint_url:` for
|
|
188
|
+
content from a separate URL):
|
|
189
|
+
|
|
190
|
+
<%= hint_link_to "User", user_path(@user) %>
|
|
191
|
+
<%= modal_link_to "Edit", path, hint: true, hint_url: hint_path %>
|
|
192
|
+
MSG
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
def chrome_source_path(filename)
|
|
198
|
+
"chrome/#{@theme}/#{filename}"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def copy_gem_fallback_partial(filename, destination_relative)
|
|
202
|
+
source = File.join(GEM_ROOT, "app/views/turbo_overlay", filename)
|
|
203
|
+
unless File.exist?(source)
|
|
204
|
+
say_status :missing, source, :red
|
|
205
|
+
return
|
|
206
|
+
end
|
|
207
|
+
create_file destination_relative, File.read(source)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def locate_application_layout
|
|
211
|
+
%w[
|
|
212
|
+
app/views/layouts/application.html.erb
|
|
213
|
+
app/views/layouts/application.html.haml
|
|
214
|
+
app/views/layouts/application.html.slim
|
|
215
|
+
].find { |p| File.exist?(File.join(destination_root, p)) }
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def detect_js_setup
|
|
219
|
+
return :importmap if File.exist?(File.join(destination_root, "config/importmap.rb"))
|
|
220
|
+
|
|
221
|
+
pkg = File.join(destination_root, "package.json")
|
|
222
|
+
if File.exist?(pkg)
|
|
223
|
+
json = File.read(pkg)
|
|
224
|
+
return :jsbundling if %w[esbuild rollup webpack bun].any? { |b| json.include?(%("#{b}")) }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
:unknown
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def detect_css_setup
|
|
231
|
+
gemfile = File.join(destination_root, "Gemfile")
|
|
232
|
+
if File.exist?(gemfile)
|
|
233
|
+
content = File.read(gemfile)
|
|
234
|
+
return :cssbundling if content.match?(/^\s*gem\s+["'](cssbundling-rails|dartsass-rails|tailwindcss-rails|sassc-rails)["']/)
|
|
235
|
+
return :propshaft if content.match?(/^\s*gem\s+["']propshaft["']/)
|
|
236
|
+
return :sprockets if content.match?(/^\s*gem\s+["']sprockets-rails["']/)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Fall back to looking for a stylesheet file.
|
|
240
|
+
return :sprockets if File.exist?(File.join(destination_root, "app/assets/stylesheets/application.css"))
|
|
241
|
+
return :sprockets if File.exist?(File.join(destination_root, "app/assets/stylesheets/application.scss"))
|
|
242
|
+
|
|
243
|
+
:unknown
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def wire_importmap_stimulus_entry
|
|
247
|
+
candidates = %w[
|
|
248
|
+
app/javascript/controllers/index.js
|
|
249
|
+
app/javascript/application.js
|
|
250
|
+
]
|
|
251
|
+
path = candidates.find { |p| File.exist?(File.join(destination_root, p)) }
|
|
252
|
+
unless path
|
|
253
|
+
@js_instructions_only = true
|
|
254
|
+
return
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
contents = File.read(File.join(destination_root, path))
|
|
258
|
+
if contents.include?(%(from "turbo_overlay")) || contents.include?(%(from 'turbo_overlay'))
|
|
259
|
+
say_status :identical, path, :blue
|
|
260
|
+
return
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
append_to_file path, <<~JS
|
|
264
|
+
|
|
265
|
+
import { register as registerTurboOverlay } from "turbo_overlay"
|
|
266
|
+
registerTurboOverlay(application, { confirm: true })
|
|
267
|
+
JS
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def wire_sprockets_stylesheet
|
|
271
|
+
candidates = %w[
|
|
272
|
+
app/assets/stylesheets/application.css
|
|
273
|
+
app/assets/stylesheets/application.css.scss
|
|
274
|
+
app/assets/stylesheets/application.scss
|
|
275
|
+
]
|
|
276
|
+
path = candidates.find { |p| File.exist?(File.join(destination_root, p)) }
|
|
277
|
+
unless path
|
|
278
|
+
@css_instructions_only = true
|
|
279
|
+
return
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
contents = File.read(File.join(destination_root, path))
|
|
283
|
+
if contents.include?("turbo_overlay")
|
|
284
|
+
say_status :identical, path, :blue
|
|
285
|
+
return
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
if path.end_with?(".css")
|
|
289
|
+
# Manifest-style: inject `*= require turbo_overlay` into the
|
|
290
|
+
# sprockets require block. Fall back to appending an import.
|
|
291
|
+
if contents.include?("*= require_tree")
|
|
292
|
+
inject_into_file path, before: " *= require_tree" do
|
|
293
|
+
" *= require turbo_overlay\n"
|
|
294
|
+
end
|
|
295
|
+
elsif contents.match?(/\*=\s+require_self/)
|
|
296
|
+
inject_into_file path, after: /\*=\s+require_self\n/ do
|
|
297
|
+
" *= require turbo_overlay\n"
|
|
298
|
+
end
|
|
299
|
+
else
|
|
300
|
+
append_to_file path, %(\n@import "turbo_overlay";\n)
|
|
301
|
+
end
|
|
302
|
+
else
|
|
303
|
+
append_to_file path, %(\n@import "turbo_overlay";\n)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def wire_propshaft_stylesheet
|
|
308
|
+
# Propshaft doesn't rewrite `@import` URLs to digested paths,
|
|
309
|
+
# so we can't inject `@import "turbo_overlay.css"` into the
|
|
310
|
+
# app's manifest CSS — the browser would 404 on the
|
|
311
|
+
# un-digested URL. Inject a `stylesheet_link_tag` into the
|
|
312
|
+
# application layout instead so propshaft emits a separately
|
|
313
|
+
# digested `<link>` for the gem's CSS.
|
|
314
|
+
layout_path = locate_application_layout
|
|
315
|
+
unless layout_path && layout_path.end_with?(".erb")
|
|
316
|
+
@css_instructions_only = true
|
|
317
|
+
return
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
contents = File.read(File.join(destination_root, layout_path))
|
|
321
|
+
if contents.include?(%(stylesheet_link_tag "turbo_overlay")) ||
|
|
322
|
+
contents.include?(%(stylesheet_link_tag 'turbo_overlay'))
|
|
323
|
+
say_status :identical, layout_path, :blue
|
|
324
|
+
return
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Insert after the first existing `stylesheet_link_tag` line we
|
|
328
|
+
# find; otherwise fall back to printing instructions.
|
|
329
|
+
first_link = contents.lines.find { |l| l.include?("stylesheet_link_tag") }
|
|
330
|
+
unless first_link
|
|
331
|
+
@css_instructions_only = true
|
|
332
|
+
return
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
indent = first_link[/^\s*/]
|
|
336
|
+
new_line = %(#{indent}<%= stylesheet_link_tag "turbo_overlay", "data-turbo-track": "reload" %>\n)
|
|
337
|
+
|
|
338
|
+
# Thor's `after:` matches a String literally; pass the raw line.
|
|
339
|
+
inject_into_file layout_path, after: first_link do
|
|
340
|
+
new_line
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def print_controller_step
|
|
345
|
+
# The generator never edits ApplicationController, so this is
|
|
346
|
+
# always a manual step.
|
|
347
|
+
say "\n #{marker(:todo)} 1. Controller concern"
|
|
348
|
+
say step_body(<<~BODY)
|
|
349
|
+
Add to ApplicationController:
|
|
350
|
+
|
|
351
|
+
class ApplicationController < ActionController::Base
|
|
352
|
+
include TurboOverlay::Controller
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
This auto-installs the overlay layout swap and preserves Turbo's
|
|
356
|
+
`turbo_rails/frame` layout for plain frame requests. For custom
|
|
357
|
+
layouts, see "A note on custom layouts" in the README.
|
|
358
|
+
BODY
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def print_layout_step
|
|
362
|
+
if @layout_instructions_only
|
|
363
|
+
say "\n #{marker(:todo)} 2. Layout stack tag"
|
|
364
|
+
say step_body(<<~BODY)
|
|
365
|
+
Add to your application layout, just before </body>:
|
|
366
|
+
|
|
367
|
+
<%= overlay_stack_tag %>
|
|
368
|
+
|
|
369
|
+
This renders the slots overlays mount into. Without it, modal /
|
|
370
|
+
drawer / popover / hint links will navigate full-page instead
|
|
371
|
+
of opening as overlays.
|
|
372
|
+
BODY
|
|
373
|
+
else
|
|
374
|
+
say "\n #{marker(:done)} 2. Layout stack tag"
|
|
375
|
+
say step_body("`<%= overlay_stack_tag %>` is present in your application layout.")
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def print_js_step
|
|
380
|
+
if @js_instructions_only
|
|
381
|
+
say "\n #{marker(:todo)} 3. JavaScript wiring"
|
|
382
|
+
say step_body(<<~BODY)
|
|
383
|
+
Add to your Stimulus entry (typically
|
|
384
|
+
app/javascript/controllers/index.js or your bundler's equivalent):
|
|
385
|
+
|
|
386
|
+
import { register as registerTurboOverlay } from "turbo_overlay"
|
|
387
|
+
registerTurboOverlay(application, { confirm: true })
|
|
388
|
+
|
|
389
|
+
jsbundling-rails apps: add the gem's `app/javascript` directory
|
|
390
|
+
to your bundler's resolve paths, OR run
|
|
391
|
+
`bin/rails g turbo_overlay:eject --js` to copy the controllers
|
|
392
|
+
into your app.
|
|
393
|
+
BODY
|
|
394
|
+
else
|
|
395
|
+
say "\n #{marker(:done)} 3. JavaScript wiring"
|
|
396
|
+
say step_body("Registered with your Stimulus entry.")
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def print_css_step
|
|
401
|
+
if @css_instructions_only
|
|
402
|
+
say "\n #{marker(:todo)} 4. Stylesheet wiring"
|
|
403
|
+
say step_body(<<~BODY)
|
|
404
|
+
Add to your stylesheet. Pick the option that matches your setup:
|
|
405
|
+
|
|
406
|
+
# propshaft — add to app/views/layouts/application.html.erb:
|
|
407
|
+
<%= stylesheet_link_tag "turbo_overlay", "data-turbo-track": "reload" %>
|
|
408
|
+
|
|
409
|
+
# sprockets manifest — add to app/assets/stylesheets/application.css:
|
|
410
|
+
*= require turbo_overlay
|
|
411
|
+
|
|
412
|
+
# cssbundling / dartsass / tailwind v4 — add to your source CSS:
|
|
413
|
+
@import "turbo_overlay";
|
|
414
|
+
|
|
415
|
+
Notes:
|
|
416
|
+
- Propshaft does not rewrite CSS `@import` URLs to digested
|
|
417
|
+
asset paths; use a separate `stylesheet_link_tag` instead so
|
|
418
|
+
the gem's CSS is served with a fingerprinted URL.
|
|
419
|
+
- cssbundling apps may also need to add the gem's
|
|
420
|
+
`app/assets/stylesheets` directory to the bundler's load
|
|
421
|
+
paths, OR run `bin/rails g turbo_overlay:eject --css` to
|
|
422
|
+
copy the stylesheet into your app.
|
|
423
|
+
BODY
|
|
424
|
+
else
|
|
425
|
+
say "\n #{marker(:done)} 4. Stylesheet wiring"
|
|
426
|
+
say step_body("Imported into your application stylesheet.")
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def marker(state)
|
|
431
|
+
case state
|
|
432
|
+
when :done then set_color("[done]", :green)
|
|
433
|
+
when :todo then set_color("[todo]", :yellow)
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# Indent each non-empty line so step bodies sit under the marker.
|
|
438
|
+
def step_body(text)
|
|
439
|
+
text.lines.map { |l| l.chomp.empty? ? "" : " #{l.chomp}" }.join("\n")
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%# Confirm dialog body, wrapped in the matching chrome at template-
|
|
2
|
+
emission time by `overlay_stack_tag`. Used for both modal- and
|
|
3
|
+
popover-style confirms; add `_confirm.html+<variant>.erb` for
|
|
4
|
+
chrome-specific tuning. JS only depends on:
|
|
5
|
+
[data-turbo-overlay-confirm-message] — text insertion point
|
|
6
|
+
[data-turbo-overlay-confirm-cancel] — cancels (resolves false)
|
|
7
|
+
[data-turbo-overlay-confirm-accept] — accepts (resolves true)
|
|
8
|
+
%>
|
|
9
|
+
<p data-turbo-overlay-confirm-message></p>
|
|
10
|
+
<div style="text-align:right;margin-top:15px;">
|
|
11
|
+
<button type="button" class="btn btn-default" data-turbo-overlay-confirm-cancel>Cancel</button>
|
|
12
|
+
<button type="button" class="btn btn-primary" data-turbo-overlay-confirm-accept>OK</button>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<% loading = local_assigns.fetch(:loading, false) %>
|
|
2
|
+
<% position = turbo_overlay_position || TurboOverlay.configuration.drawer.position %>
|
|
3
|
+
<% backdrop = turbo_overlay_backdrop? %>
|
|
4
|
+
<% close_button = local_assigns.fetch(:close) { overlay_close? } %>
|
|
5
|
+
<dialog class="<%= class_names("turbo-overlay", "turbo-overlay--drawer", "turbo-overlay--drawer-#{position}", "turbo-overlay-scaffold", "turbo-overlay--loading": loading, "turbo-overlay--no-backdrop": !backdrop) %>"
|
|
6
|
+
<% if loading %>
|
|
7
|
+
role="status"
|
|
8
|
+
aria-live="polite"
|
|
9
|
+
aria-label="Loading"
|
|
10
|
+
<% else %>
|
|
11
|
+
data-controller="turbo-overlay"
|
|
12
|
+
data-turbo-overlay-id-value="<%= turbo_overlay_id %>"
|
|
13
|
+
data-turbo-overlay-type-value="drawer"
|
|
14
|
+
data-turbo-overlay-backdrop-value="<%= backdrop %>"
|
|
15
|
+
<% if turbo_overlay_keep_open_on_redirect? %>data-turbo-overlay-keep-open-on-redirect="true"<% end %>
|
|
16
|
+
data-action="cancel->turbo-overlay#cancel click->turbo-overlay#backdropClick"
|
|
17
|
+
aria-labelledby="turbo-drawer-title-<%= turbo_overlay_id %>"
|
|
18
|
+
<% end %>>
|
|
19
|
+
<div class="panel panel-default turbo-drawer-panel" style="position:relative;">
|
|
20
|
+
<% if !loading && content_for?(:overlay_title) %>
|
|
21
|
+
<div class="panel-heading">
|
|
22
|
+
<% if close_button %>
|
|
23
|
+
<button type="button"
|
|
24
|
+
class="close"
|
|
25
|
+
data-action="turbo-overlay#close"
|
|
26
|
+
aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
27
|
+
<% end %>
|
|
28
|
+
<h4 id="turbo-drawer-title-<%= turbo_overlay_id %>" class="panel-title">
|
|
29
|
+
<%= yield(:overlay_title) %>
|
|
30
|
+
</h4>
|
|
31
|
+
</div>
|
|
32
|
+
<% elsif !loading && close_button %>
|
|
33
|
+
<button type="button"
|
|
34
|
+
class="close"
|
|
35
|
+
style="position:absolute; top:8px; right:12px; z-index:1;"
|
|
36
|
+
data-action="turbo-overlay#close"
|
|
37
|
+
aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
38
|
+
<% end %>
|
|
39
|
+
|
|
40
|
+
<div class="panel-body turbo-drawer-body">
|
|
41
|
+
<%= yield %>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<% if !loading && content_for?(:overlay_footer) %>
|
|
45
|
+
<div class="panel-footer text-right">
|
|
46
|
+
<%= yield(:overlay_footer) %>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|
|
50
|
+
</dialog>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<% loading = local_assigns.fetch(:loading, false) %>
|
|
2
|
+
<div popover="manual"
|
|
3
|
+
class="<%= class_names("turbo-overlay-hint", "popover", "turbo-overlay--loading": loading) %>"
|
|
4
|
+
style="display:block; position:fixed; max-width:20rem;"
|
|
5
|
+
<% if loading %>role="status" aria-live="polite" aria-label="Loading"<% else %>role="tooltip"<% end %>>
|
|
6
|
+
<div class="popover-content">
|
|
7
|
+
<%= yield %>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<%# Body content for an overlay's loading placeholder. Wrapped in the
|
|
2
|
+
matching chrome at template-emission time by `overlay_stack_tag`.
|
|
3
|
+
Sizing comes from the gem's CSS so the same partial fits modal,
|
|
4
|
+
drawer, popover, and hint contexts. Add `_loading.html+<variant>.erb`
|
|
5
|
+
if you need chrome-specific markup.
|
|
6
|
+
%>
|
|
7
|
+
<div class="turbo-overlay-loading__body" style="display:flex;align-items:center;justify-content:center;">
|
|
8
|
+
<span class="turbo-overlay-loading__spinner" aria-hidden="true"></span>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<% loading = local_assigns.fetch(:loading, false) %>
|
|
2
|
+
<% close_button = local_assigns.fetch(:close) { overlay_close? } %>
|
|
3
|
+
<dialog class="<%= class_names("turbo-overlay", "turbo-overlay--modal", "turbo-overlay-scaffold", "turbo-overlay--loading": loading) %>"
|
|
4
|
+
<% if loading %>
|
|
5
|
+
role="status"
|
|
6
|
+
aria-live="polite"
|
|
7
|
+
aria-label="Loading"
|
|
8
|
+
<% else %>
|
|
9
|
+
data-controller="turbo-overlay"
|
|
10
|
+
data-turbo-overlay-id-value="<%= turbo_overlay_id %>"
|
|
11
|
+
data-turbo-overlay-type-value="modal"
|
|
12
|
+
<% if turbo_overlay_keep_open_on_redirect? %>data-turbo-overlay-keep-open-on-redirect="true"<% end %>
|
|
13
|
+
data-action="cancel->turbo-overlay#cancel click->turbo-overlay#backdropClick"
|
|
14
|
+
aria-labelledby="turbo-modal-title-<%= turbo_overlay_id %>"
|
|
15
|
+
<% end %>>
|
|
16
|
+
<div class="modal-dialog modal-lg" role="document">
|
|
17
|
+
<div class="modal-content" style="position:relative;">
|
|
18
|
+
<% if !loading && content_for?(:overlay_title) %>
|
|
19
|
+
<div class="modal-header">
|
|
20
|
+
<% if close_button %>
|
|
21
|
+
<button type="button"
|
|
22
|
+
class="close"
|
|
23
|
+
data-action="turbo-overlay#close"
|
|
24
|
+
aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
25
|
+
<% end %>
|
|
26
|
+
<h4 id="turbo-modal-title-<%= turbo_overlay_id %>" class="modal-title">
|
|
27
|
+
<%= yield(:overlay_title) %>
|
|
28
|
+
</h4>
|
|
29
|
+
</div>
|
|
30
|
+
<% elsif !loading && close_button %>
|
|
31
|
+
<button type="button"
|
|
32
|
+
class="close"
|
|
33
|
+
style="position:absolute; top:8px; right:12px; z-index:1;"
|
|
34
|
+
data-action="turbo-overlay#close"
|
|
35
|
+
aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
36
|
+
<% end %>
|
|
37
|
+
|
|
38
|
+
<div class="modal-body">
|
|
39
|
+
<%= yield %>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<% if !loading && content_for?(:overlay_footer) %>
|
|
43
|
+
<div class="modal-footer">
|
|
44
|
+
<%= yield(:overlay_footer) %>
|
|
45
|
+
</div>
|
|
46
|
+
<% end %>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</dialog>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<% loading = local_assigns.fetch(:loading, false) %>
|
|
2
|
+
<% position = turbo_overlay_position || TurboOverlay.configuration.popover.position %>
|
|
3
|
+
<% align = turbo_overlay_align || TurboOverlay.configuration.popover.align %>
|
|
4
|
+
<% offset = turbo_overlay_offset || TurboOverlay.configuration.popover.offset %>
|
|
5
|
+
<% close_button = local_assigns.fetch(:close) { defined?(@_overlay_close) && @_overlay_close != false } %>
|
|
6
|
+
<dialog popover="manual"
|
|
7
|
+
class="<%= class_names("turbo-overlay", "turbo-overlay--popover", "turbo-overlay--no-backdrop", "popover", "turbo-overlay--loading": loading) %>"
|
|
8
|
+
style="display:block; position:fixed; max-width:22rem; margin:0; padding:0;"
|
|
9
|
+
<% if loading %>
|
|
10
|
+
role="status"
|
|
11
|
+
aria-live="polite"
|
|
12
|
+
aria-label="Loading"
|
|
13
|
+
<% else %>
|
|
14
|
+
data-controller="turbo-overlay"
|
|
15
|
+
data-turbo-overlay-id-value="<%= turbo_overlay_id %>"
|
|
16
|
+
data-turbo-overlay-type-value="popover"
|
|
17
|
+
data-turbo-overlay-backdrop-value="false"
|
|
18
|
+
data-turbo-overlay-position-value="<%= position %>"
|
|
19
|
+
data-turbo-overlay-align-value="<%= align %>"
|
|
20
|
+
data-turbo-overlay-offset-value="<%= offset %>"
|
|
21
|
+
<% if turbo_overlay_keep_open_on_redirect? %>data-turbo-overlay-keep-open-on-redirect="true"<% end %>
|
|
22
|
+
data-action="cancel->turbo-overlay#cancel"
|
|
23
|
+
aria-labelledby="turbo-popover-title-<%= turbo_overlay_id %>"
|
|
24
|
+
<% end %>>
|
|
25
|
+
<div style="position:relative;">
|
|
26
|
+
<% if !loading && content_for?(:overlay_title) %>
|
|
27
|
+
<h3 class="popover-title" id="turbo-popover-title-<%= turbo_overlay_id %>" style="display:flex; align-items:center; justify-content:space-between;">
|
|
28
|
+
<span><%= yield(:overlay_title) %></span>
|
|
29
|
+
<% if close_button %>
|
|
30
|
+
<button type="button"
|
|
31
|
+
class="close"
|
|
32
|
+
data-action="turbo-overlay#close"
|
|
33
|
+
aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
34
|
+
<% end %>
|
|
35
|
+
</h3>
|
|
36
|
+
<% elsif !loading && close_button %>
|
|
37
|
+
<button type="button"
|
|
38
|
+
class="close"
|
|
39
|
+
style="position:absolute; top:6px; right:10px; z-index:1;"
|
|
40
|
+
data-action="turbo-overlay#close"
|
|
41
|
+
aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
42
|
+
<% end %>
|
|
43
|
+
|
|
44
|
+
<div class="popover-content">
|
|
45
|
+
<%= yield %>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<% if !loading && content_for?(:overlay_footer) %>
|
|
49
|
+
<div class="popover-footer" style="border-top:1px solid #e5e7eb; padding:0.5rem 0.75rem; display:flex; justify-content:flex-end; gap:0.5rem;">
|
|
50
|
+
<%= yield(:overlay_footer) %>
|
|
51
|
+
</div>
|
|
52
|
+
<% end %>
|
|
53
|
+
</div>
|
|
54
|
+
</dialog>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%# Confirm dialog body, wrapped in the matching chrome at template-
|
|
2
|
+
emission time by `overlay_stack_tag`. Used for both modal- and
|
|
3
|
+
popover-style confirms; add `_confirm.html+<variant>.erb` for
|
|
4
|
+
chrome-specific tuning. JS only depends on:
|
|
5
|
+
[data-turbo-overlay-confirm-message] — text insertion point
|
|
6
|
+
[data-turbo-overlay-confirm-cancel] — cancels (resolves false)
|
|
7
|
+
[data-turbo-overlay-confirm-accept] — accepts (resolves true)
|
|
8
|
+
%>
|
|
9
|
+
<p data-turbo-overlay-confirm-message></p>
|
|
10
|
+
<div class="d-flex justify-content-end gap-2 mt-3">
|
|
11
|
+
<button type="button" class="btn btn-secondary" data-turbo-overlay-confirm-cancel>Cancel</button>
|
|
12
|
+
<button type="button" class="btn btn-primary" data-turbo-overlay-confirm-accept>OK</button>
|
|
13
|
+
</div>
|