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

250 lines
7.3 KiB
TypeScript

"use client"
import { EditorContent } from "@tiptap/react"
import { Maximize2, X } from "lucide-react"
import { readCoarsePointerMatches } from "@/hooks/use-touch-nav"
import type { ComposeState } from "@/lib/compose-context"
import type { Email } from "@/lib/email-data"
import { cn } from "@/lib/utils"
import {
MAIL_COMPOSE_TITLEBAR_CLASS,
MAIL_ICON_BTN,
} from "@/lib/mail-chrome-classes"
import { ComposeRecipientFields } from "@/components/gmail/compose/compose-recipients"
import {
ComposeBottomToolbar,
FormattingToolbar,
} from "@/components/gmail/compose/compose-toolbar"
import {
ComposeAttachmentsList,
ComposeDockTitleBar,
ComposeDropOverlay,
ComposeInlineRecipientHeader,
ComposeXsSheetHeader,
} from "@/components/gmail/compose/compose-editor-chrome"
import { useComposeWindow } from "@/components/gmail/compose/use-compose-window"
export function ComposeWindow({
compose,
threadSourceEmail = null,
isXsSheet = false,
bindXsSheetClose,
}: {
compose: ComposeState
/** Fil courant : nécessaire pour le menu Répondre / Transférer en inline */
threadSourceEmail?: Email | null
/** Plein écran dans une bottom sheet (xs) — pas de file ni réduction */
isXsSheet?: boolean
bindXsSheetClose?: (fn: (() => void) | null) => void
}) {
const {
isInline,
isEditingScheduled,
editor,
titleText,
showFormatting,
setShowFormatting,
recipientsFocused,
setRecipientsFocused,
sendMenuOpen,
setSendMenuOpen,
isDragOver,
fieldsRef,
inlineRecipientShellRef,
fileInputRef,
imageInputRef,
handleClose,
handleSend,
saveScheduledEdit,
sendScheduledFromEditNow,
applyScheduledPlanAt,
submitScheduledSendAt,
addFiles,
removeAttachment,
handleDrop,
handleDragOver,
handleDragLeave,
recipientSummary,
showReplyAllInMenu,
ThreadKindIcon,
openInlinePreset,
openDockFromInline,
recipientFieldsProps,
toggleMinimize,
toggleMaximize,
updateCompose,
} = useComposeWindow(compose, threadSourceEmail, isXsSheet, bindXsSheetClose)
const modalContent = (
<div
data-compose-window
className={cn(
"relative flex flex-col overflow-hidden bg-mail-surface text-foreground",
isInline
? "min-h-[360px] w-full rounded-xl border border-border shadow-none transition-shadow focus-within:shadow-[0_1px_4px_rgba(60,64,67,0.12)]"
: isXsSheet
? "h-full min-h-0 w-full max-w-none flex-1 rounded-none shadow-none"
: cn(
"rounded-t-lg shadow-[0_-2px_8px_rgba(0,0,0,0.08),_-4px_0_12px_rgba(0,0,0,0.12),_4px_0_12px_rgba(0,0,0,0.12)]",
compose.maximized
? readCoarsePointerMatches()
? "fixed inset-0 z-60 rounded-none"
: "fixed inset-12 z-60 rounded-lg"
: "h-[480px] w-[500px]"
)
)}
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
>
{/* Hidden file inputs */}
<input
ref={fileInputRef}
type="file"
multiple
className="hidden"
onChange={(e) => {
if (e.target.files && e.target.files.length > 0) {
addFiles(e.target.files)
e.target.value = ""
}
}}
/>
<input
ref={imageInputRef}
type="file"
multiple
accept="image/*"
className="hidden"
onChange={(e) => {
if (e.target.files && e.target.files.length > 0) {
addFiles(e.target.files)
e.target.value = ""
}
}}
/>
{/* Drop overlay */}
{isDragOver ? <ComposeDropOverlay /> : null}
{isInline ? (
<ComposeInlineRecipientHeader
compose={compose}
threadSourceEmail={threadSourceEmail}
recipientSummary={recipientSummary}
recipientsFocused={recipientsFocused}
showReplyAllInMenu={showReplyAllInMenu}
ThreadKindIcon={ThreadKindIcon}
onOpenInlinePreset={openInlinePreset}
onOpenDockFromInline={openDockFromInline}
onActivateRecipients={() => setRecipientsFocused(true)}
updateCompose={updateCompose}
recipientFieldsProps={recipientFieldsProps}
fieldsRef={fieldsRef}
inlineRecipientShellRef={inlineRecipientShellRef}
/>
) : isXsSheet ? (
<ComposeXsSheetHeader titleText={titleText} onClose={handleClose} />
) : (
<>
{/* Title bar */}
<ComposeDockTitleBar
titleText={titleText}
maximized={compose.maximized}
onMinimize={() => toggleMinimize(compose.id)}
onMaximize={() => toggleMaximize(compose.id)}
onClose={handleClose}
/>
</>
)}
{!isInline && (
<div ref={fieldsRef} className="flex shrink-0 flex-col">
<ComposeRecipientFields {...recipientFieldsProps} />
</div>
)}
{/* Editor */}
<div className="min-h-0 flex-1 overflow-y-auto">
<EditorContent editor={editor} />
</div>
<ComposeAttachmentsList
attachments={compose.attachments}
onRemove={removeAttachment}
/>
{showFormatting ? <FormattingToolbar editor={editor} /> : null}
<ComposeBottomToolbar
compose={compose}
editor={editor}
isEditingScheduled={isEditingScheduled}
showFormatting={showFormatting}
sendMenuOpen={sendMenuOpen}
setShowFormatting={setShowFormatting}
setSendMenuOpen={setSendMenuOpen}
handleSend={handleSend}
saveScheduledEdit={saveScheduledEdit}
sendScheduledFromEditNow={sendScheduledFromEditNow}
applyScheduledPlanAt={applyScheduledPlanAt}
submitScheduledSendAt={submitScheduledSendAt}
handleClose={handleClose}
fileInputRef={fileInputRef}
imageInputRef={imageInputRef}
/>
</div>
)
if (compose.minimized && !isInline && !isXsSheet) {
return (
<div
className={cn(
MAIL_COMPOSE_TITLEBAR_CLASS,
"h-9 w-[280px] cursor-pointer shadow-lg transition-shadow hover:shadow-xl"
)}
onClick={() => toggleMinimize(compose.id)}
>
<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()
toggleMaximize(compose.id)
}}
className={cn("flex h-6 w-6 items-center justify-center rounded-full", MAIL_ICON_BTN)}
>
<Maximize2 className="h-3.5 w-3.5" />
</button>
<button
type="button"
onClick={(e) => {
e.stopPropagation()
handleClose()
}}
className={cn("flex h-6 w-6 items-center justify-center rounded-full", MAIL_ICON_BTN)}
>
<X className="h-3.5 w-3.5" />
</button>
</div>
</div>
)
}
if (compose.maximized && !isInline && !isXsSheet) {
return (
<>
<div
className="fixed inset-0 z-55 bg-black/50"
onClick={() => toggleMaximize(compose.id)}
/>
{modalContent}
</>
)
}
return modalContent
}