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 %>