返回 presentation-ai
callout-node-static.tsx
根目录 / src / components / plate / ui / callout-node-static.tsx
1 "use client";
2
3 import { SlateElement, type SlateElementProps } from "platejs/static";
4
5 import { PresentationIcon } from "@/components/notebook/presentation/editor/custom-elements/presentation-icon";
6 import { cn } from "@/lib/utils";
7 import { CALLOUT_VARIANTS, getCalloutVariant } from "./callout-variants";
8
9 type StaticCalloutElementData = {
10 alignment?: unknown;
11 backgroundColor?: unknown;
12 color?: unknown;
13 icon?: unknown;
14 textColor?: unknown;
15 variant?: unknown;
16 };
17
18 function getStaticCalloutElementData(
19 element: unknown,
20 ): StaticCalloutElementData {
21 return typeof element === "object" && element !== null
22 ? (element as StaticCalloutElementData)
23 : {};
24 }
25
26 function isIconPickerName(value: unknown): value is string {
27 return typeof value === "string" && /^[A-Z][A-Za-z0-9]+$/.test(value.trim());
28 }
29
30 function getCalloutJustifyClass(alignment: unknown) {
31 if (alignment === "left") return "justify-start";
32 if (alignment === "right") return "justify-end";
33 return "justify-start";
34 }
35
36 function getCalloutTextAlignClass(alignment: unknown) {
37 if (alignment === "left") return "text-left";
38 if (alignment === "right") return "text-right";
39 return "text-left";
40 }
41
42 export function CalloutElementStatic({
43 children,
44 className,
45 ...props
46 }: SlateElementProps) {
47 const elementData = getStaticCalloutElementData(props.element);
48 const variant = getCalloutVariant(elementData.variant);
49 const variantConfig = CALLOUT_VARIANTS[variant];
50 const icon = isIconPickerName(elementData.icon)
51 ? elementData.icon
52 : variantConfig.icon;
53 const textColor =
54 typeof elementData.textColor === "string"
55 ? elementData.textColor
56 : typeof elementData.color === "string"
57 ? elementData.color
58 : undefined;
59 const backgroundColor =
60 typeof elementData.backgroundColor === "string"
61 ? elementData.backgroundColor
62 : variantConfig.backgroundColor;
63 const alignment = elementData.alignment;
64
65 return (
66 <SlateElement
67 className={cn(
68 "relative my-1 flex rounded-sm bg-muted p-4 pl-3",
69 getCalloutJustifyClass(alignment),
70 !textColor && variantConfig.textClassName,
71 "**:data-[slate-node='element']:text-current!",
72 className,
73 )}
74 style={{
75 backgroundColor,
76 color: textColor,
77 }}
78 {...props}
79 >
80 <div
81 className={cn(
82 "flex w-full gap-2 rounded-md",
83 getCalloutJustifyClass(alignment),
84 )}
85 >
86 <div
87 className={cn(
88 "size-6 shrink-0",
89 !textColor && variantConfig.textClassName,
90 )}
91 style={{ color: textColor }}
92 >
93 <PresentationIcon
94 icon={icon}
95 fallbackIcon={variantConfig.icon}
96 className="flex items-center justify-center"
97 size={18}
98 />
99 </div>
100 <div
101 className={cn(
102 "min-w-0 max-w-[calc(100%-2rem)]",
103 getCalloutTextAlignClass(alignment),
104 )}
105 >
106 {children}
107 </div>
108 </div>
109 </SlateElement>
110 );
111 }
112
112 lines Plain Text