250 lines
7.3 KiB
TypeScript
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
|
|
}
|