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