import React from 'react'; import { useRichTextEditor } from './use-rich-text-editor'; import { cn } from '../../shared/utils'; import { Button } from '../button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '../dropdown-menu'; import { Popover, PopoverContent, PopoverTrigger } from '../popover'; import { Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, List, ListOrdered, Type, Heading1, Heading2, Heading3, ChevronDown, Undo, Redo, Search, ChevronUp, X, Link as LinkIcon, } from 'lucide-react'; import { Input } from '../input'; export interface RichTextEditorProps { value: string; onChange?: (value: string) => void; placeholder?: string; className?: string; actionButton?: React.ReactNode; allowSearch?: boolean; allowLinks?: boolean; allowUndoRedo?: boolean; allowHeadings?: boolean; allowFormatting?: boolean; allowAlignment?: boolean; allowLists?: boolean; showWordCount?: boolean; showCharacterCount?: boolean; /** Disables all editing and applies opacity-50. Hides the toolbar. */ disabled?: boolean; /** Makes the editor non-editable while keeping full visual fidelity. Hides the toolbar. */ readOnly?: boolean; /** Called when the editor area receives focus. */ onFocus?: () => void; /** Called when the editor area loses focus. */ onBlur?: () => void; /** Minimum height of the editable area (CSS value, e.g. "200px"). */ minHeight?: string; /** Maximum height of the editable area (CSS value, e.g. "600px"). */ maxHeight?: string; } // ───────────────────────────────────────────────────────────────────────────── // Component // ───────────────────────────────────────────────────────────────────────────── export function RichTextEditor({ value, onChange, placeholder, className, actionButton, allowSearch = true, allowLinks = true, allowUndoRedo = true, allowHeadings = true, allowFormatting = true, allowAlignment = true, allowLists = true, showWordCount = true, showCharacterCount = true, disabled = false, readOnly = false, onFocus, onBlur, minHeight, maxHeight, }: RichTextEditorProps) { const isEditable = !disabled && !readOnly; const showToolbar = isEditable; const { editorRef, searchInputRef, linkInputRef, activeFormats, isSearchOpen, setIsSearchOpen, searchQuery, setSearchQuery, linkUrl, setLinkUrl, isLinkOpen, hasSavedSelection, wordCount, characterCount, updateActiveFormats, execCommand, handleInput, performSearch, handleCreateLink, handleUnlink, onLinkPopoverOpenChange, } = useRichTextEditor({ value, onChange }); const getCurrentBlockLabel = () => { if (activeFormats.h1) return ( <> Título 1 ); if (activeFormats.h2) return ( <> Título 2 ); if (activeFormats.h3) return ( <> Título 3 ); return ( <> Parágrafo ); }; return (
{/* Toolbar — hidden in disabled/readOnly mode */} {showToolbar && (
{allowUndoRedo && ( <>
)} {allowHeadings && ( <> e.preventDefault()} onClick={() => execCommand('formatBlock', 'P')} className="gap-2" > Parágrafo e.preventDefault()} onClick={() => execCommand('formatBlock', 'H1')} className="gap-2" > Título 1 e.preventDefault()} onClick={() => execCommand('formatBlock', 'H2')} className="gap-2" > Título 2 e.preventDefault()} onClick={() => execCommand('formatBlock', 'H3')} className="gap-2" > Título 3
)} {allowFormatting && ( <>
)} {allowAlignment && ( <>
)} {allowLists && ( <>
)} {allowLinks && ( <> {!activeFormats.link && !hasSavedSelection ? (

Selecione um texto para criar um link.

) : (
{activeFormats.link ? 'Editar Link' : 'Inserir Link'}
setLinkUrl(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); handleCreateLink(); } }} className="h-8 text-xs" />
)}
)} {(allowFormatting || allowHeadings || allowAlignment || allowLists || allowLinks) && ( )} {allowSearch && ( )} {actionButton && ( <>
{actionButton} )}
)} {/* Search Bar */} {isSearchOpen && (
setSearchQuery(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); performSearch(searchQuery, e.shiftKey); } if (e.key === 'Escape') { setIsSearchOpen(false); } }} className="h-8 pl-8 text-xs bg-background" />
Pressione Enter para buscar
)} {/* Editable Area */}
{ updateActiveFormats(); onFocus?.(); }} onBlur={onBlur} className="prose-editor min-h-full max-w-none focus:outline-none" data-placeholder={placeholder} />
{/* Footer / Status Bar */} {(showWordCount || showCharacterCount) && (
{showWordCount && ( {wordCount} {wordCount === 1 ? 'palavra' : 'palavras'} )} {showWordCount && showCharacterCount && ( )} {showCharacterCount && ( {characterCount} {characterCount === 1 ? 'caractere' : 'caracteres'} )}
)}
); }