Open image in lightbox (#767)

* Add lightbox

* Fix vertical media height (#467)

* Update dialog animation

* Fix overlay opacity

* Fix dialog animation

* Update open in new tab icon
This commit is contained in:
Ajay Bura 2022-08-19 12:15:22 +05:30 committed by GitHub
parent a417980a81
commit 4cd8f4a94c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 173 additions and 44 deletions

View file

@ -1,12 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <path d="M14 3V5H17.8L12.9 9.9L14.3 11.3L19 6.6V10.2H21V3H14Z" fill="black"/>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <path d="M5 5H10V3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V14.2H19V19H5V5Z" fill="black"/>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g>
<polygon points="14,3 14,5 17.8,5 12.9,9.9 14.3,11.3 19,6.6 19,10.2 21,10.2 21,3 "/>
<path d="M3,10.2h2V5h5V3H5C3.9,3,3,3.9,3,5V10.2z"/>
<path d="M5,14.2H3V19c0,1.1,0.9,2,2,2h5v-2H5V14.2z"/>
<path d="M19,19h-5v2h5c1.1,0,2-0.9,2-2v-4.8h-2V19z"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 703 B

After

Width:  |  Height:  |  Size: 297 B

View file

@ -42,7 +42,6 @@ function RawModal({
shouldCloseOnEsc={closeFromOutside} shouldCloseOnEsc={closeFromOutside}
shouldCloseOnOverlayClick={closeFromOutside} shouldCloseOnOverlayClick={closeFromOutside}
shouldReturnFocusAfterClose={false} shouldReturnFocusAfterClose={false}
closeTimeoutMS={300}
> >
{children} {children}
</Modal> </Modal>

View file

@ -1,27 +1,3 @@
.ReactModal__Overlay {
opacity: 0;
transition: opacity 200ms var(--fluid-slide-up);
}
.ReactModal__Overlay--after-open{
opacity: 1;
}
.ReactModal__Overlay--before-close{
opacity: 0;
}
.ReactModal__Content {
transform: translateY(100%);
transition: transform 200ms var(--fluid-slide-up);
}
.ReactModal__Content--after-open{
transform: translateY(0);
}
.ReactModal__Content--before-close{
transform: translateY(100%);
}
.raw-modal { .raw-modal {
--small-modal-width: 525px; --small-modal-width: 525px;
--medium-modal-width: 712px; --medium-modal-width: 712px;
@ -60,4 +36,31 @@
height: 100%; height: 100%;
background-color: var(--bg-overlay); background-color: var(--bg-overlay);
} }
}
.ReactModal__Overlay {
animation: raw-modal--overlay 150ms;
}
.ReactModal__Content {
animation: raw-modal--content 150ms;
}
@keyframes raw-modal--content {
0% {
transform: translateY(100px);
opacity: .5;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes raw-modal--overlay {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
} }

View file

@ -0,0 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types';
import './ImageLightbox.scss';
import FileSaver from 'file-saver';
import Text from '../../atoms/text/Text';
import RawModal from '../../atoms/modal/RawModal';
import IconButton from '../../atoms/button/IconButton';
import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
function ImageLightbox({
url, alt, isOpen, onRequestClose,
}) {
const handleDownload = () => {
FileSaver.saveAs(url, alt);
};
return (
<RawModal
className="image-lightbox__modal"
overlayClassName="image-lightbox__overlay"
isOpen={isOpen}
onRequestClose={onRequestClose}
size="large"
>
<div className="image-lightbox__header">
<Text variant="b2" weight="medium">{alt}</Text>
<IconButton onClick={() => window.open(url)} size="small" src={ExternalSVG} />
<IconButton onClick={handleDownload} size="small" src={DownloadSVG} />
</div>
<div className="image-lightbox__content">
<img src={url} alt={alt} />
</div>
</RawModal>
);
}
ImageLightbox.propTypes = {
url: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
onRequestClose: PropTypes.func.isRequired,
};
export default ImageLightbox;

View file

@ -0,0 +1,50 @@
@use '../../partials/flex';
@use '../../partials/text';
.image-lightbox__modal {
box-shadow: none;
width: unset;
gap: var(--sp-normal);
border-radius: 0;
pointer-events: none;
& .text {
color: white;
}
& .ic-raw {
background-color: white;
}
}
.image-lightbox__overlay {
background-color: var(--bg-overlay-low);
}
.image-lightbox__header > *,
.image-lightbox__content > * {
pointer-events: all;
}
.image-lightbox__header {
display: flex;
align-items: center;
& > .text {
@extend .cp-fx__item-one;
@extend .cp-txt__ellipsis;
}
}
.image-lightbox__content {
display: flex;
justify-content: center;
max-height: 90vh;
& img {
background-color: black;
object-fit: contain;
max-width: 100%;
max-height: 100%;
border-radius: var(--bo-radius);
}
}

View file

@ -8,6 +8,7 @@ import { BlurhashCanvas } from 'react-blurhash';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import IconButton from '../../atoms/button/IconButton'; import IconButton from '../../atoms/button/IconButton';
import Spinner from '../../atoms/spinner/Spinner'; import Spinner from '../../atoms/spinner/Spinner';
import ImageLightbox from '../image-lightbox/ImageLightbox';
import DownloadSVG from '../../../../public/res/ic/outlined/download.svg'; import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg'; import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
@ -124,6 +125,7 @@ function Image({
}) { }) {
const [url, setUrl] = useState(null); const [url, setUrl] = useState(null);
const [blur, setBlur] = useState(true); const [blur, setBlur] = useState(true);
const [lightbox, setLightbox] = useState(false);
useEffect(() => { useEffect(() => {
let unmounted = false; let unmounted = false;
@ -138,14 +140,42 @@ function Image({
}; };
}, []); }, []);
const toggleLightbox = () => {
if (!url) return;
setLightbox(!lightbox);
};
return ( return (
<div className="file-container"> <>
<FileHeader name={name} link={url || link} type={type} external /> <div className="file-container">
<div style={{ height: width !== null ? getNativeHeight(width, height) : 'unset' }} className="image-container"> <div
{ blurhash && blur && <BlurhashCanvas hash={blurhash} punch={1} />} style={{ height: width !== null ? getNativeHeight(width, height) : 'unset' }}
{ url !== null && <img style={{ display: blur ? 'none' : 'unset' }} onLoad={() => setBlur(false)} src={url || link} alt={name} />} className="image-container"
role="button"
tabIndex="0"
onClick={toggleLightbox}
onKeyDown={toggleLightbox}
>
{ blurhash && blur && <BlurhashCanvas hash={blurhash} punch={1} />}
{ url !== null && (
<img
style={{ display: blur ? 'none' : 'unset' }}
onLoad={() => setBlur(false)}
src={url || link}
alt={name}
/>
)}
</div>
</div> </div>
</div> {url && (
<ImageLightbox
url={url}
alt={name}
isOpen={lightbox}
onRequestClose={toggleLightbox}
/>
)}
</>
); );
} }
Image.defaultProps = { Image.defaultProps = {

View file

@ -62,6 +62,13 @@
margin: 0 !important; margin: 0 !important;
} }
} }
.image-container {
max-height: 460px;
img {
cursor: pointer;
object-fit: cover;
}
}
.video-container { .video-container {
position: relative; position: relative;

View file

@ -105,7 +105,7 @@
/* shadow and overlay */ /* shadow and overlay */
--bg-overlay: rgba(0, 0, 0, 20%); --bg-overlay: rgba(0, 0, 0, 20%);
--bg-overlay-low: rgba(0, 0, 0, 85%); --bg-overlay-low: rgba(0, 0, 0, 50%);
--bs-popup: 0 0 16px rgba(0, 0, 0, 10%); --bs-popup: 0 0 16px rgba(0, 0, 0, 10%);
@ -264,7 +264,8 @@
} }
/* shadow and overlay */ /* shadow and overlay */
--bg-overlay: rgba(0, 0, 0, 50%); --bg-overlay: rgba(0, 0, 0, 60%);
--bg-overlay-low: rgba(0, 0, 0, 80%);
--bs-popup: 0 0 16px rgba(0, 0, 0, 25%); --bs-popup: 0 0 16px rgba(0, 0, 0, 25%);