返回 presentation-ai
circular-grid-item-static.tsx
根目录 / src / components / notebook / presentation / editor / custom-elements / static / circular-grid-item-static.tsx
1 "use client";
2
3 import { NodeApi, PathApi } from "platejs";
4 import { SlateElement, type SlateElementProps } from "platejs/static";
5
6 import { cn } from "@/lib/utils";
7 import { CIRCULAR_GRID_MAX_ITEMS } from "../../lib";
8 import { type TCircularGridGroupElement } from "../../plugins/diagram-components-plugin";
9 import {
10 getCircularGridItemIndex,
11 getCircularGridItemPosition,
12 getCircularGridItemSelfAlignment,
13 getCircularGridItemTransform,
14 getCircularGridPointerDirection,
15 isVisibleCircularGridItem,
16 useCircularGridLayoutContext,
17 type CircularGridPointerDirection,
18 } from "../circular-grid";
19 import { getSmartLayoutStepColor } from "../smart-layout-gradient";
20 import {
21 getStaticDiagramTextAlignClass,
22 type StaticDiagramElement,
23 } from "./diagram-static-utils";
24
25 /** Size of the rotated square that creates the speech-bubble triangle. */
26 const POINTER_SIZE = 16;
27 const POINTER_HALF = POINTER_SIZE / 2;
28 /** How far inward from the corner the pointer sits. */
29 const POINTER_EDGE_INSET = 20;
30
31 /**
32 * Returns absolute positioning CSS for the speech-bubble pointer
33 * based on which direction it should point.
34 */
35 function getPointerPositionStyle(
36 direction: CircularGridPointerDirection,
37 ): React.CSSProperties {
38 switch (direction) {
39 case "bottom":
40 return { bottom: -POINTER_HALF, left: "50%", marginLeft: -POINTER_HALF };
41 case "bottom-right":
42 return { bottom: -POINTER_HALF, right: POINTER_EDGE_INSET };
43 case "bottom-left":
44 return { bottom: -POINTER_HALF, left: POINTER_EDGE_INSET };
45 case "right":
46 return { right: -POINTER_HALF, top: "50%", marginTop: -POINTER_HALF };
47 case "left":
48 return { left: -POINTER_HALF, top: "50%", marginTop: -POINTER_HALF };
49 case "top-right":
50 return { top: -POINTER_HALF, right: POINTER_EDGE_INSET };
51 case "top-left":
52 return { top: -POINTER_HALF, left: POINTER_EDGE_INSET };
53 }
54 }
55
56 export function CircularGridItemStatic(props: SlateElementProps) {
57 const element = props.element as StaticDiagramElement;
58 const layoutContext = useCircularGridLayoutContext();
59 const path = props.path ?? props.editor.api.findPath(props.element) ?? [0];
60 const parentPath = PathApi.parent(path);
61 const parentElement = NodeApi.get(props.editor, parentPath) as
62 | TCircularGridGroupElement
63 | undefined;
64 const fallbackIndex = path.at(-1) ?? 0;
65 const resolvedParentElement = layoutContext?.parentElement ?? parentElement;
66 const index = getCircularGridItemIndex(
67 resolvedParentElement,
68 props.element as TCircularGridGroupElement["children"][number],
69 fallbackIndex,
70 );
71 const total = Math.min(
72 layoutContext?.total ?? resolvedParentElement?.children?.length ?? 1,
73 CIRCULAR_GRID_MAX_ITEMS,
74 );
75
76 const position = getCircularGridItemPosition(index, total);
77 const selfAlign = getCircularGridItemSelfAlignment(index, total);
78 const transform = getCircularGridItemTransform(index, total);
79 const pointerDir = getCircularGridPointerDirection(index, total);
80 const bgColor = getSmartLayoutStepColor(index, total);
81 const isVisible = isVisibleCircularGridItem(index);
82
83 return (
84 <SlateElement
85 {...props}
86 className={cn(
87 "relative z-10 min-h-22 min-w-40 overflow-visible",
88 !isVisible && "hidden",
89 )}
90 style={{
91 gridRow: position.gridRow,
92 gridColumn: position.gridColumn,
93 justifySelf: selfAlign.justifySelf,
94 alignSelf: selfAlign.alignSelf,
95 transform,
96 }}
97 >
98 {/* Speech-bubble pointer (sits behind content) */}
99 <div
100 className="absolute z-0 size-4 rotate-45"
101 style={{
102 background: bgColor,
103 ...getPointerPositionStyle(pointerDir),
104 }}
105 aria-hidden="true"
106 data-decor="true"
107 data-bg-export="true"
108 />
109 {/* Content box (above pointer so the inner half is hidden) */}
110 <div
111 className={cn(
112 "relative z-1 min-h-22 min-w-0 rounded-xl px-4 py-3 text-(--presentation-foreground)",
113 getStaticDiagramTextAlignClass(
114 element.alignment ??
115 layoutContext?.alignment ??
116 resolvedParentElement?.alignment,
117 ),
118 "[&_h1]:text-xl [&_h2]:text-xl [&_h3]:text-xl [&_h4]:text-lg [&_p]:mt-1 [&_p]:text-sm",
119 )}
120 style={
121 {
122 background: bgColor,
123 "--presentation-heading": "var(--presentation-card-background)",
124 "--presentation-text": "var(--presentation-card-background)",
125 } as React.CSSProperties
126 }
127 data-bg-export="true"
128 >
129 {props.children}
130 </div>
131 </SlateElement>
132 );
133 }
134
134 lines Plain Text