278 lines
8.9 KiB
TypeScript
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>
|
|
)
|
|
}
|