import { BubbleMenu, BubbleMenuProps } from "@tiptap/react"; import { FC, useState } from "react"; import { BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, CodeIcon } from "lucide-react"; import { NodeSelector } from "./node-selector"; import { LinkSelector } from "./link-selector"; import { cn } from "../utils"; export interface BubbleMenuItem { name: string; isActive: () => boolean; command: () => void; icon: typeof BoldIcon; } type EditorBubbleMenuProps = Omit<BubbleMenuProps, "children">; export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => { const items: BubbleMenuItem[] = [ { name: "bold", isActive: () => props.editor?.isActive("bold"), command: () => props.editor?.chain().focus().toggleBold().run(), icon: BoldIcon, }, { name: "italic", isActive: () => props.editor?.isActive("italic"), command: () => props.editor?.chain().focus().toggleItalic().run(), icon: ItalicIcon, }, { name: "underline", isActive: () => props.editor?.isActive("underline"), command: () => props.editor?.chain().focus().toggleUnderline().run(), icon: UnderlineIcon, }, { name: "strike", isActive: () => props.editor?.isActive("strike"), command: () => props.editor?.chain().focus().toggleStrike().run(), icon: StrikethroughIcon, }, { name: "code", isActive: () => props.editor?.isActive("code"), command: () => props.editor?.chain().focus().toggleCode().run(), icon: CodeIcon, }, ]; const bubbleMenuProps: EditorBubbleMenuProps = { ...props, shouldShow: ({ editor }) => { if (!editor.isEditable) { return false; } if (editor.isActive("image")) { return false; } return editor.view.state.selection.content().size > 0; }, tippyOptions: { moveTransition: "transform 0.15s ease-out", onHidden: () => { setIsNodeSelectorOpen(false); setIsLinkSelectorOpen(false); }, }, }; const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false); const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false); return ( <BubbleMenu {...bubbleMenuProps} className="flex w-fit divide-x divide-custom-border-300 rounded border border-custom-border-300 bg-custom-background-100 shadow-xl" > {!props.editor.isActive("table") && ( <NodeSelector editor={props.editor!} isOpen={isNodeSelectorOpen} setIsOpen={() => { setIsNodeSelectorOpen(!isNodeSelectorOpen); setIsLinkSelectorOpen(false); }} /> )} <LinkSelector editor={props.editor!!} isOpen={isLinkSelectorOpen} setIsOpen={() => { setIsLinkSelectorOpen(!isLinkSelectorOpen); setIsNodeSelectorOpen(false); }} /> <div className="flex"> {items.map((item, index) => ( <button key={index} type="button" onClick={item.command} className={cn( "p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors", { "text-custom-text-100 bg-custom-primary-100/5": item.isActive(), } )} > <item.icon className={cn("h-4 w-4", { "text-custom-text-100": item.isActive(), })} /> </button> ))} </div> </BubbleMenu> ); };