Add a download button to the image lightbox (#41)

MessageImage has no stored original filename, so this relies on a bare
`download` attribute (no explicit filename) rather than adding
filename support server-side to match the file-attachment pattern --
the image-serving response's existing Content-Type header is already
enough for the browser to infer a sensible extension on its own.

RoomInfoPanel's Files section reuses the same shared ImageLightbox
component, so its image rows get the same download button for free
with no separate change needed there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 11:37:24 -06:00
co-authored by Claude Sonnet 5
parent 2466e76af1
commit c99a07cae1
2 changed files with 52 additions and 0 deletions
+32
View File
@@ -16,3 +16,35 @@
object-fit: contain;
border-radius: var(--radius);
}
.image-lightbox-actions {
position: absolute;
top: var(--sp-4);
right: var(--sp-4);
z-index: 1;
display: flex;
gap: 6px;
cursor: default;
}
.image-lightbox-download,
.image-lightbox-close {
width: 32px;
height: 32px;
border-radius: var(--radius);
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 1.1rem;
line-height: 1;
}
.image-lightbox-download:hover,
.image-lightbox-close:hover {
background: rgba(0, 0, 0, 0.7);
border-color: rgba(255, 255, 255, 0.4);
}
+20
View File
@@ -11,6 +11,26 @@ export function ImageLightbox({ src, onClose }: ImageLightboxProps) {
return (
<div className="image-lightbox" onClick={onClose}>
<div className="image-lightbox-actions" onClick={(e) => e.stopPropagation()}>
{/* Bare `download` (no explicit filename) -- MessageImage doesn't
store an original filename, but the server response's own
Content-Type header is enough for the browser to infer a
sensible extension on its own. */}
<a href={src} download className="image-lightbox-download" aria-label="Download">
<svg width="15" height="15" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path
d="M10 3v10m0 0-4-4m4 4 4-4M4 16h12"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</a>
<button type="button" className="image-lightbox-close" onClick={onClose} aria-label="Close">
×
</button>
</div>
<img src={src} alt="" className="image-lightbox-img" />
</div>
)