返回 presentation-ai
steps-item.tsx
1 "use client";
2
3 import { NodeApi, PathApi } from "platejs";
4 import { PlateElement, type PlateElementProps } from "platejs/react";
5
6 import { type TStepsGroupElement } from "../plugins/steps-plugin";
7 import { getDefaultColumnSize } from "../utils";
8 import { getPresentationAccentColor } from "./color-utils";
9 import { getSiblingIndexContext } from "./sibling-index";
10
11 const columnSizeToColumns: Record<string, number> = {
12 sm: 4,
13 md: 3,
14 lg: 2,
15 xl: 1,
16 };
17
18 export function StepsItem(props: PlateElementProps) {
19 const { index, parentElement } = getSiblingIndexContext<TStepsGroupElement>(
20 props.editor,
21 props.element,
22 props.path,
23 );
24 const fallbackParentPath = PathApi.parent(props.path);
25 const fallbackParentElement = NodeApi.get(
26 props.editor,
27 fallbackParentPath,
28 ) as TStepsGroupElement | undefined;
29 const resolvedParent = parentElement ?? fallbackParentElement;
30
31 const { variant = "default" } = resolvedParent ?? {};
32 const columnSize =
33 resolvedParent?.columnSize ??
34 getDefaultColumnSize(resolvedParent?.children.length ?? 0);
35
36 const columns = columnSizeToColumns[columnSize as string] ?? 3;
37
38 const isVertical = columns === 1;
39
40 // For horizontal layout, the column index resets per row
41 const colIndex = isVertical ? index : index % columns;
42
43 const primaryColor = getPresentationAccentColor(
44 props.element,
45 resolvedParent,
46 "var(--presentation-smart-layout, var(--presentation-primary))",
47 );
48
49 if (!isVertical) {
50 if (variant === "default") {
51 // Horizontal default: top bar that climbs upward (staircase effect)
52 // Items align to top, margin top decreases per column to create upward step
53 const topMargin = Math.max(0, (columns - 1 - colIndex) * 2);
54 return (
55 <PlateElement
56 {...props}
57 className="relative my-0 flex h-full min-w-0 flex-col"
58 >
59 <div
60 className="flex flex-1 flex-col"
61 style={{ marginTop: `${topMargin}rem` }}
62 >
63 <div
64 className="mb-3 h-1.5 w-full shrink-0 rounded-full"
65 data-decor="true"
66 style={{ backgroundColor: primaryColor }}
67 />
68 <div className="flex-1">{props.children}</div>
69 </div>
70 </PlateElement>
71 );
72 }
73
74 if (variant === "arrow") {
75 // Horizontal arrow: arrow SVG on top, content below
76 const topMargin = Math.max(0, (columns - 1 - colIndex) * 2);
77 return (
78 <PlateElement
79 {...props}
80 className="relative my-0 flex h-full min-w-0 flex-col"
81 >
82 <div
83 className="flex flex-1 flex-col"
84 style={{ marginTop: `${topMargin}rem` }}
85 >
86 <div
87 className="mb-3 flex w-full shrink-0 items-center pr-4"
88 data-decor="true"
89 style={{ color: primaryColor }}
90 >
91 <div className="h-3 flex-1 bg-current" />
92 <svg
93 viewBox="0 0 16 24"
94 className="-ml-px h-6 w-4 shrink-0 fill-current"
95 >
96 <path d="M0 0l16 12-16 12z" />
97 </svg>
98 </div>
99 <div className="flex-1">{props.children}</div>
100 </div>
101 </PlateElement>
102 );
103 }
104
105 if (variant === "box") {
106 // Horizontal box: full border with left accent bar
107 const topMargin = Math.max(0, (columns - 1 - colIndex) * 2);
108 return (
109 <PlateElement
110 {...props}
111 className="relative my-0 flex h-full min-w-0 flex-col"
112 >
113 <div
114 className="flex flex-1 flex-col"
115 style={{ marginTop: `${topMargin}rem` }}
116 >
117 <div
118 className="flex-1 rounded-lg border p-4"
119 style={{
120 borderLeftWidth: "6px",
121 borderColor: primaryColor,
122 }}
123 >
124 <div>{props.children}</div>
125 </div>
126 </div>
127 </PlateElement>
128 );
129 }
130 }
131
132 // Vertical layout
133 if (variant === "default") {
134 // Vertical default: left bar with progressive indentation
135 return (
136 <PlateElement {...props} className="relative my-0 min-w-0">
137 <div
138 className="py-2 pl-4"
139 style={{
140 marginLeft: `${colIndex * 1.5}rem`,
141 borderLeftWidth: "6px",
142 borderLeftStyle: "solid",
143 borderLeftColor: primaryColor,
144 }}
145 >
146 <div>{props.children}</div>
147 </div>
148 </PlateElement>
149 );
150 }
151
152 if (variant === "arrow") {
153 // Vertical arrow: down arrow SVG with progressive indentation
154 return (
155 <PlateElement {...props} className="relative my-0 min-w-0">
156 <div
157 className="flex gap-4"
158 style={{ marginLeft: `${colIndex * 1.5}rem` }}
159 >
160 <div
161 className="flex shrink-0 flex-col items-center"
162 data-decor="true"
163 style={{ color: primaryColor }}
164 >
165 <div className="w-3 flex-1 bg-current" />
166 <svg
167 viewBox="0 0 24 16"
168 className="-mt-px h-4 w-6 shrink-0 fill-current"
169 >
170 <path d="M0 0l12 16 12-16z" />
171 </svg>
172 </div>
173 <div className="min-w-0 pb-4">{props.children}</div>
174 </div>
175 </PlateElement>
176 );
177 }
178
179 if (variant === "box") {
180 // Vertical box: full border with left accent bar, progressive indentation
181 return (
182 <PlateElement {...props} className="relative my-0 min-w-0">
183 <div
184 className="w-full rounded-lg border p-4"
185 style={{
186 marginLeft: `${colIndex * 1.5}rem`,
187 width: `calc(100% - ${colIndex * 1.5}rem)`,
188 borderLeftWidth: "6px",
189 borderLeftStyle: "solid",
190 borderColor: primaryColor,
191 }}
192 >
193 <div>{props.children}</div>
194 </div>
195 </PlateElement>
196 );
197 }
198
199 // Fallback
200 return (
201 <PlateElement {...props} className="relative my-0 min-w-0">
202 {props.children}
203 </PlateElement>
204 );
205 }
206
206 lines Plain Text