Skip to content

Open additional story images in an inline lightbox#1550

Open
maebeale wants to merge 1 commit into
mainfrom
maebeale/montreal-v6
Open

Open additional story images in an inline lightbox#1550
maebeale wants to merge 1 commit into
mainfrom
maebeale/montreal-v6

Conversation

@maebeale
Copy link
Copy Markdown
Collaborator

@maebeale maebeale commented Jun 5, 2026

Closes #1520

What is the goal of this PR and why is this important?

  • On a published story, the "additional images" (gallery assets) each linked straight to the full-size file, so clicking one navigated the viewer away from the story.
  • There was no way to browse the additional images as a set.
  • This adds an inline lightbox so a clicked image opens in place, and the viewer can scroll through all of the story's additional images.

How did you approach the change?

  • Added a lightbox Stimulus controller (app/frontend/javascript/controllers/lightbox_controller.js) that opens a modal, tracks the current index, and supports next/previous, arrow keys, Escape, and backdrop-click to close.
  • Reworked app/views/assets/_display_gallery_media.html.erb so each image gallery item is a link marked as a lightbox item target; the existing full-size link is kept as the href so it still works as a no-JS fallback.
  • Added app/views/assets/_lightbox.html.erb for the modal markup (image, counter, prev/next/close controls).
  • Behavior applies wherever the gallery partial is rendered with link: true (stories, story shares, workshop logs, events, etc.); PDFs and other non-image assets keep their existing rendering.
  • Registered the controller and updated AGENTS.md.

Anything else to add?

  • Progressive enhancement: with JS disabled the gallery images still link to the full-size file (previous behavior).
  • Nav controls hide automatically when a gallery has a single image.

Test plan

  • Added spec/system/story_gallery_lightbox_spec.rb (Selenium): clicking an additional image opens it inline without navigating away, next/previous scroll through the set, and closing dismisses the modal.

Published stories link each additional (gallery) image to its full-size
file, so clicking one navigated the viewer away from the story and gave
no way to browse the images as a set. Wrap gallery images in a lightbox
that opens the image in place and lets the viewer scroll through the
whole set with next/previous controls, arrow keys, Escape, and a
backdrop click. The underlying links are preserved as a no-JS fallback.

Closes #1520

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 5, 2026 00:42
backdropClose(event) {
if (event.target === this.modalTarget) this.close()
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The full-size image URL is read from each item's href rather than a separate data attribute. That keeps a single source of truth and means the anchors double as a no-JS fallback (they still link to the file when Stimulus isn't loaded).

const count = this.itemTargets.length
this.indexValue = (this.indexValue - 1 + count) % count
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prev/next wrap around the set (modulo length) so the viewer can keep scrolling in either direction without hitting a dead end.

<% gallery_assets.each_with_index do |gallery_assets, idx| %>
<%= render "assets/display_image", item: gallery_assets, idx: idx, variant: :gallery, link: (defined?(link) ? link : nil) %>
<% gallery_assets.each_with_index do |gallery_asset, idx| %>
<% if link && gallery_asset.file.content_type.to_s.start_with?("image/") %>
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lightbox is gated on link (the existing clickable-gallery flag) AND the asset being an image. Non-image gallery assets (e.g. PDFs) fall through to the original display_image rendering, and galleries rendered without link (e.g. reports) keep their previous non-clickable behavior.

<% if link && gallery_asset.file.content_type.to_s.start_with?("image/") %>
<a href="<%= url_for(gallery_asset.file) %>"
class="flex grow"
data-lightbox-target="item"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The anchor keeps href pointing at the full-size file (no-JS fallback) and data-action="lightbox#open:prevent" intercepts the click when JS is active. Inner image is rendered with link: false so we don't nest anchors.

@maebeale maebeale marked this pull request as ready for review June 5, 2026 00:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Stimulus-powered inline lightbox for “additional images” (gallery assets) so clicking an image opens a modal in-place with next/previous navigation, instead of navigating away to the raw file URL. This implements the requested “browse as a set” behavior for published stories (and other views that render the gallery with links enabled) while keeping the existing full-size href as a no-JS fallback.

Changes:

  • Add a lightbox Stimulus controller to open/close a modal, track current image index, and navigate via controls/keyboard.
  • Update the gallery media partial to wrap image items in lightbox-enabled links and render the lightbox modal markup when linking is enabled.
  • Add a Selenium system spec covering opening/navigating/closing the lightbox and document the new controller in AGENTS.md.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
spec/system/story_gallery_lightbox_spec.rb Adds a system spec validating the lightbox UX for story additional images.
app/views/assets/_lightbox.html.erb Introduces the modal markup (image, counter, prev/next/close controls).
app/views/assets/_display_gallery_media.html.erb Wraps image gallery items as lightbox “item” targets and conditionally renders the lightbox UI.
app/frontend/javascript/controllers/lightbox_controller.js Implements the Stimulus controller logic for the lightbox modal and navigation.
app/frontend/javascript/controllers/index.js Registers the new lightbox controller with the Stimulus application.
AGENTS.md Updates the documented Stimulus controller list/count to include lightbox.
Comments suppressed due to low confidence (1)

app/frontend/javascript/controllers/index.js:117

  • Remove the trailing blank line at EOF to match the repo’s whitespace rules (no trailing blank lines).
import LightboxController from "./lightbox_controller"
application.register("lightbox", LightboxController)


Comment on lines +6 to +8
<div class="<%= resource.class.table_name %>-gallery text-<%= align %> mb-4"
data-controller="lightbox"
data-action="keydown.esc@window->lightbox#closeOnEscape keydown.left@window->lightbox#prev keydown.right@window->lightbox#next">

# Modal starts hidden
modal = find("[data-lightbox-target='modal']", visible: :all)
expect(modal[:class]).to include("hidden")
all("[data-lightbox-target='item']").first.click

expect(page).to have_current_path(story_path(story))
expect(modal[:class]).not_to include("hidden")

# Closing dismisses the modal
find("[data-action='lightbox#close']").click
expect(modal[:class]).to include("hidden")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Published story: additional images should open inline with scroll/gallery view

2 participants