fix: Gallery

This commit is contained in:
Benno Tielen 2024-11-26 16:21:12 +01:00
parent d2ba8c4727
commit 9e024ef1eb
8 changed files with 219 additions and 86 deletions

View file

@ -0,0 +1,67 @@
import styles from "./autoscroll.module.scss"
import { useEffect, useRef, useState } from 'react'
type AutoScrollProps = {
isScrolling: boolean
onTouch?: () => void
children: JSX.Element
}
/**
* This component autoscroll the content from left to right
* automatically
*
*/
export const AutoScroll = ({children, isScrolling, onTouch}: AutoScrollProps) => {
const [step, setStep] = useState(0)
const [direction, setDirection] = useState<'left' | 'right'>('right')
const wrapper = useRef<HTMLDivElement>(null)
useEffect(() => {
if(wrapper && wrapper.current) {
if (isScrolling) {
const scrollSpeed = 30; // 30px per second
const toScroll = wrapper.current.scrollWidth - window.innerWidth;
// nothing to scroll
if (toScroll <= 0) {
return;
}
const steps = toScroll/scrollSpeed * 1000
const t = setTimeout(() => {
let left = toScroll * step/steps
if(wrapper.current) {
wrapper.current.scrollTo({
left: left,
})
}
if(step >= steps) {
setDirection("left")
}
if(step <= 0) {
setDirection("right")
}
setStep(direction === "right" ? step + 1 : step - 1)
return () => clearTimeout(t);
}, 1);
}
}}, [wrapper, step, setStep, direction, setDirection])
return (
<div
className={styles.wrapper}
ref={wrapper}
onTouchStart={onTouch}
>
{children}
</div>
)
}

View file

@ -0,0 +1,30 @@
import styles from './fullscreen.module.scss'
import Image, { StaticImageData } from 'next/image'
type FullScreenProps = {
display: boolean
image: StaticImageData
alt?: string
closeClicked: () => void
nextClicked: () => void
}
export const Fullscreen = ({display, image, closeClicked, alt, nextClicked}: FullScreenProps) => {
if(display)
return (
<div className={styles.display}>
<div
className={styles.close}
onClick={closeClicked}
>x</div>
<Image
onClick={nextClicked}
className={styles.displayImage}
height={image.height}
width={image.width}
src={image.src}
alt={alt || ""}
/>
</div>
)
}

View file

@ -0,0 +1,35 @@
import { Meta, StoryObj } from '@storybook/react'
import { Gallery } from './Gallery'
import chris from "./../../assets/christophorus.jpeg"
const meta: Meta<typeof Gallery> = {
component: Gallery,
}
type Story = StoryObj<typeof Gallery>;
export default meta
export const Default: Story = {
args: {
items: [
{
id: "1",
thumbnail: chris,
image: chris,
alt: "hallo"
},
{
id: "2",
thumbnail: chris,
image: chris,
alt: "hallo"
},
{
id: "3",
thumbnail: chris,
image: chris,
alt: "hallo"
}
]
},
}

View file

