Files
Zero/apps/mail/components/create/selectors/node-selector.tsx
2025-05-20 13:56:47 +05:30

133 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
Check,
CheckSquare,
ChevronDown,
Code,
Heading1,
Heading2,
Heading3,
ListOrdered,
type LucideIcon,
TextIcon,
TextQuote,
} from 'lucide-react';
import { PopoverContent, PopoverTrigger, Popover } from '@/components/ui/popover';
import { EditorBubbleItem, useEditor } from 'novel';
import { Button } from '@/components/ui/button';
export type SelectorItem = {
name: string;
icon: LucideIcon;
command: (editor: ReturnType<typeof useEditor>['editor']) => void;
isActive: (editor: ReturnType<typeof useEditor>['editor']) => boolean;
};
const items: SelectorItem[] = [
{
name: 'Text',
icon: TextIcon,
command: (editor) => editor?.chain().focus().clearNodes().run(),
// I feel like there has to be a more efficient way to do this feel free to PR if you know how!
isActive: (editor) =>
editor
? editor.isActive('paragraph') &&
!editor.isActive('bulletList') &&
!editor.isActive('orderedList')
: false,
},
{
name: 'Heading 1',
icon: Heading1,
command: (editor) => editor?.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
isActive: (editor) => (editor ? editor.isActive('heading', { level: 1 }) : false),
},
{
name: 'Heading 2',
icon: Heading2,
command: (editor) => editor?.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
isActive: (editor) => (editor ? editor.isActive('heading', { level: 2 }) : false),
},
{
name: 'Heading 3',
icon: Heading3,
command: (editor) => editor?.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
isActive: (editor) => (editor ? editor.isActive('heading', { level: 3 }) : false),
},
{
name: 'To-do List',
icon: CheckSquare,
command: (editor) => editor?.chain().focus().clearNodes().toggleTaskList().run(),
isActive: (editor) => (editor ? editor.isActive('taskItem') : false),
},
{
name: 'Bullet List',
icon: ListOrdered,
command: (editor) => editor?.chain().focus().clearNodes().toggleBulletList().run(),
isActive: (editor) => (editor ? editor.isActive('bulletList') : false),
},
{
name: 'Numbered List',
icon: ListOrdered,
command: (editor) => editor?.chain().focus().clearNodes().toggleOrderedList().run(),
isActive: (editor) => (editor ? editor.isActive('orderedList') : false),
},
{
name: 'Quote',
icon: TextQuote,
command: (editor) => editor?.chain().focus().clearNodes().toggleBlockquote().run(),
isActive: (editor) => (editor ? editor.isActive('blockquote') : false),
},
{
name: 'Code',
icon: Code,
command: (editor) => editor?.chain().focus().clearNodes().toggleCodeBlock().run(),
isActive: (editor) => (editor ? editor.isActive('codeBlock') : false),
},
];
interface NodeSelectorProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const NodeSelector = ({ open, onOpenChange }: NodeSelectorProps) => {
const { editor } = useEditor();
if (!editor) return null;
const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? {
name: 'Multiple',
};
return (
<Popover modal={true} open={open} onOpenChange={onOpenChange}>
<PopoverTrigger
asChild
className="hover:bg-accent gap-2 rounded-none border-none focus:ring-0"
>
<Button size="sm" variant="ghost" className="gap-2">
<span className="whitespace-nowrap text-sm">{activeItem.name}</span>
<ChevronDown className="h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent sideOffset={5} align="start" className="w-48 p-1">
{items.map((item) => (
<EditorBubbleItem
key={item.name}
onSelect={(editor) => {
item.command(editor);
onOpenChange(false);
}}
className="hover:bg-accent flex cursor-pointer items-center justify-between rounded-sm px-2 py-1 text-sm"
>
<div className="flex items-center space-x-2">
<div className="rounded-sm border 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" />}
</EditorBubbleItem>
))}
</PopoverContent>
</Popover>
);
};