Skip to content

Commit 5d604f2

Browse files
committed
feat: improve exampleflow section
1 parent 9383c62 commit 5d604f2

10 files changed

Lines changed: 14406 additions & 139 deletions

src/blocks/FlowExampleBlock.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,49 @@ export const FlowExampleBlock: Block = {
9292
label: "Flow items",
9393
type: "array",
9494
fields: [
95-
flowIconField(),
9695
{
97-
name: "text",
98-
type: "text",
96+
name: "color",
97+
type: "select",
9998
required: true,
100-
localized: true,
99+
defaultValue: "brand",
100+
options: [
101+
{ label: "Brand", value: "brand" },
102+
{ label: "Yellow", value: "yellow" },
103+
{ label: "Aqua", value: "aqua" },
104+
{ label: "Blue", value: "blue" },
105+
{ label: "Pink", value: "pink" },
106+
],
107+
},
108+
{
109+
name: "outline",
110+
type: "checkbox",
111+
defaultValue: true,
112+
},
113+
{
114+
name: "segments",
115+
type: "array",
116+
required: true,
117+
minRows: 1,
118+
fields: [
119+
{
120+
name: "type",
121+
type: "select",
122+
required: true,
123+
defaultValue: "text",
124+
options: [
125+
{ label: "Text", value: "text" },
126+
{ label: "Literal", value: "literal" },
127+
{ label: "Reference", value: "reference" },
128+
{ label: "Node", value: "node" },
129+
],
130+
},
131+
{
132+
name: "value",
133+
type: "text",
134+
required: true,
135+
localized: true,
136+
},
137+
],
101138
},
102139
],
103140
},

src/components/animations/NodesAnimation.tsx

Lines changed: 3 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,8 @@
11
"use client"
22

3-
import { Card, Flex, Text } from "@code0-tech/pictor"
4-
import { IconNote, IconVariable } from "@tabler/icons-react"
3+
import { NodeDisplay, type NodeItem } from "@/components/nodes/NodeDisplay"
54
import { useInView, useReducedMotion } from "motion/react"
65
import { useEffect, useRef } from "react"
7-
import { StableBadge } from "../ui/StableBadge"
8-
9-
type NodeSegmentType = "text" | "literal" | "reference" | "node"
10-
type NodeAccent = "brand" | "yellow" | "aqua" | "blue" | "pink"
11-
12-
const ICON_COLOR_MAP: Record<NodeAccent, string> = {
13-
brand: "var(--bg-brand)",
14-
yellow: "var(--bg-yellow)",
15-
aqua: "var(--bg-aqua)",
16-
blue: "var(--bg-blue)",
17-
pink: "var(--bg-pink)",
18-
}
19-
20-
export interface NodeSegment {
21-
type: NodeSegmentType
22-
value: string
23-
}
24-
25-
export interface NodeItem {
26-
color: NodeAccent
27-
segments: NodeSegment[]
28-
outline: boolean
29-
}
30-
31-
function displayMessage(segments: NodeSegment[]) {
32-
return segments.map((segment, index) => {
33-
switch (segment.type) {
34-
case "literal":
35-
return (
36-
<StableBadge key={`${segment.type}-${segment.value}-${index}`} style={{ verticalAlign: "middle" }} color={"secondary"}>
37-
<Text size={"sm"} style={{ color: "white" }}>
38-
{segment.value}
39-
</Text>
40-
</StableBadge>
41-
)
42-
case "reference":
43-
return (
44-
<StableBadge key={`${segment.type}-${segment.value}-${index}`} style={{ verticalAlign: "middle" }} color={"warning"} border className="py-0">
45-
<IconVariable size={12} />
46-
<Text size={"sm"} style={{ color: "inherit" }}>
47-
{segment.value}
48-
</Text>
49-
</StableBadge>
50-
)
51-
case "node":
52-
return (
53-
<StableBadge key={`${segment.type}-${segment.value}-${index}`} style={{ verticalAlign: "middle", textWrap: "nowrap" }} border>
54-
<IconNote size={12} className="text-white" />
55-
<Text size={"sm"} style={{ color: "white" }}>
56-
{segment.value}
57-
</Text>
58-
</StableBadge>
59-
)
60-
case "text":
61-
return (
62-
<Text key={`${segment.type}-${index}`} size="sm" style={{ color: "inherit" }}>
63-
{segment.value}
64-
</Text>
65-
)
66-
}
67-
})
68-
}
696

