modal_stack 0.1.1
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 +37 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +748 -0
- data/Rakefile +12 -0
- data/app/assets/javascripts/modal_stack.js +756 -0
- data/app/assets/stylesheets/modal_stack/bootstrap.css +232 -0
- data/app/assets/stylesheets/modal_stack/tailwind.css +303 -0
- data/app/assets/stylesheets/modal_stack/vanilla.css +219 -0
- data/app/javascript/modal_stack/controllers/modal_stack_controller.js +149 -0
- data/app/javascript/modal_stack/controllers/modal_stack_link_controller.js +34 -0
- data/app/javascript/modal_stack/index.js +15 -0
- data/app/javascript/modal_stack/install.js +15 -0
- data/app/javascript/modal_stack/orchestrator.js +98 -0
- data/app/javascript/modal_stack/orchestrator.test.js +260 -0
- data/app/javascript/modal_stack/runtime.js +217 -0
- data/app/javascript/modal_stack/runtime.test.js +134 -0
- data/app/javascript/modal_stack/state.js +315 -0
- data/app/javascript/modal_stack/state.test.js +508 -0
- data/app/views/layouts/modal.html.erb +6 -0
- data/lib/generators/modal_stack/install/install_generator.rb +224 -0
- data/lib/generators/modal_stack/install/templates/initializer.rb +57 -0
- data/lib/modal_stack/capybara/minitest.rb +9 -0
- data/lib/modal_stack/capybara/rspec.rb +9 -0
- data/lib/modal_stack/capybara.rb +85 -0
- data/lib/modal_stack/configuration.rb +90 -0
- data/lib/modal_stack/controller_extensions.rb +73 -0
- data/lib/modal_stack/engine.rb +44 -0
- data/lib/modal_stack/helpers/modal_link_helper.rb +65 -0
- data/lib/modal_stack/helpers/modal_stack_assets_helper.rb +45 -0
- data/lib/modal_stack/helpers/modal_stack_container_helper.rb +36 -0
- data/lib/modal_stack/initializer_version_check.rb +33 -0
- data/lib/modal_stack/turbo_streams_extension.rb +73 -0
- data/lib/modal_stack/version.rb +5 -0
- data/lib/modal_stack.rb +36 -0
- metadata +130 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ModalStack
|
|
4
|
+
module Helpers
|
|
5
|
+
# Layout-level helpers for wiring modal_stack into the host app.
|
|
6
|
+
module ModalStackAssetsHelper
|
|
7
|
+
# Renders a <link> for the configured CSS provider:
|
|
8
|
+
#
|
|
9
|
+
# <%= modal_stack_stylesheet_link_tag %>
|
|
10
|
+
#
|
|
11
|
+
# Returns an empty SafeBuffer when `config.css_provider = :none`,
|
|
12
|
+
# so apps can call this unconditionally.
|
|
13
|
+
def modal_stack_stylesheet_link_tag(**)
|
|
14
|
+
provider = ModalStack.configuration.css_provider
|
|
15
|
+
return ActiveSupport::SafeBuffer.new if provider == :none
|
|
16
|
+
|
|
17
|
+
stylesheet_link_tag("modal_stack/#{provider}", **)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Renders the singleton <dialog> root that the modal-stack Stimulus
|
|
21
|
+
# controller binds to. Drop into your application layout:
|
|
22
|
+
#
|
|
23
|
+
# <%= modal_stack_dialog_tag %>
|
|
24
|
+
#
|
|
25
|
+
def modal_stack_dialog_tag(**html_options)
|
|
26
|
+
config = ModalStack.configuration
|
|
27
|
+
attrs = html_options.dup
|
|
28
|
+
attrs[:id] ||= config.dialog_id
|
|
29
|
+
|
|
30
|
+
existing_data = attrs[:data] || {}
|
|
31
|
+
controllers = [existing_data[:controller], config.stack_root_data_attribute].compact.join(" ").strip
|
|
32
|
+
attrs[:data] = existing_data.merge(controller: controllers)
|
|
33
|
+
|
|
34
|
+
content_tag(:dialog, "".html_safe, attrs)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Emits a no-op SafeBuffer for now — kept as a stable hook for apps
|
|
38
|
+
# that prefer a single line in their layout. The actual JS loading
|
|
39
|
+
# is handled by the host app's bundler / importmap.
|
|
40
|
+
def modal_stack_javascript_tag(**)
|
|
41
|
+
ActiveSupport::SafeBuffer.new
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ModalStack
|
|
4
|
+
module Helpers
|
|
5
|
+
# Wraps the layout content in the panel structure expected by the
|
|
6
|
+
# JS runtime. The layout `modal.html.erb` typically reads:
|
|
7
|
+
#
|
|
8
|
+
# <%= modal_stack_container size: :md, dismissible: true do %>
|
|
9
|
+
# <%= yield %>
|
|
10
|
+
# <% end %>
|
|
11
|
+
#
|
|
12
|
+
module ModalStackContainerHelper
|
|
13
|
+
DEFAULT_SIZE = :md
|
|
14
|
+
|
|
15
|
+
def modal_stack_container(size: DEFAULT_SIZE, dismissible: true, variant: :modal, side: nil, width: nil, height: nil, html: {},
|
|
16
|
+
&)
|
|
17
|
+
classes = ["modal-stack__panel", "modal-stack__panel--#{variant}", "modal-stack__panel--size-#{size}"]
|
|
18
|
+
classes << "modal-stack__panel--side-#{side}" if side
|
|
19
|
+
|
|
20
|
+
attrs = {
|
|
21
|
+
class: [classes, html[:class]].compact.join(" "),
|
|
22
|
+
data: {
|
|
23
|
+
modal_stack_size: size,
|
|
24
|
+
modal_stack_variant: variant,
|
|
25
|
+
modal_stack_dismissible: dismissible.to_s,
|
|
26
|
+
modal_stack_side: side,
|
|
27
|
+
modal_stack_width: width,
|
|
28
|
+
modal_stack_height: height
|
|
29
|
+
}.merge(html.fetch(:data, {})).compact
|
|
30
|
+
}.merge(html.except(:class, :data))
|
|
31
|
+
|
|
32
|
+
content_tag(:div, capture(&), **attrs)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ModalStack
|
|
4
|
+
module InitializerVersionCheck
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def perform
|
|
8
|
+
return if ModalStack.configuration.silence_initializer_warning
|
|
9
|
+
|
|
10
|
+
stamped = ModalStack.configuration.initializer_version
|
|
11
|
+
shipped = ModalStack::INITIALIZER_VERSION
|
|
12
|
+
|
|
13
|
+
if stamped.nil?
|
|
14
|
+
warn(
|
|
15
|
+
"[modal_stack] config/initializers/modal_stack.rb has no " \
|
|
16
|
+
"config.initializer_version. The initializer template shipped " \
|
|
17
|
+
"with #{shipped} introduces options the older template did " \
|
|
18
|
+
"not have — regenerate with `bin/rails g modal_stack:install " \
|
|
19
|
+
"--skip-layout --force`. Set " \
|
|
20
|
+
"`config.silence_initializer_warning = true` to silence."
|
|
21
|
+
)
|
|
22
|
+
elsif stamped != shipped
|
|
23
|
+
warn(
|
|
24
|
+
"[modal_stack] config/initializers/modal_stack.rb is stamped " \
|
|
25
|
+
"for v#{stamped} but the gem ships v#{shipped}. The template " \
|
|
26
|
+
"may have new options — review the diff or regenerate with " \
|
|
27
|
+
"`bin/rails g modal_stack:install --skip-layout --force`. Set " \
|
|
28
|
+
"`config.silence_initializer_warning = true` to silence."
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ModalStack
|
|
4
|
+
# Custom Turbo Stream actions for stack manipulation. Mixed into
|
|
5
|
+
# Turbo::Streams::TagBuilder via the :turbo_streams_tag_builder load hook
|
|
6
|
+
# so the standard `turbo_stream.foo(...)` form keeps working alongside.
|
|
7
|
+
module TurboStreamsExtension
|
|
8
|
+
HISTORY_MODES = %i[push replace].freeze
|
|
9
|
+
|
|
10
|
+
# Push a new layer on top of the stack. The content is rendered using
|
|
11
|
+
# the same options as Turbo's standard stream actions
|
|
12
|
+
# (partial:/locals:/template:/...).
|
|
13
|
+
#
|
|
14
|
+
# variant: :modal (default) | :drawer | :bottom_sheet | :confirmation
|
|
15
|
+
# dismissible: true (default) | false
|
|
16
|
+
# url: override the URL associated with this layer (defaults to the request path)
|
|
17
|
+
# side: only meaningful for :drawer — :left | :right | :top | :bottom
|
|
18
|
+
# size: :sm | :md | :lg | :xl | string
|
|
19
|
+
# width/height: CSS length values (e.g. "42rem", "70vh", "min(90vw, 56rem)")
|
|
20
|
+
def modal_push(content = nil, variant: :modal, dismissible: true, url: nil, side: nil, size: nil, width: nil, height: nil, **rendering,
|
|
21
|
+
&)
|
|
22
|
+
template = render_template(ModalStack::TARGET_ID, content, **rendering, &)
|
|
23
|
+
turbo_stream_action_tag(
|
|
24
|
+
:modal_push,
|
|
25
|
+
target: ModalStack::TARGET_ID,
|
|
26
|
+
template: template,
|
|
27
|
+
data: modal_data(variant: variant, dismissible: dismissible, url: url, side: side, size: size, width: width, height: height)
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Pop the top layer.
|
|
32
|
+
def modal_pop
|
|
33
|
+
turbo_stream_action_tag(:modal_pop, target: ModalStack::TARGET_ID)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Replace the top layer's content. Defaults to history.replaceState
|
|
37
|
+
# (no new history entry). Pass history: :push for a wizard-step semantic
|
|
38
|
+
# where browser-back returns to the previous step.
|
|
39
|
+
def modal_replace(content = nil, variant: nil, dismissible: nil, url: nil, history: :replace, layer_id: nil, side: nil, size: nil,
|
|
40
|
+
width: nil, height: nil, **rendering, &)
|
|
41
|
+
raise ArgumentError, "history: must be #{HISTORY_MODES.inspect}, got #{history.inspect}" unless HISTORY_MODES.include?(history)
|
|
42
|
+
|
|
43
|
+
template = render_template(ModalStack::TARGET_ID, content, **rendering, &)
|
|
44
|
+
turbo_stream_action_tag(
|
|
45
|
+
:modal_replace,
|
|
46
|
+
target: ModalStack::TARGET_ID,
|
|
47
|
+
template: template,
|
|
48
|
+
data: modal_data(
|
|
49
|
+
variant: variant,
|
|
50
|
+
dismissible: dismissible,
|
|
51
|
+
url: url,
|
|
52
|
+
side: side,
|
|
53
|
+
size: size,
|
|
54
|
+
width: width,
|
|
55
|
+
height: height,
|
|
56
|
+
history_mode: history,
|
|
57
|
+
layer_id: layer_id
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Tear down the entire stack.
|
|
63
|
+
def modal_close_all
|
|
64
|
+
turbo_stream_action_tag(:modal_close_all, target: ModalStack::TARGET_ID)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def modal_data(**attrs)
|
|
70
|
+
attrs.compact
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/modal_stack.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "modal_stack/version"
|
|
4
|
+
|
|
5
|
+
module ModalStack
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
# Default IDs / headers — exposed for code that needs the values
|
|
9
|
+
# without instantiating Configuration. Configuration overrides them
|
|
10
|
+
# at the application level (config.dialog_id =, config.request_header =).
|
|
11
|
+
TARGET_ID = "modal-stack-root"
|
|
12
|
+
REQUEST_HEADER = "X-Modal-Stack-Request"
|
|
13
|
+
|
|
14
|
+
# Bumped when config/initializers/modal_stack.rb gains/loses an option,
|
|
15
|
+
# so apps that haven't regenerated their initializer get a one-line
|
|
16
|
+
# boot warning. Independent from the gem's VERSION.
|
|
17
|
+
INITIALIZER_VERSION = "0.1.0"
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def configuration
|
|
21
|
+
@configuration ||= Configuration.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def configure
|
|
25
|
+
yield configuration
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def reset_configuration!
|
|
29
|
+
@configuration = Configuration.new
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
require_relative "modal_stack/configuration"
|
|
35
|
+
require_relative "modal_stack/initializer_version_check"
|
|
36
|
+
require "modal_stack/engine" if defined?(Rails::Engine)
|
metadata
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: modal_stack
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Florian Gagnaire
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-05-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: railties
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.2'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.2'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: turbo-rails
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: zeitwerk
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.6'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.6'
|
|
55
|
+
description: |
|
|
56
|
+
modal_stack adds a navigation stack on top of Hotwire: push N modals/drawers/bottom
|
|
57
|
+
sheets, deep-link the top of the stack via native Rails URLs, get full browser
|
|
58
|
+
history (back/forward) support, and drive everything from imperative Turbo Stream
|
|
59
|
+
actions (modal_push, modal_pop, modal_replace).
|
|
60
|
+
email:
|
|
61
|
+
- gagnaire.flo@gmail.com
|
|
62
|
+
executables: []
|
|
63
|
+
extensions: []
|
|
64
|
+
extra_rdoc_files: []
|
|
65
|
+
files:
|
|
66
|
+
- CHANGELOG.md
|
|
67
|
+
- CODE_OF_CONDUCT.md
|
|
68
|
+
- LICENSE.txt
|
|
69
|
+
- README.md
|
|
70
|
+
- Rakefile
|
|
71
|
+
- app/assets/javascripts/modal_stack.js
|
|
72
|
+
- app/assets/stylesheets/modal_stack/bootstrap.css
|
|
73
|
+
- app/assets/stylesheets/modal_stack/tailwind.css
|
|
74
|
+
- app/assets/stylesheets/modal_stack/vanilla.css
|
|
75
|
+
- app/javascript/modal_stack/controllers/modal_stack_controller.js
|
|
76
|
+
- app/javascript/modal_stack/controllers/modal_stack_link_controller.js
|
|
77
|
+
- app/javascript/modal_stack/index.js
|
|
78
|
+
- app/javascript/modal_stack/install.js
|
|
79
|
+
- app/javascript/modal_stack/orchestrator.js
|
|
80
|
+
- app/javascript/modal_stack/orchestrator.test.js
|
|
81
|
+
- app/javascript/modal_stack/runtime.js
|
|
82
|
+
- app/javascript/modal_stack/runtime.test.js
|
|
83
|
+
- app/javascript/modal_stack/state.js
|
|
84
|
+
- app/javascript/modal_stack/state.test.js
|
|
85
|
+
- app/views/layouts/modal.html.erb
|
|
86
|
+
- lib/generators/modal_stack/install/install_generator.rb
|
|
87
|
+
- lib/generators/modal_stack/install/templates/initializer.rb
|
|
88
|
+
- lib/modal_stack.rb
|
|
89
|
+
- lib/modal_stack/capybara.rb
|
|
90
|
+
- lib/modal_stack/capybara/minitest.rb
|
|
91
|
+
- lib/modal_stack/capybara/rspec.rb
|
|
92
|
+
- lib/modal_stack/configuration.rb
|
|
93
|
+
- lib/modal_stack/controller_extensions.rb
|
|
94
|
+
- lib/modal_stack/engine.rb
|
|
95
|
+
- lib/modal_stack/helpers/modal_link_helper.rb
|
|
96
|
+
- lib/modal_stack/helpers/modal_stack_assets_helper.rb
|
|
97
|
+
- lib/modal_stack/helpers/modal_stack_container_helper.rb
|
|
98
|
+
- lib/modal_stack/initializer_version_check.rb
|
|
99
|
+
- lib/modal_stack/turbo_streams_extension.rb
|
|
100
|
+
- lib/modal_stack/version.rb
|
|
101
|
+
homepage: https://github.com/Metalzoid/modal_stack
|
|
102
|
+
licenses:
|
|
103
|
+
- MIT
|
|
104
|
+
metadata:
|
|
105
|
+
homepage_uri: https://github.com/Metalzoid/modal_stack
|
|
106
|
+
source_code_uri: https://github.com/Metalzoid/modal_stack
|
|
107
|
+
changelog_uri: https://github.com/Metalzoid/modal_stack/blob/main/CHANGELOG.md
|
|
108
|
+
bug_tracker_uri: https://github.com/Metalzoid/modal_stack/issues
|
|
109
|
+
rubygems_mfa_required: 'true'
|
|
110
|
+
allowed_push_host: https://rubygems.org
|
|
111
|
+
post_install_message:
|
|
112
|
+
rdoc_options: []
|
|
113
|
+
require_paths:
|
|
114
|
+
- lib
|
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: 3.2.0
|
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
requirements: []
|
|
126
|
+
rubygems_version: 3.5.22
|
|
127
|
+
signing_key:
|
|
128
|
+
specification_version: 4
|
|
129
|
+
summary: Stackable modals, drawers and bottom sheets for Hotwire-powered Rails apps.
|
|
130
|
+
test_files: []
|