返回 presentation-ai
slope-item.tsx
1 "use client";
2
3 import { NodeApi, PathApi } from "platejs";
4 import {
5 PlateElement,
6 useReadOnly,
7 type PlateElementProps,
8 } from "platejs/react";
9
10 import { IconPicker } from "@/components/ui/icon-picker";
11 import { cn } from "@/lib/utils";
12 import {
13 type TSlopeGroupElement,
14 type TSlopeItemElement,
15 } from "../plugins/diagram-components-plugin";
16 import { getPresentationAccentColor } from "./color-utils";
17 import { PresentationIcon } from "./presentation-icon";
18 import { getSiblingIndexContext } from "./sibling-index";
19 import { getSmartLayoutStepColor } from "./smart-layout-gradient";
20
21 const SLOPE_MIN_HEIGHT_PX = 245;
22 const SLOPE_HEIGHT_STEP_PX = 42;
23
24 export function SlopeItem(props: PlateElementProps<TSlopeItemElement>) {
25 const readOnly = useReadOnly();
26 const { index, parentElement } = getSiblingIndexContext<TSlopeGroupElement>(
27 props.editor,
28 props.element,
29 props.path,
30 );
31 const fallbackParentPath = PathApi.parent(props.path);
32 const fallbackParentElement = NodeApi.get(
33 props.editor,
34 fallbackParentPath,
35 ) as TSlopeGroupElement | undefined;
36 const resolvedParentElement = parentElement ?? fallbackParentElement;
37 const total = resolvedParentElement?.children?.length || 1;
38 const alignment = props.element.alignment ?? "center";
39 const background = getPresentationAccentColor(
40 props.element,
41 resolvedParentElement,
42 getSmartLayoutStepColor(index, total),
43 );
44
45 const handleIconChange = (icon: string) => {
46 if (readOnly) return;
47 const itemPath = props.editor.api.findPath(props.element);
48 if (!itemPath) return;
49 props.editor.tf.setNodes({ icon }, { at: itemPath });
50 };
51
52 return (
53 <PlateElement
54 {...props}
55 className="group/slope-item relative flex h-full min-w-0 flex-1 flex-col items-center justify-start"
56 >
57 <div
58 className="relative flex w-full max-w-44 min-w-28 flex-1 flex-col"
59 style={{ marginTop: (total - 1 - index) * SLOPE_HEIGHT_STEP_PX }}
60 >
61 <div
62 className="relative flex w-full flex-1 flex-col items-center rounded-t-full px-5 pt-5 pb-8 text-center"
63 style={
64 {
65 background,
66 minHeight: SLOPE_MIN_HEIGHT_PX,
67 "--presentation-heading": "var(--presentation-card-background)",
68 "--presentation-text": "var(--presentation-card-background)",
69 } as React.CSSProperties
70 }
71 data-bg-export="true"
72 >
73 <div
74 className="mb-9 flex size-30 shrink-0 items-center justify-center rounded-full bg-(--presentation-background) text-(--presentation-muted-foreground)"
75 contentEditable={false}
76 data-decor="true"
77 data-slate-void="true"
78 >
79 {readOnly ? (
80 <PresentationIcon
81 icon={props.element.icon}
82 fallbackIcon="FaLightbulb"
83 size={96}
84 />
85 ) : (
86 <IconPicker
87 defaultIcon={props.element.icon}
88 hidePlaceholderWhenEmpty
89 onIconSelect={handleIconChange}
90 onIconRemove={() => handleIconChange("")}
91 className="size-24 rounded-full border-transparent bg-transparent text-(--presentation-muted-foreground) shadow-none hover:bg-black/5 [&_svg]:size-24!"
92 size="lg"
93 />
94 )}
95 </div>
96 <div
97 className={cn(
98 "w-full min-w-0 text-(--presentation-foreground) [&_h1]:text-2xl [&_h2]:text-2xl [&_h3]:text-2xl [&_h4]:text-xl [&_p]:mt-2 [&_p]:text-sm",
99 "[&_:is(.presentation-heading)]:[-webkit-background-clip:unset!important;]",
100 "[&_:is(.presentation-heading)]:[-webkit-text-fill-color:unset!important;]",
101 "[&_:is(.presentation-heading)]:[background-clip:unset!important;]",
102 "[&_:is(.presentation-heading)]:[background:none!important;]",
103 "[&_:is(.presentation-heading)]:text-(--presentation-text)!",
104 alignment === "left" && "text-left **:text-left",
105 alignment === "center" && "text-center **:text-center",
106 alignment === "right" && "text-right **:text-right",
107 )}
108 >
109 {props.children}
110 </div>
111 </div>
112 </div>
113 </PlateElement>
114 );
115 }
116
116 lines Plain Text