707
function NodeRow({ nodes, direction, active }: { nodes: NodeItem[]; direction: "left" | "right"; active: boolean }) {
718
const animationRef = useRef<HTMLDivElement>(null)
@@ -105,26 +42,12 @@ function NodeRow({ nodes, direction, active }: { nodes: NodeItem[]; direction: "
10542
>
10643
<div ref={listRef} className="flex items-start gap-4">
10744
{nodes.map((node, index) => (
108-
<Card key={`${direction}-${node.color}-${index}`} paddingSize="xs" py="0.35" borderColor="info" color="primary" outline={node.outline}>
109-
<Flex align="center" style={{ gap: "0.7rem" }}>
110-
<IconNote color={ICON_COLOR_MAP[node.color]} size={16} className="shrink-0" />
111-
<Flex align="center" wrap="wrap" className="text-secondary!" style={{ gap: "0.35rem" }}>
112-
{displayMessage(node.segments)}
113-
</Flex>
114-
</Flex>
115-
</Card>
45+
<NodeDisplay key={`${direction}-${node.color}-${index}`} node={node} />
11646
))}
11747
</div>
11848
<div className="flex items-start gap-4" aria-hidden="true">
11949
{nodes.map((node, index) => (
120-
<Card key={`${direction}-clone-${node.color}-${index}`} paddingSize="xs" py="0.35" borderColor="info" color="primary" outline={node.outline}>
121-
<Flex align="center" style={{ gap: "0.7rem" }}>
122-
<IconNote color={ICON_COLOR_MAP[node.color]} size={16} className="shrink-0" />
123-
<Flex align="center" wrap="wrap" className="text-secondary!" style={{ gap: "0.35rem" }}>
124-
{displayMessage(node.segments)}
125-
</Flex>
126-
</Flex>
127-
</Card>
50+
<NodeDisplay key={`${direction}-clone-${node.color}-${index}`} node={node} />
12851
))}
12952
</div>
13053
</div>

src/components/blog/TriggerMarkdownBlock.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { NodesAnimation, type NodeItem, type NodeSegment } from "../animations/NodesAnimation"
1+
import { NodesAnimation } from "../animations/NodesAnimation"
2+
import type { NodeItem, NodeSegment } from "../nodes/NodeDisplay"
23

34
interface TriggerMarkdownBlockProps {
45
source: string
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"use client"
2+
3+
import { StableBadge } from "@/components/ui/StableBadge"
4+
import { Card, Flex, Text } from "@code0-tech/pictor"
5+
import { IconNote, IconVariable } from "@tabler/icons-react"
6+
7+
export type NodeSegmentType = "text" | "literal" | "reference" | "node"
8+
export type NodeAccent = "brand" | "yellow" | "aqua" | "blue" | "pink"
9+
10+
const ICON_COLOR_MAP: Record<NodeAccent, string> = {
11+
brand: "var(--bg-brand)",
12+
yellow: "var(--bg-yellow)",
13+
aqua: "var(--bg-aqua)",
14+
blue: "var(--bg-blue)",
15+
pink: "var(--bg-pink)",
16+
}
17+
18+
export function getNodeAccentColor(accent: NodeAccent) {
19+
return ICON_COLOR_MAP[accent]
20+
}
21+
22+
export interface NodeSegment {
23+
type: NodeSegmentType
24+
value: string
25+
}
26+
27+
export interface NodeItem {
28+
color: NodeAccent
29+
segments: NodeSegment[]
30+
outline: boolean
31+
}
32+
33+
export function NodeMessage({ segments }: { segments: NodeSegment[] }) {
34+
return segments.map((segment, index) => {
35+
switch (segment.type) {
36+
case "literal":
37+
return (
38+
<StableBadge key={`${segment.type}-${segment.value}-${index}`} style={{ verticalAlign: "middle" }} color="secondary">
39+
<Text size="sm" style={{ color: "white" }}>
40+
{segment.value}
41+
</Text>
42+
</StableBadge>
43+
)
44+
case "reference":
45+
return (
46+
<StableBadge key={`${segment.type}-${segment.value}-${index}`} style={{ verticalAlign: "middle" }} color="warning" border className="py-0">
47+
<IconVariable size={12} />
48+
<Text size="sm" style={{ color: "inherit" }}>
49+
{segment.value}
50+
</Text>
51+
</StableBadge>
52+
)
53+
case "node":
54+
return (
55+
<StableBadge key={`${segment.type}-${segment.value}-${index}`} style={{ verticalAlign: "middle", textWrap: "nowrap" }} border>
56+
<IconNote size={12} className="text-white" />
57+
<Text size="sm" style={{ color: "white" }}>
58+
{segment.value}
59+
</Text>
60+
</StableBadge>
61+
)
62+
case "text":
63+
return (
64+
<Text key={`${segment.type}-${index}`} size="sm" style={{ color: "inherit" }}>
65+
{segment.value}
66+
</Text>
67+
)
68+
}
69+
})
70+
}
71+
72+
export function NodeDisplay({ node }: { node: NodeItem }) {
73+
return (
74+
<Card paddingSize="xs" py="0.35" borderColor="info" color="primary" outline={node.outline}>
75+
<Flex align="center" style={{ gap: "0.7rem" }}>
76+
<IconNote color={ICON_COLOR_MAP[node.color]} size={16} className="shrink-0" />
77+
<Flex align="center" wrap="wrap" className="text-secondary!" style={{ gap: "0.35rem" }}>
78+
<NodeMessage segments={node.segments} />
79+
</Flex>
80+
</Flex>
81+
</Card>
82+
)
83+
}

src/components/sections/FlowExampleSection.tsx

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { getIcon } from "@/components/IconRenderer"
22
import { StaggerContainer, StaggerItem } from "@/components/animations/Stagger"
33
import { Card } from "@/components/ui/Card"
4+
import { DotBackground } from "@/components/ui/DotBackground"
45
import { Section } from "@/components/ui/Section"
56
import type { FlowExampleLayoutBlock } from "@/lib/cms"
67
import { cn } from "@/lib/utils"
7-
import { FlowExampleDiagram, type FlowDiagramNode } from "./client/FlowExampleDiagram"
8+
import type { NodeAccent, NodeSegmentType } from "@/components/nodes/NodeDisplay"
9+
import { FlowExampleDiagram, type FlowDiagramItem, type FlowDiagramNode } from "./client/FlowExampleDiagram"
810

911
interface FlowExampleSectionProps {
1012
content?: FlowExampleLayoutBlock | null
@@ -19,27 +21,45 @@ export function FlowExampleSection({ content }: FlowExampleSectionProps) {
1921
icon: getIcon(trigger.icon, 20),
2022
text: trigger.name,
2123
}
22-
const items: FlowDiagramNode[] =
24+
const items: FlowDiagramItem[] =
2325
content.flow?.items?.map((item, index) => ({
2426
id: String(item.id ?? index),
25-
icon: getIcon(item.icon, 20, item.id ?? index),
26-
text: item.text,
27+
node: {
28+
color: item.color as NodeAccent,
29+
outline: item.outline !== false,
30+
segments: item.segments.map((segment) => ({
31+
type: segment.type as NodeSegmentType,
32+
value: segment.value,
33+
})),
34+
},
2735
})) ?? []
2836
const isCenter = content.sectionLayout === "flowCenter"
2937
const isRight = content.sectionLayout === "flowRight"
38+
const showBorder = Boolean(content.showBorder)
3039
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-
>
40+
<div className={cn("relative m-2 min-w-0 overflow-hidden rounded-2xl border border-white/10 bg-light", isCenter ? "min-h-72" : "min-h-72 lg:min-h-96", isRight && "lg:order-2")}>
41+
<DotBackground
42+
className="opacity-50 mask-[radial-gradient(ellipse_at_center,black_35%,transparent_85%)]"
43+
dotColor="rgba(255,255,255,0.14)"
44+
dotSize={1}
45+
spacing={18}
46+
/>
3847
<FlowExampleDiagram trigger={triggerNode} items={items} />
3948
</div>
4049
)
4150
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}>
51+
<StaggerContainer
52+
className={cn(
53+
"flex flex-col justify-center",
54+
showBorder && "p-8 md:p-10",
55+
!showBorder && isCenter && "pt-8 md:pt-10",
56+
!showBorder && !isCenter && !isRight && "py-8 pl-8 md:py-10 md:pl-10",
57+
!showBorder && isRight && "py-8 pr-8 md:py-10 md:pr-10",
58+
isRight && "lg:order-1"
59+
)}
60+
delayChildren={0.06}
61+
staggerChildren={0.08}
62+
>
4363
{content.contentHeading && (
4464
<StaggerItem as="h2" y={14} duration={0.38} className="text-3xl font-semibold leading-tight tracking-tight text-white md:text-4xl">
4565
{content.contentHeading}
@@ -53,18 +73,32 @@ export function FlowExampleSection({ content }: FlowExampleSectionProps) {
5373
</StaggerContainer>
5474
)
5575

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>
76+
const example = (
77+
<article className={cn("relative z-10 overflow-hidden", isCenter ? "flex flex-col" : "grid lg:grid-cols-2")}>
78+
{isCenter ? (
79+
<>
80+
{flow}
81+
{(content.contentHeading || content.contentDescription) && body}
82+
</>
83+
) : (
84+
<>
85+
{flow}
86+
{body}
87+
</>
88+
)}
89+
</article>
90+
)
5791

5892
return (
59-
<Section
60-
heading={content.sectionHeading}
61-
description={content.sectionDescription}
62-
linkButton={content.sectionLinkButton}
63-
funnelType="center"
64-
animation={{ preset: "none" }}
65-
>
93+
<Section heading={content.sectionHeading} description={content.sectionDescription} linkButton={content.sectionLinkButton} funnelType="center" animation={{ preset: "none" }}>
6694
<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}
95+
{showBorder ? (
96+
<Card size="lg" variant="light" className="p-2!">
97+
{example}
98+
</Card>
99+
) : (
100+
example
101+
)}
68102
</div>
69103
</Section>
70104
)

0 commit comments

Comments
 (0)