modal_stack 0.4.2 → 0.4.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ef421729ab66f062b0cd608549828f9cec8dd70a8854ad48b94baf2932c70b0
4
- data.tar.gz: 3de87974331a9634bc32baba6563f20b34da3121594a32627335bfc13b13d8df
3
+ metadata.gz: 992372f08bce8ea31105e8cba05c60035014852c197c38b325648eaebf154820
4
+ data.tar.gz: 70db58c91eda0101149fde86cec748812c7b8aff3f3908a30d6178538000c98f
5
5
  SHA512:
6
- metadata.gz: 3c217b0849b43aece36f81f333cd78169db098009f4f04cab70ab79f5788949cf4f3c0e99a6aa658ea35e5700d120477e98a8903ead7ca1481f63063068d4abd
7
- data.tar.gz: 4897264315c5f453abc0302b2f1b436db73ba38f5724c79c568631489d0efdb5b90fa653137ddcdc60326fac0461c3fe0d1dfc20762151c199748454fcb26d33
6
+ metadata.gz: 98043c481ff734f042b88f2117b766fb1dc2110dfdacbad71d66de39471999d15e1dfb368e9466964d1127b809ed4bb3a8aea4fb83847aba6163d717f75eb4bf
7
+ data.tar.gz: 46b3b5491dd13836ee207e62b42d2a3023ee1b65ac75da634d40a944608822b2a40e102ef39861f0208ab47a17afb4dbe0d529341c8d9611adce2ef9ba600ab5
data/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.3] - 2026-05-25
10
+
11
+ ### Added
12
+ - **`title:` option on `modal_stack_container`** — renders a `<header class="modal-stack__panel-header">` containing an `<h2 class="modal-stack__panel-title">` above the panel content. No output when omitted, so existing panels are unaffected.
13
+ - **`close_button:` option on `modal_stack_container`** — renders a `×` button (`<button class="modal-stack__panel-close">`) wired to `modal-stack#pop`. Defaults to the `dismissible:` value, so dismissible layers get a close button for free and locked layers (`dismissible: false`) get none by default. Pass `close_button: false` to suppress it explicitly on a dismissible layer.
14
+ - **CSS** for the new header slot in all four presets (`tailwind_v4`, `tailwind_v3`, `bootstrap`, `vanilla`): `.modal-stack__panel-header` (flexbox row), `.modal-stack__panel-title` (flex-1), `.modal-stack__panel-close` (transparent button, opacity hover, focus-visible ring).
15
+ - `title` and `show_close` passed as **separate partial locals** to `_panel.html.erb` so host apps overriding the partial can consume them individually rather than receiving a pre-rendered HTML blob.
16
+
9
17
  ## [0.4.2] - 2026-05-15
10
18
 
11
19
  ### Added
data/README.md CHANGED
@@ -279,9 +279,30 @@ options at the call site:
279
279
  ```
280
280
 
281
281
  `modal_stack_container` accepts `size:`, `variant:`, `side:`, `width:`,
282
- `height:`, `dismissible:`, and an `html: { class:, data:, ... }` Hash for
282
+ `height:`, `dismissible:`, `title:`, `close_button:`, and an `html: { class:, data:, ... }` Hash for
283
283
  extra attributes on the wrapping `<div>`.
284
284
 
285
+ Pass `title:` to render a `<header>` with an `<h2>` above the panel content.
286
+ `close_button:` (default: inherits `dismissible:`) renders a `×` button wired to
287
+ `modal-stack#pop` — locked layers (`dismissible: false`) get no close button by default:
288
+
289
+ ```erb
290
+ <%# Dismissible — title + auto close button %>
291
+ <%= modal_stack_container title: "Edit project", size: :md do %>
292
+ <%= render "form", project: @project %>
293
+ <% end %>
294
+
295
+ <%# Locked — title only, no × (close_button: defaults to false) %>
296
+ <%= modal_stack_container title: "Are you sure?",
297
+ variant: :confirmation,
298
+ dismissible: false do %>
299
+ <button data-action="click->modal-stack#pop">Confirm</button>
300
+ <% end %>
301
+
302
+ <%# Explicit override %>
303
+ <%= modal_stack_container title: "Info", close_button: false do %>…<% end %>
304
+ ```
305
+
285
306
  ### Stack-aware controllers
286
307
 
287
308
  `modal_stack_layout` switches the controller's layout to `modal` **only**
@@ -484,7 +505,7 @@ Injected into `ActionView::Base` by the engine — available in every view.
484
505
  | Helper | Description |
485
506
  | ------------------------------------------------- | ----------- |
