feature: gallery

This commit is contained in:
Benno Tielen 2024-11-20 17:25:22 +01:00
parent 0afc27b028
commit 2d01ae6dbf
10 changed files with 273 additions and 7 deletions

View file

@ -11,6 +11,8 @@ import styles from "./styles.module.scss"
import { HTMLText } from '@/components/Text/HTMLText' import { HTMLText } from '@/components/Text/HTMLText'
import { Button } from '@/components/Button/Button' import { Button } from '@/components/Button/Button'
import { ContactSection } from '@/compositions/ContactSection/ContactSection' import { ContactSection } from '@/compositions/ContactSection/ContactSection'
import { Gallery } from '@/components/Gallery/Gallery'
import { transformGallery } from '@/utils/dto/gallery'
async function fetchBlog(id: string) { async function fetchBlog(id: string) {
const res = await fetch(`http://localhost:3000/api/blog/${id}`) const res = await fetch(`http://localhost:3000/api/blog/${id}`)
@ -77,6 +79,14 @@ export default async function BlogPage({ params }: { params: Promise<{id: string
<ContactSection key={item.id} title={item.title} description={item.description} /> <ContactSection key={item.id} title={item.title} description={item.description} />
) )
} }
if (item.blockType === "gallery") {
return (
<Section key={item.id}>
<Gallery items={transformGallery(item.items)} />
</Section>
)
}
})} })}
</div> </div>

View file

@ -103,7 +103,7 @@ export default async function Home() {
</Container> </Container>
</ContentWithSlider> </ContentWithSlider>
<ContactSection /> <ContactSection title={"Kontakt"} description={"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec molestie ligula. Maecenas sollicitudin molestie velit id ultricies. Nulla luctus eleifend est, et hendrerit nunc pretium in. Nullam leo tortor, tincidunt in sodales dapibus, faucibus vel lacus. Nunc ornare lacus eu dui consequat, fermentum tincidunt felis blandit. Vivamus a turpis eros. Donec faucibus mi in magna cursus maximus. Duis ac elit posuere, bibendum nunc vel, consectetur neque. Aliquam non bibendum quam. Nulla facilisi. Vivamus eu iaculis felis. Donec elit augue, pretium eget pretium ullamcorper, vestibulum eu lorem. Praesent gravida condimentum tortor, in dignissim mauris elementum quis. Morbi a venenatis odio. Vivamus quis magna eget elit pellentesque elementum. Nulla facilisi."} />
</> </>
) )
} }

View file

@ -3,6 +3,7 @@ import { isAdminOrEmployee } from '@/collections/access/admin'
import { ParagraphBlock } from '@/collections/blocks/Paragraph' import { ParagraphBlock } from '@/collections/blocks/Paragraph'
import { DocumentBlock } from '@/collections/blocks/Document' import { DocumentBlock } from '@/collections/blocks/Document'
import { ContactformBlock } from '@/collections/blocks/Contactform' import { ContactformBlock } from '@/collections/blocks/Contactform'
import { GalleryBlock } from '@/collections/blocks/Gallery'
export const Blog: CollectionConfig = { export const Blog: CollectionConfig = {
@ -54,7 +55,8 @@ export const Blog: CollectionConfig = {
blocks: [ blocks: [
ParagraphBlock, ParagraphBlock,
DocumentBlock, DocumentBlock,
ContactformBlock ContactformBlock,
GalleryBlock
], ],
required: true required: true
}, },

View file

@ -26,13 +26,15 @@ export const Media: CollectionConfig = {
height: 400, height: 400,
position: 'centre', position: 'centre',
}, },
{
name: 'gallery',
height: 500,
width: undefined,
position: 'centre',
},
{ {
name: 'tablet', name: 'tablet',
width: 1024, width: 1024,
// By specifying `undefined` or leaving a height undefined,
// the image will be sized to a certain width,
// but it will retain its original aspect ratio
// and calculate a height automatically.
height: undefined, height: undefined,
position: 'centre', position: 'centre',
}, },

View file

@ -0,0 +1,28 @@
import { Block } from 'payload'
export const GalleryBlock: Block = {
slug: 'gallery',
fields: [
{
name: 'items',
label: {
de: 'Bilder'
},
type: 'array',
required: true,
fields: [
{
name: 'photo',
label: {
de: 'Bild'
},
type: 'upload',
relationTo: 'media',
required: true
},
],
minRows: 3,
maxRows: 12
}
]
}

View file

