357 lines
11 KiB
TypeScript
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'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>
|
|
)
|
|
}
|