ultisuite-client/components/gmail/email-view/email-view-toolbar.tsx
2026-05-25 13:52:40 +02:00

308 lines
10 KiB
TypeScript

"use client"
import { useState } from "react"
import {
Star,
Reply,
Forward,
MoreVertical,
Printer,
Trash2,
Mail,
Ban,
ShieldAlert,
Fish,
Flag,
SlidersHorizontal,
Languages,
Download,
Code2,
MessageCircleWarning,
ChevronDown,
TriangleAlert,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
import { avatarColor, cleanSenderName, senderInitial } from "@/lib/sender-display"
import { MailDateText } from "@/components/gmail/mail-date-text"
import { ContactHoverCard } from "@/components/gmail/contact-hover-card"
import { EmailViewDetailsPopover } from "@/components/gmail/email-view/email-view-details-popover"
import type { MessageHeaderDetails } from "@/lib/mail-message-header-details"
import {
MAIL_ICON_BTN,
MAIL_MENU_SURFACE_WIDE_CLASS,
MAIL_TOOLTIP_CONTENT_CLASS,
} from "@/lib/mail-chrome-classes"
const MESSAGE_MORE_MENU_CLASS = MAIL_MENU_SURFACE_WIDE_CLASS
const MENU_ICON_CLASS = "size-[18px] shrink-0 text-muted-foreground"
export interface EmailViewMessageToolbarProps {
sender: string
senderEmail: string
headerDetails: MessageHeaderDetails
dateIso: string
isSpam: boolean
isLast: boolean
starred: boolean
onToggleStar?: () => void
onCollapse?: () => void
onPrintConversation?: () => void
onReply?: () => void
onForward?: () => void
detailsOpen?: boolean
onDetailsOpenChange?: (open: boolean) => void
messageId: string
}
export function EmailViewMessageToolbar({
sender,
senderEmail,
headerDetails,
dateIso,
isSpam,
isLast,
starred,
onToggleStar,
onCollapse,
onPrintConversation,
onReply,
onForward,
detailsOpen,
onDetailsOpenChange,
messageId,
}: EmailViewMessageToolbarProps) {
const name = cleanSenderName(sender)
const [internalDetailsOpen, setInternalDetailsOpen] = useState(false)
const detailsIsOpen = detailsOpen ?? internalDetailsOpen
const setDetailsIsOpen = onDetailsOpenChange ?? setInternalDetailsOpen
return (
<>
<div
className={cn("flex items-start gap-3 px-4 py-3", !isLast && "cursor-pointer")}
onClick={
!isLast
? () => {
setDetailsIsOpen(false)
onCollapse?.()
}
: undefined
}
>
{isSpam ? (
<div
className="flex h-10 w-10 shrink-0 self-start items-center justify-center rounded-full bg-muted text-amber-600"
aria-label="Expéditeur ou message suspect (spam)"
>
<TriangleAlert className="size-[22px]" strokeWidth={2} aria-hidden />
</div>
) : (
<div
className="flex h-10 w-10 shrink-0 self-start items-center justify-center rounded-full text-sm font-bold text-white"
style={{ backgroundColor: avatarColor(name) }}
>
{senderInitial(name)}
</div>
)}
<div className="min-w-0 flex-1 flex flex-col gap-1" data-selectable-text>
<div className="min-w-0 truncate text-sm leading-snug">
<ContactHoverCard
displayName={sender}
email={senderEmail}
onTriggerClick={!isLast ? (e) => e.stopPropagation() : undefined}
className="inline min-w-0 max-w-full align-baseline"
>
<span className="font-semibold text-foreground">{name}</span>
{senderEmail ? (
<span className="text-muted-foreground"> &lt;{senderEmail}&gt;</span>
) : null}
</ContactHoverCard>
</div>
<div className="flex items-center gap-1">
<EmailViewDetailsPopover
summary={headerDetails.recipientSummary}
details={headerDetails}
open={detailsIsOpen}
onOpenChange={setDetailsIsOpen}
isSpam={isSpam}
messageId={messageId}
/>
</div>
</div>
<div className="flex shrink-0 flex-col items-end gap-1 self-start pt-0.5">
<div className="flex items-center gap-1">
<MailDateText
iso={dateIso}
variant="preview"
className="hidden text-xs text-muted-foreground sm:inline"
/>
{onToggleStar ? (
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onToggleStar()
}}
className="flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-full text-muted-foreground/60 hover:bg-black/4 hover:text-muted-foreground"
aria-label={starred ? "Retirer des favoris" : "Marquer comme favori"}
>
<Star
strokeWidth={starred ? 0 : 1.25}
className={cn(
"size-4",
starred
? "fill-amber-300 stroke-none text-amber-300"
: "fill-transparent stroke-muted-foreground/60"
)}
/>
</button>
) : (
<Star
strokeWidth={1.25}
className="ml-1 size-4 fill-transparent stroke-muted-foreground/60"
/>
)}
<Tooltip delayDuration={400}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className={cn("h-8 w-8", MAIL_ICON_BTN)}
aria-label="Répondre"
onClick={(e) => {
e.stopPropagation()
onReply?.()
}}
>
<Reply className="h-[18px] w-[18px]" strokeWidth={1.5} />
</Button>
</TooltipTrigger>
<TooltipContent
side="bottom"
className={cn(MAIL_TOOLTIP_CONTENT_CLASS, "text-xs")}
>
Répondre
</TooltipContent>
</Tooltip>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className={cn("h-8 w-8", MAIL_ICON_BTN)}
aria-label="Plus d'actions"
onClick={(e) => e.stopPropagation()}
>
<MoreVertical className="h-[18px] w-[18px]" strokeWidth={1.5} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
sideOffset={4}
className={MESSAGE_MORE_MENU_CLASS}
>
<DropdownMenuItem
onSelect={() => {
onReply?.()
}}
>
<Reply className={MENU_ICON_CLASS} strokeWidth={1.5} />
Répondre
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => {
onForward?.()
}}
>
<Forward className={MENU_ICON_CLASS} strokeWidth={1.5} />
Transférer
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Trash2 className={MENU_ICON_CLASS} strokeWidth={1.5} />
Supprimer
</DropdownMenuItem>
<DropdownMenuItem>
<Mail className={MENU_ICON_CLASS} strokeWidth={1.5} />
Marquer comme non lus à partir d&apos;ici
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Ban className={MENU_ICON_CLASS} strokeWidth={1.5} />
Bloquer « {name} »
</DropdownMenuItem>
<DropdownMenuItem>
<ShieldAlert className={MENU_ICON_CLASS} strokeWidth={1.5} />
Signaler comme spam
</DropdownMenuItem>
<DropdownMenuItem>
<Fish className={MENU_ICON_CLASS} strokeWidth={1.5} />
Signaler comme hameçonnage
</DropdownMenuItem>
<DropdownMenuItem>
<Flag className={MENU_ICON_CLASS} strokeWidth={1.5} />
Signaler un contenu illégal
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<SlidersHorizontal className={MENU_ICON_CLASS} strokeWidth={1.5} />
Filtrer les messages similaires
</DropdownMenuItem>
<DropdownMenuItem>
<Languages className={MENU_ICON_CLASS} strokeWidth={1.5} />
Traduire
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => {
onPrintConversation?.()
}}
>
<Printer className={MENU_ICON_CLASS} strokeWidth={1.5} />
Imprimer
</DropdownMenuItem>
<DropdownMenuItem>
<Download className={MENU_ICON_CLASS} strokeWidth={1.5} />
Télécharger le message
</DropdownMenuItem>
<DropdownMenuItem>
<Code2 className={MENU_ICON_CLASS} strokeWidth={1.5} />
Afficher l&apos;original
</DropdownMenuItem>
<DropdownMenuItem>
<MessageCircleWarning className={MENU_ICON_CLASS} strokeWidth={1.5} />
Partager pour aider à améliorer Google
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<MailDateText
iso={dateIso}
variant="previewShort"
className="text-xs text-muted-foreground sm:hidden"
/>
</div>
</div>
</>
)
}