mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-27 01:03:08 +03:00
feat(GlobalCarousel): update to new design
This commit is contained in:
parent
ba989feacd
commit
42e1f05e26
792
components/system/components/GlobalCarousel/index.js
Normal file
792
components/system/components/GlobalCarousel/index.js
Normal file
@ -0,0 +1,792 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/common/svg";
|
||||
import * as System from "~/components/system";
|
||||
import * as Styles from "~/common/styles";
|
||||
import * as Jumpers from "~/components/system/components/GlobalCarousel/jumpers";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { Alert } from "~/components/core/Alert";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
useDetectTextOverflow,
|
||||
useEscapeKey,
|
||||
useEventListener,
|
||||
useLockScroll,
|
||||
} from "~/common/hooks";
|
||||
import { Show } from "~/components/utility/Show";
|
||||
import { ModalPortal } from "~/components/core/ModalPortal";
|
||||
|
||||
import SlateMediaObject from "~/components/core/SlateMediaObject";
|
||||
import LinkIcon from "~/components/core/LinkIcon";
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* Carousel Header
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const VisitLinkButton = ({ file }) => {
|
||||
return (
|
||||
<System.ButtonTertiary
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
color: Constants.semantic.textGrayDark,
|
||||
padding: "4px 8px 7px",
|
||||
marginLeft: 4,
|
||||
minHeight: 30,
|
||||
}}
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
type="link"
|
||||
>
|
||||
<LinkIcon file={file} width={16} height={16} style={{ marginRight: 4 }} />
|
||||
<span style={{ whiteSpace: "nowrap" }}>Visit site</span>
|
||||
</System.ButtonTertiary>
|
||||
);
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const useCarouselJumperControls = () => {
|
||||
const [isControlVisible, setControlVisibility] = React.useState(false);
|
||||
const showControl = () => setControlVisibility(true);
|
||||
const hideControl = () => setControlVisibility(false);
|
||||
return [isControlVisible, { showControl, hideControl }];
|
||||
};
|
||||
|
||||
const STYLES_HEADER_WRAPPER = (theme) => css`
|
||||
${Styles.HORIZONTAL_CONTAINER_CENTERED};
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
padding: 13px 24px 10px;
|
||||
color: ${theme.semantic.textGrayDark};
|
||||
border-bottom: 1px solid ${theme.semantic.borderGrayLight};
|
||||
box-shadow: ${theme.shadow.lightSmall};
|
||||
z-index: 1;
|
||||
|
||||
background-color: ${theme.semantic.bgWhite};
|
||||
@supports ((-webkit-backdrop-filter: blur(15px)) or (backdrop-filter: blur(15px))) {
|
||||
background-color: ${theme.semantic.bgBlurWhiteOP};
|
||||
-webkit-backdrop-filter: blur(15px);
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_ACTION_BUTTON = css`
|
||||
${Styles.BUTTON_RESET};
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
`;
|
||||
|
||||
function CarouselHeader({
|
||||
viewer,
|
||||
data,
|
||||
external,
|
||||
isOwner,
|
||||
file,
|
||||
current,
|
||||
total,
|
||||
onAction,
|
||||
onClose,
|
||||
...props
|
||||
}) {
|
||||
const [isHeaderVisible, setHeaderVisibility] = React.useState(true);
|
||||
const timeoutRef = React.useRef();
|
||||
|
||||
const showHeader = () => {
|
||||
clearTimeout(timeoutRef.current);
|
||||
setHeaderVisibility(true);
|
||||
};
|
||||
|
||||
const hideHeader = () => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setHeaderVisibility(false);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
timeoutRef.current = setTimeout(hideHeader, 3000);
|
||||
return () => clearTimeout(timeoutRef.current);
|
||||
}, []);
|
||||
|
||||
// NOTE(amine): Detect if the text is overflowing to show the MORE button
|
||||
const elementRef = React.useRef();
|
||||
const isBodyOverflowing = useDetectTextOverflow({ ref: elementRef }, [file]);
|
||||
|
||||
// NOTE(amine): jumpers handlers
|
||||
const [
|
||||
isFileDescriptionVisible,
|
||||
{ showControl: showFileDescription, hideControl: hideFileDescription },
|
||||
] = useCarouselJumperControls();
|
||||
|
||||
const [isMoreInfoVisible, { showControl: showMoreInfo, hideControl: hideMoreInfo }] =
|
||||
useCarouselJumperControls();
|
||||
|
||||
const [isEditInfoVisible, { showControl: showEditInfo, hideControl: hideEditInfo }] =
|
||||
useCarouselJumperControls();
|
||||
|
||||
const [isShareFileVisible, { showControl: showShareFile, hideControl: hideShareFile }] =
|
||||
useCarouselJumperControls();
|
||||
|
||||
const [isEditChannelsVisible, { showControl: showEditChannels, hideControl: hideEditChannels }] =
|
||||
useCarouselJumperControls();
|
||||
|
||||
const isJumperOpen =
|
||||
isFileDescriptionVisible ||
|
||||
isMoreInfoVisible ||
|
||||
isEditInfoVisible ||
|
||||
isShareFileVisible ||
|
||||
isEditChannelsVisible;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalPortal>
|
||||
{isOwner && (
|
||||
<Jumpers.EditInfo file={file} isOpen={isEditInfoVisible} onClose={hideEditInfo} />
|
||||
)}
|
||||
{isOwner && (
|
||||
<Jumpers.EditChannels
|
||||
viewer={viewer}
|
||||
file={file}
|
||||
onAction={onAction}
|
||||
isOpen={isEditChannelsVisible}
|
||||
onClose={hideEditChannels}
|
||||
/>
|
||||
)}
|
||||
<Jumpers.FileDescription
|
||||
file={file}
|
||||
isOpen={isFileDescriptionVisible}
|
||||
onClose={hideFileDescription}
|
||||
/>
|
||||
<Jumpers.MoreInfo
|
||||
viewer={viewer}
|
||||
external={external}
|
||||
isOwner={isOwner}
|
||||
file={file}
|
||||
isOpen={isMoreInfoVisible}
|
||||
onClose={hideMoreInfo}
|
||||
/>
|
||||
<Jumpers.Share
|
||||
file={file}
|
||||
data={data}
|
||||
isOpen={isShareFileVisible}
|
||||
onClose={hideShareFile}
|
||||
/>
|
||||
</ModalPortal>
|
||||
|
||||
<motion.nav
|
||||
css={STYLES_HEADER_WRAPPER}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: isHeaderVisible || isJumperOpen ? 1 : 0 }}
|
||||
onMouseEnter={showHeader}
|
||||
onMouseLeave={hideHeader}
|
||||
{...props}
|
||||
>
|
||||
<div>
|
||||
<div css={Styles.HORIZONTAL_CONTAINER}>
|
||||
<System.H5 color="textBlack" as="h1">
|
||||
{file?.name || file?.filename}
|
||||
</System.H5>
|
||||
<System.H5 color="textGray" as="p" style={{ marginLeft: 32 }}>
|
||||
{current} / {total}
|
||||
</System.H5>
|
||||
</div>
|
||||
|
||||
<div css={Styles.HORIZONTAL_CONTAINER_CENTERED} style={{ marginRight: 150 }}>
|
||||
<System.P3
|
||||
ref={elementRef}
|
||||
style={{ marginTop: 1, wordBreak: "break-all" }}
|
||||
nbrOflines={1}
|
||||
color="textBlack"
|
||||
>
|
||||
{file.body}
|
||||
</System.P3>
|
||||
<Show when={isBodyOverflowing}>
|
||||
<System.H6
|
||||
css={Styles.BUTTON_RESET}
|
||||
color="blue"
|
||||
as="button"
|
||||
onClick={showFileDescription}
|
||||
>
|
||||
MORE
|
||||
</System.H6>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<div css={Styles.HORIZONTAL_CONTAINER_CENTERED} style={{ marginLeft: "auto" }}>
|
||||
<div css={Styles.HORIZONTAL_CONTAINER_CENTERED}>
|
||||
<Show when={isOwner}>
|
||||
<button onClick={showEditInfo} css={STYLES_ACTION_BUTTON}>
|
||||
<SVG.Edit style={{ pointerEvents: "none" }} />
|
||||
</button>
|
||||
</Show>
|
||||
<Show when={isOwner}>
|
||||
<button
|
||||
onClick={showEditChannels}
|
||||
style={{ marginLeft: 4 }}
|
||||
css={STYLES_ACTION_BUTTON}
|
||||
>
|
||||
<SVG.Hash style={{ pointerEvents: "none" }} />
|
||||
</button>
|
||||
</Show>
|
||||
<button onClick={showShareFile} style={{ marginLeft: 4 }} css={STYLES_ACTION_BUTTON}>
|
||||
<SVG.Share style={{ pointerEvents: "none" }} />
|
||||
</button>
|
||||
<button onClick={showMoreInfo} style={{ marginLeft: 4 }} css={STYLES_ACTION_BUTTON}>
|
||||
<SVG.InfoCircle style={{ pointerEvents: "none" }} />
|
||||
</button>
|
||||
{file.isLink ? <VisitLinkButton file={file} /> : null}
|
||||
</div>
|
||||
<div style={{ marginLeft: 80 }}>
|
||||
<button onClick={onClose} css={STYLES_ACTION_BUTTON}>
|
||||
<SVG.Dismiss />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.nav>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const STYLES_CAROUSEL_MOBILE_HEADER = (theme) => css`
|
||||
position: relative;
|
||||
${Styles.HORIZONTAL_CONTAINER_CENTERED};
|
||||
padding: 7px 8px 3px;
|
||||
color: ${theme.semantic.textGrayDark};
|
||||
border-bottom: 1px solid ${theme.semantic.borderGrayLight};
|
||||
|
||||
background-color: ${theme.semantic.bgWhite};
|
||||
@supports ((-webkit-backdrop-filter: blur(15px)) or (backdrop-filter: blur(15px))) {
|
||||
background-color: ${theme.semantic.bgBlurWhiteOP};
|
||||
-webkit-backdrop-filter: blur(15px);
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_CAROUSEL_MOBILE_FOOTER = (theme) => css`
|
||||
${Styles.HORIZONTAL_CONTAINER_CENTERED};
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
border-top: 1px solid ${theme.semantic.borderGrayLight};
|
||||
color: ${theme.semantic.textGrayDark};
|
||||
|
||||
background-color: ${theme.semantic.bgWhite};
|
||||
@supports ((-webkit-backdrop-filter: blur(15px)) or (backdrop-filter: blur(15px))) {
|
||||
background-color: ${theme.semantic.bgBlurWhite};
|
||||
-webkit-backdrop-filter: blur(15px);
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_CAROUSEL_MOBILE_SLIDE_COUNT = css`
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
`;
|
||||
|
||||
function CarouselHeaderMobile({ current, total, onClose, onNextSlide, onPreviousSlide }) {
|
||||
return (
|
||||
<nav css={STYLES_CAROUSEL_MOBILE_HEADER} style={{ justifyContent: "space-between" }}>
|
||||
<div style={{ width: 76 }}>
|
||||
<button css={STYLES_ACTION_BUTTON} onClick={onPreviousSlide}>
|
||||
<SVG.ChevronLeft width={16} height={16} />
|
||||
</button>
|
||||
<button style={{ marginLeft: 12 }} css={STYLES_ACTION_BUTTON} onClick={onNextSlide}>
|
||||
<SVG.ChevronRight width={16} height={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<System.H5 color="textGray" as="p" css={STYLES_CAROUSEL_MOBILE_SLIDE_COUNT}>
|
||||
{current} / {total}
|
||||
</System.H5>
|
||||
|
||||
<div style={{ textAlign: "right" }}>
|
||||
<button onClick={onClose} css={STYLES_ACTION_BUTTON}>
|
||||
<SVG.Dismiss />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
function CarouselFooterMobile({ file, onAction, external, isOwner, data, viewer }) {
|
||||
const [isEditInfoVisible, { showControl: showEditInfo, hideControl: hideEditInfo }] =
|
||||
useCarouselJumperControls();
|
||||
|
||||
const [isShareFileVisible, { showControl: showShareFile, hideControl: hideShareFile }] =
|
||||
useCarouselJumperControls();
|
||||
|
||||
const [isMoreInfoVisible, { showControl: showMoreInfo, hideControl: hideMoreInfo }] =
|
||||
useCarouselJumperControls();
|
||||
|
||||
const [isEditChannelsVisible, { showControl: showEditChannels, hideControl: hideEditChannels }] =
|
||||
useCarouselJumperControls();
|
||||
return (
|
||||
<>
|
||||
<ModalPortal>
|
||||
{isOwner && (
|
||||
<Jumpers.EditInfoMobile file={file} isOpen={isEditInfoVisible} onClose={hideEditInfo} />
|
||||
)}
|
||||
{isOwner && (
|
||||
<Jumpers.EditChannelsMobile
|
||||
viewer={viewer}
|
||||
file={file}
|
||||
onAction={onAction}
|
||||
isOpen={isEditChannelsVisible}
|
||||
onClose={hideEditChannels}
|
||||
/>
|
||||
)}
|
||||
<Jumpers.ShareMobile
|
||||
file={file}
|
||||
isOpen={isShareFileVisible}
|
||||
data={data}
|
||||
onClose={hideShareFile}
|
||||
/>
|
||||
<Jumpers.MoreInfoMobile
|
||||
viewer={viewer}
|
||||
external={external}
|
||||
isOwner={isOwner}
|
||||
file={file}
|
||||
isOpen={isMoreInfoVisible}
|
||||
onClose={hideMoreInfo}
|
||||
/>
|
||||
</ModalPortal>
|
||||
<nav css={STYLES_CAROUSEL_MOBILE_FOOTER}>
|
||||
<Show when={isOwner}>
|
||||
<button css={STYLES_ACTION_BUTTON} onClick={showEditInfo}>
|
||||
<SVG.Edit />
|
||||
</button>
|
||||
</Show>
|
||||
<Show when={isOwner}>
|
||||
<button style={{ marginLeft: 4 }} css={STYLES_ACTION_BUTTON} onClick={showEditChannels}>
|
||||
<SVG.Hash />
|
||||
</button>
|
||||
</Show>
|
||||
<button style={{ marginLeft: 4 }} css={STYLES_ACTION_BUTTON} onClick={showShareFile}>
|
||||
<SVG.Share />
|
||||
</button>
|
||||
<button style={{ marginLeft: 4 }} css={STYLES_ACTION_BUTTON} onClick={showMoreInfo}>
|
||||
<SVG.InfoCircle />
|
||||
</button>
|
||||
{file.isLink ? <VisitLinkButton file={file} /> : null}
|
||||
</nav>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* Carousel Controls
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const useCarouselKeyCommands = ({ handleNext, handlePrevious, handleClose }) => {
|
||||
const handleKeyDown = (e) => {
|
||||
const inputs = document.querySelectorAll("input");
|
||||
for (let elem of inputs) {
|
||||
if (document.activeElement === elem) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const textareas = document.querySelectorAll("textarea");
|
||||
for (let elem of textareas) {
|
||||
if (document.activeElement === elem) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "Right":
|
||||
case "ArrowRight":
|
||||
handleNext();
|
||||
break;
|
||||
case "Left":
|
||||
case "ArrowLeft":
|
||||
handlePrevious();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
useEscapeKey(handleClose);
|
||||
|
||||
useEventListener({ type: "keydown", handler: handleKeyDown });
|
||||
};
|
||||
|
||||
const STYLES_CONTROLS_BUTTON = (theme) => css`
|
||||
${Styles.BUTTON_RESET};
|
||||
background-color: ${theme.semantic.bgGrayLight};
|
||||
border-radius: 8px;
|
||||
border: 1px solid ${theme.semantic.borderGrayLight};
|
||||
padding: 10px;
|
||||
box-shadow: ${theme.shadow.lightMedium};
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_CONTROLS_WRAPPER = css`
|
||||
${Styles.CONTAINER_CENTERED};
|
||||
position: absolute;
|
||||
width: 122px;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
`;
|
||||
|
||||
function CarouselControls({
|
||||
enableNextSlide,
|
||||
enablePreviousSlide,
|
||||
onNextSlide,
|
||||
onPreviousSlide,
|
||||
onClose,
|
||||
}) {
|
||||
useCarouselKeyCommands({
|
||||
handleNext: onNextSlide,
|
||||
handlePrevious: onPreviousSlide,
|
||||
handleClose: onClose,
|
||||
});
|
||||
|
||||
const [areControlsVisible, setControlsVisibility] = React.useState(true);
|
||||
const timeoutRef = React.useRef();
|
||||
|
||||
const showControls = () => {
|
||||
clearTimeout(timeoutRef.current);
|
||||
setControlsVisibility(true);
|
||||
};
|
||||
|
||||
const hideControls = () => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setControlsVisibility(false);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
timeoutRef.current = setTimeout(hideControls, 3000);
|
||||
return () => clearTimeout(timeoutRef.current);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onMouseEnter={showControls}
|
||||
onMouseLeave={hideControls}
|
||||
css={STYLES_CONTROLS_WRAPPER}
|
||||
style={{ left: 0, justifyContent: "flex-start" }}
|
||||
>
|
||||
{enablePreviousSlide ? (
|
||||
<motion.button
|
||||
onClick={onPreviousSlide}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: areControlsVisible ? 1 : 0 }}
|
||||
css={STYLES_CONTROLS_BUTTON}
|
||||
>
|
||||
<SVG.ChevronLeft width={16} />
|
||||
</motion.button>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
onMouseEnter={showControls}
|
||||
onMouseLeave={hideControls}
|
||||
css={STYLES_CONTROLS_WRAPPER}
|
||||
style={{ right: 0, justifyContent: "flex-end" }}
|
||||
>
|
||||
{enableNextSlide ? (
|
||||
<motion.button
|
||||
onClick={onNextSlide}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: areControlsVisible ? 1 : 0 }}
|
||||
css={STYLES_CONTROLS_BUTTON}
|
||||
>
|
||||
<SVG.ChevronRight width={16} />
|
||||
</motion.button>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* Carousel Content
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const STYLES_CONTENT = (theme) => css`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@media (max-width: ${theme.sizes.mobile}px) {
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_PREVIEW_WRAPPER = (theme) => css`
|
||||
${Styles.CONTAINER_CENTERED};
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@media (max-width: ${theme.sizes.mobile}px) {
|
||||
min-height: 75vh;
|
||||
height: 75vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
export function CarouselContent({
|
||||
carouselType,
|
||||
objects,
|
||||
index,
|
||||
data,
|
||||
isMobile,
|
||||
viewer,
|
||||
sidebar,
|
||||
style,
|
||||
onClose,
|
||||
}) {
|
||||
const file = objects?.[index];
|
||||
|
||||
let isRepost = false;
|
||||
if (carouselType === "SLATE") isRepost = data?.ownerId !== file.ownerId;
|
||||
|
||||
useLockScroll();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Alert
|
||||
viewer={viewer}
|
||||
noWarning
|
||||
id={isMobile ? "slate-mobile-alert" : null}
|
||||
style={
|
||||
isMobile
|
||||
? null
|
||||
: {
|
||||
bottom: 0,
|
||||
top: "auto",
|
||||
paddingRight: sidebar ? `calc(${Constants.sizes.sidebar}px + 48px)` : "auto",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div css={STYLES_CONTENT} style={style} onClick={onClose}>
|
||||
<div css={STYLES_PREVIEW_WRAPPER}>
|
||||
<SlateMediaObject file={file} isMobile={isMobile} />
|
||||
</div>
|
||||
|
||||
<div css={Styles.MOBILE_ONLY} style={{ padding: "13px 16px 44px", width: "100%" }}>
|
||||
<System.H5 color="textBlack" as="h1">
|
||||
{file?.name || file?.filename}
|
||||
</System.H5>
|
||||
<Show when={file?.body}>
|
||||
<System.P2 color="textBlack" style={{ marginTop: 4 }}>
|
||||
{file?.body}
|
||||
</System.P2>
|
||||
</Show>
|
||||
<Show when={file.isLink}>
|
||||
<div style={{ marginTop: 5 }} css={Styles.HORIZONTAL_CONTAINER_CENTERED}>
|
||||
<LinkIcon file={file} width={12} height={12} />
|
||||
<System.P2 as="a" nbrOflines={1} href={file.url} style={{ marginLeft: 5 }}>
|
||||
{file.url}
|
||||
</System.P2>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* Global Carousel
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
const useCarouselViaParams = ({ index, params, objects, onChange }) => {
|
||||
const findSelectedIndex = () => {
|
||||
const cid = params?.cid;
|
||||
if (!cid) return -1;
|
||||
|
||||
let index = objects.findIndex((elem) => elem.cid === cid);
|
||||
return index;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (index !== -1) return;
|
||||
|
||||
const selectedIndex = findSelectedIndex();
|
||||
if (selectedIndex !== index) onChange(index);
|
||||
}, [params?.cid]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (params?.cid) {
|
||||
const index = findSelectedIndex();
|
||||
onChange(index);
|
||||
}
|
||||
}, [params]);
|
||||
};
|
||||
|
||||
const getCarouselHandlers = ({ index, objects, params, onChange, onAction }) => {
|
||||
const handleNext = (e) => {
|
||||
if (e) e.stopPropagation();
|
||||
|
||||
let nextIndex = index + 1;
|
||||
if (nextIndex >= objects.length) return;
|
||||
|
||||
let { cid } = objects[nextIndex];
|
||||
onChange(nextIndex);
|
||||
onAction({ type: "UPDATE_PARAMS", params: { ...params, cid }, redirect: true });
|
||||
};
|
||||
|
||||
const handlePrevious = (e) => {
|
||||
if (e) e.stopPropagation();
|
||||
|
||||
let prevIndex = index - 1;
|
||||
if (prevIndex < 0) return;
|
||||
|
||||
let { cid } = objects[prevIndex];
|
||||
onChange(prevIndex);
|
||||
onAction({ type: "UPDATE_PARAMS", params: { params, cid }, redirect: true });
|
||||
};
|
||||
|
||||
const handleClose = (e) => {
|
||||
if (e) e.stopPropagation(), e.preventDefault();
|
||||
|
||||
let params = { ...params };
|
||||
delete params.cid;
|
||||
onAction({ type: "UPDATE_PARAMS", params, redirect: true });
|
||||
onChange(-1);
|
||||
};
|
||||
return { handleNext, handlePrevious, handleClose };
|
||||
};
|
||||
|
||||
const STYLES_ROOT = (theme) => css`
|
||||
${Styles.VERTICAL_CONTAINER};
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
color: ${Constants.system.white};
|
||||
z-index: ${Constants.zindex.modal};
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
|
||||
// Note(Amine): we're using the blur filter to fix a weird backdrop-filter's bug in chrome
|
||||
|
||||
filter: blur(0px);
|
||||
@supports ((-webkit-backdrop-filter: blur(15px)) or (backdrop-filter: blur(15px))) {
|
||||
-webkit-backdrop-filter: blur(15px);
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
background-color: ${theme.semantic.bgWhite};
|
||||
}
|
||||
|
||||
@keyframes global-carousel-fade-in {
|
||||
from {
|
||||
transform: translateX(8px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0px);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
animation: global-carousel-fade-in 400ms ease;
|
||||
`;
|
||||
|
||||
export function GlobalCarousel({
|
||||
carouselType,
|
||||
objects,
|
||||
index,
|
||||
params,
|
||||
data,
|
||||
isMobile,
|
||||
onChange,
|
||||
onAction,
|
||||
viewer,
|
||||
external,
|
||||
isOwner,
|
||||
sidebar,
|
||||
style,
|
||||
}) {
|
||||
const file = objects?.[index];
|
||||
const isCarouselOpen = (carouselType || index > 0 || index <= objects.length) && !!file;
|
||||
|
||||
useCarouselViaParams({ index, params, objects, onChange });
|
||||
|
||||
if (!isCarouselOpen) return null;
|
||||
|
||||
const { handleNext, handlePrevious, handleClose } = getCarouselHandlers({
|
||||
index,
|
||||
objects,
|
||||
params,
|
||||
onChange,
|
||||
onAction,
|
||||
});
|
||||
|
||||
return (
|
||||
<div css={STYLES_ROOT}>
|
||||
{!isMobile ? (
|
||||
<CarouselControls
|
||||
enableNextSlide={index < objects.length - 1}
|
||||
enablePreviousSlide={index > 0}
|
||||
onNextSlide={handleNext}
|
||||
onPreviousSlide={handlePrevious}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{isMobile ? (
|
||||
<CarouselHeaderMobile
|
||||
current={index + 1}
|
||||
total={objects.length}
|
||||
onPreviousSlide={handlePrevious}
|
||||
onNextSlide={handleNext}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
) : (
|
||||
<CarouselHeader
|
||||
viewer={viewer}
|
||||
external={external}
|
||||
isOwner={isOwner}
|
||||
data={data}
|
||||
file={file}
|
||||
current={index + 1}
|
||||
total={objects.length}
|
||||
onAction={onAction}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
)}
|
||||
<CarouselContent
|
||||
carouselType={carouselType}
|
||||
objects={objects}
|
||||
index={index}
|
||||
data={data}
|
||||
isMobile={isMobile}
|
||||
viewer={viewer}
|
||||
sidebar={sidebar}
|
||||
style={style}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
|
||||
{isMobile ? (
|
||||
<CarouselFooterMobile
|
||||
file={file}
|
||||
viewer={viewer}
|
||||
data={data}
|
||||
external={external}
|
||||
onAction={onAction}
|
||||
isOwner={isOwner}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user