@ -2,13 +2,15 @@
import styles from './styles.module.scss' import styles from './styles.module.scss'
import Image, { StaticImageData } from 'next/image' import Image, { StaticImageData } from 'next/image'
import { useCallback, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { AutoScroll } from '@/components/Gallery/AutoScroll'
import { Fullscreen } from '@/components/Gallery/Fullscreen'
type ImageType = { type ImageType = {
url: string, src: string,
width: number, width: number,
height: number height: number
} } | StaticImageData
export type GalleryItem = { export type GalleryItem = {
id: string; id: string;
@ -29,22 +31,32 @@ type GalleryProps = {
const GalleryItem = ({ thumbnail, alt, onClick }: GalleryItemProps) => { const GalleryItem = ({ thumbnail, alt, onClick }: GalleryItemProps) => {
return ( return (
<Image onClick={onClick} className={styles.item} src={thumbnail.url} height={thumbnail.height} width={thumbnail.width} alt={alt} /> <Image
onClick={onClick}
className={styles.item}
src={thumbnail.src}
height={thumbnail.height}
width={thumbnail.width}
alt={alt}
/>
) )
} }
export const Gallery = ({ items }: GalleryProps) => { export const Gallery = ({ items }: GalleryProps) => {
const [display, setDisplay] = useState(false) const [displayFullscreen, setDisplayFullscreen] = useState(false)
const [idx, setIdx] = useState(0) const [idx, setIdx] = useState(0)
const [isScrolling, setIsScrolling] = useState(true)
const displayImage = useCallback((n: number) => { const displayImage = useCallback((n: number) => {
setIdx(n); setIdx(n);
setDisplay(true); setDisplayFullscreen(true);
}, [setDisplay, setIdx]); setIsScrolling(false)
}, [setDisplayFullscreen, setIdx, setIsScrolling]);
const next = useCallback(() => { const next = useCallback(() => {
setIdx((idx + 1) % items.length) setIdx((idx + 1) % items.length)
}, [idx, setIdx, items]) }, [idx, setIdx, items])
if(items.length == 0) { if(items.length == 0) {
return null; return null;
} }
@ -52,6 +64,10 @@ export const Gallery = ({ items }: GalleryProps) => {
const { image, alt } = items[idx] const { image, alt } = items[idx]
return ( return (
<> <>
<AutoScroll
isScrolling={isScrolling}
onTouch={() => setIsScrolling(false)}
>
<div className={styles.container}> <div className={styles.container}>
{items.map((item, index) => {items.map((item, index) =>
<GalleryItem <GalleryItem
@ -61,18 +77,14 @@ export const Gallery = ({ items }: GalleryProps) => {
alt={item.alt} alt={item.alt}
/>)} />)}
</div> </div>
{display && </AutoScroll>
<div className={styles.display}>
<div className={styles.close} onClick={() => setDisplay(false)}>x</div> <Fullscreen
<Image display={displayFullscreen}
onClick={next} image={image}
className={styles.displayImage} closeClicked={() => setDisplayFullscreen(false)}
height={image.height} nextClicked={next}
width={image.width} />
src={image.url}
alt={alt} />
</div>
}
</> </>

View file

@ -0,0 +1,4 @@
.wrapper {
overflow: scroll;
scrollbar-width: none;
}

View file

@ -0,0 +1,42 @@
@import "template.scss";
.display {
position: fixed;
top: 0;
left: 0;
z-index: 99;
width: 100vw;
height: 100vh;
background-color: rgba(63, 63, 63, 0.82);
backdrop-filter: blur(8px);
display: flex;
justify-content: center;
align-items: center;
}
.displayImage {
display: block;
max-height: 90vh;
max-width: 90vw;
object-fit: contain;
}
.close {
color: #ffffff;
font-weight: bold;
position: fixed;
top: 35px;
right: 35px;
background-color: $base-color;
border-radius: 50%;
width: 50px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.close:hover {
background-color: $shade1;
}

View file

@ -4,66 +4,9 @@
display: flex; display: flex;
gap: 30px; gap: 30px;
flex-wrap: nowrap; flex-wrap: nowrap;
overflow: hidden;
--width: -500px;
}
.display {
position: fixed;
top: 0;
left: 0;
z-index: 99;
width: 100vw;
height: 100vh;
background-color: rgba(63, 63, 63, 0.82);
backdrop-filter: blur(8px);
display: flex;
justify-content: center; justify-content: center;
align-items: center;
}
.displayImage {
display: block;
max-height: 90vh;
max-width: 90vw;
object-fit: contain;
}
.close {
color: #ffffff;
font-weight: bold;
position: fixed;
top: 10px;
right: 10px;
background-color: $base-color;
border-radius: 50%;
width: 50px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.close:hover {
background-color: $shade1;
}
@keyframes slide {
0% {
transform: translateX(var(--width));
}
50% {
transform: translateX(0);
}
100% {
transform: translateX(var(--width));
}
} }
.item { .item {
animation-name: slide;
animation-duration: 60s;
animation-iteration-count: infinite;
animation-timing-function: linear;
cursor: pointer; cursor: pointer;
} }

View file

@ -23,12 +23,12 @@ export const transformGallery = (items: Items) => {
alt: item.photo.alt, alt: item.photo.alt,
id: item.id, id: item.id,
thumbnail: { thumbnail: {
url: thumbnail, src: thumbnail,
width: tWidth, width: tWidth,
height: tHeight, height: tHeight,
}, },
image: { image: {
url: image, src: image,
width: iWidth, width: iWidth,
height: iHeight height: iHeight
} }