返回 presentation-ai
table-toolbar-button.tsx
根目录 / src / components / plate / ui / table-toolbar-button.tsx
1 "use client";
2
3 import { TablePlugin, useTableMergeState } from "@platejs/table/react";
4 import { type DropdownMenuProps } from "@radix-ui/react-dropdown-menu";
5 import {
6 ArrowDown,
7 ArrowLeft,
8 ArrowRight,
9 ArrowUp,
10 Combine,
11 Grid3x3Icon,
12 Table,
13 Trash2Icon,
14 Ungroup,
15 XIcon,
16 } from "lucide-react";
17 import { KEYS } from "platejs";
18 import { useEditorPlugin, useEditorSelector } from "platejs/react";
19 import * as React from "react";
20
21 import {
22 DropdownMenu,
23 DropdownMenuContent,
24 DropdownMenuGroup,
25 DropdownMenuItem,
26 DropdownMenuSub,
27 DropdownMenuSubContent,
28 DropdownMenuSubTrigger,
29 DropdownMenuTrigger,
30 } from "@/components/plate/ui/dropdown-menu";
31 import { cn } from "@/lib/utils";
32 import { ToolbarButton } from "./toolbar";
33
34 export function TableToolbarButton(props: DropdownMenuProps) {
35 const tableSelected = useEditorSelector(
36 (editor) => editor.api.some({ match: { type: KEYS.table } }),
37 [],
38 );
39
40 const { editor, tf } = useEditorPlugin(TablePlugin);
41 const [open, setOpen] = React.useState(false);
42 const mergeState = useTableMergeState();
43
44 return (
45 <DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>
46 <DropdownMenuTrigger asChild>
47 <ToolbarButton pressed={open} tooltip="Table" isDropdown>
48 <Table />
49 </ToolbarButton>
50 </DropdownMenuTrigger>
51
52 <DropdownMenuContent className="flex w-45 min-w-0 flex-col" align="start">
53 <DropdownMenuGroup>
54 <DropdownMenuSub>
55 <DropdownMenuSubTrigger className="gap-2 data-disabled:pointer-events-none data-disabled:opacity-50">
56 <Grid3x3Icon className="size-4" />
57 <span>Table</span>
58 </DropdownMenuSubTrigger>
59 <DropdownMenuSubContent className="m-0 p-0">
60 <TablePicker />
61 </DropdownMenuSubContent>
62 </DropdownMenuSub>
63
64 <DropdownMenuSub>
65 <DropdownMenuSubTrigger
66 className="gap-2 data-disabled:pointer-events-none data-disabled:opacity-50"
67 disabled={!tableSelected}
68 >
69 <div className="size-4" />
70 <span>Cell</span>
71 </DropdownMenuSubTrigger>
72 <DropdownMenuSubContent>
73 <DropdownMenuItem
74 className="min-w-45"
75 disabled={!mergeState.canMerge}
76 onSelect={() => {
77 tf.table.merge();
78 editor.tf.focus();
79 }}
80 >
81 <Combine />
82 Merge cells
83 </DropdownMenuItem>
84 <DropdownMenuItem
85 className="min-w-45"
86 disabled={!mergeState.canSplit}
87 onSelect={() => {
88 tf.table.split();
89 editor.tf.focus();
90 }}
91 >
92 <Ungroup />
93 Split cell
94 </DropdownMenuItem>
95 </DropdownMenuSubContent>
96 </DropdownMenuSub>
97
98 <DropdownMenuSub>
99 <DropdownMenuSubTrigger
100 className="gap-2 data-disabled:pointer-events-none data-disabled:opacity-50"
101 disabled={!tableSelected}
102 >
103 <div className="size-4" />
104 <span>Row</span>
105 </DropdownMenuSubTrigger>
106 <DropdownMenuSubContent>
107 <DropdownMenuItem
108 className="min-w-45"
109 disabled={!tableSelected}
110 onSelect={() => {
111 tf.insert.tableRow({ before: true });
112 editor.tf.focus();
113 }}
114 >
115 <ArrowUp />
116 Insert row before
117 </DropdownMenuItem>
118 <DropdownMenuItem
119 className="min-w-45"
120 disabled={!tableSelected}
121 onSelect={() => {
122 tf.insert.tableRow();
123 editor.tf.focus();
124 }}
125 >
126 <ArrowDown />
127 Insert row after
128 </DropdownMenuItem>
129 <DropdownMenuItem
130 className="min-w-45"
131 disabled={!tableSelected}
132 onSelect={() => {
133 tf.remove.tableRow();
134 editor.tf.focus();
135 }}
136 >
137 <XIcon />
138 Delete row
139 </DropdownMenuItem>
140 </DropdownMenuSubContent>
141 </DropdownMenuSub>
142
143 <DropdownMenuSub>
144 <DropdownMenuSubTrigger
145 className="gap-2 data-disabled:pointer-events-none data-disabled:opacity-50"
146 disabled={!tableSelected}
147 >
148 <div className="size-4" />
149 <span>Column</span>
150 </DropdownMenuSubTrigger>
151 <DropdownMenuSubContent>
152 <DropdownMenuItem
153 className="min-w-45"
154 disabled={!tableSelected}
155 onSelect={() => {
156 tf.insert.tableColumn({ before: true });
157 editor.tf.focus();
158 }}
159 >
160 <ArrowLeft />
161 Insert column before
162 </DropdownMenuItem>
163 <DropdownMenuItem
164 className="min-w-45"
165 disabled={!tableSelected}
166 onSelect={() => {
167 tf.insert.tableColumn();
168 editor.tf.focus();
169 }}
170 >
171 <ArrowRight />
172 Insert column after
173 </DropdownMenuItem>
174 <DropdownMenuItem
175 className="min-w-45"
176 disabled={!tableSelected}
177 onSelect={() => {
178 tf.remove.tableColumn();
179 editor.tf.focus();
180 }}
181 >
182 <XIcon />
183 Delete column
184 </DropdownMenuItem>
185 </DropdownMenuSubContent>
186 </DropdownMenuSub>
187
188 <DropdownMenuItem
189 className="min-w-45"
190 disabled={!tableSelected}
191 onSelect={() => {
192 tf.remove.table();
193 editor.tf.focus();
194 }}
195 >
196 <Trash2Icon />
197 Delete table
198 </DropdownMenuItem>
199 </DropdownMenuGroup>
200 </DropdownMenuContent>
201 </DropdownMenu>
202 );
203 }
204
205 function TablePicker() {
206 const { editor, tf } = useEditorPlugin(TablePlugin);
207
208 const [tablePicker, setTablePicker] = React.useState({
209 grid: Array.from({ length: 8 }, () => Array.from({ length: 8 }).fill(0)),
210 size: { colCount: 0, rowCount: 0 },
211 });
212
213 const onCellMove = (rowIndex: number, colIndex: number) => {
214 const newGrid = [...tablePicker.grid];
215
216 for (let i = 0; i < newGrid.length; i++) {
217 for (let j = 0; j < newGrid[i]!.length; j++) {
218 newGrid![i]![j] =
219 i >= 0 && i <= rowIndex && j >= 0 && j <= colIndex ? 1 : 0;
220 }
221 }
222
223 setTablePicker({
224 grid: newGrid,
225 size: { colCount: colIndex + 1, rowCount: rowIndex + 1 },
226 });
227 };
228
229 return (
230 <button
231 type="button"
232 className="m-0 flex! flex-col p-0"
233 onClick={() => {
234 tf.insert.table(tablePicker.size, { select: true });
235 editor.tf.focus();
236 }}
237 >
238 <div className="grid size-32.5 grid-cols-8 gap-0.5 p-1">
239 {tablePicker.grid.map((rows, rowIndex) =>
240 rows.map((value, columIndex) => {
241 return (
242 <div
243 key={`(${rowIndex},${columIndex})`}
244 className={cn(
245 "col-span-1 size-3 border border-solid bg-secondary",
246 !!value && "border-current",
247 )}
248 onMouseMove={() => {
249 onCellMove(rowIndex, columIndex);
250 }}
251 />
252 );
253 }),
254 )}
255 </div>
256
257 <div className="text-center text-xs text-current">
258 {tablePicker.size.rowCount} x {tablePicker.size.colCount}
259 </div>
260 </button>
261 );
262 }
263
263 lines Plain Text