返回 presentation-ai
LayoutFloatingToolbarButtons.tsx
根目录 / src / components / presentation / floating-toolbar / LayoutFloatingToolbarButtons.tsx
1 "use client";
2
3 import { BlockSelectionPlugin } from "@platejs/selection/react";
4 import { TablePlugin } from "@platejs/table/react";
5 import { type DropdownMenuProps } from "@radix-ui/react-dropdown-menu";
6 import { KEYS, type NodeEntry, type TElement } from "platejs";
7 import { useEditorSelector, usePluginOption } from "platejs/react";
8
9 import {
10 BUTTON_ELEMENT,
11 CONTRIBUTOR_ELEMENT,
12 getLayoutChangeTargetEntry,
13 LABEL_ELEMENT,
14 PRESENTATION_TITLE_ELEMENT,
15 } from "@/components/notebook/presentation/editor/lib";
16 import { TooltipProvider } from "@/components/ui/tooltip";
17 // Sub-components
18 import { AlignmentControl } from "./AlignmentControl";
19 import { CalloutVariantControl } from "./CalloutVariantControl";
20 import { ChartControls } from "./ChartControls";
21 import { ColorControl } from "./ColorControl";
22 import { ColumnLayoutControls } from "./ColumnLayoutControls";
23 import { ColumnSizeSlider } from "./ColumnSizeSlider";
24 import {
25 CustomItemControls,
26 CustomItemDeleteButton,
27 } from "./CustomItemControls";
28 import { DeleteButton } from "./DeleteButton";
29 import { ElementTypeSelector } from "./ElementTypeSelector";
30 import { EmbedControls } from "./EmbedControls";
31 import { IconListMediaSizeSlider } from "./IconListMediaSizeSlider";
32 import { ImageControls } from "./ImageControls";
33 import { InfographicControls } from "./InfographicControls";
34 import { LayoutEditorButton } from "./LayoutPreviewSheet";
35 import { OrientationControl } from "./OrientationControl";
36 import { PresentationTableToolbarControls } from "./PresentationTableToolbarControls";
37 import { SidednessControl } from "./SidednessControl";
38 import { ToggleControls } from "./ToggleControls";
39 import { ToolbarProvider, useToolbarContext } from "./ToolbarContext";
40 import { VariantControl } from "./VariantControl";
41
42 const COMPACT_STANDALONE_ELEMENT_TYPES = new Set<string>([
43 BUTTON_ELEMENT,
44 CONTRIBUTOR_ELEMENT,
45 KEYS.callout,
46 LABEL_ELEMENT,
47 PRESENTATION_TITLE_ELEMENT,
48 ]);
49
50 function ToolbarContent() {
51 const {
52 editor,
53 element,
54 elementId,
55 handleLayoutChange,
56 isImageElement,
57 isInfographicElement,
58 isLayoutChildElement,
59 isMediaEmbedElement,
60 isCurrentElementChart,
61 } = useToolbarContext();
62 const isCompactStandaloneElement =
63 typeof element?.type === "string" &&
64 COMPACT_STANDALONE_ELEMENT_TYPES.has(element.type);
65 const selectedCells = usePluginOption(TablePlugin, "selectedCells");
66 const selectedIds = usePluginOption(BlockSelectionPlugin, "selectedIds");
67 const isSelectionInTable = useEditorSelector(
68 (currentEditor) => currentEditor.api.some({ match: { type: KEYS.table } }),
69 [],
70 );
71 const hasSelectedTableOrRow = Array.from(selectedIds ?? []).some(
72 (blockId) => {
73 const entry = editor.api.node({
74 at: [],
75 id: String(blockId),
76 }) as NodeEntry<TElement> | undefined;
77 const [selectedElement] = entry ?? [];
78
79 return (
80 selectedElement?.type === KEYS.table ||
81 selectedElement?.type === KEYS.tr
82 );
83 },
84 );
85 const isTableSelectionActive =
86 element?.type === KEYS.table ||
87 hasSelectedTableOrRow ||
88 isSelectionInTable ||
89 (selectedCells?.length ?? 0) > 0;
90 const layoutTargetEntry = getLayoutChangeTargetEntry(editor, elementId);
91 const [layoutTargetElement] = layoutTargetEntry ?? [];
92 const layoutTargetElementId =
93 typeof layoutTargetElement?.id === "string"
94 ? layoutTargetElement.id
95 : elementId;
96
97 if (isTableSelectionActive) {
98 return (
99 <TooltipProvider>
100 <div className="flex items-center gap-1">
101 <PresentationTableToolbarControls />
102 </div>
103 </TooltipProvider>
104 );
105 }
106
107 // For image elements, only show ImageControls
108 if (isImageElement) {
109 return (
110 <TooltipProvider>
111 <div className="flex items-center gap-1">
112 <ImageControls />
113 </div>
114 </TooltipProvider>
115 );
116 }
117
118 // For infographic elements, only show InfographicControls
119 if (isInfographicElement) {
120 return (
121 <TooltipProvider>
122 <div className="flex items-center gap-1">
123 <InfographicControls />
124 <DeleteButton />
125 </div>
126 </TooltipProvider>
127 );
128 }
129
130 if (isMediaEmbedElement) {
131 return (
132 <TooltipProvider>
133 <div className="flex items-center gap-1">
134 <EmbedControls />
135 <AlignmentControl />
136 </div>
137 </TooltipProvider>
138 );
139 }
140
141 if (isCompactStandaloneElement) {
142 return (
143 <TooltipProvider>
144 <div className="flex items-center gap-1">
145 {element?.type === KEYS.callout ? (
146 <CalloutVariantControl />
147 ) : (
148 <VariantControl />
149 )}
150 <ColorControl />
151 <AlignmentControl />
152 <DeleteButton />
153 </div>
154 </TooltipProvider>
155 );
156 }
157
158 if (isLayoutChildElement) {
159 return (
160 <TooltipProvider>
161 <div className="flex items-center gap-1">
162 <CustomItemControls />
163 <ColorControl />
164 <AlignmentControl />
165 <CustomItemDeleteButton />
166 </div>
167 </TooltipProvider>
168 );
169 }
170
171 // For other elements, show the full toolbar
172 return (
173 <TooltipProvider>
174 <div className="flex items-center gap-1">
175 <ElementTypeSelector />
176 {!isCurrentElementChart && (
177 <LayoutEditorButton
178 editorId={editor.id}
179 elementId={layoutTargetElementId}
180 element={
181 (layoutTargetElement as Record<string, unknown> | undefined) ??
182 element
183 }
184 onApplyLayout={handleLayoutChange}
185 />
186 )}
187 <OrientationControl />
188 <SidednessControl />
189 <VariantControl />
190 <ToggleControls />
191 <ColumnLayoutControls />
192 <ColumnSizeSlider />
193 <IconListMediaSizeSlider />
194 <ChartControls />
195 <ColorControl />
196 <AlignmentControl />
197 <DeleteButton />
198 </div>
199 </TooltipProvider>
200 );
201 }
202
203 export function LayoutFloatingToolbarButtons(_props: DropdownMenuProps) {
204 return (
205 <ToolbarProvider>
206 <ToolbarContent />
207 </ToolbarProvider>
208 );
209 }
210
210 lines Plain Text