diff --git a/app/assets/stylesheets/animation.css b/app/assets/stylesheets/animation.css index a6275ded4..5a90f679c 100644 --- a/app/assets/stylesheets/animation.css +++ b/app/assets/stylesheets/animation.css @@ -39,6 +39,16 @@ 75% { transform: translateX(1rem); } } + @keyframes slide-up { + from { transform: translateY(2rem); } + to { transform: translateY(0); } + } + + @keyframes slide-down { + from { transform: translateY(0); } + to { transform: translateY(2rem); } + } + @keyframes success { 0% { background-color: var(--color-border-darker); scale: 0.8; } 33% { background-color: var(--color-border-darker); scale: 1; } diff --git a/app/assets/stylesheets/lightbox.css b/app/assets/stylesheets/lightbox.css index e90bfd819..a5e5cec2d 100644 --- a/app/assets/stylesheets/lightbox.css +++ b/app/assets/stylesheets/lightbox.css @@ -12,46 +12,46 @@ max-width: unset; overflow: hidden; padding: var(--block-space-half) var(--inline-space); - place-items: center; &::backdrop { -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px); - background-color: oklch(var(--lch-canvas) / 50%); - } - - /* Default transition state */ - &, &::backdrop, img { - opacity: 0; - transition: var(--dialog-duration) allow-discrete; - transition-property: display, opacity, overlay, transform; + background-color: oklch(var(--lch-black) / 50%); } img { - transform: translateY(10dvh); + animation: slide-down var(--dialog-duration); + animation-fill-mode: forwards; + display: block; + margin: auto; } - &[open] { - display: grid; - - &, &::backdrop, img { - opacity: 1; - transform: translateY(0); - } + /* Closed state */ + &, + &::backdrop { + opacity: 0; + transition: var(--dialog-duration) allow-discrete; + transition-property: display, opacity, overlay; } - @starting-style { - &[open], - &[open]::backdrop { + /* Open state */ + &[open], + &[open]::backdrop { + opacity: 1; + + @starting-style { opacity: 0; } + + img { + animation: slide-up var(--dialog-duration); + } } } .lightbox__btn { - align-self: start; - grid-area: 1/1; - justify-self: end; + inset: var(--block-space-half) var(--inline-space) auto auto; + position: absolute; } .lightbox__image { @@ -59,4 +59,9 @@ max-inline-size: calc(100dvw - (var(--inline-space) * 2)); max-block-size: calc(100dvh - (var(--block-space) * 2)); } + + /* Prevent body from scrolling when lightbox is open */ + html:has(.lightbox[open]) { + overflow: clip; + } } diff --git a/app/javascript/controllers/lightbox_controller.js b/app/javascript/controllers/lightbox_controller.js index 70cb61d38..9032bcb02 100644 --- a/app/javascript/controllers/lightbox_controller.js +++ b/app/javascript/controllers/lightbox_controller.js @@ -4,15 +4,11 @@ export default class extends Controller { static targets = [ "image", "dialog", "zoomedImage" ] connect() { - this.dialogTarget.addEventListener('transitionend', this.onTransitionEnd.bind(this)) + this.dialogTarget.addEventListener('transitionend', this.handleTransitionEnd.bind(this)) } - // Only remove the image src after the CSS transition has finished - onTransitionEnd(event) { - if (this._waitingForTransition && !this.dialogTarget.open) { - this._waitingForTransition = false - this.zoomedImageTarget.src = "" - } + disconnect() { + this.dialogTarget.removeEventListener('transitionend', this.handleTransitionEnd.bind(this)) } open(event) { @@ -20,8 +16,16 @@ export default class extends Controller { this.#set(event.target.closest("a")) } + // Wait for the transition to finish before resetting the image + handleTransitionEnd(event) { + if (event.target === this.dialogTarget && !this.dialogTarget.open) { + this.reset() + } + } + reset() { - this._waitingForTransition = true + this.zoomedImageTarget.src = "" + this.dispatch('closed') } #set(target) { diff --git a/app/views/layouts/_lightbox.html.erb b/app/views/layouts/_lightbox.html.erb index ac30fc601..085b1c10e 100644 --- a/app/views/layouts/_lightbox.html.erb +++ b/app/views/layouts/_lightbox.html.erb @@ -1,5 +1,5 @@ <%= tag.dialog class:"lightbox", aria: { label: "Image Viewer (Press escape to close)" }, - data: { controller: "dialog", dialog_target: "dialog", lightbox_target: "dialog", action: "close->lightbox#reset" } do %> + data: { controller: "dialog", dialog_target: "dialog", dialog_modal_value: "true", lightbox_target: "dialog" } do %>