@ -0,0 +1,76 @@
'use client'
import styles from './styles.module.scss'
import Image, { StaticImageData } from 'next/image'
import { useCallback, useState } from 'react'
type ImageType = {
url: string,
width: number,
height: number
}
export type GalleryItem = {
id: string;
thumbnail: ImageType ;
image: ImageType;
alt: string;
}
type GalleryItemProps = {
thumbnail: ImageType,
alt: string,
onClick?: () => void
}
type GalleryProps = {
items: GalleryItem[];
}
const GalleryItem = ({ thumbnail, alt, onClick }: GalleryItemProps) => {
return (
<Image onClick={onClick} className={styles.item} src={thumbnail.url} height={thumbnail.height} width={thumbnail.width} alt={alt} />
)
}
export const Gallery = ({ items }: GalleryProps) => {
const [display, setDisplay] = useState(false)
const [idx, setIdx] = useState(0)
const { image, alt } = items[idx]
const displayImage = useCallback((n: number) => {
setIdx(n);
setDisplay(true);
}, [setDisplay, setIdx]);
const next = useCallback(() => {
setIdx((idx + 1) % items.length)
}, [idx, setIdx, items])
return (
<>
<div className={styles.container}>
{items.map((item, index) =>
<GalleryItem
onClick={() => displayImage(index)}
key={item.id}
thumbnail={item.thumbnail}
alt={item.alt}
/>)}
</div>
{display &&
<div className={styles.display}>
<div className={styles.close} onClick={() => setDisplay(false)}>x</div>
<Image
onClick={next}
className={styles.displayImage}
height={image.height}
width={image.width}
src={image.url}
alt={alt} />
</div>
}
</>
)
}

View file

@ -0,0 +1,69 @@
@import "template.scss";
.container {
display: flex;
gap: 30px;
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;
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 {
animation-name: slide;
animation-duration: 60s;
animation-iteration-count: infinite;
animation-timing-function: linear;
cursor: pointer;
}

View file

@ -65,7 +65,6 @@ export const Home = () => {
backgroundColor={"soft"} backgroundColor={"soft"}
/> />
<ContactSection />
</> </>
) )
} }

View file

@ -165,6 +165,14 @@ export interface Media {
filesize?: number | null; filesize?: number | null;
filename?: string | null; filename?: string | null;
}; };
gallery?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
tablet?: { tablet?: {
url?: string | null; url?: string | null;
width?: number | null; width?: number | null;
@ -251,6 +259,15 @@ export interface Blog {
blockName?: string | null; blockName?: string | null;
blockType: 'contactform'; blockType: 'contactform';
} }
| {
items: {
photo: string | Media;
id?: string | null;
}[];
id?: string | null;
blockName?: string | null;
blockType: 'gallery';
}
)[]; )[];
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@ -609,6 +626,18 @@ export interface BlogSelect<T extends boolean = true> {
id?: T; id?: T;
blockName?: T; blockName?: T;
}; };
gallery?:
| T
| {
items?:
| T
| {
photo?: T;
id?: T;
};
id?: T;
blockName?: T;
};
}; };
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
@ -787,6 +816,16 @@ export interface MediaSelect<T extends boolean = true> {
filesize?: T; filesize?: T;
filename?: T; filename?: T;
}; };
gallery?:
| T
| {
url?: T;
width?: T;
height?: T;
mimeType?: T;
filesize?: T;
filename?: T;
};
tablet?: tablet?:
| T | T
| { | {

41
src/utils/dto/gallery.ts Normal file
View file

@ -0,0 +1,41 @@
import { Media } from '@/payload-types'
import { GalleryItem } from '@/components/Gallery/Gallery'
type Items = {
photo: string | Media;
id?: string | null;
}[];
export const transformGallery = (items: Items) => {
const galleryItems: GalleryItem[] = []
items.forEach(item => {
if (typeof item === "object" && typeof item.photo === "object" && item.id) {
const thumbnail = item.photo.sizes?.gallery?.url;
const tWidth = item.photo.sizes?.gallery?.width;
const tHeight = item.photo.sizes?.gallery?.height;
const image = item.photo.sizes?.tablet?.url;
const iWidth = item.photo.sizes?.tablet?.width;
const iHeight = item.photo.sizes?.tablet?.height;
if (thumbnail && image && tWidth && tHeight && iWidth && iHeight) {
galleryItems.push({
alt: item.photo.alt,
id: item.id,
thumbnail: {
url: thumbnail,
width: tWidth,
height: tHeight,
},
image: {
url: image,
width: iWidth,
height: iHeight
}
})
}
}
})
return galleryItems
}