486
507
  | `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. |
487
- | `modal_stack_container(size:, variant:, side:, width:, height:, dismissible:, back:, transition:, html: {}) { ... }` | Wraps a panel view with the markup the JS runtime expects. `back: true` injects a back-button slot wired to `modal-stack#pathBack` (hidden by CSS at the first frame); `transition:` writes `data-modal-stack-transition` for host CSS hooks. |
508
+ | `modal_stack_container(size:, variant:, side:, width:, height:, dismissible:, back:, transition:, title:, close_button:, html: {}) { ... }` | Wraps a panel view with the markup the JS runtime expects. `back: true` injects a back-button slot wired to `modal-stack#pathBack` (hidden by CSS at the first frame); `transition:` writes `data-modal-stack-transition` for host CSS hooks. `title:` renders a `<header>` with `<h2>`; `close_button:` (default: `dismissible:` value) renders a `×` close button. |
488
509
  | `modal_back_link(name = nil, **opts) { ... }` | Renders a `<button>` wired to the `modal-stack-back-link` Stimulus controller. Pass `steps:` (default `1`) to walk back multiple frames in a single click — clamped at the first frame, never closes the layer. Block form supported for custom markup (e.g. `<%= modal_back_link(class: "btn") { "← Back" } %>`). |
489
510
  | `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`. |
490
511
  | `modal_stack_dialog_tag(**html_options)` | Emits the singleton `<dialog id="modal-stack-root" data-controller="modal-stack">`. Drop just before `</body>`. |
@@ -331,6 +331,41 @@ body[data-modal-stack-locked] {
331
331
  display: none;
332
332
  }
333
333
 
