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;
/** Title/aria-label for the undo button. @default "Desfazer" */
undoLabel?: string;
/** Title/aria-label for the redo button. @default "Refazer" */
redoLabel?: string;
/** Title/aria-label for the bold button. @default "Negrito" */
boldLabel?: string;
/** Title/aria-label for the italic button. @default "Itálico" */
italicLabel?: string;
/** Title/aria-label for the underline button. @default "Sublinhado" */
underlineLabel?: string;
/** Title/aria-label for the align-left button. @default "Alinhar à esquerda" */
alignLeftLabel?: string;
/** Title/aria-label for the align-center button. @default "Centralizar" */
alignCenterLabel?: string;
/** Title/aria-label for the align-right button. @default "Alinhar à direita" */
alignRightLabel?: string;
/** Title/aria-label for the bulleted-list button. @default "Lista com marcadores" */
bulletListLabel?: string;
/** Title/aria-label for the numbered-list button. @default "Lista numerada" */
numberedListLabel?: string;
/** Title/aria-label for the insert-link button. @default "Inserir link" */
insertLinkLabel?: string;
/** Placeholder for the link URL input. @default "https://..." */
linkPlaceholder?: string;
/** Title/aria-label for the remove-link button. @default "Remover link" */
removeLinkLabel?: string;
/** Title/aria-label for the clear-formatting button. @default "Limpar formatação" */
clearFormattingLabel?: string;
/** Title/aria-label for the toolbar search-toggle button. @default "Buscar" */
searchLabel?: string;
/** Placeholder for the search input. @default "Buscar no texto..." */
searchPlaceholder?: string;
/** Title for the search-previous-match button. @default "Anterior" */
searchPreviousLabel?: string;
/** Title for the search-next-match button. @default "Próximo" */
searchNextLabel?: string;
/** Title for the close-search button. @default "Fechar busca" */
closeSearchLabel?: string;
/** Aria-label for the editable area, used when `placeholder` is not set. @default "Editor de texto" */
editorLabel?: 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,
undoLabel = 'Desfazer',
redoLabel = 'Refazer',
boldLabel = 'Negrito',
italicLabel = 'Itálico',
underlineLabel = 'Sublinhado',
alignLeftLabel = 'Alinhar à esquerda',
alignCenterLabel = 'Centralizar',
alignRightLabel = 'Alinhar à direita',
bulletListLabel = 'Lista com marcadores',
numberedListLabel = 'Lista numerada',
insertLinkLabel = 'Inserir link',
linkPlaceholder = 'https://...',
removeLinkLabel = 'Remover link',
clearFormattingLabel = 'Limpar formatação',
searchLabel = 'Buscar',
searchPlaceholder = 'Buscar no texto...',
searchPreviousLabel = 'Anterior',
searchNextLabel = 'Próximo',
closeSearchLabel = 'Fechar busca',
editorLabel = 'Editor de texto',
}: 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 (
<>
Selecione um texto para criar um link.
) : (