返回 presentation-ai
staircase-item-static.tsx
根目录 / src / components / notebook / presentation / editor / custom-elements / static / staircase-item-static.tsx
1 import { NodeApi, PathApi } from "platejs";
2 import { SlateElement, type SlateElementProps } from "platejs/static";
3 import { useLayoutEffect, useRef, useState } from "react";
4
5 import { cn } from "@/lib/utils";
6 import {
7 type TStairGroupElement,
8 type TStairItemElement,
9 } from "../../plugins/staircase-plugin";
10 import { getAlignmentClasses } from "../../utils";
11 import { PresentationIcon } from "../presentation-icon";
12
13 const STAIR_MIN_BLOCK_HEIGHT = 48;
14
15 export function StairItemStatic(props: SlateElementProps<TStairItemElement>) {
16 const path = props.editor.api.findPath(props.element) ?? [-1];
17 const parentPath = PathApi.parent(path);
18 const parentElement = NodeApi.get(
19 props.editor,
20 parentPath,
21 ) as TStairGroupElement;
22
23 const totalItems = parentElement?.children?.length || 1;
24 const index = (path?.at(-1) as number) ?? 0;
25 const alignment =
26 props.element.alignment ?? parentElement?.alignment ?? "left";
27 const { icon } = props.element;
28 const markerColor =
29 (parentElement?.color as string) || "var(--presentation-smart-layout)";
30
31 // Refs and state for dynamic height
32 const contentRef = useRef<HTMLDivElement>(null);
33 const [blockHeight, setBlockHeight] = useState(STAIR_MIN_BLOCK_HEIGHT);
34
35 // ResizeObserver to dynamically adjust height based on content height
36 useLayoutEffect(() => {
37 if (!contentRef.current) return;
38
39 const updateHeight = () => {
40 const contentHeight =
41 contentRef.current?.getBoundingClientRect().height ??
42 STAIR_MIN_BLOCK_HEIGHT;
43 const nextHeight = Math.max(
44 Math.ceil(contentHeight),
45 STAIR_MIN_BLOCK_HEIGHT,
46 );
47
48 setBlockHeight((currentHeight) =>
49 Math.abs(currentHeight - nextHeight) > 0.5 ? nextHeight : currentHeight,
50 );
51 };
52
53 updateHeight();
54 const resizeObserver = new ResizeObserver(updateHeight);
55 resizeObserver.observe(contentRef.current);
56
57 return () => resizeObserver.disconnect();
58 }, []);
59
60 // Calculate a width ramp similar to non-static design, but driven by totalItems
61 const baseWidth = 70;
62 const maxWidth = 220;
63 const increment = (maxWidth - baseWidth) / (totalItems - 1 || 1);
64 const widthPx = baseWidth + index * increment;
65
66 const variant = parentElement?.variant;
67 const isInside = variant === "inside";
68
69 // For inside variant, use percentage-based widths
70 const baseWidthPercent = 30;
71 const maxWidthPercent = 70;
72 const incrementPercent =
73 (maxWidthPercent - baseWidthPercent) / (totalItems - 1 || 1);
74 const widthPercent = baseWidthPercent + index * incrementPercent;
75
76 if (isInside) {
77 return (
78 <div className={cn("group/stair-item relative w-full")}>
79 <div
80 className={cn(
81 "flex w-full border-b border-gray-700",
82 alignment === "right" && "justify-end",
83 alignment !== "right" && "justify-start",
84 )}
85 >
86 <div
87 data-shape="rect"
88 data-shape-text={String(index + 1)}
89 data-fill-color={
90 (parentElement?.color as string) ||
91 "var(--presentation-smart-layout)"
92 }
93 data-text-color="var(--presentation-background)"
94 style={
95 {
96 width: `${widthPercent}%`,
97 backgroundColor: markerColor,
98 color: "var(--presentation-background)",
99 "--presentation-heading": "var(--presentation-card-background)",
100 "--presentation-text": "var(--presentation-card-background)",
101 } as React.CSSProperties
102 }
103 className="flex min-h-15 shrink-0 flex-col justify-center rounded-md px-4 py-3"
104 >
105 <SlateElement
106 ref={contentRef}
107 className="w-full font-normal"
108 {...props}
109 >
110 {props.children}
111 </SlateElement>
112 </div>
113 </div>
114 </div>
115 );
116 }
117
118 return (
119 <div className={cn("group/stair-item relative w-full")}>
120 <div
121 className={cn(
122 "flex items-center gap-4 border-b border-gray-700",
123 alignment === "right" && "flex-row-reverse",
124 )}
125 >
126 {/* Width-growing block with number */}
127 <div
128 data-shape="rect"
129 data-shape-text={String(index + 1)}
130 data-fill-color={
131 (parentElement?.color as string) ||
132 "var(--presentation-smart-layout)"
133 }
134 data-text-color="var(--presentation-background)"
135 style={{
136 width: `${widthPx}px`,
137 height: `${blockHeight}px`,
138 backgroundColor: markerColor,
139 color: "var(--presentation-background)",
140 }}
141 className="flex shrink-0 items-center justify-center rounded-md text-2xl font-bold"
142 >
143 {icon ? <PresentationIcon icon={icon} size={24} /> : index + 1}
144 </div>
145
146 <SlateElement
147 ref={contentRef}
148 className={cn(
149 "min-w-0 flex flex-1 flex-col justify-center self-center",
150 getAlignmentClasses(alignment),
151 )}
152 {...props}
153 >
154 {props.children}
155 </SlateElement>
156 </div>
157 </div>
158 );
159 }
160
160 lines Plain Text