import React, { useEffect, useState, useCallback } from "react"; import { Check, ChevronDown } from "lucide-react"; // editor import { EditorMenuItemNames, EditorRefApi } from "@plane/document-editor"; // ui import { CustomMenu, Tooltip } from "@plane/ui"; // constants import { TOOLBAR_ITEMS, TYPOGRAPHY_ITEMS, ToolbarMenuItem } from "@/constants/editor"; // helpers import { cn } from "@/helpers/common.helper"; type Props = { editorRef: EditorRefApi; }; type ToolbarButtonProps = { item: ToolbarMenuItem; isActive: boolean; executeCommand: (commandName: EditorMenuItemNames) => void; }; const ToolbarButton: React.FC<ToolbarButtonProps> = React.memo((props) => { const { item, isActive, executeCommand } = props; return ( <Tooltip tooltipContent={ <p className="flex flex-col gap-1 text-center text-xs"> <span className="font-medium">{item.name}</span> {item.shortcut && <kbd className="text-custom-text-400">{item.shortcut.join(" + ")}</kbd>} </p> } > <button key={item.key} type="button" onClick={() => executeCommand(item.key)} className={cn("grid size-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80", { "bg-custom-background-80 text-custom-text-100": isActive, })} > <item.icon className={cn("size-4", { "text-custom-text-100": isActive, })} /> </button> </Tooltip> ); }); ToolbarButton.displayName = "ToolbarButton"; const toolbarItems = TOOLBAR_ITEMS.document; export const PageToolbar: React.FC<Props> = ({ editorRef }) => { const [activeStates, setActiveStates] = useState<Record<string, boolean>>({}); const updateActiveStates = useCallback(() => { const newActiveStates: Record<string, boolean> = {}; Object.values(toolbarItems) .flat() .forEach((item) => { newActiveStates[item.key] = editorRef.isMenuItemActive(item.key); }); setActiveStates(newActiveStates); }, [editorRef]); useEffect(() => { const unsubscribe = editorRef.onStateChange(updateActiveStates); updateActiveStates(); return () => unsubscribe(); }, [editorRef, updateActiveStates]); const activeTypography = TYPOGRAPHY_ITEMS.find((item) => editorRef.isMenuItemActive(item.key)); return ( <div className="flex flex-wrap items-center divide-x divide-custom-border-200"> <CustomMenu customButton={ <span className="text-custom-text-300 text-sm border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 h-7 w-24 rounded px-2 flex items-center justify-between gap-2 whitespace-nowrap text-left"> {activeTypography?.name || "Text"} <ChevronDown className="flex-shrink-0 size-3" /> </span> } className="pr-2" placement="bottom-start" closeOnSelect maxHeight="lg" > {TYPOGRAPHY_ITEMS.map((item) => ( <CustomMenu.MenuItem key={item.key} className="flex items-center justify-between gap-2" onClick={() => editorRef.executeMenuItemCommand(item.key)} > <span className="flex items-center gap-2"> <item.icon className="size-3" /> {item.name} </span> {activeTypography?.key === item.key && <Check className="size-3 text-custom-text-300 flex-shrink-0" />} </CustomMenu.MenuItem> ))} </CustomMenu> {Object.keys(toolbarItems).map((key) => ( <div key={key} className="flex items-center gap-0.5 px-2 first:pl-0 last:pr-0"> {toolbarItems[key].map((item) => ( <ToolbarButton key={item.key} item={item} isActive={activeStates[item.key]} executeCommand={editorRef.executeMenuItemCommand} /> ))} </div> ))} </div> ); };