Skip to content

Commit

Permalink
components/commmon: Gallery component improvements (#1708)
Browse files Browse the repository at this point in the history
* components/commmon: Gallery component improvements
- Improved logic to render the Gallery Component more efficiently
- Expanded the size of the images, when FullScreen is toggled
- Added a button to minimize fullscreen slider

* common/Gallery: Fix component crash when child is a single image
In some situations, only a single image is uploaded, and in that case child.props.children is an object rather, than an array to map it over.

* common/ImageSlider: Hide backdrop on FullScreenImageSlider
Avoid minimizing gallery due to accidental touches
  • Loading branch information
sashko9807 authored Jan 20, 2024
1 parent 852fdd7 commit 04c5d2d
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 116 deletions.
33 changes: 16 additions & 17 deletions src/components/client/campaign-news/CampaignNewsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -158,21 +157,21 @@ export default function CampaignNewsList({ articles }: Props) {
{article.newsFiles.length > 0 && (
<>
<Grid container item gap={1} xs={'auto'} style={{ maxWidth: '100%' }}>
{images.map((file, index) => {
return (
<Grid item key={file.id}>
<WithFullScreenSlider
images={images}
src={file.src}
width={220}
height={140}
alt={file.fileName}
index={index}
style={{ objectFit: 'scale-down' }}
/>
</Grid>
)
})}
<Gallery images={images}>
{images.map((file) => {
return (
<Grid item key={file.id}>
<Image
src={file.src}
width={220}
height={140}
alt={file.fileName}
style={{ objectFit: 'scale-down' }}
/>
</Grid>
)
})}
</Gallery>
</Grid>
</>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/client/campaigns/CampaignDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -158,7 +158,7 @@ export default function CampaignDetails({ campaign }: Props) {
<ReactQuill readOnly theme="bubble" value={campaign.description} />
</Grid>
<Grid item xs={12}>
<CampaignImageSlider sliderImages={sliderImages} />
<ImageSlider sliderImages={sliderImages} />
</Grid>
<Grid item xs={12}>
<Divider />
Expand Down
32 changes: 16 additions & 16 deletions src/components/client/campaigns/CampaignNewsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -299,20 +298,21 @@ export default function CampaignNewsSection({ campaign, canCreateArticle }: Prop
))}
</Grid>
<Grid container item gap={1}>
{images.map((file, index) => {
return (
<Grid item key={file.id}>
<WithFullScreenSlider
images={images}
src={file.src}
width={164}
height={120}
alt={file.id}
index={index}
/>
</Grid>
)
})}
<Gallery images={images}>
{images.map((file) => {
return (
<Grid item key={file.id}>
<Image
src={file.src}
width={220}
height={140}
alt={file.fileName}
style={{ objectFit: 'scale-down' }}
/>
</Grid>
)
})}
</Gallery>
</Grid>
</Grid>
)}
Expand Down
76 changes: 76 additions & 0 deletions src/components/common/Gallery.tsx
Original file line number Diff line number Diff line change
@@ -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<number>(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 && (
<FullScreenImageSlider
sliderImages={images}
onOpen={toggleFullScreeSlider}
onClose={onCloseFullScreenSlider}
initialImage={initialImage}
/>
)}
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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')(() => ({
Expand All @@ -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': {
Expand All @@ -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': {
Expand All @@ -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',
},
},

Expand All @@ -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 = {
Expand All @@ -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
}
Expand All @@ -150,21 +158,20 @@ export default function CampaignImageSlider({ sliderImages }: Props) {
<Typography variant="h4" sx={{ my: theme.spacing(3), fontWeight: '500' }}>
{t('campaigns:campaign.gallery')}
</Typography>
<Slider {...settings} className={classes.slider}>
{sliderImages.map((image, index) => (
<div key={index}>
<WithFullScreenSlider
images={sliderImages}
<Gallery images={sliderImages}>
<Slider {...settings} className={classes.slider}>
{sliderImages.map((image, index) => (
<Image
src={image.src}
alt={image.fileName}
height={300}
width={500}
style={{ objectFit: 'contain' }}
index={index}
key={index}
/>
</div>
))}
</Slider>
))}
</Slider>
</Gallery>
</Root>
)
}
Expand Down Expand Up @@ -195,14 +202,25 @@ export const FullScreenImageSlider = ({
return (
<Modal
open={onOpen}
hideBackdrop
onClose={onClose}
sx={{
width: '100vw',
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0,0,0,0.8)',
backgroundColor: 'rgba(0,0,0,0.9)',
cursor: 'grab',
}}>
<Root style={{ width: 1000, maxWidth: '100%' }}>
<Root>
<IconButton
size="large"
onClick={onClose}
className={classes.minimizeFullScreenBtn}
aria-label="minimize gallery">
<FullscreenExitIcon />
</IconButton>
<Slider {...newSettings} className={classes.carouselFullScreen} ref={sliderRef}>
{sliderImages.map((image, index) => (
<div key={index} style={{ position: 'relative', maxWidth: '100%' }}>
Expand Down
Loading

0 comments on commit 04c5d2d

Please sign in to comment.