返回 presentation-ai
compare-side-static.tsx
根目录 / src / components / notebook / presentation / editor / custom-elements / static / compare-side-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 { type TCompareGroupElement } from "../../plugins/compare-plugin";
6 import {
7 getAlignmentClasses,
8 getDefaultColumnSize,
9 type PresentationColumnSize,
10 } from "../../utils";
11
12 export function CompareSideStatic(props: SlateElementProps) {
13 const path = props.editor.api.findPath(props.element) ?? [-1];
14 const parentPath = PathApi.parent(path);
15 const parentElement = NodeApi.get(props.editor, parentPath);
16
17 const { alignment = "left" } = parentElement as {
18 alignment?: "left" | "center" | "right";
19 color?: string;
20 columnSize?: "sm" | "md" | "lg" | "xl";
21 children?: unknown[];
22 };
23 const index = path.at(-1) ?? 0;
24 const childCount =
25 (parentElement as { children?: unknown[] })?.children?.length ?? 0;
26 const columnSize =
27 (parentElement as TCompareGroupElement | undefined)?.columnSize ??
28 getDefaultColumnSize(childCount);
29 const columns = getColumnCount(columnSize, childCount);
30 const isVertical = columnSize === "xl";
31 const showSeparator = shouldShowSeparator({
32 columns,
33 index,
34 itemCount: childCount,
35 });
36 const shouldSpanFullRow =
37 columns === 2 && childCount % columns === 1 && index === childCount - 1;
38 const accentColor =
39 (parentElement?.color as string) || "var(--presentation-primary)";
40
41 return (
42 <div
43 className={cn(
44 "relative flex h-full min-w-0 flex-1 items-stretch",
45 isVertical && "flex-col items-center gap-4",
46 )}
47 style={shouldSpanFullRow ? { gridColumn: "1 / -1" } : undefined}
48 >
49 <div
50 data-bg-export="true"
51 className={cn(
52 "grid h-full w-full rounded-xl border bg-card p-6 shadow-md",
53 "border-t-4",
54 )}
55 style={{
56 backgroundColor: "var(--presentation-background)",
57 color: "var(--presentation-text)",
58 borderColor: "hsl(var(--border))",
59 borderTopColor:
60 (parentElement?.color as string) || "var(--presentation-primary)",
61 }}
62 >
63 <SlateElement
64 {...props}
65 className={cn("h-full", getAlignmentClasses(alignment))}
66 >
67 {props.children}
68 </SlateElement>
69 </div>
70 {showSeparator ? (
71 <CompareSeparator accentColor={accentColor} isVertical={isVertical} />
72 ) : null}
73 </div>
74 );
75 }
76
77 function CompareSeparator({
78 accentColor,
79 isVertical,
80 }: {
81 accentColor: string;
82 isVertical: boolean;
83 }) {
84 return (
85 <div
86 className={cn(
87 "flex shrink-0 items-center justify-center self-center",
88 !isVertical && "absolute -right-9 top-1/2 z-10 -translate-y-1/2",
89 )}
90 aria-hidden
91 >
92 <div
93 data-shape="ellipse"
94 data-shape-text="VS"
95 data-fill-color={accentColor}
96 data-text-color="var(--presentation-background)"
97 className="grid size-12 place-items-center rounded-full text-sm font-bold shadow-xs"
98 style={{
99 backgroundColor: accentColor,
100 color: "var(--presentation-background)",
101 pointerEvents: "none",
102 }}
103 >
104 VS
105 </div>
106 </div>
107 );
108 }
109
110 function getColumnCount(
111 columnSize: TCompareGroupElement["columnSize"],
112 itemCount: number,
113 ) {
114 const columnSizeToColumns = {
115 lg: 2,
116 md: 3,
117 sm: 4,
118 xl: 1,
119 } satisfies Record<PresentationColumnSize, number>;
120 const maxColumns = columnSize
121 ? columnSizeToColumns[columnSize]
122 : columnSizeToColumns.md;
123
124 return Math.max(1, Math.min(itemCount, maxColumns));
125 }
126
127 function shouldShowSeparator({
128 columns,
129 index,
130 itemCount,
131 }: {
132 columns: number;
133 index: number;
134 itemCount: number;
135 }) {
136 const isLastItem = index === itemCount - 1;
137 const isRowEnd = (index + 1) % columns === 0;
138
139 return !isLastItem && (columns === 1 || !isRowEnd);
140 }
141
141 lines Plain Text