返回 presentation-ai
cycle-element-static.tsx
根目录 / src / components / notebook / presentation / editor / custom-elements / static / cycle-element-static.tsx
1 import { type Path } from "platejs";
2 import {
3 ElementStatic,
4 SlateElement,
5 type SlateElementProps,
6 } from "platejs/static";
7 import { type ReactNode } from "react";
8
9 import { cn } from "@/lib/utils";
10 import { type TCycleItemElement } from "../../plugins/cycle-plugin";
11 import { CycleWheel } from "../cycle-element";
12 import {
13 CYCLE_WHEEL_SIZE_PX,
14 CycleContext,
15 useCycleFitScale,
16 type CycleColumnSide,
17 } from "../cycle-element";
18
19 function StaticCycleColumn({
20 children,
21 isMultiColumn,
22 side,
23 }: {
24 children: ReactNode[];
25 isMultiColumn: boolean;
26 side: CycleColumnSide;
27 }) {
28 const contextValue = { isMultiColumn, side };
29
30 if (children.length === 0) {
31 return <div className="min-w-0" aria-hidden="true" />;
32 }
33
34 return (
35 <CycleContext.Provider value={contextValue}>
36 <div
37 className={cn(
38 "flex min-w-0 flex-col justify-center",
39 isMultiColumn ? "h-full gap-4" : "gap-1",
40 )}
41 >
42 {children}
43 </div>
44 </CycleContext.Provider>
45 );
46 }
47
48 const EMPTY_DECORATE = () => [];
49
50 type StaticCycleItemEntry = {
51 item: TCycleItemElement;
52 path: Path;
53 };
54
55 function renderStaticCycleItems(
56 editor: SlateElementProps["editor"],
57 entries: StaticCycleItemEntry[],
58 ) {
59 return entries.map(({ item, path }, index) => {
60 const key =
61 typeof item.id === "string" || typeof item.id === "number"
62 ? item.id
63 : `cycle-item-${path.at(-1) ?? index}`;
64
65 return (
66 <div key={key} className="min-w-0">
67 <ElementStatic
68 decorate={EMPTY_DECORATE}
69 decorations={[]}
70 editor={editor}
71 element={item}
72 path={path}
73 />
74 </div>
75 );
76 });
77 }
78
79 export function CycleElementStatic(props: SlateElementProps) {
80 const element = props.element as {
81 alignment?: "left" | "center" | "right";
82 variant?: "cycle" | "flower" | "ring" | "circle";
83 children?: unknown[];
84 };
85 const { alignment = "center", variant = "cycle" } = element;
86 const cycleItems = (element.children ?? []) as TCycleItemElement[];
87 const cycleItemEntries = cycleItems.map((item, index) => ({
88 item,
89 path: [...props.path, index],
90 }));
91 const totalChildren = cycleItems.length;
92 const totalSegments = totalChildren > 0 ? totalChildren : 6;
93 const { containerRef, layoutRef, frameStyle, fitStyle } =
94 useCycleFitScale<HTMLDivElement>();
95 const leftChildren = renderStaticCycleItems(
96 props.editor,
97 cycleItemEntries.filter((_, index) => index % 2 === 0),
98 );
99 const rightChildren = renderStaticCycleItems(
100 props.editor,
101 cycleItemEntries.filter((_, index) => index % 2 === 1),
102 );
103
104 return (
105 <SlateElement {...props} className={cn("relative my-0", props.className)}>
106 <div
107 className={cn(
108 "flex w-full",
109 alignment === "left" && "justify-start",
110 alignment === "right" && "justify-end",
111 alignment === "center" && "justify-center",
112 )}
113 >
114 <div
115 ref={containerRef}
116 className="@container/cycle-layout mx-auto w-full"
117 >
118 <div className="mx-auto" style={frameStyle}>
119 <div
120 ref={layoutRef}
121 className="grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-stretch gap-4"
122 style={fitStyle}
123 >
124 <StaticCycleColumn isMultiColumn side="left">
125 {leftChildren}
126 </StaticCycleColumn>
127
128 <CycleWheel
129 items={cycleItems}
130 sizePx={CYCLE_WHEEL_SIZE_PX}
131 totalSegments={totalSegments}
132 variant={variant}
133 />
134
135 <StaticCycleColumn isMultiColumn side="right">
136 {rightChildren}
137 </StaticCycleColumn>
138 </div>
139 </div>
140 </div>
141 </div>
142 </SlateElement>
143 );
144 }
145
145 lines Plain Text