Skip to content

Commit b80d436

Browse files
committed
feat: brand section marquee, enabled paylod live preview
1 parent 36d080b commit b80d436

4 files changed

Lines changed: 109 additions & 53 deletions

File tree

src/components/sections/BrandSection.tsx

Lines changed: 29 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,32 @@
11
"use client"
22

3-
import React from "react"
3+
import { LogoItem } from "@/components/ui/LogoItem"
4+
import { LogoMarquee } from "@/components/ui/LogoMarquee"
45
import { Section } from "@/components/ui/Section"
5-
import Image from "next/image"
6+
import { BrandLayoutBlock } from "@/lib/cms"
67
import type { Media } from "@/payload-types"
78
import { m as motion, type Variants } from "motion/react"
8-
import { BrandLayoutBlock } from "@/lib/cms"
9-
import { getMediaUrl } from "@/lib/media"
109

1110
interface BrandSectionProps {
1211
content?: BrandLayoutBlock | null
1312
}
1413

1514
export function BrandSection({ content }: BrandSectionProps) {
16-
if (!content) return
15+
if (!content || !content.logos) return
1716

18-
const logos = (content.logos ?? [])
17+
const logos = content.logos
1918
.map((item) => item.logo)
2019
.filter((logo) => Boolean((logo as Media)?.url))
21-
.slice(0, 4)
20+
const shouldLoopDesktop = logos.length > 4
2221

2322
const staggerContainer: Variants = {
2423
hidden: {},
2524
show: {
2625
transition: {
2726
staggerChildren: 0.08,
2827
delayChildren: 0.06,
29-
},
30-
},
28+
}
29+
}
3130
}
3231

3332
const staggerItem: Variants = {
@@ -36,62 +35,39 @@ export function BrandSection({ content }: BrandSectionProps) {
3635
opacity: 1,
3736
y: 0,
3837
transition: { duration: 0.38, ease: [0.22, 1, 0.36, 1] },
39-
},
38+
}
4039
}
4140

4241
return (
4342
<Section showBlur={false} showFunnel={false} animationPreset="slide-right" className="-mt-32">
4443
<motion.div
45-
className="flex w-full flex-col items-center justify-center gap-8 px-8 pt-16 md:px-16 lg:flex-row"
44+
className="flex w-full flex-col items-center justify-center gap-8 pt-16 lg:flex-row"
4645
variants={staggerContainer}
4746
initial="hidden"
4847
whileInView="show"
4948
viewport={{ once: true, amount: 0.3 }}
5049
>
51-
<motion.p variants={staggerItem} className={"text-center text-white/75 lg:flex lg:text-left"}>
50+
<motion.p variants={staggerItem} className={"w-full text-center text-white/75 lg:w-1/3 lg:shrink-0 lg:text-left"}>
5251
{content.description}
5352
</motion.p>
54-
<motion.div variants={staggerContainer} className={"w-full grid grid-cols-2 md:grid-cols-4 gap-16 text-white/75 text-center"}>
55-
{logos.length > 0 ? (
56-
logos.map((item, index) => {
57-
const href = (item as Media & { href?: string | null }).href
58-
const logo = item as Media
59-
const logoUrl = getMediaUrl(logo.url)
60-
61-
return (
62-
<motion.div variants={staggerItem} className="relative w-full h-14" key={`${logo.id ?? logo.url ?? index}`}>
63-
{href ? (
64-
<a href={href} className="relative block h-full w-full">
65-
<Image
66-
src={logoUrl}
67-
alt={logo.alt}
68-
fill
69-
unoptimized
70-
className="object-contain brightness-0 invert"
71-
sizes="(min-width: 768px) 20vw, 40vw"
72-
/>
73-
</a>
74-
) : (
75-
<Image
76-
src={logoUrl}
77-
alt={logo.alt}
78-
fill
79-
unoptimized
80-
className="object-contain brightness-0 invert"
81-
sizes="(min-width: 768px) 20vw, 40vw"
82-
/>
83-
)}
84-
</motion.div>
85-
)
86-
})
87-
) : (
88-
<>
89-
<motion.p variants={staggerItem} className={"text-4xl font-bold"}>Logo1</motion.p>
90-
<motion.p variants={staggerItem} className={"text-4xl font-bold"}>Logo2</motion.p>
91-
<motion.p variants={staggerItem} className={"text-4xl font-bold"}>Logo3</motion.p>
92-
<motion.p variants={staggerItem} className={"text-4xl font-bold"}>Logo4</motion.p>
93-
</>
94-
)}
53+
<motion.div
54+
variants={staggerContainer}
55+
className={shouldLoopDesktop
56+
? "hidden"
57+
: "hidden w-full min-w-0 grid-cols-4 gap-16 text-center text-white/75 md:grid lg:flex-1"
58+
}
59+
>
60+
{logos.map((item, index) => (
61+
<motion.div variants={staggerItem} key={`${(item as Media).id ?? (item as Media).url ?? index}-${index}`}>
62+
<LogoItem logo={item} className="w-full" />
63+
</motion.div>
64+
))}
65+
</motion.div>
66+
<motion.div
67+
variants={staggerItem}
68+
className={shouldLoopDesktop ? "w-full min-w-0 lg:flex-1" : "w-full md:hidden"}
69+
>
70+
<LogoMarquee logos={logos} />
9571
</motion.div>
9672
</motion.div>
9773
</Section>

src/components/ui/LogoItem.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Media } from "@/payload-types"
2+
import { getMediaUrl } from "@/lib/media"
3+
import { cn } from "@/lib/utils"
4+
import Image from "next/image"
5+
6+
interface LogoItemProps {
7+
logo: number | Media
8+
className?: string
9+
sizes?: string
10+
}
11+
12+
export function LogoItem({ logo: logoValue, className, sizes = "(min-width: 768px) 20vw, 40vw" }: LogoItemProps) {
13+
const href = (logoValue as Media & { href?: string | null }).href
14+
const logo = logoValue as Media
15+
const logoUrl = getMediaUrl(logo.url)
16+
17+
const image = (
18+
<Image
19+
src={logoUrl}
20+
alt={logo.alt}
21+
fill
22+
unoptimized
23+
className="object-contain brightness-0 invert"
24+
sizes={sizes}
25+
/>
26+
)
27+
28+
return (
29+
<div className={cn("relative h-14", className)}>
30+
{href ? (
31+
<a href={href} className="relative block h-full w-full">
32+
{image}
33+
</a>
34+
) : image}
35+
</div>
36+
)
37+
}

