799
799
useEffect(() => {
const handleClickOutside = (event) => {
if (!headingRef.current?.contains(event.target)) {
setHeadingDropdownOpen(false);
}
if (!alignmentRef.current?.contains(event.target)) {
setAlignmentDropdownOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
useEffect(() => {
const updateEmptyState = () => {
setIsEmpty(editor?.isEmpty ?? true);
};
updateEmptyState();
editor?.on('update', updateEmptyState);
return () => editor?.off('update', updateEmptyState);
}, [editor]);
useEffect(() => {
const updateHeadingState = () => {
const active = [1, 2, 3].find((lvl) => editor?.isActive('heading', { level: lvl }));
setSelectedHeading(active ? String(active) : '0');
};
updateHeadingState();
editor?.on('update', updateHeadingState);
return () => editor?.off('update', updateHeadingState);
}, [editor]);
return (
<div className="max-w-4xl mx-auto mt-8 bg-white p-2 rounded-lg shadow space-y-4">
<div className="flex flex-wrap gap-2 items-center pb-3 text-base">
{/* Heading Dropdown */}
<div className="relative" ref={headingRef}>
<button
onClick={() => {
setHeadingDropdownOpen((prev) => !prev);
setAlignmentDropdownOpen(false);
}}
className="p-1 px-2 rounded flex items-center justify-between w-30 text-base bg-
gray-400 text-white"
>
{selectedHeading === '0' ? 'Normal Text' : `Heading ${selectedHeading}`}
<FaChevronDown className="ml-1 w-3 h-3" />
</button>
{headingDropdownOpen && (
<div className="absolute mt-1 w-30 bg-white rounded shadow z-10 text-base">
{[{ label: 'Normal Text', value: '0', size: 'text-base' },
{ label: 'Heading 1', value: '1', size: 'text-xl' },
{ label: 'Heading 2', value: '2', size: 'text-2xl' },
{ label: 'Heading 3', value: '3', size: 'text-3xl' }]
.map(({ label, value, size }) => (
<button
key={value}
onClick={() => {
const valueNum = parseInt(value);
if (value === '0') {
editor.chain().focus().setParagraph().run();
} else {
const isActive = editor.isActive('heading', { level: valueNum });
editor.chain().focus().toggleHeading({ level: valueNum }).run();
if (isActive) setSelectedHeading('0');
}
setSelectedHeading(value);
setHeadingDropdownOpen(false);
}}
className={`block w-full text-left px-4 py-2 truncate overflow-hidden text-base
${selectedHeading === value ? 'bg-gray-400 text-white' : 'hover:bg-gray-100'} ${size}`}
>
{label}
</button>
))}
</div>
)}
</div>
{alignmentDropdownOpen && (
<div className="absolute bg-white shadow-lg rounded mt-1 w-24 z-10 text-base">
<button
onClick={() => handleAlignmentChange('left')}
className="flex items-center p-2 hover:bg-gray-100 w-full"
>
<FaAlignLeft className="mr-2 w-3 h-3" /> Left
</button>
<button
onClick={() => handleAlignmentChange('center')}
className="flex items-center p-2 hover:bg-gray-100 w-full"
>
<FaAlignCenter className="mr-2 w-3 h-3" /> Center
</button>
<button
onClick={() => handleAlignmentChange('right')}
className="flex items-center p-2 hover:bg-gray-100 w-full"
>
<FaAlignRight className="mr-2 w-3 h-3" /> Right
</button>
</div>
)}
</div>
const commands = {
toggleBold: () => chain.toggleBold().run(),
toggleItalic: () => chain.toggleItalic().run(),
toggleUnderline: () => chain.toggleUnderline().run(),
toggleStrike: () => chain.toggleStrike().run(),
toggleCodeBlock: () => chain.toggleCodeBlock().run(),
toggleHighlight: () => chain.toggleHighlight().run(),
toggleBulletList: () => {
if (editor.isActive('bulletList')) {
editor.commands.liftListItem('listItem');
} else {
chain.toggleBulletList().run();
}
},
toggleOrderedList: () => {
if (editor.isActive('orderedList')) {
editor.commands.liftListItem('listItem');
} else {
chain.toggleOrderedList().run();
}
},
};
commands[command]?.();
};