Skip to content

Commit 9383c62

Browse files
committed
feat: flow example section init
1 parent 1ceac69 commit 9383c62

10 files changed

Lines changed: 14329 additions & 1 deletion

File tree

src/blocks/FlowExampleBlock.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { iconField } from "@mvriu5/payload-icon-picker"
2+
import type { Block, Field } from "payload"
3+
4+
function flowIconField(): Field {
5+
return iconField({
6+
name: "icon",
7+
label: "Icon",
8+
required: true,
9+
placeholder: "Search icons",
10+
noResultsLabel: "No icons found",
11+
admin: { position: "sidebar" },
12+
})
13+
}
14+
15+
export const FlowExampleBlock: Block = {
16+
slug: "flowExample",
17+
labels: {
18+
singular: "Flow Example",
19+
plural: "Flow Examples",
20+
},
21+
fields: [
22+
{
23+
type: "collapsible",
24+
label: "Section",
25+
fields: [
26+
{
27+
name: "sectionHeading",
28+
label: "Section Heading",
29+
type: "text",
30+
localized: true,
31+
},
32+
{
33+
name: "sectionLayout",
34+
label: "Flow Layout",
35+
type: "select",
36+
required: true,
37+
defaultValue: "flowCenter",
38+
options: [
39+
{ label: "Flow center", value: "flowCenter" },
40+
{ label: "Flow left", value: "flowLeft" },
41+
{ label: "Flow right", value: "flowRight" },
42+
],
43+
},
44+
{
45+
name: "sectionDescription",
46+
label: "Section Description",
47+
type: "textarea",
48+
localized: true,
49+
},
50+
{
51+
name: "sectionLinkButton",
52+
label: "Section Link Button",
53+
type: "group",
54+
fields: [
55+
{ name: "label", type: "text", localized: true },
56+
{ name: "url", type: "text" },
57+
],
58+
},
59+
],
60+
},
61+
{
62+
name: "contentHeading",
63+
label: "Content Heading",
64+
type: "text",
65+
localized: true,
66+
},
67+
{
68+
name: "contentDescription",
69+
label: "Content Description",
70+
type: "textarea",
71+
localized: true,
72+
},
73+
{
74+
name: "flow",
75+
type: "group",
76+
fields: [
77+
{
78+
name: "trigger",
79+
type: "group",
80+
fields: [
81+
flowIconField(),
82+
{
83+
name: "name",
84+
type: "text",
85+
required: true,
86+
localized: true,
87+
},
88+
],
89+
},
90+
{
91+
name: "items",
92+
label: "Flow items",
93+
type: "array",
94+
fields: [
95+
flowIconField(),
96+
{
97+
name: "text",
98+
type: "text",
99+
required: true,
100+
localized: true,
101+
},
102+
],
103+
},
104+
],
105+
},
106+
{
107+
name: "showBorder",
108+
label: "Show Border",
109+
type: "checkbox",
110+
defaultValue: false,
111+
},
112+
],
113+
}

src/collections/pages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { StandaloneBlock } from "@/blocks/StandaloneBlock"
2323
import { VideoBlock } from "@/blocks/VideoBlock"
2424
import { WideHeroBlock } from "@/blocks/WideHeroBlock"
2525
import { StatsBlock } from "@/blocks/StatsBlock"
26+
import { FlowExampleBlock } from "@/blocks/FlowExampleBlock"
2627

