返回 presentation-ai
1 "use client";
2
3 import { PlateElement, type PlateElementProps } from "platejs/react";
4 import { useId } from "react";
5
6 import { cn } from "@/lib/utils";
7 import { usePresentationState } from "@/states/presentation-state";
8 import { type TBoxGroupElement } from "../plugins/box-plugin";
9 import { columnSizeVariant, getDefaultColumnSize } from "../utils";
10 import {
11 ALTERNATING_BOX_COLUMN_GAP_PX,
12 ALTERNATING_BOX_MIN_ROW_HEIGHT_PX,
13 ALTERNATING_BOX_ROW_GAP_PX,
14 getAlternatingBoxPlacementCss,
15 useAlternatingBoxFitScale,
16 } from "./alternating-box-layout";
17 import { getDiagramFitFrameStyle } from "./diagram-fit";
18
19 function getBoxGroupJustifyClass(
20 alignment: TBoxGroupElement["alignment"] = "center",
21 ) {
22 if (alignment === "left") return "justify-start";
23 if (alignment === "right") return "justify-end";
24 return "justify-center";
25 }
26
27 export default function BoxGroup(props: PlateElementProps<TBoxGroupElement>) {
28 const isPresenting = usePresentationState((state) => state.isPresenting);
29 const alternatingLayoutId = useId();
30 const {
31 alignment = "center",
32 boxType = "solid",
33 orientation = "horizontal",
34 } = props.element;
35 const columnSize =
36 props.element.columnSize ??
37 getDefaultColumnSize(props.element.children.length);
38 const isAlternating = boxType === "alternating";
39 const alternatingItemCount = Math.max(props.element.children.length, 1);
40 const alternatingPlacementCss = getAlternatingBoxPlacementCss(
41 alternatingLayoutId,
42 alternatingItemCount,
43 orientation,
44 );
45 const { containerRef, fitStyle, frameStyle, layoutRef } =
46 useAlternatingBoxFitScale<HTMLDivElement>(
47 alternatingItemCount,
48 orientation,
49 );
50
51 if (isAlternating) {
52 return (
53 <PlateElement {...props} className="relative mb-4">
54 <div
55 ref={containerRef}
56 className={cn(
57 "flex w-full overflow-visible",
58 getBoxGroupJustifyClass(alignment),
59 )}
60 >
61 <div
62 style={{
63 ...frameStyle,
64 ...getDiagramFitFrameStyle(alignment),
65 }}
66 >
67 <style>{alternatingPlacementCss}</style>
68 <div ref={layoutRef} className="relative" style={fitStyle}>
69 <div
70 aria-hidden="true"
71 className="pointer-events-none absolute rounded-full"
72 data-decor="true"
73 style={{
74 background:
75 "color-mix(in oklch, var(--presentation-smart-layout, var(--presentation-primary)) 72%, transparent)",
76 boxShadow:
77 "0 0 0 1px color-mix(in oklch, var(--presentation-background) 65%, transparent)",
78 ...(orientation === "vertical"
79 ? {
80 bottom: 0,
81 left: "50%",
82 top: 0,
83 transform: "translateX(-50%)",
84 width: 2,
85 zIndex: 20,
86 }
87 : {
88 height: 2,
89 left: 0,
90 right: 0,
91 top: "50%",
92 transform: "translateY(-50%)",
93 zIndex: 20,
94 }),
95 }}
96 />
97 <div
98 className="relative z-10 grid items-stretch *:min-w-0"
99 data-alternating-box-layout={alternatingLayoutId}
100 style={{
101 columnGap: ALTERNATING_BOX_COLUMN_GAP_PX,
102 gridTemplateColumns:
103 orientation === "vertical"
104 ? "repeat(2, minmax(0, 1fr))"
105 : `repeat(${alternatingItemCount}, minmax(0, 1fr))`,
106 gridTemplateRows:
107 orientation === "vertical"
108 ? `repeat(${alternatingItemCount}, minmax(${ALTERNATING_BOX_MIN_ROW_HEIGHT_PX}px, auto))`
109 : `repeat(2, minmax(${ALTERNATING_BOX_MIN_ROW_HEIGHT_PX}px, 1fr))`,
110 rowGap: ALTERNATING_BOX_ROW_GAP_PX,
111 }}
112 >
113 {props.children}
114 </div>
115 </div>
116 </div>
117 </div>
118 </PlateElement>
119 );
120 }
121
122 return (
123 <PlateElement {...props} className="relative mb-4">
124 <div
125 className={cn(
126 "max-w-full",
127 isPresenting && "items-stretch *:min-w-0",
128 columnSizeVariant({ columnSize }),
129 boxType === "joined" || boxType === "joined-icon" ? "" : "gap-4",
130 )}
131 >
132 {props.children}
133 </div>
134 </PlateElement>
135 );
136 }
137
137 lines Plain Text