返回 presentation-ai
insert-toolbar-button.tsx
根目录 / src / components / plate / ui / insert-toolbar-button.tsx
1 "use client";
2
3 import { type DropdownMenuProps } from "@radix-ui/react-dropdown-menu";
4 import {
5 CalendarIcon,
6 ChevronRightIcon,
7 Columns3Icon,
8 FileCodeIcon,
9 FilmIcon,
10 Heading1Icon,
11 Heading2Icon,
12 Heading3Icon,
13 ImageIcon,
14 Link2Icon,
15 ListIcon,
16 ListOrderedIcon,
17 MinusIcon,
18 PilcrowIcon,
19 PlusIcon,
20 QuoteIcon,
21 RadicalIcon,
22 SquareIcon,
23 TableIcon,
24 TableOfContentsIcon,
25 } from "lucide-react";
26 import { KEYS, type TElement } from "platejs";
27 import { useEditorRef, type PlateEditor } from "platejs/react";
28 import * as React from "react";
29
30 import { PRESENTATION_TITLE_ELEMENT } from "@/components/notebook/presentation/editor/lib";
31 import {
32 DropdownMenu,
33 DropdownMenuContent,
34 DropdownMenuItem,
35 DropdownMenuTrigger,
36 } from "@/components/plate/ui/dropdown-menu";
37 import {
38 insertBlock,
39 insertInlineElement,
40 } from "@/components/plate/utils/transforms";
41 import { ToolbarButton, ToolbarMenuGroup } from "./toolbar";
42
43 type Group = {
44 group: string;
45 items: Item[];
46 };
47
48 interface Item {
49 icon: React.ReactNode;
50 value: string;
51 onSelect: (editor: PlateEditor, value: string) => void;
52 focusEditor?: boolean;
53 label?: string;
54 nodeType?: string;
55 props?: Partial<TElement>;
56 }
57
58 const groups: Group[] = [
59 {
60 group: "Basic blocks",
61 items: [
62 {
63 icon: <Heading1Icon />,
64 label: "Title",
65 props: { variant: "title" },
66 value: PRESENTATION_TITLE_ELEMENT,
67 },
68 {
69 icon: <Heading2Icon />,
70 label: "Display",
71 props: { variant: "display" },
72 value: "presentation-title-display",
73 nodeType: PRESENTATION_TITLE_ELEMENT,
74 },
75 {
76 icon: <Heading3Icon />,
77 label: "Humongous",
78 props: { variant: "humongous" },
79 value: "presentation-title-humongous",
80 nodeType: PRESENTATION_TITLE_ELEMENT,
81 },
82 {
83 icon: <PilcrowIcon />,
84 label: "Paragraph",
85 value: KEYS.p,
86 },
87 {
88 icon: <Heading1Icon />,
89 label: "Heading 1",
90 value: "h1",
91 },
92 {
93 icon: <Heading2Icon />,
94 label: "Heading 2",
95 value: "h2",
96 },
97 {
98 icon: <Heading3Icon />,
99 label: "Heading 3",
100 value: "h3",
101 },
102 {
103 icon: <TableIcon />,
104 label: "Table",
105 value: KEYS.table,
106 },
107 {
108 icon: <FileCodeIcon />,
109 label: "Code",
110 value: KEYS.codeBlock,
111 },
112 {
113 icon: <QuoteIcon />,
114 label: "Quote",
115 value: KEYS.blockquote,
116 },
117 {
118 icon: <MinusIcon />,
119 label: "Divider",
120 value: KEYS.hr,
121 },
122 ].map((item) => ({
123 ...item,
124 onSelect: (editor) => {
125 insertBlock(editor, item.nodeType ?? item.value, {
126 props: item.props,
127 });
128 },
129 })),
130 },
131 {
132 group: "Lists",
133 items: [
134 {
135 icon: <ListIcon />,
136 label: "Bulleted list",
137 value: KEYS.ul,
138 },
139 {
140 icon: <ListOrderedIcon />,
141 label: "Numbered list",
142 value: KEYS.ol,
143 },
144 {
145 icon: <SquareIcon />,
146 label: "To-do list",
147 value: KEYS.listTodo,
148 },
149 {
150 icon: <ChevronRightIcon />,
151 label: "Toggle list",
152 value: KEYS.toggle,
153 },
154 ].map((item) => ({
155 ...item,
156 onSelect: (editor, value) => {
157 insertBlock(editor, value);
158 },
159 })),
160 },
161 {
162 group: "Media",
163 items: [
164 {
165 icon: <ImageIcon />,
166 label: "Image",
167 value: KEYS.img,
168 },
169 {
170 icon: <FilmIcon />,
171 label: "Embed",
172 value: KEYS.mediaEmbed,
173 },
174 ].map((item) => ({
175 ...item,
176 onSelect: (editor, value) => {
177 insertBlock(editor, value);
178 },
179 })),
180 },
181 {
182 group: "Advanced blocks",
183 items: [
184 {
185 icon: <TableOfContentsIcon />,
186 label: "Table of contents",
187 value: KEYS.toc,
188 },
189 {
190 icon: <Columns3Icon />,
191 label: "3 columns",
192 value: "action_three_columns",
193 },
194 {
195 focusEditor: false,
196 icon: <RadicalIcon />,
197 label: "Equation",
198 value: KEYS.equation,
199 },
200 ].map((item) => ({
201 ...item,
202 onSelect: (editor, value) => {
203 insertBlock(editor, value);
204 },
205 })),
206 },
207 {
208 group: "Inline",
209 items: [
210 {
211 icon: <Link2Icon />,
212 label: "Link",
213 value: KEYS.link,
214 },
215 {
216 focusEditor: true,
217 icon: <CalendarIcon />,
218 label: "Date",
219 value: KEYS.date,
220 },
221 {
222 focusEditor: false,
223 icon: <RadicalIcon />,
224 label: "Inline Equation",
225 value: KEYS.inlineEquation,
226 },
227 ].map((item) => ({
228 ...item,
229 onSelect: (editor, value) => {
230 insertInlineElement(editor, value);
231 },
232 })),
233 },
234 ];
235
236 export function InsertToolbarButton(props: DropdownMenuProps) {
237 const editor = useEditorRef();
238 const [open, setOpen] = React.useState(false);
239
240 return (
241 <DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>
242 <DropdownMenuTrigger asChild>
243 <ToolbarButton pressed={open} tooltip="Insert" isDropdown>
244 <PlusIcon />
245 </ToolbarButton>
246 </DropdownMenuTrigger>
247
248 <DropdownMenuContent
249 className="flex max-h-125 min-w-0 flex-col overflow-y-auto"
250 align="start"
251 >
252 {groups.map(({ group, items: nestedItems }) => (
253 <ToolbarMenuGroup key={group} label={group}>
254 {nestedItems.map(({ icon, label, value, onSelect }) => (
255 <DropdownMenuItem
256 key={value}
257 className="min-w-45"
258 onSelect={() => {
259 onSelect(editor, value);
260 editor.tf.focus();
261 }}
262 >
263 {icon}
264 {label}
265 </DropdownMenuItem>
266 ))}
267 </ToolbarMenuGroup>
268 ))}
269 </DropdownMenuContent>
270 </DropdownMenu>
271 );
272 }
273
273 lines Plain Text