diff --git a/src/components/TableBubbleMenu.tsx b/src/components/TableBubbleMenu.tsx new file mode 100644 index 0000000..79b4835 --- /dev/null +++ b/src/components/TableBubbleMenu.tsx @@ -0,0 +1,187 @@ +import { type Component, createSignal, createEffect, For } from "solid-js"; +import { + Trash2, + Type +} from "lucide-solid"; +import { cn } from "@/lib/utils"; +import type { Editor } from "@tiptap/core"; + +// Custom Icons +const AddRowAbove = () => ( + + + + +); + +const AddRowBelow = () => ( + + + + +); + +const AddColLeft = () => ( + + + + +); + +const AddColRight = () => ( + + + + +); + +const DeleteRow = () => ( + + + + +); + +const DeleteCol = () => ( + + + + +); + +const MergeCells = () => ( + + + + + + +); + +const SplitCell = () => ( + + + + + + +); + +interface TableAction { + label: string; + icon: Component; + command: () => void; + iconClass?: string; +} + +interface TableGroup { + name: string; + actions: TableAction[]; +} + +interface TableBubbleMenuProps { + editor: Editor; +} + +export const TableBubbleMenu: Component = (props) => { + const [isVisible, setIsVisible] = createSignal(false); + + createEffect(() => { + const updateVisibility = () => { + const editor = props.editor; + if (!editor) return; + setIsVisible(editor.isActive("table")); + }; + + props.editor.on("selectionUpdate", updateVisibility); + props.editor.on("transaction", updateVisibility); + + return () => { + props.editor.off("selectionUpdate", updateVisibility); + props.editor.off("transaction", updateVisibility); + }; + }); + + const groups: TableGroup[] = [ + { + name: "Rows", + actions: [ + { label: "Insert Row Above", icon: AddRowAbove, command: () => props.editor.chain().focus().addRowBefore().run() }, + { label: "Insert Row Below", icon: AddRowBelow, command: () => props.editor.chain().focus().addRowAfter().run() }, + { label: "Delete Row", icon: DeleteRow, iconClass: "text-destructive", command: () => props.editor.chain().focus().deleteRow().run() }, + ] + }, + { + name: "Cols", + actions: [ + { label: "Insert Column Left", icon: AddColLeft, command: () => props.editor.chain().focus().addColumnBefore().run() }, + { label: "Insert Column Right", icon: AddColRight, command: () => props.editor.chain().focus().addColumnAfter().run() }, + { label: "Delete Column", icon: DeleteCol, iconClass: "text-destructive", command: () => props.editor.chain().focus().deleteColumn().run() }, + ] + }, + { + name: "Cells", + actions: [ + { label: "Merge Cells", icon: MergeCells, command: () => props.editor.chain().focus().mergeCells().run() }, + { label: "Split Cell", icon: SplitCell, command: () => props.editor.chain().focus().splitCell().run() }, + { label: "Header Row", icon: Type, command: () => props.editor.chain().focus().toggleHeaderRow().run() }, + ] + }, + { + name: "Table", + actions: [ + { label: "Delete Table", icon: Trash2, iconClass: "text-destructive", command: () => props.editor.chain().focus().deleteTable().run() }, + ] + } + ]; + + return ( +
{ + e.preventDefault(); + e.stopPropagation(); + }} + > +
+
+ + {(group, groupIdx) => ( + <> +
+ + {(action) => ( + + )} + +
+ {groupIdx() < groups.length - 1 && ( +
+ )} + + )} + +
+
+
+ ); +}; diff --git a/src/components/TaskEditor.tsx b/src/components/TaskEditor.tsx index 849d245..fc7b6ba 100644 --- a/src/components/TaskEditor.tsx +++ b/src/components/TaskEditor.tsx @@ -35,6 +35,7 @@ import { Video } from "@/lib/extensions/video"; import { VideoUpload } from "@/lib/extensions/video-upload"; import { FileAttachment } from "@/lib/extensions/file-attachment"; import { FileUpload } from "@/lib/extensions/file-upload"; +import { TableBubbleMenu } from "./TableBubbleMenu"; interface TaskEditorProps { content?: string; @@ -335,6 +336,9 @@ export const TaskEditor: Component = (props) => { Click to insert newline above
+ + {(e) => } +