2728
export const Pages: CollectionConfig = {
2829
slug: "pages",
@@ -95,6 +96,7 @@ export const Pages: CollectionConfig = {
9596
WideHeroBlock,
9697
ListFeatureSection,
9798
StatsBlock,
99+
FlowExampleBlock,
98100
],
99101
required: false,
100102
localized: true,

src/components/PageBlockRenderer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { StandaloneCardSection } from "./sections/StandaloneCardSection"
2020
import { VideoSection } from "./sections/VideoSection"
2121
import { WideHeroSection } from "./sections/WideHeroSection"
2222
import { StatsSection } from "./sections/StatsSection"
23+
import { FlowExampleSection } from "./sections/FlowExampleSection"
2324

2425
type PageBlock = NonNullable<Page["layout"]>[number]
2526

@@ -53,6 +54,7 @@ const pageBlockRenderers: Partial<Record<PageBlock["blockType"], BlockRenderer>>
5354
widehero: (block) => <WideHeroSection content={block as Extract<PageBlock, { blockType: "widehero" }>} />,
5455
listFeature: (block) => <ListFeatureSection content={block as Extract<PageBlock, { blockType: "listFeature" }>} />,
5556
stats: (block) => <StatsSection content={block as Extract<PageBlock, { blockType: "stats" }>} />,
57+
flowExample: (block) => <FlowExampleSection content={block as Extract<PageBlock, { blockType: "flowExample" }>} />,
5658
}
5759

