From 2d01ae6dbfc82191735ab7695b4a59db9436333f Mon Sep 17 00:00:00 2001 From: Benno Tielen Date: Wed, 20 Nov 2024 17:25:22 +0100 Subject: [PATCH] feature: gallery --- src/app/blog/[id]/page.tsx | 10 +++ src/app/page.tsx | 2 +- src/collections/Blog.ts | 4 +- src/collections/Media.ts | 10 +-- src/collections/blocks/Gallery.ts | 28 +++++++++ src/components/Gallery/Gallery.tsx | 76 +++++++++++++++++++++++ src/components/Gallery/styles.module.scss | 69 ++++++++++++++++++++ src/pageComponents/Home/Home.tsx | 1 - src/payload-types.ts | 39 ++++++++++++ src/utils/dto/gallery.ts | 41 ++++++++++++ 10 files changed, 273 insertions(+), 7 deletions(-) create mode 100644 src/collections/blocks/Gallery.ts create mode 100644 src/components/Gallery/Gallery.tsx create mode 100644 src/components/Gallery/styles.module.scss create mode 100644 src/utils/dto/gallery.ts diff --git a/src/app/blog/[id]/page.tsx b/src/app/blog/[id]/page.tsx index b2f3bb9..a2e0179 100644 --- a/src/app/blog/[id]/page.tsx +++ b/src/app/blog/[id]/page.tsx @@ -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 ) } + + if (item.blockType === "gallery") { + return ( +
+ +
+ ) + } })} diff --git a/src/app/page.tsx b/src/app/page.tsx index 5af4ee2..3408e31 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -103,7 +103,7 @@ export default async function Home() { - + ) } diff --git a/src/collections/Blog.ts b/src/collections/Blog.ts index 49c6d99..3baab58 100644 --- a/src/collections/Blog.ts +++ b/src/collections/Blog.ts @@ -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 }, diff --git a/src/collections/Media.ts b/src/collections/Media.ts index fce39df..5342c6a 100644 --- a/src/collections/Media.ts +++ b/src/collections/Media.ts @@ -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', }, diff --git a/src/collections/blocks/Gallery.ts b/src/collections/blocks/Gallery.ts new file mode 100644 index 0000000..f4bb06d --- /dev/null +++ b/src/collections/blocks/Gallery.ts @@ -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 + } + ] +} \ No newline at end of file diff --git a/src/components/Gallery/Gallery.tsx b/src/components/Gallery/Gallery.tsx new file mode 100644 index 0000000..48528b1 --- /dev/null +++ b/src/components/Gallery/Gallery.tsx @@ -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 ( + {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 ( + <> +
+ {items.map((item, index) => + displayImage(index)} + key={item.id} + thumbnail={item.thumbnail} + alt={item.alt} + />)} +
+ {display && +
+
setDisplay(false)}>x
+ {alt} +
+ } + + + + ) +} \ No newline at end of file diff --git a/src/components/Gallery/styles.module.scss b/src/components/Gallery/styles.module.scss new file mode 100644 index 0000000..c7c172c --- /dev/null +++ b/src/components/Gallery/styles.module.scss @@ -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; +} \ No newline at end of file diff --git a/src/pageComponents/Home/Home.tsx b/src/pageComponents/Home/Home.tsx index d49b7e2..e2cbdfe 100644 --- a/src/pageComponents/Home/Home.tsx +++ b/src/pageComponents/Home/Home.tsx @@ -65,7 +65,6 @@ export const Home = () => { backgroundColor={"soft"} /> - ) } \ No newline at end of file diff --git a/src/payload-types.ts b/src/payload-types.ts index cfc2314..5e3472a 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -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 { 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 { filesize?: T; filename?: T; }; + gallery?: + | T + | { + url?: T; + width?: T; + height?: T; + mimeType?: T; + filesize?: T; + filename?: T; + }; tablet?: | T | { diff --git a/src/utils/dto/gallery.ts b/src/utils/dto/gallery.ts new file mode 100644 index 0000000..f3d3784 --- /dev/null +++ b/src/utils/dto/gallery.ts @@ -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 +} \ No newline at end of file