ultisuite-client/components/gmail/compose/compose-formatting-dropdowns.tsx
2026-05-20 18:22:36 +02:00

278 lines
8.9 KiB
TypeScript

"use client"
import { useState } from "react"
import { useEditor } from "@tiptap/react"
import {
ChevronDown,
AlignLeft,
AlignCenter,
AlignRight,
AlignJustify,
Palette,
ALargeSmall,
CaseSensitive,
} from "lucide-react"
import { cn } from "@/lib/utils"
import {
MAIL_COMPOSE_MENU_SELECTED_CLASS,
MAIL_COMPOSE_TOOLBAR_BTN_ACTIVE,
} from "@/lib/mail-chrome-classes"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { COMPOSE_PORTAL_Z } from "./compose-shared"
export const FONT_FAMILIES = [
{ label: "Sans Serif", value: "sans-serif" },
{ label: "Serif", value: "serif" },
{ label: "Monospace", value: "monospace" },
{ label: "Cursive", value: "cursive" },
{ label: "Comic Sans MS", value: "Comic Sans MS, cursive" },
{ label: "Garamond", value: "Garamond, serif" },
{ label: "Georgia", value: "Georgia, serif" },
{ label: "Impact", value: "Impact, sans-serif" },
{ label: "Tahoma", value: "Tahoma, sans-serif" },
{ label: "Trebuchet MS", value: "Trebuchet MS, sans-serif" },
{ label: "Verdana", value: "Verdana, sans-serif" },
]
export const FONT_SIZES = [
{ label: "Très petit", value: "10px" },
{ label: "Petit", value: "13px" },
{ label: "Normal", value: "" },
{ label: "Grand", value: "18px" },
{ label: "Très grand", value: "24px" },
{ label: "Énorme", value: "32px" },
]
export const TEXT_COLORS = [
"#000000", "#434343", "#666666", "#999999", "#cccccc", "#efefef", "#f3f3f3", "#ffffff",
"#fb4934", "#fe8019", "#fabd2f", "#b8bb26", "#8ec07c", "#83a598", "#d3869b", "#ebdbb2",
"#cc241d", "#d65d0e", "#d79921", "#98971a", "#689d6a", "#458588", "#b16286", "#a89984",
"#9d0006", "#af3a03", "#b57614", "#79740e", "#427b58", "#076678", "#8f3f71", "#7c6f64",
]
export function AlignmentDropdown({
editor,
btnClass,
activeClass,
}: {
editor: NonNullable<ReturnType<typeof useEditor>>
btnClass: string
activeClass: string
}) {
const currentIcon = editor.isActive({ textAlign: "center" })
? AlignCenter
: editor.isActive({ textAlign: "right" })
? AlignRight
: editor.isActive({ textAlign: "justify" })
? AlignJustify
: AlignLeft
const CurrentIcon = currentIcon
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
className={cn(btnClass, "w-auto gap-0.5 px-1")}
title="Alignement"
>
<CurrentIcon className="h-4 w-4" />
<ChevronDown className="h-3 w-3" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className={cn("min-w-[160px]", COMPOSE_PORTAL_Z)}
>
<DropdownMenuItem
onSelect={() => editor.chain().focus().setTextAlign("left").run()}
className={cn(editor.isActive({ textAlign: "left" }) && MAIL_COMPOSE_MENU_SELECTED_CLASS)}
>
<AlignLeft className="h-4 w-4" /> Aligner à gauche
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => editor.chain().focus().setTextAlign("center").run()}
className={cn(editor.isActive({ textAlign: "center" }) && MAIL_COMPOSE_MENU_SELECTED_CLASS)}
>
<AlignCenter className="h-4 w-4" /> Centrer
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => editor.chain().focus().setTextAlign("right").run()}
className={cn(editor.isActive({ textAlign: "right" }) && MAIL_COMPOSE_MENU_SELECTED_CLASS)}
>
<AlignRight className="h-4 w-4" /> Aligner à droite
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => editor.chain().focus().setTextAlign("justify").run()}
className={cn(editor.isActive({ textAlign: "justify" }) && MAIL_COMPOSE_TOOLBAR_BTN_ACTIVE)}
>
<AlignJustify className="h-4 w-4" /> Justifier
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
export function FontDropdown({
editor,
btnClass,
}: {
editor: NonNullable<ReturnType<typeof useEditor>>
btnClass: string
}) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button type="button" className={cn(btnClass, "w-auto gap-0.5 px-1")} title="Police">
<CaseSensitive className="h-4 w-4" />
<ChevronDown className="h-3 w-3" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className={cn("max-h-[280px] min-w-[180px] overflow-y-auto", COMPOSE_PORTAL_Z)}
>
{FONT_FAMILIES.map((f) => (
<DropdownMenuItem
key={f.value}
onSelect={() => editor.chain().focus().setMark("textStyle", { fontFamily: f.value }).run()}
style={{ fontFamily: f.value }}
className={cn(
editor.isActive("textStyle", { fontFamily: f.value }) && MAIL_COMPOSE_TOOLBAR_BTN_ACTIVE
)}
>
{f.label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}
export function FontSizeDropdown({
editor,
btnClass,
}: {
editor: NonNullable<ReturnType<typeof useEditor>>
btnClass: string
}) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button type="button" className={cn(btnClass, "w-auto gap-0.5 px-1")} title="Taille du texte">
<ALargeSmall className="h-4 w-4" />
<ChevronDown className="h-3 w-3" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className={cn("min-w-[140px]", COMPOSE_PORTAL_Z)}
>
{FONT_SIZES.map((s) => (
<DropdownMenuItem
key={s.label}
onSelect={() => {
if (s.value) {
editor.chain().focus().setMark("textStyle", { fontSize: s.value }).run()
} else {
editor.chain().focus().setMark("textStyle", { fontSize: null }).removeEmptyTextStyle().run()
}
}}
style={s.value ? { fontSize: s.value } : undefined}
className={cn(
s.value && editor.isActive("textStyle", { fontSize: s.value }) && MAIL_COMPOSE_TOOLBAR_BTN_ACTIVE
)}
>
{s.label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}
export function ColorDropdown({
editor,
btnClass,
}: {
editor: NonNullable<ReturnType<typeof useEditor>>
btnClass: string
}) {
const [tab, setTab] = useState<"text" | "bg">("text")
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button type="button" className={cn(btnClass, "w-auto gap-0.5 px-1")} title="Couleur du texte">
<Palette className="h-4 w-4" />
<ChevronDown className="h-3 w-3" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className={cn("w-[268px] p-2", COMPOSE_PORTAL_Z)}
onCloseAutoFocus={(e) => e.preventDefault()}
>
<div className="mb-2 flex gap-1 border-b border-border pb-2">
<button
type="button"
className={cn(
"flex-1 rounded px-2 py-1 text-xs font-medium transition-colors",
tab === "text" ? MAIL_COMPOSE_TOOLBAR_BTN_ACTIVE : "text-[#5f6368] hover:bg-[#f1f3f4]"
)}
onClick={() => setTab("text")}
>
Couleur du texte
</button>
<button
type="button"
className={cn(
"flex-1 rounded px-2 py-1 text-xs font-medium transition-colors",
tab === "bg" ? MAIL_COMPOSE_TOOLBAR_BTN_ACTIVE : "text-[#5f6368] hover:bg-[#f1f3f4]"
)}
onClick={() => setTab("bg")}
>
Couleur de fond
</button>
</div>
<div className="grid grid-cols-8 gap-1">
{TEXT_COLORS.map((color) => (
<button
key={`${tab}-${color}`}
type="button"
className="h-6 w-6 rounded border border-border hover:scale-110 transition-transform"
style={{ backgroundColor: color }}
title={color}
onClick={() => {
if (tab === "text") {
editor.chain().focus().setColor(color).run()
} else {
editor.chain().focus().setMark("textStyle", { backgroundColor: color }).run()
}
}}
/>
))}
</div>
<button
type="button"
className="mt-2 w-full rounded px-2 py-1 text-xs text-muted-foreground hover:bg-accent transition-colors"
onClick={() => {
if (tab === "text") {
editor.chain().focus().unsetColor().run()
} else {
editor.chain().focus().setMark("textStyle", { backgroundColor: null }).removeEmptyTextStyle().run()
}
}}
>
Réinitialiser
</button>
</DropdownMenuContent>
</DropdownMenu>
)
}