返回 presentation-ai
TablePanelPopover.tsx
根目录 / src / components / presentation / edit-panel / sections / TablePanelPopover.tsx
1 "use client";
2
3 import { Button } from "@/components/ui/button";
4 import {
5 Popover,
6 PopoverContent,
7 PopoverTrigger,
8 } from "@/components/ui/popover";
9 import { cn } from "@/lib/utils";
10 import { usePresentationState } from "@/states/presentation-state";
11 import { Table } from "lucide-react";
12 import { KEYS, type TElement } from "platejs";
13 import { useState } from "react";
14
15 // Helper function to create a table node with specified dimensions
16 const createTableNode = (rows: number, cols: number): TElement => {
17 const createParagraph = () => ({
18 type: KEYS.p,
19 children: [{ text: "" }],
20 });
21
22 const createCell = () => ({
23 type: KEYS.td,
24 children: [createParagraph()],
25 });
26
27 const createRow = () => ({
28 type: KEYS.tr,
29 children: Array.from({ length: cols }, createCell),
30 });
31
32 return {
33 type: KEYS.table,
34 children: Array.from({ length: rows }, createRow),
35 } as unknown as TElement;
36 };
37
38 export function TablePanelPopover() {
39 const [rows, setRows] = useState(0);
40 const [cols, setCols] = useState(0);
41 const [isOpen, setIsOpen] = useState(false);
42
43 // Access presentation state
44 const slides = usePresentationState((s) => s.slides);
45 const currentSlideId = usePresentationState((s) => s.currentSlideId);
46 const setSlides = usePresentationState((s) => s.setSlides);
47
48 const handleMouseEnter = (r: number, c: number) => {
49 setRows(r);
50 setCols(c);
51 };
52
53 const handleClick = () => {
54 if (rows === 0 || cols === 0) return;
55
56 // Create table node
57 const tableNode = createTableNode(rows, cols);
58
59 // Update slide content by ID
60 const updatedSlides = slides.map((slide) => {
61 if (slide.id === currentSlideId) {
62 return {
63 ...slide,
64 content: [...slide.content, tableNode],
65 };
66 }
67 return slide;
68 });
69
70 // Update slides state
71 setSlides(updatedSlides);
72
73 // Close popover
74 setIsOpen(false);
75 };
76
77 return (
78 <Popover open={isOpen} onOpenChange={setIsOpen}>
79 <PopoverTrigger asChild>
80 <Button size="icon" variant="ghost" className="size-12">
81 <Table className="h-5 w-5" />
82 </Button>
83 </PopoverTrigger>
84 <PopoverContent side="left" className="w-auto p-4" align="start">
85 <div className="flex flex-col gap-2">
86 <div className="text-sm font-medium text-muted-foreground">
87 {rows > 0 && cols > 0 ? `${cols}x${rows} Table` : "Insert Table"}
88 </div>
89 <div
90 className="grid gap-1"
91 style={{
92 gridTemplateColumns: "repeat(10, 1fr)",
93 }}
94 onMouseLeave={() => {
95 setRows(0);
96 setCols(0);
97 }}
98 >
99 {Array.from({ length: 100 }).map((_, i) => {
100 const r = Math.floor(i / 10) + 1;
101 const c = (i % 10) + 1;
102 const isSelected = r <= rows && c <= cols;
103
104 return (
105 <div
106 key={i}
107 className={cn(
108 "h-4 w-4 cursor-pointer rounded-sm border transition-colors",
109 isSelected
110 ? "border-primary bg-primary"
111 : "border-muted-foreground/20 bg-muted hover:border-primary/50",
112 )}
113 onMouseEnter={() => handleMouseEnter(r, c)}
114 onClick={handleClick}
115 />
116 );
117 })}
118 </div>
119 </div>
120 </PopoverContent>
121 </Popover>
122 );
123 }
124
124 lines Plain Text