5860
function renderPageBlock(block: PageBlock, options: PageBlockRenderOptions) {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { getIcon } from "@/components/IconRenderer"
2+
import { StaggerContainer, StaggerItem } from "@/components/animations/Stagger"
3+
import { Card } from "@/components/ui/Card"
4+
import { Section } from "@/components/ui/Section"
5+
import type { FlowExampleLayoutBlock } from "@/lib/cms"
6+
import { cn } from "@/lib/utils"
7+
import { FlowExampleDiagram, type FlowDiagramNode } from "./client/FlowExampleDiagram"
8+
9+
interface FlowExampleSectionProps {
10+
content?: FlowExampleLayoutBlock | null
11+
}
12+
13+
export function FlowExampleSection({ content }: FlowExampleSectionProps) {
14+
const trigger = content?.flow?.trigger
15+
if (!content || !trigger?.name) return null
16+
17+
const triggerNode: FlowDiagramNode = {
18+
id: "trigger",
19+
icon: getIcon(trigger.icon, 20),
20+
text: trigger.name,
21+
}
22+
const items: FlowDiagramNode[] =
23+
content.flow?.items?.map((item, index) => ({
24+
id: String(item.id ?? index),
25+
icon: getIcon(item.icon, 20, item.id ?? index),
26+
text: item.text,
27+
})) ?? []
28+
const isCenter = content.sectionLayout === "flowCenter"
29+
const isRight = content.sectionLayout === "flowRight"
30+
const flow = (
31+
<div
32+
className={cn(
33+
"relative m-2 min-w-0 overflow-hidden rounded-2xl border border-white/10 bg-primary/40",
34+
isCenter ? "min-h-72" : "min-h-72 lg:min-h-96",
35+
isRight && "lg:order-2"
36+
)}
37+
>
38+
<FlowExampleDiagram trigger={triggerNode} items={items} />
39+
</div>
40+
)
41+
const body = (
42+
<StaggerContainer className={cn("flex flex-col justify-center p-8 md:p-10", isRight && "lg:order-1")} delayChildren={0.06} staggerChildren={0.08}>
43+
{content.contentHeading && (
44+
<StaggerItem as="h2" y={14} duration={0.38} className="text-3xl font-semibold leading-tight tracking-tight text-white md:text-4xl">
45+
{content.contentHeading}
46+
</StaggerItem>
47+
)}
48+
{content.contentDescription && (
49+
<StaggerItem as="p" y={14} duration={0.38} className="mt-4 max-w-2xl text-base leading-7 text-secondary md:text-lg">
50+
{content.contentDescription}
51+
</StaggerItem>
52+
)}
53+
</StaggerContainer>
54+
)
55+
56+
const example = <article className={cn("relative z-10 overflow-hidden", isCenter ? "flex flex-col" : "grid lg:grid-cols-2")}>{isCenter ? <>{flow}{(content.contentHeading || content.contentDescription) && body}</> : <>{flow}{body}</>}</article>
57+
58+
return (
59+
<Section
60+
heading={content.sectionHeading}
61+
description={content.sectionDescription}
62+
linkButton={content.sectionLinkButton}
63+
funnelType="center"
64+
animation={{ preset: "none" }}
65+
>
66+
<div className={cn("mx-auto w-full", isCenter && "max-w-5xl")}>
67+
{content.showBorder ? <Card size="lg" variant="light" className="p-2!">{example}</Card> : example}
68+
</div>
69+
</Section>
70+
)
71+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"use client"
2+
3+
import { Card, Flex, Text } from "@code0-tech/pictor"
4+
import { m as motion, useReducedMotion } from "motion/react"
5+
import type { ReactNode } from "react"
6+
7+
export interface FlowDiagramNode {
8+
icon: ReactNode
9+
text: string
10+
id: string
11+
}
12+
13+
function FlowNode({ node, trigger }: { node: FlowDiagramNode; trigger?: boolean }) {
14+
return (
15+
<Card paddingSize="xs" py="0.35" borderColor="info" color="primary" outline className="relative z-10 min-w-44 shrink-0">
16+
<Flex align="center" style={{ gap: "0.7rem" }}>
17+
<span className={trigger ? "shrink-0 text-brand" : "shrink-0 text-white"}>{node.icon}</span>
18+
<Flex align="center" wrap="wrap" className="text-secondary!" style={{ gap: "0.35rem" }}>
19+
<Text size="sm" style={{ color: "inherit" }}>
20+
{node.text}
21+
</Text>
22+
</Flex>
23+
</Flex>
24+
</Card>
25+
)
26+
}
27+
28+
export function FlowExampleDiagram({ trigger, items }: { trigger: FlowDiagramNode; items: FlowDiagramNode[] }) {
29+
const reducedMotion = useReducedMotion()
30+
const nodes = [trigger, ...items]
31+
32+
return (
33+
<motion.div
34+
className="flex h-full min-h-64 flex-col items-center justify-center overflow-hidden px-8 py-12"
35+
initial="hidden"
36+
whileInView="show"
37+
viewport={{ once: true, amount: 0.35 }}
38+
>
39+
{nodes.map((node, index) => (
40+
<div className="flex flex-col items-center" key={node.id}>
41+
{index > 0 && (
42+
<div className="relative h-12 w-px overflow-hidden bg-white/10 md:h-16" aria-hidden="true">
43+
<motion.div
44+
className="absolute inset-0 origin-top bg-brand"
45+
variants={{
46+
hidden: { scaleY: reducedMotion ? 1 : 0 },
47+
show: { scaleY: 1 },
48+
}}
49+
transition={{ duration: 0.55, delay: 0.22 + index * 0.2, ease: [0.22, 1, 0.36, 1] }}
50+
/>
51+
<motion.span
52+
className="absolute left-1/2 size-1.5 -translate-x-1/2 rounded-full bg-white"
53+
variants={{
54+
hidden: { top: "0%", opacity: 0 },
55+
show: { top: "100%", opacity: [0, 1, 0] },
56+
}}
57+
transition={{ duration: 0.55, delay: 0.22 + index * 0.2, ease: "easeInOut" }}
58+
/>
59+
</div>
60+
)}
61+
<motion.div
62+
variants={{
63+
hidden: { opacity: reducedMotion ? 1 : 0, y: reducedMotion ? 0 : 12 },
64+
show: { opacity: 1, y: 0 },
65+
}}
66+
transition={{ duration: 0.38, delay: index * 0.2, ease: [0.22, 1, 0.36, 1] }}
67+
>
68+
<FlowNode node={node} trigger={index === 0} />
69+
</motion.div>
70+
</div>
71+
))}
72+
</motion.div>
73+
)
74+
}

src/lib/cms.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type VideoLayoutBlock = Extract<PageLayoutBlock, { blockType: "video" }>
3333
export type WideHeroLayoutBlock = Extract<PageLayoutBlock, { blockType: "widehero" }>
3434
export type ListFeatureLayoutBlock = Extract<PageLayoutBlock, { blockType: "listFeature" }>
3535
export type StatsLayoutBlock = Extract<PageLayoutBlock, { blockType: "stats" }>
36+
export type FlowExampleLayoutBlock = Extract<PageLayoutBlock, { blockType: "flowExample" }>
3637

3738
type FeatureSlug = Feature["slug"]
3839
interface FeatureItem {

0 commit comments

Comments
 (0)