diff --git a/src/components/client/campaign-news/CampaignNewsList.tsx b/src/components/client/campaign-news/CampaignNewsList.tsx index 3ed1f36cd..3d2a5277d 100644 --- a/src/components/client/campaign-news/CampaignNewsList.tsx +++ b/src/components/client/campaign-news/CampaignNewsList.tsx @@ -17,7 +17,7 @@ import { QuillStypeWrapper } from 'components/common/QuillStyleWrapper' import { scrollToTop } from './utils/scrollToTop' import { getArticleHeight } from './utils/getArticleHeight' -import withFullScreenSlider from 'components/common/withFullScreenSlider' +import Gallery from 'components/common/Gallery' const PREFIX = 'CampaignNewsSection' const classes = { @@ -83,7 +83,6 @@ export default function CampaignNewsList({ articles }: Props) { const { t, i18n } = useTranslation('news') const INITIAL_HEIGHT_LIMIT = 400 const [isExpanded, expandContent] = useShowMoreContent() - const WithFullScreenSlider = withFullScreenSlider(Image) return ( <> {articles?.map((article, index: number) => { @@ -158,21 +157,21 @@ export default function CampaignNewsList({ articles }: Props) { {article.newsFiles.length > 0 && ( <> - {images.map((file, index) => { - return ( - - - - ) - })} + + {images.map((file) => { + return ( + + {file.fileName} + + ) + })} + )} diff --git a/src/components/client/campaigns/CampaignDetails.tsx b/src/components/client/campaigns/CampaignDetails.tsx index 6cf7451e6..b6a48bce6 100644 --- a/src/components/client/campaigns/CampaignDetails.tsx +++ b/src/components/client/campaigns/CampaignDetails.tsx @@ -12,7 +12,7 @@ import SecurityIcon from '@mui/icons-material/Security' import { styled } from '@mui/material/styles' import DonationWishes from './DonationWishes' -import CampaignImageSlider from 'components/common/CampaignImageSlider' +import { ImageSlider } from 'components/common/ImageSlider' import CampaignInfo from './CampaignInfo/CampaignInfo' import CampaignInfoGraphics from './CampaignInfoGraphics' import CampaignInfoOperator from './CampaignInfoOperator' @@ -158,7 +158,7 @@ export default function CampaignDetails({ campaign }: Props) { - + diff --git a/src/components/client/campaigns/CampaignNewsSection.tsx b/src/components/client/campaigns/CampaignNewsSection.tsx index 43c1c2976..fa4a19709 100644 --- a/src/components/client/campaigns/CampaignNewsSection.tsx +++ b/src/components/client/campaigns/CampaignNewsSection.tsx @@ -28,7 +28,7 @@ import { sanitizeHTML } from 'common/util/htmlUtils' import { QuillStypeWrapper } from 'components/common/QuillStyleWrapper' import { scrollToTop } from '../campaign-news/utils/scrollToTop' import { getArticleHeight } from '../campaign-news/utils/getArticleHeight' -import withFullScreenSlider from 'components/common/withFullScreenSlider' +import Gallery from 'components/common/Gallery' const PREFIX = 'NewsTimeline' @@ -161,7 +161,6 @@ type Props = { export default function CampaignNewsSection({ campaign, canCreateArticle }: Props) { const { t, i18n } = useTranslation('news') const { small }: { small: boolean } = useMobile() - const WithFullScreenSlider = withFullScreenSlider(Image) const INITIAL_HEIGHT_LIMIT = 200 const [isExpanded, expandContent] = useShowMoreContent() @@ -299,20 +298,21 @@ export default function CampaignNewsSection({ campaign, canCreateArticle }: Prop ))} - {images.map((file, index) => { - return ( - - - - ) - })} + + {images.map((file) => { + return ( + + {file.fileName} + + ) + })} + )} diff --git a/src/components/common/Gallery.tsx b/src/components/common/Gallery.tsx new file mode 100644 index 000000000..0a479f408 --- /dev/null +++ b/src/components/common/Gallery.tsx @@ -0,0 +1,76 @@ +import React, { useMemo, useRef, useState } from 'react' +import { FullScreenImageSlider } from './ImageSlider' +import { ImageSlider } from 'components/common/campaign-file/roles' + +type ChildComponentProps = { + children: JSX.Element[] | JSX.Element + images: ImageSlider[] +} + +/** + * Gallery component which allows images to expand whenever clicked. + * @param {React.JSX.Element | React.JSX.Element[]} children Single React Element , or array of React Elements. + * @param {ImageSlider[]} images List of images to show when gallery is expanded + * @returns + */ +export default function Gallery({ children, images }: ChildComponentProps) { + const initialImage = useRef(0) + const [toggleFullScreeSlider, setToggleFullScreenSlider] = useState(false) + + const onOpenFullScreenSlider = (index: number) => { + setToggleFullScreenSlider(true) + initialImage.current = index + } + + const onCloseFullScreenSlider = () => { + setToggleFullScreenSlider(false) + initialImage.current = 0 + } + + const NestedChild = useMemo(() => { + const childrenCount = React.Children.count(children) + + //if childrenCount > 1, assume list of thumbnail images are shown + if (childrenCount > 1) { + return React.Children.map(children, (child, index) => { + return React.cloneElement(child, { + onClick: () => onOpenFullScreenSlider(index), + style: { cursor: 'pointer' }, + }) + }) + } + + //if childrenCount === 1, assume the child is a component such as slider or gallery grid, and the list of images are nested. + return React.Children.map(children, (child) => { + //In some situations the child could be a single JSX.Element containing an image, thus child.props.children is an object rather than array + if (images.length === 1) { + return React.cloneElement(child, { + style: { cursor: 'pointer' }, + onClick: () => onOpenFullScreenSlider(0), + }) + } + + const modifiedNestedChild = child.props.children.map((elem: JSX.Element, index: number) => { + return React.cloneElement(elem, { + onClick: () => onOpenFullScreenSlider(index), + }) + }) + + return React.cloneElement(child, { style: { cursor: 'pointer' } }, modifiedNestedChild) + }) + }, []) + + return ( + <> + {NestedChild} + {toggleFullScreeSlider && ( + + )} + + ) +} diff --git a/src/components/common/CampaignImageSlider.tsx b/src/components/common/ImageSlider.tsx similarity index 75% rename from src/components/common/CampaignImageSlider.tsx rename to src/components/common/ImageSlider.tsx index d4dd9ea1a..d2f94b66f 100644 --- a/src/components/common/CampaignImageSlider.tsx +++ b/src/components/common/ImageSlider.tsx @@ -4,16 +4,19 @@ import 'slick-carousel/slick/slick.css' import 'slick-carousel/slick/slick-theme.css' import Image from 'next/image' import { styled } from '@mui/material/styles' -import { Modal, Typography } from '@mui/material' +import { IconButton, Modal, Typography } from '@mui/material' import { useTranslation } from 'next-i18next' import theme from 'common/theme' import { ImageSlider } from 'components/common/campaign-file/roles' -import withFullScreenSlider from './withFullScreenSlider' +import Gallery from './Gallery' +import FullscreenExitIcon from '@mui/icons-material/FullscreenExit' +const PREFIX = 'ImageSlider' const classes = { - container: 'container', - slider: 'slider', - carouselFullScreen: 'carouselFullScreen', + container: `${PREFIX}-container`, + slider: `${PREFIX}-slider`, + carouselFullScreen: `${PREFIX}-fullScreen`, + minimizeFullScreenBtn: `${PREFIX}-minimizeFullScreenBtn`, } const Root = styled('div')(() => ({ @@ -30,7 +33,7 @@ const Root = styled('div')(() => ({ zIndex: 3, }, '& .slick-next': { - right: theme.spacing(3.5), + right: theme.spacing(4), zIndex: 3, }, '& .slick-next::before, .slick-prev::before': { @@ -49,19 +52,16 @@ const Root = styled('div')(() => ({ }, }, [`& .${classes.carouselFullScreen}`]: { - maxWidth: '100%', - maxHeight: '100%', + width: '100vw', '& .slick-prev': { - left: theme.spacing(1), - zIndex: 3, + left: '5vw', }, '& .slick-slide': { position: 'relative', }, '& .slick-next': { - right: theme.spacing(3.5), - zIndex: 3, + right: '5vw', }, '& .slick-track img': { @@ -70,18 +70,15 @@ const Root = styled('div')(() => ({ }, '& .slick-track': { - aspectRatio: 20, - minHeight: 350, + height: '42vh', [theme.breakpoints.up(600)]: { - aspectRatio: 45, + height: '80vh', }, [theme.breakpoints.up(800)]: { - minHeight: 370, - aspectRatio: 49, + height: '88vh', }, [theme.breakpoints.up(1024)]: { - minHeight: 540, - aspectRatio: 34, + height: '90vh', }, }, @@ -100,6 +97,18 @@ const Root = styled('div')(() => ({ color: theme.palette.primary.main, }, }, + [`& .${classes.minimizeFullScreenBtn}`]: { + position: 'absolute', + color: 'white', + right: 0, + top: 0, + cursor: 'pointer', + zIndex: 9999, + + ['svg']: { + fontSize: 50, + }, + }, })) type Props = { @@ -123,9 +132,8 @@ const settings: Settings = { ], } -export default function CampaignImageSlider({ sliderImages }: Props) { +export function ImageSlider({ sliderImages }: Props) { const { t } = useTranslation() - const WithFullScreenSlider = withFullScreenSlider(Image) if (sliderImages.length === 0) { return null } @@ -150,21 +158,20 @@ export default function CampaignImageSlider({ sliderImages }: Props) { {t('campaigns:campaign.gallery')} - - {sliderImages.map((image, index) => ( -
- + + {sliderImages.map((image, index) => ( + {image.fileName} -
- ))} -
+ ))} + + ) } @@ -195,14 +202,25 @@ export const FullScreenImageSlider = ({ return ( - + + + + {sliderImages.map((image, index) => (
diff --git a/src/components/common/withFullScreenSlider.tsx b/src/components/common/withFullScreenSlider.tsx deleted file mode 100644 index 66b81547b..000000000 --- a/src/components/common/withFullScreenSlider.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useRef, useState } from 'react' -import { FullScreenImageSlider } from './CampaignImageSlider' -import { ImageProps } from 'next/image' -import { ImageSlider } from 'components/common/campaign-file/roles' - -type ChildComponentProps = ImageProps & { - images: ImageSlider[] - index: number -} - -/** - * HOC which expands an Image whenever user clicks it - * @param WrappedComponent Next's `Image` component - * @param images: An array of returned Images - * @param index: Index of the clicked image - * @returns - */ -export default function withFullScreenSlider(WrappedComponent: React.ComponentType) { - const [toggleFullScreeSlider, setToggleFullScreenSlider] = useState(false) - const initialImage = useRef(0) - - const onOpenFullScreenSlider = (index: number) => { - setToggleFullScreenSlider(true) - initialImage.current = index - } - - const onCloseFullScreenSlider = () => { - setToggleFullScreenSlider(false) - initialImage.current = 0 - } - - return (props: ChildComponentProps) => ( - <> - onOpenFullScreenSlider(props.index)} - style={{ ...props.style, cursor: 'pointer' }} - /> - {toggleFullScreeSlider && props.index === props.images.length - 1 && ( - - )} - - ) -}