Major changes
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Bold,
|
||||
Italic,
|
||||
Strikethrough,
|
||||
Heading1,
|
||||
Heading2,
|
||||
Heading3,
|
||||
Link,
|
||||
Image,
|
||||
List,
|
||||
ListOrdered,
|
||||
Quote,
|
||||
Code,
|
||||
FileCode,
|
||||
Table,
|
||||
Undo2,
|
||||
Redo2,
|
||||
ChevronDown,
|
||||
Plus,
|
||||
Minus,
|
||||
FileText,
|
||||
AlertTriangle,
|
||||
Info,
|
||||
CheckCircle,
|
||||
} from 'lucide-react';
|
||||
|
||||
interface EditorToolbarProps {
|
||||
onBold: () => void;
|
||||
onItalic: () => void;
|
||||
onStrikethrough: () => void;
|
||||
onHeading: (level: number) => void;
|
||||
onLink: () => void;
|
||||
onImage: () => void;
|
||||
onBulletList: () => void;
|
||||
onNumberedList: () => void;
|
||||
onBlockquote: () => void;
|
||||
onCode: () => void;
|
||||
onCodeBlock: () => void;
|
||||
onTable: () => void;
|
||||
onUndo: () => void;
|
||||
onRedo: () => void;
|
||||
canUndo: boolean;
|
||||
canRedo: boolean;
|
||||
disabled?: boolean;
|
||||
onInsertTemplate?: (template: string) => void;
|
||||
}
|
||||
|
||||
const TEMPLATES = {
|
||||
infobox: `> **Note:** This is an informational callout.`,
|
||||
warning: `> **Warning:** This requires attention.`,
|
||||
tip: `> **Tip:** Here's a helpful suggestion.`,
|
||||
divider: `\n---\n`,
|
||||
footnote: `[^1]: Reference text here.`,
|
||||
definition: `**Term**\n: Definition of the term goes here.`,
|
||||
collapse: `<details>\n<summary>Click to expand</summary>\n\nHidden content goes here.\n\n</details>`,
|
||||
};
|
||||
|
||||
export function EditorToolbar({
|
||||
onBold,
|
||||
onItalic,
|
||||
onStrikethrough,
|
||||
onHeading,
|
||||
onLink,
|
||||
onImage,
|
||||
onBulletList,
|
||||
onNumberedList,
|
||||
onBlockquote,
|
||||
onCode,
|
||||
onCodeBlock,
|
||||
onTable,
|
||||
onUndo,
|
||||
onRedo,
|
||||
canUndo,
|
||||
canRedo,
|
||||
disabled = false,
|
||||
onInsertTemplate,
|
||||
}: EditorToolbarProps) {
|
||||
const [showHeadingMenu, setShowHeadingMenu] = useState(false);
|
||||
const [showInsertMenu, setShowInsertMenu] = useState(false);
|
||||
const headingRef = useRef<HTMLDivElement>(null);
|
||||
const insertRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Close menus when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (headingRef.current && !headingRef.current.contains(e.target as Node)) {
|
||||
setShowHeadingMenu(false);
|
||||
}
|
||||
if (insertRef.current && !insertRef.current.contains(e.target as Node)) {
|
||||
setShowInsertMenu(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="editor-toolbar-enhanced">
|
||||
{/* Undo/Redo group */}
|
||||
<div className="toolbar-group">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onUndo}
|
||||
disabled={disabled || !canUndo}
|
||||
title="Undo (Ctrl+Z)"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<Undo2 className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onRedo}
|
||||
disabled={disabled || !canRedo}
|
||||
title="Redo (Ctrl+Shift+Z)"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<Redo2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="toolbar-divider" />
|
||||
|
||||
{/* Heading dropdown */}
|
||||
<div className="toolbar-group heading-dropdown-container" ref={headingRef}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowHeadingMenu(!showHeadingMenu)}
|
||||
disabled={disabled}
|
||||
className="toolbar-btn heading-btn"
|
||||
>
|
||||
<Heading1 className="w-4 h-4" />
|
||||
<ChevronDown className="w-3 h-3 ml-1" />
|
||||
</Button>
|
||||
{showHeadingMenu && (
|
||||
<div className="heading-dropdown">
|
||||
<button
|
||||
className="heading-option"
|
||||
onClick={() => { onHeading(1); setShowHeadingMenu(false); }}
|
||||
>
|
||||
<Heading1 className="w-5 h-5" />
|
||||
<span>Heading 1</span>
|
||||
</button>
|
||||
<button
|
||||
className="heading-option"
|
||||
onClick={() => { onHeading(2); setShowHeadingMenu(false); }}
|
||||
>
|
||||
<Heading2 className="w-5 h-5" />
|
||||
<span>Heading 2</span>
|
||||
</button>
|
||||
<button
|
||||
className="heading-option"
|
||||
onClick={() => { onHeading(3); setShowHeadingMenu(false); }}
|
||||
>
|
||||
<Heading3 className="w-5 h-5" />
|
||||
<span>Heading 3</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="toolbar-divider" />
|
||||
|
||||
{/* Text formatting group */}
|
||||
<div className="toolbar-group">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onBold}
|
||||
disabled={disabled}
|
||||
title="Bold (Ctrl+B)"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<Bold className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onItalic}
|
||||
disabled={disabled}
|
||||
title="Italic (Ctrl+I)"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<Italic className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onStrikethrough}
|
||||
disabled={disabled}
|
||||
title="Strikethrough"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<Strikethrough className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="toolbar-divider" />
|
||||
|
||||
{/* Links and media group */}
|
||||
<div className="toolbar-group">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onLink}
|
||||
disabled={disabled}
|
||||
title="Insert Link (Ctrl+K)"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<Link className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onImage}
|
||||
disabled={disabled}
|
||||
title="Insert Image"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<Image className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="toolbar-divider" />
|
||||
|
||||
{/* Lists and quotes group */}
|
||||
<div className="toolbar-group">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onBulletList}
|
||||
disabled={disabled}
|
||||
title="Bullet List"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<List className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onNumberedList}
|
||||
disabled={disabled}
|
||||
title="Numbered List"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<ListOrdered className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onBlockquote}
|
||||
disabled={disabled}
|
||||
title="Blockquote"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<Quote className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="toolbar-divider" />
|
||||
|
||||
{/* Code group */}
|
||||
<div className="toolbar-group">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onCode}
|
||||
disabled={disabled}
|
||||
title="Inline Code"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<Code className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onCodeBlock}
|
||||
disabled={disabled}
|
||||
title="Code Block"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<FileCode className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="toolbar-divider" />
|
||||
|
||||
{/* Table */}
|
||||
<div className="toolbar-group">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onTable}
|
||||
disabled={disabled}
|
||||
title="Insert Table"
|
||||
className="toolbar-btn"
|
||||
>
|
||||
<Table className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="toolbar-divider" />
|
||||
|
||||
{/* Insert templates menu */}
|
||||
{onInsertTemplate && (
|
||||
<div className="toolbar-group heading-dropdown-container" ref={insertRef}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowInsertMenu(!showInsertMenu)}
|
||||
disabled={disabled}
|
||||
className="toolbar-btn heading-btn"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<span className="text-xs ml-1">Insert</span>
|
||||
<ChevronDown className="w-3 h-3 ml-1" />
|
||||
</Button>
|
||||
{showInsertMenu && (
|
||||
<div className="heading-dropdown insert-dropdown">
|
||||
<button
|
||||
className="heading-option"
|
||||
onClick={() => { onInsertTemplate(TEMPLATES.infobox); setShowInsertMenu(false); }}
|
||||
>
|
||||
<Info className="w-4 h-4 text-blue-500" />
|
||||
<span>Info Box</span>
|
||||
</button>
|
||||
<button
|
||||
className="heading-option"
|
||||
onClick={() => { onInsertTemplate(TEMPLATES.warning); setShowInsertMenu(false); }}
|
||||
>
|
||||
<AlertTriangle className="w-4 h-4 text-yellow-500" />
|
||||
<span>Warning Box</span>
|
||||
</button>
|
||||
<button
|
||||
className="heading-option"
|
||||
onClick={() => { onInsertTemplate(TEMPLATES.tip); setShowInsertMenu(false); }}
|
||||
>
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span>Tip Box</span>
|
||||
</button>
|
||||
<div className="dropdown-divider" />
|
||||
<button
|
||||
className="heading-option"
|
||||
onClick={() => { onInsertTemplate(TEMPLATES.divider); setShowInsertMenu(false); }}
|
||||
>
|
||||
<Minus className="w-4 h-4" />
|
||||
<span>Horizontal Rule</span>
|
||||
</button>
|
||||
<button
|
||||
className="heading-option"
|
||||
onClick={() => { onInsertTemplate(TEMPLATES.definition); setShowInsertMenu(false); }}
|
||||
>
|
||||
<FileText className="w-4 h-4" />
|
||||
<span>Definition</span>
|
||||
</button>
|
||||
<button
|
||||
className="heading-option"
|
||||
onClick={() => { onInsertTemplate(TEMPLATES.collapse); setShowInsertMenu(false); }}
|
||||
>
|
||||
<ChevronDown className="w-4 h-4" />
|
||||
<span>Collapsible Section</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user