feature: gallery
This commit is contained in:
parent
0afc27b028
commit
2d01ae6dbf
10 changed files with 273 additions and 7 deletions
|
|
@ -11,6 +11,8 @@ import styles from "./styles.module.scss"
|
|||
import { HTMLText } from '@/components/Text/HTMLText'
|
||||
import { Button } from '@/components/Button/Button'
|
||||
import { ContactSection } from '@/compositions/ContactSection/ContactSection'
|
||||
import { Gallery } from '@/components/Gallery/Gallery'
|
||||
import { transformGallery } from '@/utils/dto/gallery'
|
||||
|
||||
async function fetchBlog(id: string) {
|
||||
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} />
|
||||
)
|
||||
}
|
||||
|
||||
if (item.blockType === "gallery") {
|
||||
return (
|
||||
<Section key={item.id}>
|
||||
<Gallery items={transformGallery(item.items)} />
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export default async function Home() {
|
|||
</Container>
|
||||
</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."} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { isAdminOrEmployee } from '@/collections/access/admin'
|
|||
import { ParagraphBlock } from '@/collections/blocks/Paragraph'
|
||||
import { DocumentBlock } from '@/collections/blocks/Document'
|
||||
import { ContactformBlock } from '@/collections/blocks/Contactform'
|
||||
import { GalleryBlock } from '@/collections/blocks/Gallery'
|
||||
|
||||
|
||||
export const Blog: CollectionConfig = {
|
||||
|
|
@ -54,7 +55,8 @@ export const Blog: CollectionConfig = {
|
|||
blocks: [
|
||||
ParagraphBlock,
|
||||
DocumentBlock,
|
||||
ContactformBlock
|
||||
ContactformBlock,
|
||||
GalleryBlock
|
||||
],
|
||||
required: true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,13 +26,15 @@ export const Media: CollectionConfig = {
|
|||
height: 400,
|
||||
position: 'centre',
|
||||
},
|
||||
{
|
||||
name: 'gallery',
|
||||
height: 500,
|
||||
width: undefined,
|
||||
position: 'centre',
|
||||
},
|
||||
{
|
||||
name: 'tablet',
|
||||
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,
|
||||
position: 'centre',
|
||||
},
|
||||
|
|
|
|||
28
src/collections/blocks/Gallery.ts
Normal file
28
src/collections/blocks/Gallery.ts
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
76
src/components/Gallery/Gallery.tsx
Normal file
76
src/components/Gallery/Gallery.tsx
Normal 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>
|
||||
}
|
||||
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
69
src/components/Gallery/styles.module.scss
Normal file
69
src/components/Gallery/styles.module.scss
Normal 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;
|
||||
}
|
||||
|
|
@ -65,7 +65,6 @@ export const Home = () => {
|
|||
backgroundColor={"soft"}
|
||||
/>
|
||||
|
||||
<ContactSection />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -165,6 +165,14 @@ export interface Media {
|
|||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
gallery?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
tablet?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
|
|
@ -251,6 +259,15 @@ export interface Blog {
|
|||
blockName?: string | null;
|
||||
blockType: 'contactform';
|
||||
}
|
||||
| {
|
||||
items: {
|
||||
photo: string | Media;
|
||||
id?: string | null;
|
||||
}[];
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'gallery';
|
||||
}
|
||||
)[];
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
|
|
@ -609,6 +626,18 @@ export interface BlogSelect<T extends boolean = true> {
|
|||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
gallery?:
|
||||
| T
|
||||
| {
|
||||
items?:
|
||||
| T
|
||||
| {
|
||||
photo?: T;
|
||||
id?: T;
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
|
|
@ -787,6 +816,16 @@ export interface MediaSelect<T extends boolean = true> {
|
|||
filesize?: T;
|
||||
filename?: T;
|
||||
};
|
||||
gallery?:
|
||||
| T
|
||||
| {
|
||||
url?: T;
|
||||
width?: T;
|
||||
height?: T;
|
||||
mimeType?: T;
|
||||
filesize?: T;
|
||||
filename?: T;
|
||||
};
|
||||
tablet?:
|
||||
| T
|
||||
| {
|
||||
|
|
|
|||
41
src/utils/dto/gallery.ts
Normal file
41
src/utils/dto/gallery.ts
Normal 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
|
||||
}
|
||||
Loading…
Reference in a new issue