334
+ /* --- Header slot ------------------------------------------------- */
335
+
336
+ .modal-stack__panel-header {
337
+ display: flex;
338
+ align-items: center;
339
+ justify-content: space-between;
340
+ gap: 0.75rem;
341
+ }
342
+
343
+ .modal-stack__panel-title {
344
+ margin: 0;
345
+ flex: 1;
346
+ min-width: 0;
347
+ }
348
+
349
+ .modal-stack__panel-close {
350
+ font: inherit;
351
+ background: transparent;
352
+ border: 0;
353
+ padding: 0.25rem;
354
+ cursor: pointer;
355
+ color: inherit;
356
+ opacity: 0.55;
357
+ line-height: 1;
358
+ font-size: 1.4em;
359
+ flex-shrink: 0;
360
+ transition: opacity 150ms ease;
361
+ }
362
+ .modal-stack__panel-close:hover { opacity: 1; }
363
+ .modal-stack__panel-close:focus-visible {
364
+ outline: 2px solid currentColor;
365
+ outline-offset: 2px;
366
+ border-radius: 2px;
367
+ }
368
+
334
369
  @media (prefers-reduced-motion: reduce) {
335
370
  #modal-stack-root,
336
371
  #modal-stack-root::backdrop,
@@ -359,6 +359,41 @@ body[data-modal-stack-locked] {
359
359
  display: none;
360
360
  }
361
361
 
362
+ /* --- Header slot ------------------------------------------------- */
363
+
364
+ .modal-stack__panel-header {
365
+ display: flex;
366
+ align-items: center;
367
+ justify-content: space-between;
368
+ gap: 0.75rem;
369
+ }
370
+
371
+ .modal-stack__panel-title {
372
+ margin: 0;
373
+ flex: 1;
374
+ min-width: 0;
375
+ }
376
+
377
+ .modal-stack__panel-close {
378
+ font: inherit;
379
+ background: transparent;
380
+ border: 0;
381
+ padding: 0.25rem;
382
+ cursor: pointer;
383
+ color: inherit;
384
+ opacity: 0.55;
385
+ line-height: 1;
386
+ font-size: 1.4em;
387
+ flex-shrink: 0;
388
+ transition: opacity 150ms ease;
389
+ }
390
+ .modal-stack__panel-close:hover { opacity: 1; }
391
+ .modal-stack__panel-close:focus-visible {
392
+ outline: 2px solid currentColor;
393
+ outline-offset: 2px;
394
+ border-radius: 2px;
395
+ }
396
+
362
397
  /* --- Reduced motion ---------------------------------------------- */
363
398
 
364
399
  @media (prefers-reduced-motion: reduce) {
@@ -360,6 +360,41 @@ body[data-modal-stack-locked] {
360
360
  display: none;
361
361
  }
362
362
 
363
+ /* --- Header slot ------------------------------------------------- */
364
+
365
+ .modal-stack__panel-header {
366
+ display: flex;
367
+ align-items: center;
368
+ justify-content: space-between;
369
+ gap: 0.75rem;
370
+ }
371
+
372
+ .modal-stack__panel-title {
373
+ margin: 0;
374
+ flex: 1;
375
+ min-width: 0;
376
+ }
377
+
378
+ .modal-stack__panel-close {
379
+ font: inherit;
380
+ background: transparent;
381
+ border: 0;
382
+ padding: 0.25rem;
383
+ cursor: pointer;
384
+ color: inherit;
385
+ opacity: 0.55;
386
+ line-height: 1;
387
+ font-size: 1.4em;
388
+ flex-shrink: 0;
389
+ transition: opacity 150ms ease;
390
+ }
391
+ .modal-stack__panel-close:hover { opacity: 1; }
392
+ .modal-stack__panel-close:focus-visible {
393
+ outline: 2px solid currentColor;
394
+ outline-offset: 2px;
395
+ border-radius: 2px;
396
+ }
397
+
363
398
  /* --- Reduced motion ---------------------------------------------- */
364
399
 
365
400
  @media (prefers-reduced-motion: reduce) {
@@ -326,6 +326,41 @@ body[data-modal-stack-locked] {
326
326
  display: none;
327
327
  }
328
328
 
329
+ /* --- Header slot ------------------------------------------------- */
330
+
331
+ .modal-stack__panel-header {
332
+ display: flex;
333
+ align-items: center;
334
+ justify-content: space-between;
335
+ gap: 0.75rem;
336
+ }
337
+
338
+ .modal-stack__panel-title {
339
+ margin: 0;
340
+ flex: 1;
341
+ min-width: 0;
342
+ }
343
+
344
+ .modal-stack__panel-close {
345
+ font: inherit;
346
+ background: transparent;
347
+ border: 0;
348
+ padding: 0.25rem;
349
+ cursor: pointer;
350
+ color: inherit;
351
+ opacity: 0.55;
352
+ line-height: 1;
353
+ font-size: 1.4em;
354
+ flex-shrink: 0;
355
+ transition: opacity 150ms ease;
356
+ }
357
+ .modal-stack__panel-close:hover { opacity: 1; }
358
+ .modal-stack__panel-close:focus-visible {
359
+ outline: 2px solid currentColor;
360
+ outline-offset: 2px;
361
+ border-radius: 2px;
362
+ }
363
+
329
364
  @media (prefers-reduced-motion: reduce) {
330
365
  #modal-stack-root,
331
366
  #modal-stack-root::backdrop,
@@ -1,4 +1,17 @@
1
1
  <%= content_tag(:div, wrapper_attrs) do %>
2
+ <% if local_assigns[:title].present? || local_assigns[:show_close] %>
3
+ <header class="modal-stack__panel-header">
4
+ <% if local_assigns[:title].present? %>
5
+ <h2 class="modal-stack__panel-title"><%= title %></h2>
6
+ <% end %>
7
+ <% if local_assigns[:show_close] %>
8
+ <button type="button"
9
+ class="modal-stack__panel-close"
10
+ data-action="click->modal-stack#pop"
11
+ aria-label="<%= I18n.t("modal_stack.close", default: "Close") %>">&#215;</button>
12
+ <% end %>
13
+ </header>
14
+ <% end %>
2
15
  <%= back_button %>
3
16
  <%= content %>
4
17
  <% end %>
@@ -13,15 +13,18 @@ module ModalStack
13
13
  DEFAULT_SIZE = :md
14
14
 
15
15
  def modal_stack_container(size: DEFAULT_SIZE, dismissible: true, variant: :modal, side: nil, width: nil, height: nil,
16
- back: false, transition: nil, html: {}, &)
16
+ back: false, transition: nil, html: {},
17
+ title: nil, close_button: nil, &)
17
18
  attrs = build_panel_attrs(size: size, variant: variant, side: side,
18
19
  dismissible: dismissible, width: width,
19
20
  height: height, transition: transition, html: html)
20
21
  body = capture(&)
21
22
  back_html = back ? modal_stack_container_back_button : nil
23
+ show_close = close_button.nil? ? dismissible : close_button
22
24
 
23
25
  render partial: "modal_stack/panel", locals: {
24
26
  content: body, back_button: back_html, wrapper_attrs: attrs,
27
+ title: title, show_close: show_close,
25
28
  size: size, variant: variant, dismissible: dismissible,
26
29
  side: side, width: width, height: height, transition: transition
27
30
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ModalStack
4
- VERSION = "0.4.2"
4
+ VERSION = "0.4.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modal_stack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Gagnaire
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-15 00:00:00.000000000 Z
11
+ date: 2026-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties