plane/web/components/pages/editor/header/toolbar.tsx
Aaryan Khandelwal 84fd1dca4b
[WEB-436] chore: added h4 to h6 heading options (#4304)
* chore: added h4 to h6 heading options

* fix: build errors
2024-04-29 16:04:37 +05:30

120 lines
3.9 KiB
TypeScript

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>
);
};