src/components/ui/LogoMarquee.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"use client"
2+
3+
import type { Media } from "@/payload-types"
4+
import { LogoItem } from "@/components/ui/LogoItem"
5+
import { m as motion } from "motion/react"
6+
7+
interface LogoMarqueeProps {
8+
logos: Array<number | Media>
9+
duration?: number
10+
}
11+
12+
export function LogoMarquee({ logos, duration = 18 }: LogoMarqueeProps) {
13+
const marqueeLogos = [...logos, ...logos]
14+
if (logos.length === 0) return null
15+
16+
return (
17+
<div className="relative w-full overflow-hidden">
18+
<div aria-hidden="true" className="pointer-events-none absolute inset-y-0 left-0 z-10 w-12 bg-linear-to-r from-primary to-transparent" />
19+
<div aria-hidden="true" className="pointer-events-none absolute inset-y-0 right-0 z-10 w-12 bg-linear-to-l from-primary to-transparent" />
20+
<motion.div
21+
className="flex w-max gap-16"
22+
animate={{ x: ["0%", "-50%"] }}
23+
transition={{
24+
duration,
25+
ease: "linear",
26+
repeat: Infinity,
27+
}}
28+
>
29+
{marqueeLogos.map((item, index) => (
30+
<LogoItem
31+
key={`${(item as Media).id ?? (item as Media).url ?? index}-${index}`}
32+
logo={item}
33+
className="w-32 shrink-0 sm:w-40 md:w-40 lg:w-48"
34+
/>
35+
))}
36+
</motion.div>
37+
</div>
38+
)
39+
}

src/payload.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ export default buildConfig({
5151
importMap: {
5252
baseDir: path.resolve(dirname),
5353
},
54+
livePreview: {
55+
url: 'http://localhost:3000',
56+
collections: ['pages', 'jobs', 'actions', 'teamMembers', 'features', 'blog'],
57+
},
5458
},
5559
localization: {
5660
locales: ['en', 'de'],

0 commit comments

Comments
 (0)