diff --git a/components/email/rich-text-editor.tsx b/components/email/rich-text-editor.tsx index 28da13a6..f59704d4 100644 --- a/components/email/rich-text-editor.tsx +++ b/components/email/rich-text-editor.tsx @@ -41,6 +41,7 @@ import { Heading1, Heading2, Table as TableIcon, + Baseline, Trash2, Rows3, Columns3, @@ -143,6 +144,14 @@ function ToolbarSeparator() { const TABLE_PICKER_ROWS = 6; const TABLE_PICKER_COLS = 8; +// Preset text colours (2 x 8). Inline `style="color: …"` survives email +// round-trips; the TextStyle/Color extensions are already registered to +// preserve pasted colours - this palette just adds a UI to set them. +const TEXT_COLORS = [ + "#000000", "#5f6368", "#9aa0a6", "#c5221f", "#e8710a", "#f9ab00", "#188038", "#1967d2", + "#7627bb", "#c2185b", "#795548", "#fa5252", "#fd7e14", "#40c057", "#4dabf7", "#e64980", +]; + function TableSizePicker({ onPick }: { onPick: (rows: number, cols: number) => void }) { const [hover, setHover] = useState<{ r: number; c: number } | null>(null); return ( @@ -336,6 +345,19 @@ export function RichTextEditor({ const [tableMenuOpen, setTableMenuOpen] = useState(false); const tableWrapperRef = useRef(null); + const [colorMenuOpen, setColorMenuOpen] = useState(false); + const colorWrapperRef = useRef(null); + + useEffect(() => { + if (!colorMenuOpen) return; + const handler = (e: MouseEvent) => { + if (colorWrapperRef.current && !colorWrapperRef.current.contains(e.target as Node)) { + setColorMenuOpen(false); + } + }; + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + }, [colorMenuOpen]); useEffect(() => { if (!tableMenuOpen) return; @@ -386,6 +408,49 @@ export function RichTextEditor({ > +
+ setColorMenuOpen((v) => !v)} + title="Text color" + > + {/* The icon itself previews the active colour - no layout shift. */} + + + {colorMenuOpen && ( +
+
+ {TEXT_COLORS.map((color) => ( +
+
+ +
+ )} +