Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add loading state to ImageGallery (#3462) #4084

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 37 additions & 42 deletions packages/components/src/ImageGallery/ImageGallery.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useEffect, useRef, useState } from 'react'
import styled from '@emotion/styled'
import PhotoSwipeLightbox from 'photoswipe/lightbox'
import { Box, Flex, Image as ThemeImage } from 'theme-ui'
import { Flex, Image as ThemeImage } from 'theme-ui'

import { Icon } from '../Icon/Icon'
import { ImageGalleryThumbnail } from '../ImageGalleryThumbnail/ImageGalleryThumbnail'
import { Loader } from '../Loader/Loader'

import type { PhotoSwipeOptions } from 'photoswipe/lightbox'
import type { CardProps } from 'theme-ui'

import 'photoswipe/style.css'

Expand Down Expand Up @@ -35,18 +36,9 @@ interface IState {
activeImageIndex: number
showLightbox: boolean
images: Array<IImageGalleryItem>
showActiveImgLoading: boolean
}

const ThumbCard = styled<CardProps & React.ComponentProps<any>>(Box)`
cursor: pointer;
padding: 5px;
overflow: hidden;
transition: 0.2s ease-in-out;
&:hover {
transform: translateY(-5px);
}
`

const NavButton = styled('button')`
background: transparent;
border: 0;
Expand All @@ -62,6 +54,7 @@ export const ImageGallery = (props: ImageGalleryProps) => {
activeImageIndex: 0,
showLightbox: false,
images: [],
showActiveImgLoading: true,
})
const lightbox = useRef<PhotoSwipeLightbox>()

Expand Down Expand Up @@ -113,6 +106,14 @@ export const ImageGallery = (props: ImageGalleryProps) => {
setState({
...state,
activeImageIndex: imageIndex,
showActiveImgLoading: imageIndex !== activeImageIndex,
})
}

const setActiveImgLoaded = () => {
setState({
...state,
showActiveImgLoading: false,
})
}

Expand All @@ -132,7 +133,16 @@ export const ImageGallery = (props: ImageGalleryProps) => {

return activeImage ? (
<Flex sx={{ flexDirection: 'column' }}>
<Flex sx={{ width: '100%', position: 'relative' }}>
{state.showActiveImgLoading && (
<Loader sx={{ position: 'absolute', alignSelf: 'center' }} />
)}
<Flex
sx={{
width: '100%',
position: 'relative',
opacity: `${state.showActiveImgLoading ? 0 : 1}`,
}}
>
<ThemeImage
loading="lazy"
data-cy="active-image"
Expand All @@ -144,9 +154,8 @@ export const ImageGallery = (props: ImageGalleryProps) => {
height: [300, 450],
}}
src={activeImage.downloadUrl}
onClick={() => {
triggerLightbox()
}}
onClick={triggerLightbox}
onLoad={setActiveImgLoaded}
alt={activeImage.alt ?? activeImage.name}
crossOrigin=""
/>
Expand Down Expand Up @@ -193,36 +202,22 @@ export const ImageGallery = (props: ImageGalleryProps) => {
</>
) : null}
</Flex>
{showThumbnails ? (
{showThumbnails && (
<Flex sx={{ width: '100%', flexWrap: 'wrap' }} mx={[2, 2, '-5px']}>
{images.map((image, index: number) => (
<ThumbCard
data-cy="thumbnail"
data-testid="thumbnail"
mb={3}
mt={4}
opacity={image === activeImage ? 1.0 : 0.5}
onClick={() => setActive(index)}
key={index}
>
<ThemeImage
loading="lazy"
src={image.thumbnailUrl}
key={index}
alt={image.alt ?? image.name}
sx={{
width: 100,
height: 67,
objectFit: props.allowPortrait ? 'contain' : 'cover',
borderRadius: 1,
border: '1px solid offWhite',
}}
crossOrigin=""
/>
</ThumbCard>
<ImageGalleryThumbnail
activeImageIndex={activeImageIndex}
allowPortrait={props.allowPortrait ?? false}
setActiveIndex={setActive}
key={image.thumbnailUrl}
alt={image.alt}
index={index}
name={image.name}
thumbnailUrl={image.thumbnailUrl}
/>
))}
</Flex>
) : null}
)}
</Flex>
) : null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { ImageGalleryThumbnail } from './ImageGalleryThumbnail'

import type { Meta, StoryFn } from '@storybook/react'
import type { ImageGalleryThumbnailProps } from './ImageGalleryThumbnail'

export default {
title: 'Layout/ImageGallery/ImageGalleryThumbnail',
component: ImageGalleryThumbnail,
} as Meta<typeof ImageGalleryThumbnail>

export const Default: StoryFn<typeof ImageGalleryThumbnail> = (
props: ImageGalleryThumbnailProps,
) => {
return (
<ImageGalleryThumbnail
{...props}
activeImageIndex={0}
allowPortrait={false}
alt="alt"
name="name"
index={0}
setActiveIndex={() => {}}
thumbnailUrl="https://picsum.photos/id/29/150/150"
/>
)
}

export const AllowPortrait: StoryFn<typeof ImageGalleryThumbnail> = (
props: ImageGalleryThumbnailProps,
) => {
return (
<ImageGalleryThumbnail
{...props}
activeImageIndex={0}
allowPortrait={true}
alt="alt"
name="name"
index={0}
setActiveIndex={() => {}}
thumbnailUrl="https://picsum.photos/id/29/150/150"
/>
)
}

export const DisallowPortrait: StoryFn<typeof ImageGalleryThumbnail> = (
props: ImageGalleryThumbnailProps,
) => {
return (
<ImageGalleryThumbnail
{...props}
activeImageIndex={0}
allowPortrait={false}
alt="alt"
name="name"
index={0}
setActiveIndex={() => {}}
thumbnailUrl="https://picsum.photos/id/29/150/150"
/>
)
}

export const ImageIsActive: StoryFn<typeof ImageGalleryThumbnail> = (
props: ImageGalleryThumbnailProps,
) => {
return (
<ImageGalleryThumbnail
{...props}
activeImageIndex={0}
allowPortrait={false}
alt="alt"
name="name"
index={0}
setActiveIndex={() => {}}
thumbnailUrl="https://picsum.photos/id/29/150/150"
/>
)
}

export const ImageIsNotActive: StoryFn<typeof ImageGalleryThumbnail> = (
props: ImageGalleryThumbnailProps,
) => {
return (
<ImageGalleryThumbnail
{...props}
activeImageIndex={1}
allowPortrait={false}
alt="alt"
name="name"
index={0}
setActiveIndex={() => {}}
thumbnailUrl="https://picsum.photos/id/29/150/150"
/>
)
}

export const ThumbnailUrlInvalidAltText: StoryFn<
typeof ImageGalleryThumbnail
> = (props: ImageGalleryThumbnailProps) => {
return (
<ImageGalleryThumbnail
{...props}
activeImageIndex={1}
allowPortrait={false}
alt="alt"
name="name"
index={0}
setActiveIndex={() => {}}
thumbnailUrl="https://fastly.picsum.photos/404"
/>
)
}

export const ThumbnailUrlInvalidNameText: StoryFn<
typeof ImageGalleryThumbnail
> = (props: ImageGalleryThumbnailProps) => {
return (
<ImageGalleryThumbnail
{...props}
activeImageIndex={1}
allowPortrait={false}
name="name"
index={0}
setActiveIndex={() => {}}
thumbnailUrl="https://fastly.picsum.photos/404"
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import '@testing-library/jest-dom/vitest'

import { describe, expect, it, vi } from 'vitest'

import { render } from '../test/utils'
import { ImageGalleryThumbnail } from './ImageGalleryThumbnail'

describe('ImageGalleryThumbnail', () => {
it('calls Callback, when image clicked', () => {
const mockFn = vi.fn()
const { getByTestId } = render(
<ImageGalleryThumbnail
activeImageIndex={0}
allowPortrait={false}
alt="alt"
name="name"
index={0}
setActiveIndex={mockFn}
thumbnailUrl="https://picsum.photos/id/29/150/150"
/>,
)
getByTestId('thumbnail').click()
expect(mockFn).toHaveBeenCalledTimes(1)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { useState } from 'react'
import styled from '@emotion/styled'
import { Box, Image as ThemeImage } from 'theme-ui'

import { Loader } from '../Loader/Loader'

import type { CardProps } from 'theme-ui'

import 'photoswipe/style.css'

export interface ImageGalleryThumbnailProps {
setActiveIndex: (index: number) => void
allowPortrait: boolean
activeImageIndex: number
thumbnailUrl: string
index: number
alt?: string
name?: string
}

export const ThumbCard = styled<CardProps & React.ComponentProps<any>>(Box)`
cursor: pointer;
padding: 5px;
overflow: hidden;
transition: 0.2s ease-in-out;
&:hover {
transform: translateY(-5px);
}
`

export const ImageGalleryThumbnail = (props: ImageGalleryThumbnailProps) => {
const [thumbnailLoaded, setThumbnailLoaded] = useState<boolean>(false)

return (
<>
{!thumbnailLoaded && (
<Loader sx={{ mb: 3, mt: 4, width: 100, height: 67 }} />
)}
<ThumbCard
data-cy="thumbnail"
data-testid="thumbnail"
mb={3}
mt={4}
opacity={props.index === props.activeImageIndex ? 1.0 : 0.5}
onClick={() => props.setActiveIndex(props.index)}
>
<ThemeImage
loading="lazy"
src={props.thumbnailUrl}
alt={props.alt ?? props.name}
onLoad={() => setThumbnailLoaded(true)}
onError={() => setThumbnailLoaded(true)}
sx={{
width: thumbnailLoaded ? 100 : 0,
height: 67,
objectFit: props.allowPortrait ? 'contain' : 'cover',
borderRadius: 1,
border: '1px solid offWhite',
}}
crossOrigin=""
/>
</ThumbCard>
</>
)
}
4 changes: 3 additions & 1 deletion packages/components/src/MapFilterList/MapFilterList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export const MapFilterList = (props: IProps) => {
const isActive = (checkingFilter: string) =>
!!activeFilters.find((filter) => filter.label === checkingFilter)

const buttonLabel = `${pinCount} result${pinCount === 1 ? '' : 's'} in current view`
const buttonLabel = `${pinCount} result${
pinCount === 1 ? '' : 's'
} in current view`

return (
<Flex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export const MemberTypeVerticalList = (props: IProps) => {
)
return (
<CardButton
data-cy={`MemberTypeVerticalList-Item${isSelected ? '-active' : ''}`}
data-cy={`MemberTypeVerticalList-Item${
isSelected ? '-active' : ''
}`}
data-testid="MemberTypeVerticalList-Item"
title={item._id}
key={index}
Expand Down
Loading