返回 presentation-ai
timeline-item-static.tsx
根目录 / src / components / notebook / presentation / editor / custom-elements / static / timeline-item-static.tsx
1 import { NodeApi, PathApi } from "platejs";
2 import { SlateElement, type SlateElementProps } from "platejs/static";
3
4 import { cn } from "@/lib/utils";
5 import {
6 type TTimelineGroupElement,
7 type TTimelineItemElement,
8 } from "../../plugins/timeline-plugin";
9 import { getAlignmentClasses } from "../../utils";
10 import { PresentationIcon } from "../presentation-icon";
11 import {
12 circleVariants,
13 connectorLineVariants,
14 containerVariants,
15 contentVariants,
16 } from "../timeline-item";
17
18 const HORIZONTAL_DOUBLE_CONTENT_CLASS =
19 "w-[min(22rem,calc(200%-2.5rem))] max-w-none px-3 text-center [&_h3]:mb-2 [&_h3]:text-base [&_p]:text-xs [&_p]:leading-snug";
20 const VERTICAL_DOUBLE_CONTENT_CLASS =
21 "relative z-10 min-h-40 max-w-full py-5 [&_h3]:mb-2 [&_h3]:text-base [&_p]:text-xs [&_p]:leading-snug";
22
23 export function TimelineItemStatic(props: SlateElementProps) {
24 const element = props.element as TTimelineItemElement;
25 const parentPath = PathApi.parent(
26 props.editor.api.findPath(props.element) ?? [-1],
27 );
28 const parentElement = NodeApi.get(
29 props.editor,
30 parentPath,
31 ) as TTimelineGroupElement;
32 const orientation = parentElement.orientation ?? "vertical";
33 const sidedness = parentElement.sidedness ?? "single";
34 const showLine = parentElement.showLine ?? true;
35 const numbered = parentElement.numbered ?? true;
36 const variant = parentElement.variant ?? "default";
37 const index =
38 (props.editor.api.findPath(props.element)?.at(-1) as number) ?? 0;
39 const totalItems = parentElement.children.length;
40 const itemNumber = index + 1;
41 const isEven =
42 orientation === "horizontal" && sidedness === "double"
43 ? index % 2 === 0
44 : itemNumber % 2 === 0;
45 const isFirstItem = index === 0;
46 const isLastItem = index === totalItems - 1;
47
48 const alignment = parentElement.alignment ?? "left";
49 const horizontalDoubleEdgeAlignmentClass =
50 sidedness === "double" && orientation === "horizontal"
51 ? isFirstItem
52 ? "justify-self-start"
53 : isLastItem
54 ? "justify-self-end"
55 : "justify-self-center"
56 : "";
57 const horizontalDoubleTextAlignmentClass =
58 sidedness === "double" && orientation === "horizontal"
59 ? isFirstItem
60 ? "items-start text-left [&>*]:self-start [&>*]:text-left"
61 : isLastItem
62 ? "items-end text-right [&>*]:self-end [&>*]:text-right"
63 : "items-center text-center [&>*]:self-center [&>*]:text-center"
64 : "";
65 const verticalDoubleOverlapClass =
66 sidedness === "double" && orientation === "vertical"
67 ? isFirstItem
68 ? "-mb-10 pb-10"
69 : isLastItem
70 ? "-mt-10 pt-10"
71 : "-my-10 py-10"
72 : "";
73 const verticalDoubleTextAlignmentClass =
74 sidedness === "double" && orientation === "vertical"
75 ? isEven
76 ? "items-start text-left [&>*]:self-start [&>*]:text-left"
77 : "items-end text-right [&>*]:self-end [&>*]:text-right"
78 : "";
79 const timelineColor =
80 (parentElement.color as string) || "var(--presentation-smart-layout)";
81 const timelineTextColor = "var(--presentation-background)";
82 const { icon } = element;
83 return (
84 //* Container
85 <div
86 className={cn(
87 containerVariants({
88 orientation,
89 sidedness,
90 isEven,
91 showLine,
92 alignment,
93 }),
94 sidedness === "single" &&
95 alignment === "left" &&
96 orientation === "horizontal" &&
97 "flex-col",
98 sidedness === "single" &&
99 alignment === "right" &&
100 orientation === "horizontal" &&
101 "flex-col-reverse",
102 sidedness === "single" &&
103 alignment === "left" &&
104 orientation === "vertical" &&
105 "flex-row",
106 sidedness === "single" &&
107 alignment === "right" &&
108 orientation === "vertical" &&
109 "flex-row-reverse",
110 )}
111 >
112 {/* Circle */}
113 <div
114 data-shape="ellipse"
115 data-shape-role="timeline-marker"
116 data-fill-color={timelineColor}
117 data-text-color={timelineTextColor}
118 data-shape-text={numbered ? String(itemNumber) : undefined}
119 className={cn(
120 circleVariants({ orientation, sidedness }),
121 sidedness === "single" && alignment === "right" && "rotate-180",
122 sidedness === "single" &&
123 alignment === "right" &&
124 orientation === "horizontal" &&
125 "translate-y-3",
126 sidedness === "double" &&
127 orientation === "horizontal" &&
128 "row-start-2",
129 )}
130 style={
131 {
132 backgroundColor: timelineColor,
133 color: timelineTextColor,
134 "--ring-color": timelineColor,
135 "--before-bg": timelineColor,
136 } as React.CSSProperties & {
137 "--ring-color": string;
138 "--before-bg": string;
139 }
140 }
141 >
142 {showLine ? (
143 <div
144 aria-hidden="true"
145 data-shape="rect"
146 data-shape-role="timeline-connector"
147 data-fill-color={timelineColor}
148 data-orientation={orientation}
149 className={cn(
150 connectorLineVariants({ orientation, sidedness, isEven }),
151 )}
152 />
153 ) : null}
154 <div
155 className={cn(
156 "relative z-10",
157 sidedness === "single" && alignment === "right" && "rotate-180",
158 )}
159 >
160 {icon ? (
161 <PresentationIcon icon={icon} size={18} />
162 ) : numbered ? (
163 itemNumber
164 ) : (
165 ""
166 )}
167 </div>
168 </div>
169 {/* Content */}
170 <SlateElement
171 className={cn(
172 "max-w-full",
173 contentVariants({ orientation, sidedness }),
174 getAlignmentClasses(alignment),
175 sidedness === "double" &&
176 orientation === "horizontal" &&
177 isEven &&
178 cn(
179 "row-start-1 justify-end pb-8",
180 horizontalDoubleEdgeAlignmentClass,
181 horizontalDoubleTextAlignmentClass,
182 HORIZONTAL_DOUBLE_CONTENT_CLASS,
183 ),
184 sidedness === "double" &&
185 orientation === "horizontal" &&
186 !isEven &&
187 cn(
188 "row-start-3 justify-start pt-8",
189 horizontalDoubleEdgeAlignmentClass,
190 horizontalDoubleTextAlignmentClass,
191 HORIZONTAL_DOUBLE_CONTENT_CLASS,
192 ),
193 sidedness === "double" &&
194 orientation === "vertical" &&
195 cn(
196 VERTICAL_DOUBLE_CONTENT_CLASS,
197 verticalDoubleOverlapClass,
198 verticalDoubleTextAlignmentClass,
199 ),
200 variant === "boxes" &&
201 "flex-1 w-full rounded-xl border bg-card p-4 text-card-foreground shadow-sm",
202 )}
203 {...props}
204 >
205 {props.children}
206 </SlateElement>
207 </div>
208 );
209 }
210
210 lines Plain Text