modal_stack 0.1.1 → 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 +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +73 -26
- data/app/assets/javascripts/modal_stack.js +230 -41
- data/app/assets/stylesheets/modal_stack/bootstrap.css +7 -8
- data/app/assets/stylesheets/modal_stack/{tailwind.css → tailwind_v3.css} +19 -12
- data/app/assets/stylesheets/modal_stack/tailwind_v4.css +311 -0
- data/app/assets/stylesheets/modal_stack/vanilla.css +7 -8
- data/app/javascript/modal_stack/controllers/modal_stack_controller.js +52 -13
- data/app/javascript/modal_stack/controllers/modal_stack_link_controller.js +29 -7
- data/app/javascript/modal_stack/orchestrator.js +136 -4
- data/app/javascript/modal_stack/orchestrator.test.js +218 -2
- data/app/javascript/modal_stack/runtime.js +91 -10
- data/app/javascript/modal_stack/runtime.test.js +138 -1
- data/app/javascript/modal_stack/state.js +142 -8
- data/app/javascript/modal_stack/state.test.js +89 -5
- data/lib/generators/modal_stack/install/install_generator.rb +18 -4
- data/lib/generators/modal_stack/install/templates/initializer.rb +19 -6
- data/lib/modal_stack/configuration.rb +44 -5
- data/lib/modal_stack/controller_extensions.rb +8 -1
- data/lib/modal_stack/helpers/modal_stack_assets_helper.rb +26 -6
- data/lib/modal_stack/version.rb +1 -1
- data/lib/modal_stack.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a4ea30705b3d178cd2be1ad1a3b0a7b5487b40490b327d0e727b29e9e644f06f
|
|
4
|
+
data.tar.gz: '049b2eafb22af9cffd3ec3d454a23550dff04273cb23e4b96146a3716a62a383'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: da14584321b5c28cd93ecbc9dd260865313c5ccd62e17d7846a734c3da47b54e3f0ac20b703542cb14d8cc93fe3611c5de0d7e17c06900ecab92c554ae5c806f
|
|
7
|
+
data.tar.gz: 111ac685cf469d968fb71288cc13822d059e97212a01b098d3766ce0136990ac1b0db4384777a86245fb6923cb225fc62c23543979d79fe18efbc1d2b1e8dbfe
|
data/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
12
12
|
|
|
13
13
|
### Fixed
|
|
14
14
|
|
|
15
|
+
## [0.3.0] - 2026-05-03
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Prefetch cache + dedupe** in `Orchestrator`: fragments fetched for `push` / `replaceTop` are cached per URL (TTL 30 s) and concurrent prefetches for the same URL share a single in-flight request. Aborts pending requests on `closeAll` and on stack-mismatching `popstate`.
|
|
19
|
+
- **Hover/focus prefetch** on `modal-stack-link`: links warm the cache on `pointerenter` / `focus` so the modal opens with no network latency on click. Opt out with `data-modal-stack-link-prefetch="false"`.
|
|
20
|
+
- **`Orchestrator#prefetch(url)`** public method (and matching `ModalStackController#prefetch`) for warming the cache from app code.
|
|
21
|
+
- **Request-scoped configuration** via `controller.modal_stack_config` (helper-method exposed). Rendering helpers now read configuration once per request instead of once per call.
|
|
22
|
+
- **`:tailwind_v4` CSS preset** that chains on Tailwind v4 `@theme` tokens (`--color-*`, `--radius-*`, `--shadow-*`, `--ease-*`, `--container-*`), so the modal stack inherits the host project's Tailwind theme automatically. Fallbacks match the Tailwind v4 defaults so the preset still renders correctly when `@theme` isn't redefined (or when Tailwind v4 isn't installed).
|
|
23
|
+
- **`:tailwind_v3` CSS preset** — the previous `:tailwind` preset, renamed for clarity. Static values aligned with Tailwind v3 defaults (Tailwind v3 doesn't expose tokens as CSS variables).
|
|
24
|
+
- `Configuration::CSS_PROVIDER_ALIASES` constant for legacy `:tailwind` → `:tailwind_v3` normalization.
|
|
25
|
+
- New tests: prefetch dedupe / cache hit / TTL / abort-on-closeAll / `prefetch` API; CSS-derived leave timeout; `:tailwind` alias normalization; generator default + alias mapping.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- **Animation safety timeout** is now derived from the `--modal-stack-duration` CSS variable on the `<dialog>` (1.5× the declared duration, floored at 300 ms). Falls back to 600 ms when the variable is unset, so existing host CSS keeps working.
|
|
29
|
+
- `BrowserRuntime#fetchFragment` accepts an `{ signal }` option to support `AbortController` cancellation.
|
|
30
|
+
- **CSS preset perf overhaul**: removed animated blur from all four presets — animating `backdrop-filter: blur(2px)` on a fullscreen surface costs ~190 ms/frame on Hi-DPI displays (Retina, 4K), which collapsed modal animations to ~5 fps.
|
|
31
|
+
- The old `--modal-stack-backdrop-blur` variable (radius only) is replaced with `--modal-stack-backdrop-filter` (full filter expression). Default is `none`, which lets Chrome skip the filter pass entirely — `backdrop-filter: blur(0)` still allocated a filter layer, so a `0` radius wasn't actually free. Apps that want a blurred backdrop now opt in with `:root { --modal-stack-backdrop-filter: blur(8px); }` (any filter expression accepted, not just `blur()`).
|
|
32
|
+
- `backdrop-filter` is no longer in the backdrop's `transition` list — when the user opts in, the filter is applied statically when the dialog opens, so the cost is paid once and the per-frame compositor work disappears.
|
|
33
|
+
- `filter: blur(0.5px)` on inert (underlying) layers has been dropped — only `opacity: 0.5` remains. The blur was visually negligible on screen but forced an extra GPU layer per stacked modal.
|
|
34
|
+
- The `filter` property has been removed from the layer's `transition` list since nothing animates it any more.
|
|
35
|
+
- **Backdrop fade now runs in parallel with the layer's leave animation** when closing the last modal (or `closeAll`). Previously the reducer emitted `[unmountTopLayer, …, closeDialog, …]` and the orchestrator awaited each command sequentially — `closeDialog` only fired after the layer's 220 ms `transitionend`, so the backdrop fade-out was perceived *after* the modal was gone (effective close time ≈ 440 ms). The reducer now emits `closeDialog` first; it's synchronous on the runtime, so the dialog's exit transition (opacity, backdrop background, `display`/`overlay` `allow-discrete`) starts immediately and runs alongside the layer's `[data-leaving]` transition. Total close time is now ≈ 220 ms. Fix applied to `pop`, `closeAll`, and the two `handlePopstate` branches that close the dialog.
|
|
36
|
+
- **`config.css_provider` default** is now `:tailwind_v3` (was `:tailwind`). Existing apps with `config.css_provider = :tailwind` keep working — the alias is normalized to `:tailwind_v3` on assignment, with no rendered CSS change.
|
|
37
|
+
- **Generator default `--css-provider`** is now `tailwind_v4` for new installs (was `tailwind`). Run `bin/rails g modal_stack:install --css-provider=tailwind_v3` to opt back in to the v3 preset, or pass the legacy `tailwind` flag — it's normalized to `tailwind_v3` in the generated initializer.
|
|
38
|
+
- The `app/assets/stylesheets/modal_stack/tailwind.css` file has been renamed to `tailwind_v3.css`. Sprockets manifests written by previous installs that contain `//= link modal_stack/tailwind.css` need the line updated to `tailwind_v3.css` (or `tailwind_v4.css`). Importmap / jsbundling installs aren't affected — they don't reference the stylesheet by filename.
|
|
39
|
+
- `INITIALIZER_VERSION` bumped to `0.3.0` because the generator template documents the new providers.
|
|
40
|
+
|
|
41
|
+
## [0.2.0] - 2026-05-03
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- **`max_depth` enforcement**: pushes past the cap are now intercepted by the reducer. The new `config.max_depth_strategy` (`:warn` default, `:raise`, `:silent`) controls behaviour. The cap can be disabled with `config.max_depth = nil`.
|
|
45
|
+
- **`ModalStackDepthError`** JS class, thrown by `push()` under the `:raise` strategy. Exported from `state.js`.
|
|
46
|
+
- **Scrollbar-width compensation**: `BrowserRuntime#lockScroll` now sets `--modal-stack-scrollbar-width` on `<html>` so the host CSS can offset fixed elements without layout shift. The CSS variable was already referenced by the Tailwind / Bootstrap / vanilla presets — this completes the wiring.
|
|
47
|
+
- **`modal_stack:error` custom event**: malformed Turbo Stream payloads (bad `data-*`, fetch failures) no longer crash the page. The error is logged and re-emitted as a bubbling `CustomEvent` on the `<dialog>` so apps can surface UI feedback.
|
|
48
|
+
- **JSDoc** on the JS public surface (`state.js`, `runtime.js`, `orchestrator.js`) — including `Layer`, `Stack`, `Command`, and `Transition` typedefs.
|
|
49
|
+
- New tests: max_depth strategies, scrollbar-width compensation, missing-handler error message, default_dismissible/max_depth/max_depth_strategy validation, dialog tag wiring.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
- `Configuration#default_dismissible=` now raises `ArgumentError` on non-boolean values (was a silent `attr_accessor`).
|
|
53
|
+
- `Configuration#max_depth=` now coerces strings, accepts `nil`, and rejects non-positive integers.
|
|
54
|
+
- `Orchestrator` constructor accepts `maxDepth` + `maxDepthStrategy`. The Stimulus controller forwards them via `data-modal-stack-max-depth-value` / `data-modal-stack-max-depth-strategy-value`, which `modal_stack_dialog_tag` now emits from the gem's configuration.
|
|
55
|
+
- The "runtime missing handler" error message now lists the runtime's known handlers and the current stack depth.
|
|
56
|
+
- `INITIALIZER_VERSION` bumped to `0.2.0` because the generator template gained `config.max_depth_strategy`.
|
|
57
|
+
|
|
15
58
|
## [0.1.1] - 2026-05-02
|
|
16
59
|
|
|
17
60
|
### Added
|
data/README.md
CHANGED
|
@@ -9,9 +9,10 @@ browser back/forward support, and drive everything from imperative Turbo
|
|
|
9
9
|
Stream actions (`modal_push`, `modal_pop`, `modal_replace`, `modal_close_all`).
|
|
10
10
|
|
|
11
11
|
[](https://github.com/Metalzoid/modal_stack/actions)
|
|
12
|
-
[](https://rubygems.org/gems/modal_stack)
|
|
13
|
+
[](https://rubygems.org/gems/modal_stack)
|
|
14
|
+
[](https://www.ruby-lang.org/)
|
|
15
|
+
[](https://rubyonrails.org/)
|
|
15
16
|
[](LICENSE.txt)
|
|
16
17
|
|
|
17
18
|
</div>
|
|
@@ -140,10 +141,11 @@ $ bin/rails g modal_stack:install --mode=sprockets # legacy apps
|
|
|
140
141
|
Pick the CSS preset that matches your stack:
|
|
141
142
|
|
|
142
143
|
```bash
|
|
143
|
-
$ bin/rails g modal_stack:install --css-provider=
|
|
144
|
-
$ bin/rails g modal_stack:install --css-provider=
|
|
145
|
-
$ bin/rails g modal_stack:install --css-provider=
|
|
146
|
-
$ bin/rails g modal_stack:install --css-provider=
|
|
144
|
+
$ bin/rails g modal_stack:install --css-provider=tailwind_v4 # default — chains on Tailwind v4 @theme tokens
|
|
145
|
+
$ bin/rails g modal_stack:install --css-provider=tailwind_v3 # static values aligned with Tailwind v3
|
|
146
|
+
$ bin/rails g modal_stack:install --css-provider=bootstrap # picks up Bootstrap 5 vars
|
|
147
|
+
$ bin/rails g modal_stack:install --css-provider=vanilla # framework-free
|
|
148
|
+
$ bin/rails g modal_stack:install --css-provider=none # bring your own CSS
|
|
147
149
|
```
|
|
148
150
|
|
|
149
151
|
### What the generator does
|
|
@@ -204,13 +206,14 @@ Everything lives in `config/initializers/modal_stack.rb`:
|
|
|
204
206
|
```ruby
|
|
205
207
|
ModalStack.configure do |config|
|
|
206
208
|
# ─── Presentation ─────────────────────────────────────────────────
|
|
207
|
-
config.css_provider = :
|
|
209
|
+
config.css_provider = :tailwind_v4 # :tailwind_v3 | :tailwind_v4 | :bootstrap | :vanilla | :none
|
|
208
210
|
config.default_variant = :modal # :modal | :drawer | :bottom_sheet | :confirmation
|
|
209
211
|
config.default_size = :md # :sm | :md | :lg | :xl
|
|
210
212
|
config.default_dismissible = true # ESC + backdrop click close the layer
|
|
211
213
|
|
|
212
214
|
# ─── Behavior ─────────────────────────────────────────────────────
|
|
213
|
-
config.max_depth = 5 # hard cap on nested layers
|
|
215
|
+
config.max_depth = 5 # hard cap on nested layers (nil to disable)
|
|
216
|
+
config.max_depth_strategy = :warn # :warn | :raise | :silent
|
|
214
217
|
config.respect_reduced_motion = true # honor prefers-reduced-motion
|
|
215
218
|
config.replace_turbo_confirm = false # use modal_stack confirmations for data-turbo-confirm
|
|
216
219
|
|
|
@@ -393,8 +396,17 @@ The `<dialog>` itself is opened on first push, closed on last pop. Page
|
|
|
393
396
|
scroll is locked while any layer is open (`<body data-modal-stack-locked>`)
|
|
394
397
|
so the page beneath doesn't scroll under your finger on touch devices.
|
|
395
398
|
|
|
396
|
-
`max_depth` (default `5`) is a hard ceiling
|
|
397
|
-
|
|
399
|
+
`max_depth` (default `5`) is a hard ceiling on the number of stacked layers,
|
|
400
|
+
on the assumption that going past it usually means you have a state-machine
|
|
401
|
+
bug. The behaviour is controlled by `config.max_depth_strategy`:
|
|
402
|
+
|
|
403
|
+
| Strategy | Behaviour |
|
|
404
|
+
| ---------- | -------------------------------------------------------------------- |
|
|
405
|
+
| `:warn` | (default) The push is dropped and `console.warn` logs a message. |
|
|
406
|
+
| `:raise` | The JS runtime throws `ModalStackDepthError` (caught by the stream-action error boundary, see below). |
|
|
407
|
+
| `:silent` | The push is dropped without logging. |
|
|
408
|
+
|
|
409
|
+
Set `config.max_depth = nil` to disable the cap entirely.
|
|
398
410
|
|
|
399
411
|
---
|
|
400
412
|
|
|
@@ -410,13 +422,14 @@ ModalStack.reset_configuration! # test-fixture helper
|
|
|
410
422
|
|
|
411
423
|
| Attribute | Type | Default | Description |
|
|
412
424
|
| ---------------------------- | ------- | ------------------------ | ----------- |
|
|
413
|
-
| `css_provider` | Symbol | `:
|
|
425
|
+
| `css_provider` | Symbol | `:tailwind_v3` | One of `:tailwind_v3`, `:tailwind_v4`, `:bootstrap`, `:vanilla`, `:none`. Determines which stylesheet `modal_stack_stylesheet_link_tag` resolves to. The legacy `:tailwind` is accepted and normalized to `:tailwind_v3`. New installs default to `:tailwind_v4`. Validated. |
|
|
414
426
|
| `assets_mode` | Symbol | `:auto` | One of `:importmap`, `:jsbundling`, `:sprockets`, `:auto`. Used by the generator. Validated. |
|
|
415
427
|
| `default_variant` | Symbol | `:modal` | `:modal`, `:drawer`, `:bottom_sheet`, or `:confirmation`. Validated. |
|
|
416
428
|
| `default_size` | Symbol | `:md` | `:sm`, `:md`, `:lg`, `:xl`. Validated. |
|
|
417
429
|
| `default_dismissible` | Boolean | `true` | Default for `dismissible:` when omitted. |
|
|
418
430
|
| `default_classes` | Hash | `{ ... }` | Hash of extra CSS class strings keyed by `:modal_panel`, `:drawer_panel`, `:bottom_sheet_panel`, `:confirmation_panel`. Useful for adding utility classes on top of the chosen preset. |
|
|
419
|
-
| `max_depth` | Integer | `5` | Hard cap on stack depth
|
|
431
|
+
| `max_depth` | Integer | `5` | Hard cap on stack depth. Coerced from strings; set to `nil` to disable. Validated. |
|
|
432
|
+
| `max_depth_strategy` | Symbol | `:warn` | One of `:warn`, `:raise`, `:silent`. See [Stack depth & inertness](#stack-depth--inertness). Validated. |
|
|
420
433
|
| `request_header` | String | `"X-Modal-Stack-Request"` | HTTP header used by the JS runtime to signal stack-originated fetches. Read by `modal_stack_request?`. |
|
|
421
434
|
| `dialog_id` | String | `"modal-stack-root"` | The id of the singleton `<dialog>`. Override only on name collision. |
|
|
422
435
|
| `stack_root_data_attribute` | String | `"modal-stack"` | The Stimulus `data-controller` value attached to the `<dialog>`. |
|
|
@@ -434,7 +447,7 @@ Injected into `ActionView::Base` by the engine — available in every view.
|
|
|
434
447
|
| ------------------------------------------------- | ----------- |
|
|
435
448
|
| `modal_link_to(name, options, html_options)` | Renders a `link_to` wired to push a layer when clicked. Accepts the modal options (`as:`, `side:`, `size:`, `width:`, `height:`, `dismissible:`) on top of standard `link_to` arguments. Falls back to plain `link_to` for Hotwire Native requests. |
|
|
436
449
|
| `modal_stack_container(size:, variant:, side:, width:, height:, dismissible:, html: {}) { ... }` | Wraps a panel view with the markup the JS runtime expects. Renders a `<div>` carrying the size/variant/dismissible/dimension data attributes. |
|
|
437
|
-
| `modal_stack_stylesheet_link_tag(**options)` | Emits `<link rel="stylesheet">` for the configured preset (`modal_stack/
|
|
450
|
+
| `modal_stack_stylesheet_link_tag(**options)` | Emits `<link rel="stylesheet">` for the configured preset (`modal_stack/tailwind_v4.css`, etc.). Returns an empty SafeBuffer when `css_provider = :none`. |
|
|
438
451
|
| `modal_stack_dialog_tag(**html_options)` | Emits the singleton `<dialog id="modal-stack-root" data-controller="modal-stack">`. Drop just before `</body>`. |
|
|
439
452
|
| `modal_stack_javascript_tag` | Reserved hook for layouts; currently a no-op (JS is loaded via your bundler / importmap). |
|
|
440
453
|
|
|
@@ -503,11 +516,11 @@ The package exports a small functional core + a browser adapter:
|
|
|
503
516
|
import {
|
|
504
517
|
// pure reducer — no IO, no DOM
|
|
505
518
|
createStack, push, pop, replaceTop, closeAll, handlePopstate,
|
|
506
|
-
snapshot, restore, topLayer, VARIANTS,
|
|
519
|
+
snapshot, restore, topLayer, VARIANTS, ModalStackDepthError,
|
|
507
520
|
|
|
508
521
|
// orchestrator + browser runtime
|
|
509
522
|
Orchestrator, BrowserRuntime,
|
|
510
|
-
FRAGMENT_HEADER, SNAPSHOT_KEY,
|
|
523
|
+
FRAGMENT_HEADER, SNAPSHOT_KEY, SCROLLBAR_WIDTH_VAR,
|
|
511
524
|
} from "modal_stack"
|
|
512
525
|
|
|
513
526
|
import { install } from "modal_stack/install"
|
|
@@ -518,6 +531,39 @@ entry point your `application.js` calls. The reducer is
|
|
|
518
531
|
side-effect-free and 100% covered; the browser adapter is the only
|
|
519
532
|
file that touches `<dialog>`, `history`, `fetch`, and `sessionStorage`.
|
|
520
533
|
|
|
534
|
+
The reducer's command type vocabulary (`mountLayer`, `morphTopLayer`,
|
|
535
|
+
`unmountTopLayer`, `unmountAllLayers`, `showDialog`, `closeDialog`,
|
|
536
|
+
`lockScroll`, `unlockScroll`, `inertLayer`, `pushHistory`,
|
|
537
|
+
`replaceHistory`, `historyBack`, `rebuildFromSnapshot`, `persistSnapshot`,
|
|
538
|
+
`clearSnapshot`) forms the contract between `state.js` and any runtime —
|
|
539
|
+
swap in a custom adapter (e.g. for Hotwire Native) by implementing one
|
|
540
|
+
method per command name.
|
|
541
|
+
|
|
542
|
+
#### Custom events
|
|
543
|
+
|
|
544
|
+
The `<dialog>` emits two `CustomEvent`s that bubble to `document`:
|
|
545
|
+
|
|
546
|
+
| Event | `detail` | Fired when |
|
|
547
|
+
| ---------------------- | ------------------------------------------- | ---------- |
|
|
548
|
+
| `modal_stack:ready` | `{ stackId }` | The Stimulus controller has connected and the orchestrator is ready. |
|
|
549
|
+
| `modal_stack:error` | `{ action, error }` | A Turbo Stream action (`modal_push`/`modal_pop`/`modal_replace`/`modal_close_all`) threw or rejected. The page is not crashed; surface UI feedback in the listener. |
|
|
550
|
+
|
|
551
|
+
```js
|
|
552
|
+
document.addEventListener("modal_stack:error", (event) => {
|
|
553
|
+
const { action, error } = event.detail;
|
|
554
|
+
showFlash(`Modal action ${action} failed: ${error.message}`);
|
|
555
|
+
});
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
#### Scrollbar-width compensation
|
|
559
|
+
|
|
560
|
+
When the first layer is pushed, `BrowserRuntime#lockScroll` measures
|
|
561
|
+
`window.innerWidth - documentElement.clientWidth` and writes the result
|
|
562
|
+
to `--modal-stack-scrollbar-width` on `<html>`. The shipped CSS presets
|
|
563
|
+
already consume the variable (`padding-right: var(--modal-stack-scrollbar-width, 0)`)
|
|
564
|
+
so fixed elements don't jump rightward on lock. If you maintain custom
|
|
565
|
+
CSS, compose your fixed-position rules against the same variable.
|
|
566
|
+
|
|
521
567
|
### Capybara helpers
|
|
522
568
|
|
|
523
569
|
For system specs, opt in by requiring the RSpec entrypoint:
|
|
@@ -550,7 +596,7 @@ $ bin/rails g modal_stack:install [flags]
|
|
|
550
596
|
| Flag | Type | Default | Values |
|
|
551
597
|
| --------------------- | ------- | ----------- | ------ |
|
|
552
598
|
| `--mode` | String | `auto` | `auto`, `importmap`, `jsbundling`, `sprockets` |
|
|
553
|
-
| `--css-provider` | String | `
|
|
599
|
+
| `--css-provider` | String | `tailwind_v4` | `tailwind_v3`, `tailwind_v4`, `bootstrap`, `vanilla`, `none` (legacy `tailwind` accepted, normalized to `tailwind_v3`) |
|
|
554
600
|
| `--skip-layout` | Boolean | `false` | When set, doesn't inject the stylesheet/dialog helpers into `application.html.erb` |
|
|
555
601
|
| `--skip-js` | Boolean | `false` | When set, skips the Importmap pin / package install / Stimulus install wiring |
|
|
556
602
|
| `--skip-initializer` | Boolean | `false` | When set, doesn't generate `config/initializers/modal_stack.rb` |
|
|
@@ -569,17 +615,18 @@ safe.
|
|
|
569
615
|
|
|
570
616
|
## 🎨 CSS presets & theming
|
|
571
617
|
|
|
572
|
-
|
|
618
|
+
Four opinionated stylesheets ship with the gem. Pick one with
|
|
573
619
|
`config.css_provider`:
|
|
574
620
|
|
|
575
|
-
| Preset
|
|
576
|
-
|
|
|
577
|
-
| `:
|
|
578
|
-
| `:
|
|
579
|
-
| `:
|
|
580
|
-
| `:
|
|
621
|
+
| Preset | File | Best for |
|
|
622
|
+
| --------------- | ----------------------------------------------------- | -------- |
|
|
623
|
+
| `:tailwind_v4` | `app/assets/stylesheets/modal_stack/tailwind_v4.css` | Tailwind v4 apps — chains on `@theme` tokens (`--color-*`, `--radius-*`, `--shadow-*`, `--container-*`) so the modal picks up your theme automatically. Falls back to Tailwind defaults when `@theme` isn't redefined. |
|
|
624
|
+
| `:tailwind_v3` | `app/assets/stylesheets/modal_stack/tailwind_v3.css` | Tailwind v3 apps — static values aligned with Tailwind v3 defaults (v3 doesn't expose tokens as CSS variables). Legacy `:tailwind` is accepted as an alias. |
|
|
625
|
+
| `:bootstrap` | `app/assets/stylesheets/modal_stack/bootstrap.css` | Picks up Bootstrap 5 CSS variables |
|
|
626
|
+
| `:vanilla` | `app/assets/stylesheets/modal_stack/vanilla.css` | Framework-free, neutral defaults |
|
|
627
|
+
| `:none` | — | Bring your own CSS |
|
|
581
628
|
|
|
582
|
-
All
|
|
629
|
+
All presets are driven by the same `--modal-stack-*` CSS variables.
|
|
583
630
|
Override on `:root` to retheme without touching the gem:
|
|
584
631
|
|
|
585
632
|
```css
|
|
@@ -676,7 +723,7 @@ modal_stack/
|
|
|
676
723
|
├── app/
|
|
677
724
|
│ ├── assets/
|
|
678
725
|
│ │ ├── javascripts/modal_stack.js # pre-built importmap bundle (committed)
|
|
679
|
-
│ │ └── stylesheets/modal_stack/ #
|
|
726
|
+
│ │ └── stylesheets/modal_stack/ # tailwind_v3 / tailwind_v4 / bootstrap / vanilla presets
|
|
680
727
|
│ ├── javascript/modal_stack/ # ES module sources + bun tests
|
|
681
728
|
│ │ ├── state.js # pure reducer (100% coverage)
|
|
682
729
|
│ │ ├── orchestrator.js # state → command translator
|