ultisuite-client/components/gmail/compose/compose-editor-chrome.tsx
2026-05-20 16:01:08 +02:00

357 lines
11 KiB
TypeScript

"use client"
import type { RefObject } from "react"
import {
ChevronDown,
Forward,
Maximize2,
Minimize2,
Paperclip,
Pencil,
Reply,
ReplyAll,
SquareArrowOutUpRight,
X,
Image as ImageIcon,
} from "lucide-react"
import type { ComposeState } from "@/lib/compose-context"
import type { Email } from "@/lib/email-data"
import { cn } from "@/lib/utils"
import {
MAIL_COMPOSE_DROP_ZONE_CLASS,
MAIL_COMPOSE_TITLEBAR_CLASS,
MAIL_ICON_BTN,
} from "@/lib/mail-chrome-classes"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { COMPOSE_PORTAL_Z } from "./compose-shared"
import type { ComposeRecipientFieldsProps } from "./compose-recipients"
import { ComposeRecipientFields } from "./compose-recipients"
export function ComposeDropOverlay() {
return (
<div className={MAIL_COMPOSE_DROP_ZONE_CLASS}>
<div className="text-center">
<Paperclip className="mx-auto h-8 w-8 text-primary" />
<p className="mt-2 text-sm font-medium text-primary">Déposer les fichiers ici</p>
</div>
</div>
)
}
export interface ComposeInlineRecipientHeaderProps {
compose: ComposeState
threadSourceEmail: Email | null
recipientSummary: string
recipientsFocused: boolean
showReplyAllInMenu: boolean
ThreadKindIcon: typeof Reply
onOpenInlinePreset: (kind: "reply" | "replyAll" | "forward") => void
onOpenDockFromInline: (opts?: { focusSubject?: boolean }) => void
onActivateRecipients: () => void
updateCompose: (id: string, patch: Partial<ComposeState>) => void
recipientFieldsProps: ComposeRecipientFieldsProps
fieldsRef: RefObject<HTMLDivElement | null>
inlineRecipientShellRef: RefObject<HTMLDivElement | null>
}
export function ComposeInlineRecipientHeader({
compose,
threadSourceEmail,
recipientSummary,
recipientsFocused,
showReplyAllInMenu,
ThreadKindIcon,
onOpenInlinePreset,
onOpenDockFromInline,
onActivateRecipients,
updateCompose,
recipientFieldsProps,
fieldsRef,
inlineRecipientShellRef,
}: ComposeInlineRecipientHeaderProps) {
return (
<div ref={inlineRecipientShellRef} className="flex shrink-0 flex-col">
<div
className="flex h-10 shrink-0 items-center gap-2 bg-mail-surface px-2"
title={
compose.threading
? `In-Reply-To: ${compose.threading.inReplyTo}\nReferences: ${compose.threading.references.join(" ")}`
: undefined
}
>
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<button
type="button"
className="flex h-8 shrink-0 items-center gap-0.5 rounded-full px-1.5 text-muted-foreground transition-colors hover:bg-accent"
title="Type de message"
>
<ThreadKindIcon className="h-[18px] w-[18px] shrink-0" strokeWidth={1.75} />
<ChevronDown className="h-4 w-4 shrink-0" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className={cn("min-w-[260px]", COMPOSE_PORTAL_Z)}
onCloseAutoFocus={(e) => e.preventDefault()}
>
<DropdownMenuItem
disabled={!threadSourceEmail}
onSelect={() => onOpenInlinePreset("reply")}
>
<Reply className="mr-2 h-4 w-4 shrink-0 text-muted-foreground" strokeWidth={1.75} />
Répondre
</DropdownMenuItem>
{showReplyAllInMenu ? (
<DropdownMenuItem
disabled={!threadSourceEmail}
onSelect={() => onOpenInlinePreset("replyAll")}
>
<ReplyAll className="mr-2 h-4 w-4 shrink-0 text-muted-foreground" strokeWidth={1.75} />
Répondre à tous
</DropdownMenuItem>
) : null}
<DropdownMenuItem
disabled={!threadSourceEmail}
onSelect={() => onOpenInlinePreset("forward")}
>
<Forward className="mr-2 h-4 w-4 shrink-0 text-muted-foreground" strokeWidth={1.75} />
Transférer
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={() => onOpenDockFromInline({ focusSubject: true })}>
<Pencil className="mr-2 h-4 w-4 shrink-0 text-muted-foreground" strokeWidth={1.75} />
Modifier l&apos;objet
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => onOpenDockFromInline()}>
<SquareArrowOutUpRight className="mr-2 h-4 w-4 shrink-0 text-muted-foreground" strokeWidth={1.75} />
Ouvrir une fenêtre de réponse
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<button
type="button"
className="min-w-0 flex-1 truncate rounded px-1 py-1 text-left text-sm text-foreground hover:bg-accent/70"
onClick={onActivateRecipients}
>
{recipientSummary}
</button>
{!recipientsFocused && (!compose.showCc || !compose.showBcc) ? (
<div className="flex shrink-0 items-center gap-2">
{!compose.showCc ? (
<button
type="button"
onClick={() => {
updateCompose(compose.id, { showCc: true })
onActivateRecipients()
}}
className="text-sm text-muted-foreground hover:text-foreground hover:underline"
>
Cc
</button>
) : null}
{!compose.showBcc ? (
<button
type="button"
onClick={() => {
updateCompose(compose.id, { showBcc: true })
onActivateRecipients()
}}
className="text-sm text-muted-foreground hover:text-foreground hover:underline"
>
Cci
</button>
) : null}
</div>
) : null}
<button
type="button"
className={cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-full", MAIL_ICON_BTN)}
title="Ouvrir dans une fenêtre"
onClick={() => onOpenDockFromInline()}
>
<SquareArrowOutUpRight className="h-[18px] w-[18px]" strokeWidth={1.75} />
</button>
</div>
<div
ref={fieldsRef}
className={cn("flex shrink-0 flex-col", !recipientsFocused && "hidden")}
>
<ComposeRecipientFields {...recipientFieldsProps} />
</div>
</div>
)
}
export function ComposeXsSheetHeader({
titleText,
onClose,
}: {
titleText: string
onClose: () => void
}) {
return (
<div
className={cn(
"flex h-11 shrink-0 items-center border-b border-border bg-muted px-3",
"pt-[max(_0.25rem,env(safe-area-inset-top))]"
)}
>
<span className="min-w-0 flex-1 truncate text-sm font-medium text-foreground">
{titleText}
</span>
<button
type="button"
onClick={onClose}
className={cn(
"flex h-8 w-8 items-center justify-center rounded-full",
MAIL_ICON_BTN
)}
title="Fermer"
>
<X className="h-4 w-4" />
</button>
</div>
)
}
export function ComposeDockTitleBar({
titleText,
maximized,
onMinimize,
onMaximize,
onClose,
}: {
titleText: string
maximized: boolean
onMinimize: () => void
onMaximize: () => void
onClose: () => void
}) {
return (
<div className={MAIL_COMPOSE_TITLEBAR_CLASS} onClick={onMinimize}>
<span className="min-w-0 flex-1 truncate text-sm font-medium text-foreground">
{titleText}
</span>
<div className="flex items-center gap-0.5">
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onMinimize()
}}
className={cn(
"flex h-6 w-6 items-center justify-center rounded-full",
MAIL_ICON_BTN
)}
title="Réduire"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
className="shrink-0"
aria-hidden
>
<line x1="5" y1="17" x2="19" y2="17" />
</svg>
</button>
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onMaximize()
}}
className={cn(
"flex h-6 w-6 items-center justify-center rounded-full",
MAIL_ICON_BTN
)}
title={maximized ? "Réduire la fenêtre" : "Plein écran"}
>
{maximized ? (
<Minimize2 className="h-3.5 w-3.5" />
) : (
<Maximize2 className="h-3.5 w-3.5" />
)}
</button>
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onClose()
}}
className={cn(
"flex h-6 w-6 items-center justify-center rounded-full",
MAIL_ICON_BTN
)}
title="Fermer"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
</div>
)
}
export function ComposeAttachmentsList({
attachments,
onRemove,
}: {
attachments: ComposeState["attachments"]
onRemove: (attId: string) => void
}) {
if (attachments.length === 0) return null
return (
<div className="flex max-h-[120px] shrink-0 flex-col gap-1 overflow-y-auto border-t border-border px-3 py-2">
{attachments.map((att) => (
<div
key={att.id}
className="flex items-center gap-2 rounded-lg border border-border bg-muted px-3 py-1.5"
>
{att.type.startsWith("image/") ? (
<ImageIcon className="h-4 w-4 shrink-0 text-primary" />
) : (
<Paperclip className="h-4 w-4 shrink-0 text-muted-foreground" />
)}
<span className="min-w-0 flex-1 truncate text-sm text-foreground">
{att.name}
</span>
<span className="shrink-0 text-xs text-muted-foreground">
{att.size < 1024
? `${att.size} o`
: att.size < 1048576
? `${(att.size / 1024).toFixed(1)} Ko`
: `${(att.size / 1048576).toFixed(1)} Mo`}
</span>
<button
type="button"
onClick={() => onRemove(att.id)}
className={cn(
"flex h-5 w-5 shrink-0 items-center justify-center rounded-full",
MAIL_ICON_BTN
)}
title="Supprimer"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
))}
</div>
)
}