Skip to content

Commit a009954

Browse files
committed
feat: standalone card section
1 parent 16da754 commit a009954

5 files changed

Lines changed: 258 additions & 0 deletions

File tree

src/blocks/StandaloneBlock.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import type { Block } from "payload"
2+
3+
export const StandaloneBlock: Block = {
4+
slug: "standaloneCard",
5+
labels: {
6+
singular: "Standalone Card",
7+
plural: "Standalone Card Blocks",
8+
},
9+
fields: [
10+
{
11+
name: "title",
12+
type: "text",
13+
required: true,
14+
localized: true,
15+
},
16+
{
17+
name: "description",
18+
type: "textarea",
19+
required: false,
20+
localized: true,
21+
},
22+
{
23+
name: "showImageBorder",
24+
label: "Show Image Border",
25+
type: "checkbox",
26+
defaultValue: true,
27+
},
28+
{
29+
name: "sectionLayout",
30+
label: "Section Layout",
31+
type: "select",
32+
required: true,
33+
defaultValue: "imageRight",
34+
options: [
35+
{
36+
label: "Image right",
37+
value: "imageRight",
38+
},
39+
{
40+
label: "Image left",
41+
value: "imageLeft",
42+
},
43+
{
44+
label: "Image fullscreen",
45+
value: "imageFullscreen",
46+
},
47+
],
48+
},
49+
{
50+
name: "gradient",
51+
label: "Gradient",
52+
type: "select",
53+
required: false,
54+
defaultValue: "blue",
55+
options: [
56+
{
57+
label: "Blue",
58+
value: "blue",
59+
},
60+
{
61+
label: "Yellow",
62+
value: "yellow",
63+
},
64+
{
65+
label: "Pink",
66+
value: "pink",
67+
},
68+
{
69+
label: "Aqua",
70+
value: "aqua",
71+
},
72+
{
73+
label: "Brand",
74+
value: "brand",
75+
},
76+
{
77+
label: "Neutral",
78+
value: "neutral",
79+
},
80+
],
81+
},
82+
{
83+
name: "gradientDirection",
84+
label: "Gradient Direction",
85+
type: "select",
86+
required: false,
87+
defaultValue: "topLeft",
88+
options: [
89+
{
90+
label: "Top left",
91+
value: "topLeft",
92+
},
93+
{
94+
label: "Top right",
95+
value: "topRight",
96+
},
97+
{
98+
label: "Bottom left",
99+
value: "bottomLeft",
100+
},
101+
{
102+
label: "Bottom right",
103+
value: "bottomRight",
104+
},
105+
],
106+
},
107+
{
108+
name: "bulletPoints",
109+
label: "Bullet Points",
110+
type: "text",
111+
required: false,
112+
hasMany: true,
113+
localized: true,
114+
},
115+
{
116+
name: "image",
117+
label: "Image",
118+
type: "upload",
119+
relationTo: "media",
120+
required: false,
121+
},
122+
{
123+
name: "link",
124+
label: "Link",
125+
type: "group",
126+
fields: [
127+
{
128+
name: "label",
129+
type: "text",
130+
required: false,
131+
localized: true,
132+
},
133+
{
134+
name: "url",
135+
type: "text",
136+
required: false,
137+
},
138+
],
139+
},
140+
],
141+
}

src/collections/pages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { OffsetCardsBlock } from "../blocks/OffsetCardsBlock"
1515
import { RoadmapBlock } from "../blocks/RoadmapBlock"
1616
import { ScrollCardBlock } from "../blocks/ScrollCardBlock"
1717
import type { CollectionConfig } from "payload"
18+
import { StandaloneBlock } from "@/blocks/StandaloneBlock"
1819

1920
export const Pages: CollectionConfig = {
2021
slug: "pages",
@@ -79,6 +80,7 @@ export const Pages: CollectionConfig = {
7980
CardRowBlock,
8081
RoadmapBlock,
8182
ScrollCardBlock,
83+
StandaloneBlock,
8284
],
8385
required: false,
8486
localized: true,

src/components/PageBlockRenderer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { SwipeCardSection } from "@/components/sections/SwipeCardSection"
1212
import type { AppLocale } from "@/lib/i18n"
1313
import type { Page } from "@/payload-types"
1414
import React, { type ReactNode } from "react"
15+
import { StandaloneCardSection } from "./sections/StandaloneCardSection"
1516

1617
type PageBlock = NonNullable<Page["layout"]>[number]
1718

@@ -37,6 +38,7 @@ const pageBlockRenderers: Partial<Record<PageBlock["blockType"], BlockRenderer>>
3738
roadmap: (block) => <RoadmapSection content={block as Extract<PageBlock, { blockType: "roadmap" }>} />,
3839
scrollCards: (block) => <ScrollCardSection content={block as Extract<PageBlock, { blockType: "scrollCards" }>} />,
3940
swipeCards: (block) => <SwipeCardSection content={block as Extract<PageBlock, { blockType: "swipeCards" }>} />,
41+
standaloneCard: (block) => <StandaloneCardSection content={block as Extract<PageBlock, { blockType: "standaloneCard" }>} />,
4042
}
4143

