Merge pull request #836 from basecamp/lightbox-improvements
Lightbox improvements
This commit is contained in:
@@ -53,6 +53,9 @@
|
||||
--focus-ring-offset: 1px;
|
||||
--focus-ring-size: 2px;
|
||||
|
||||
/* Dialogs */
|
||||
--dialog-duration: 150ms;
|
||||
|
||||
@media (max-width: 799px) {
|
||||
--tray-size: var(--footer-height);
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
@layer components {
|
||||
:is(.dialog) {
|
||||
--backdrop-speed: 150ms;
|
||||
--speed: 150ms;
|
||||
|
||||
border: 0;
|
||||
opacity: 0;
|
||||
transform: scale(0.2);
|
||||
transform-origin: top center;
|
||||
transition:
|
||||
display var(--speed) allow-discrete,
|
||||
opacity var(--speed),
|
||||
overlay var(--speed) allow-discrete,
|
||||
transform var(--speed);
|
||||
transition: var(--dialog-duration) allow-discrete;
|
||||
transition-property: display, opacity, overlay, transform;
|
||||
|
||||
&::backdrop {
|
||||
background-color: var(--color-black);
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
transition:
|
||||
display var(--backdrop-speed) allow-discrete,
|
||||
opacity var(--backdrop-speed),
|
||||
overlay var(--backdrop-speed) allow-discrete;
|
||||
transition: var(--dialog-duration) allow-discrete;
|
||||
transition-property: display, opacity, overlay;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
@layer components {
|
||||
.lightbox {
|
||||
--backdrop-speed: 150ms;
|
||||
--dialog-duration: 350ms;
|
||||
--lightbox-padding: 3vmin;
|
||||
|
||||
background-color: oklch(var(--lch-canvas) / 0.66);
|
||||
background-color: transparent;
|
||||
block-size: 100dvh;
|
||||
border: 0;
|
||||
inline-size: 100dvw;
|
||||
@@ -11,49 +12,75 @@
|
||||
max-height: unset;
|
||||
max-width: unset;
|
||||
overflow: hidden;
|
||||
padding: var(--block-space-half) var(--inline-space);
|
||||
padding: var(--lightbox-padding);
|
||||
text-align: center;
|
||||
|
||||
&::backdrop {
|
||||
-webkit-backdrop-filter: blur(66px);
|
||||
backdrop-filter: blur(66px);
|
||||
background-color: var(--color-ink);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
backdrop-filter: blur(16px);
|
||||
background-color: oklch(var(--lch-black) / 50%);
|
||||
}
|
||||
|
||||
/* Closed state */
|
||||
&,
|
||||
&::backdrop {
|
||||
opacity: 0;
|
||||
transition:
|
||||
display var(--backdrop-speed) allow-discrete,
|
||||
opacity var(--backdrop-speed),
|
||||
overlay var(--backdrop-speed) allow-discrete;
|
||||
transition: var(--dialog-duration) allow-discrete;
|
||||
transition-property: display, opacity, overlay;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
display: grid;
|
||||
/* Open state */
|
||||
&[open],
|
||||
&[open]::backdrop {
|
||||
opacity: 1;
|
||||
place-items: center;
|
||||
|
||||
&::backdrop {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
&[open] {
|
||||
@starting-style {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&[open]::backdrop {
|
||||
opacity: 0;
|
||||
.lightbox__figure {
|
||||
animation: slide-up var(--dialog-duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightbox__btn {
|
||||
align-self: start;
|
||||
grid-area: 1/1;
|
||||
justify-self: end;
|
||||
.lightbox__actions {
|
||||
display: flex;
|
||||
gap: 1ch;
|
||||
inset: var(--lightbox-padding) var(--lightbox-padding) auto auto;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.lightbox__figure {
|
||||
align-self: stretch;
|
||||
animation-fill-mode: forwards;
|
||||
animation: slide-down var(--dialog-duration);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--lightbox-padding);
|
||||
margin: 0 auto;
|
||||
max-block-size: 100%;
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.lightbox__caption {
|
||||
color: var(--color-ink-inverted);
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.lightbox__image {
|
||||
grid-area: 1/1;
|
||||
max-inline-size: calc(100dvw - (var(--inline-space) * 2));
|
||||
max-block-size: calc(100dvh - (var(--block-space) * 2));
|
||||
flex: 1;
|
||||
min-block-size: 0;
|
||||
}
|
||||
|
||||
/* Prevent body from scrolling when lightbox is open */
|
||||
html:has(.lightbox[open]) {
|
||||
overflow: clip;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,26 +153,18 @@
|
||||
}
|
||||
|
||||
.popup--animated {
|
||||
--backdrop-speed: 150ms;
|
||||
--speed: 150ms;
|
||||
|
||||
opacity: 0;
|
||||
transform: scale(0.2) translateX(-50%);
|
||||
transform-origin: top left;
|
||||
transition:
|
||||
display var(--speed) allow-discrete,
|
||||
opacity var(--speed),
|
||||
overlay var(--speed) allow-discrete,
|
||||
transform var(--speed);
|
||||
transition: var(--dialog-duration) allow-discrete;
|
||||
transition-property: display, opacity, overlay, transform;
|
||||
|
||||
&::backdrop {
|
||||
background-color: var(--color-always-black);
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
transition:
|
||||
display var(--backdrop-speed) allow-discrete,
|
||||
opacity var(--backdrop-speed),
|
||||
overlay var(--backdrop-speed) allow-discrete;
|
||||
transition: var(--dialog-duration) allow-discrete;
|
||||
transition-property: display, opacity, overlay;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
|
||||
@@ -1,18 +1,42 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "image", "dialog", "zoomedImage" ]
|
||||
static targets = [ "caption", "image", "dialog", "zoomedImage" ]
|
||||
|
||||
connect() {
|
||||
this.dialogTarget.addEventListener('transitionend', this.handleTransitionEnd.bind(this))
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.dialogTarget.removeEventListener('transitionend', this.handleTransitionEnd.bind(this))
|
||||
}
|
||||
|
||||
open(event) {
|
||||
this.dialogTarget.showModal()
|
||||
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.zoomedImageTarget.src = ""
|
||||
this.captionTarget.innerText = ""
|
||||
this.dispatch('closed')
|
||||
}
|
||||
|
||||
#set(target) {
|
||||
this.zoomedImageTarget.src = target.href
|
||||
const imageSrc = target.href
|
||||
const caption = target.dataset.lightboxCaptionValue
|
||||
|
||||
this.zoomedImageTarget.src = imageSrc
|
||||
|
||||
if (caption) {
|
||||
this.captionTarget.innerText = caption
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,3 @@
|
||||
<span class="for-screen-reader">Zoom background image</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -49,3 +49,12 @@
|
||||
<%= render "cards/messages", card: @card %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= render "layouts/lightbox" do %>
|
||||
<% if @card.image.attached? %>
|
||||
<%= button_to card_image_path(@card), method: :delete, class: "btn" do %>
|
||||
<%= icon_tag "trash" %>
|
||||
<span class="for-screen-reader">Remove background image</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<%= 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 %>
|
||||
<img src="" class="lightbox__image" data-lightbox-target="zoomedImage" />
|
||||
data: { controller: "dialog", dialog_target: "dialog", dialog_modal_value: "true", lightbox_target: "dialog" } do %>
|
||||
|
||||
<div class="lightbox__btn">
|
||||
<figure class="lightbox__figure">
|
||||
<img src="" class="lightbox__image" data-lightbox-target="zoomedImage" />
|
||||
<figcaption class="lightbox__caption" data-lightbox-target="caption"></figcaption>
|
||||
</figure>
|
||||
|
||||
<div class="lightbox__actions">
|
||||
<%= yield %>
|
||||
<button class="btn fill-white" title="Close (esc)" data-action="dialog#close keydown.esc->dialog#close:stop" data-controller="hotkey">
|
||||
<%= icon_tag "remove" %>
|
||||
<span class="for-screen-reader">Close image viewer (esc)</span>
|
||||
|
||||
@@ -31,7 +31,5 @@
|
||||
</div>
|
||||
<% end %>
|
||||
</footer>
|
||||
|
||||
<%= render "layouts/lightbox" %>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user