返回 presentation-ai
box-item-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 {
6 type TBoxGroupElement,
7 type TBoxItemElement,
8 } from "../../plugins/box-plugin";
9 import { getAlignmentClasses, getDefaultColumnSize } from "../../utils";
10 import { boxItemVariants } from "../box-item";
11 import { PresentationIcon } from "../presentation-icon";
12
13 const ICON_BOX_TYPES = new Set(["icon", "top-circle", "joined-icon"]);
14 const PRIMARY_ACCENT_BOX_TYPES = new Set([
15 "outline",
16 "sideline",
17 "side-label",
18 "top-label",
19 "top-circle",
20 "joined",
21 "joined-icon",
22 "leaf",
23 "labeled",
24 ]);
25
26 export function BoxItemStatic(props: SlateElementProps<TBoxItemElement>) {
27 const { editor, element } = props;
28 const path = editor.api.findPath(element) ?? [-1];
29 const parentPath = PathApi.parent(path);
30 const parentElement = NodeApi.get(editor, parentPath) as TBoxGroupElement;
31
32 const boxType = parentElement?.boxType ?? "solid";
33 const columnSize =
34 parentElement?.columnSize ??
35 getDefaultColumnSize(parentElement?.children.length ?? 0);
36 const { icon } = element as unknown as { icon?: string };
37 const itemIndex = path[path.length - 1] ?? 0;
38 const isLastItem = parentElement?.children
39 ? itemIndex === parentElement.children.length - 1
40 : false;
41
42 const isEndOfRow =
43 (columnSize === "md" && (itemIndex + 1) % 3 === 0) ||
44 (columnSize === "lg" && (itemIndex + 1) % 2 === 0);
45 const isStartOfRow =
46 itemIndex > 0 &&
47 ((columnSize === "md" && itemIndex % 3 === 0) ||
48 (columnSize === "lg" && itemIndex % 2 === 0));
49 const accentColor =
50 (element.color as string | undefined) ??
51 (parentElement?.color as string | undefined) ??
52 "var(--presentation-primary)";
53 const shouldUseAccent = PRIMARY_ACCENT_BOX_TYPES.has(boxType);
54
55 // Get alignment - use item alignment if set, otherwise inherit from parent
56 const itemAlignment = element.alignment;
57 const parentAlignment = parentElement?.alignment;
58 const alignment = itemAlignment ?? parentAlignment ?? "left";
59
60 return (
61 <SlateElement {...props} className="@container/box-item self-stretch">
62 <div
63 className={cn(
64 boxItemVariants({
65 boxType,
66 }),
67 "grid size-full flex-1",
68 ICON_BOX_TYPES.has(boxType)
69 ? "grid-flow-row gap-2"
70 : "auto-cols-fr grid-flow-col gap-4",
71 boxType === "joined-icon" && "overflow-visible",
72 boxType === "joined-icon" &&
73 !isLastItem &&
74 !isEndOfRow &&
75 "pr-10! @[600px]/box-item:pr-4!",
76 boxType === "joined-icon" &&
77 !isLastItem &&
78 "@[600px]/box-item:pb-10!",
79 boxType === "joined-icon" &&
80 itemIndex > 0 &&
81 !isStartOfRow &&
82 "pl-10! @[600px]/box-item:pl-4!",
83 boxType === "joined-icon" &&
84 itemIndex > 0 &&
85 "@[600px]/box-item:pt-10!",
86 boxType === "labeled" && "relative",
87 boxType === "alternating" && "text-center",
88 "[&_:is(.presentation-heading)]:[-webkit-background-clip:unset!important;]",
89 "[&_:is(.presentation-heading)]:[-webkit-text-fill-color:unset!important;]",
90 "[&_:is(.presentation-heading)]:[background-clip:unset!important;]",
91 "[&_:is(.presentation-heading)]:[background:none!important;]",
92 "[&_:is(.presentation-heading)]:text-(--presentation-text)!",
93 )}
94 data-bg-export="true"
95 style={{
96 borderColor: accentColor,
97 color: shouldUseAccent ? accentColor : undefined,
98 borderRadius:
99 boxType !== "joined" &&
100 boxType !== "joined-icon" &&
101 boxType !== "leaf" &&
102 boxType !== "side-label" &&
103 boxType !== "top-label"
104 ? "var(--presentation-card-border-radius, 0.5rem)"
105 : undefined,
106 boxShadow:
107 boxType === "solid"
108 ? "var(--presentation-card-shadow, 0 1px 3px rgba(0,0,0,0.12))"
109 : undefined,
110 }}
111 >
112 {boxType === "labeled" ? (
113 <div
114 className="absolute top-0 right-0 left-0 flex h-12 items-center justify-center @[600px]/box-item:right-auto @[600px]/box-item:bottom-0 @[600px]/box-item:h-auto @[600px]/box-item:w-12 @[600px]/box-item:flex-col"
115 style={{ backgroundColor: accentColor }}
116 >
117 <div className="relative z-10 flex size-10 items-center justify-center text-lg font-bold text-white">
118 {icon ? (
119 <PresentationIcon icon={icon} size={20} />
120 ) : (
121 <span>{itemIndex + 1}</span>
122 )}
123 </div>
124 </div>
125 ) : null}
126 {boxType === "joined-icon" && !isLastItem ? (
127 <div
128 className={cn(
129 "absolute top-1/2 right-0 z-10 flex size-10 translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-md border-2 bg-(--presentation-background) @[600px]/box-item:top-auto @[600px]/box-item:right-1/2 @[600px]/box-item:bottom-0 @[600px]/box-item:translate-x-1/2 @[600px]/box-item:translate-y-1/2",
130 isEndOfRow && "hidden @[600px]/box-item:flex",
131 )}
132 style={{ borderColor: accentColor }}
133 >
134 {icon ? <PresentationIcon icon={icon} size={18} /> : null}
135 </div>
136 ) : null}
137 {ICON_BOX_TYPES.has(boxType) &&
138 boxType !== "joined-icon" &&
139 (icon || boxType === "top-circle") ? (
140 <div
141 className={cn(
142 "z-10 flex items-center justify-center",
143 boxType === "top-circle"
144 ? "absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border-2"
145 : "size-8 shadow-none",
146 )}
147 style={
148 boxType === "top-circle"
149 ? {
150 backgroundColor: accentColor,
151 borderColor: accentColor,
152 color: "var(--presentation-background)",
153 width: "2.75rem",
154 height: "2.75rem",
155 }
156 : {
157 backgroundColor: "transparent",
158 borderColor: "transparent",
159 }
160 }
161 >
162 {icon ? <PresentationIcon icon={icon} size={20} /> : null}
163 </div>
164 ) : null}
165 <div className={cn("w-full", getAlignmentClasses(alignment))}>
166 {props.children}
167 </div>
168 </div>
169 </SlateElement>
170 );
171 }
172
172 lines Plain Text