forked from github/plane
131 lines
4.0 KiB
TypeScript
131 lines
4.0 KiB
TypeScript
import { Editor } from "@tiptap/core";
|
|
import {
|
|
Check,
|
|
ChevronDown,
|
|
Heading1,
|
|
Heading2,
|
|
Heading3,
|
|
TextQuote,
|
|
ListOrdered,
|
|
TextIcon,
|
|
Code,
|
|
CheckSquare,
|
|
} from "lucide-react";
|
|
import { Dispatch, FC, SetStateAction } from "react";
|
|
|
|
import { BubbleMenuItem } from ".";
|
|
import { cn } from "../utils";
|
|
|
|
interface NodeSelectorProps {
|
|
editor: Editor;
|
|
isOpen: boolean;
|
|
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
|
}
|
|
|
|
export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen }) => {
|
|
const items: BubbleMenuItem[] = [
|
|
{
|
|
name: "Text",
|
|
icon: TextIcon,
|
|
command: () => editor.chain().focus().toggleNode("paragraph", "paragraph").run(),
|
|
isActive: () =>
|
|
editor.isActive("paragraph") &&
|
|
!editor.isActive("bulletList") &&
|
|
!editor.isActive("orderedList"),
|
|
},
|
|
{
|
|
name: "H1",
|
|
icon: Heading1,
|
|
command: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
|
isActive: () => editor.isActive("heading", { level: 1 }),
|
|
},
|
|
{
|
|
name: "H2",
|
|
icon: Heading2,
|
|
command: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
|
isActive: () => editor.isActive("heading", { level: 2 }),
|
|
},
|
|
{
|
|
name: "H3",
|
|
icon: Heading3,
|
|
command: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
|
isActive: () => editor.isActive("heading", { level: 3 }),
|
|
},
|
|
{
|
|
name: "To-do List",
|
|
icon: CheckSquare,
|
|
command: () => editor.chain().focus().toggleTaskList().run(),
|
|
isActive: () => editor.isActive("taskItem"),
|
|
},
|
|
{
|
|
name: "Bullet List",
|
|
icon: ListOrdered,
|
|
command: () => editor.chain().focus().toggleBulletList().run(),
|
|
isActive: () => editor.isActive("bulletList"),
|
|
},
|
|
{
|
|
name: "Numbered List",
|
|
icon: ListOrdered,
|
|
command: () => editor.chain().focus().toggleOrderedList().run(),
|
|
isActive: () => editor.isActive("orderedList"),
|
|
},
|
|
{
|
|
name: "Quote",
|
|
icon: TextQuote,
|
|
command: () =>
|
|
editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
|
|
isActive: () => editor.isActive("blockquote"),
|
|
},
|
|
{
|
|
name: "Code",
|
|
icon: Code,
|
|
command: () => editor.chain().focus().toggleCodeBlock().run(),
|
|
isActive: () => editor.isActive("codeBlock"),
|
|
},
|
|
];
|
|
|
|
const activeItem = items.filter((item) => item.isActive()).pop() ?? {
|
|
name: "Multiple",
|
|
};
|
|
|
|
return (
|
|
<div className="relative h-full">
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="flex h-full items-center gap-1 whitespace-nowrap p-2 text-sm font-medium text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5"
|
|
>
|
|
<span>{activeItem?.name}</span>
|
|
<ChevronDown className="h-4 w-4" />
|
|
</button>
|
|
|
|
{isOpen && (
|
|
<section className="fixed top-full z-[99999] mt-1 flex w-48 flex-col overflow-hidden rounded border border-custom-border-300 bg-custom-background-100 p-1 shadow-xl animate-in fade-in slide-in-from-top-1">
|
|
{items.map((item, index) => (
|
|
<button
|
|
key={index}
|
|
type="button"
|
|
onClick={() => {
|
|
item.command();
|
|
setIsOpen(false);
|
|
}}
|
|
className={cn(
|
|
"flex items-center justify-between rounded-sm px-2 py-1 text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100",
|
|
{ "bg-custom-primary-100/5 text-custom-text-100": activeItem.name === item.name }
|
|
)}
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<div className="rounded-sm border border-custom-border-300 p-1">
|
|
<item.icon className="h-3 w-3" />
|
|
</div>
|
|
<span>{item.name}</span>
|
|
</div>
|
|
{activeItem.name === item.name && <Check className="h-4 w-4" />}
|
|
</button>
|
|
))}
|
|
</section>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|