4244
function renderPageBlock(block: PageBlock, options: PageBlockRenderOptions) {
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"use client"
2+
3+
import { LinkButton } from "@/components/ui/LinkButton"
4+
import { Section } from "@/components/ui/Section"
5+
import { getMediaUrl } from "@/lib/media"
6+
import { cn } from "@/lib/utils"
7+
import type { Media } from "@/payload-types"
8+
import Image from "next/image"
9+
import { Card } from "../ui/Card"
10+
11+
interface StandaloneCardSectionProps {
12+
content?: any | null
13+
}
14+
15+
function getImage(image: number | Media | null | undefined) {
16+
return typeof image === "object" ? image : null
17+
}
18+
19+
export function StandaloneCardSection({ content }: StandaloneCardSectionProps) {
20+
if (!content || !content.title) return null
21+
22+
const image = getImage(content.image)
23+
const imageUrl = getMediaUrl(image?.url)
24+
const itemSettings = content as {
25+
sectionLayout?: "imageRight" | "imageLeft" | "imageFullscreen" | null
26+
showImageBorder?: boolean | null
27+
gradient?: string | null
28+
gradientDirection?: string | null
29+
}
30+
const isImageLeft = itemSettings.sectionLayout === "imageLeft"
31+
const isFullscreen = itemSettings.sectionLayout === "imageFullscreen"
32+
const showImageBorder = itemSettings.showImageBorder ?? true
33+
34+
return (
35+
<Section showFunnel={false} animation={{ preset: "none" }} className="h-[calc(100vh-6rem)]">
36+
<Card
37+
size="lg"
38+
gradientDirection={itemSettings.gradientDirection as any}
39+
radialGradient={itemSettings.gradient as any}
40+
className={cn(isFullscreen ? "relative h-[80%] overflow-hidden bg-primary p-0" : "relative grid h-[80%] overflow-hidden bg-primary p-12 md:grid-cols-[0.95fr_1.05fr]")}
41+
>
42+
{isFullscreen ? (
43+
<div className={cn("relative z-10 h-full w-full overflow-hidden rounded-3xl", showImageBorder && "border border-white/10")}>
44+
{imageUrl && <Image src={imageUrl} alt={image?.alt ?? content.title} fill sizes="100vw" className="object-cover object-center" />}
45+
</div>
46+
) : (
47+
<>
48+
<div className={cn("relative z-10 flex h-full flex-col justify-center gap-8 rounded-3xl", isImageLeft && "md:order-2")}>
49+
<div className="flex flex-col gap-4">
50+
<h2 className="max-w-xl text-3xl font-semibold text-white md:text-5xl">{content.title}</h2>
51+
<p className="max-w-xl text-base leading-7 text-white/75 md:text-lg">{content.description}</p>
52+
</div>
53+
54+
<div className="space-y-6">
55+
<ul className="grid gap-2 text-sm text-white/75 md:text-base">
56+
{content.bulletPoints?.map((point: any, pointIndex: number) => (
57+
<li key={`point-${pointIndex}`} className="flex items-start gap-3">
58+
<span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-brand" />
59+
<span>{point}</span>
60+
</li>
61+
))}
62+
</ul>
63+
64+
{content.link?.label && content.link?.url && <LinkButton href={content.link.url}>{content.link.label}</LinkButton>}
65+
</div>
66+
</div>
67+
68+
<div className={cn("relative z-10 aspect-video w-full self-center overflow-hidden rounded-2xl", showImageBorder && "border border-white/10", isImageLeft && "md:order-1")}>
69+
{imageUrl && <Image src={imageUrl} alt={image?.alt ?? content.title} fill sizes="(min-width: 768px) 50vw, 100vw" className="object-contain object-center" />}
70+
</div>
71+
</>
72+
)}
73+
</Card>
74+
</Section>
75+
)
76+
}

src/payload-types.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,23 @@ export interface Page {
496496
blockName?: string | null;
497497
blockType: 'scrollCards';
498498
}
499+
| {
500+
title: string;
501+
description?: string | null;
502+
showImageBorder?: boolean | null;
503+
sectionLayout: 'imageRight' | 'imageLeft' | 'imageFullscreen';
504+
gradient?: ('blue' | 'yellow' | 'pink' | 'aqua' | 'brand' | 'neutral') | null;
505+
gradientDirection?: ('topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight') | null;
506+
bulletPoints?: string[] | null;
507+
image?: (number | null) | Media;
508+
link?: {
509+
label?: string | null;
510+
url?: string | null;
511+
};
512+
id?: string | null;
513+
blockName?: string | null;
514+
blockType: 'standaloneCard';
515+
}
499516
)[]
500517
| null;
501518
meta?: {
@@ -1321,6 +1338,26 @@ export interface PagesSelect<T extends boolean = true> {
13211338
id?: T;
13221339
blockName?: T;
13231340
};
1341+
standaloneCard?:
1342+
| T
1343+
| {
1344+
title?: T;
1345+
description?: T;
1346+
showImageBorder?: T;
1347+
sectionLayout?: T;
1348+
gradient?: T;
1349+
gradientDirection?: T;
1350+
bulletPoints?: T;
1351+
image?: T;
1352+
link?:
1353+
| T
1354+
| {
1355+
label?: T;
1356+
url?: T;
1357+
};
1358+
id?: T;
1359+
blockName?: T;
1360+
};
13241361
};
13251362
meta?:
13261363
| T

0 commit comments

Comments
 (0)