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

116 lines
3.6 KiB
TypeScript

"use client"
import { useRef, useCallback, useMemo } from "react"
import { useIsXs } from "@/hooks/use-xs"
import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet"
import { useComposeWindows } from "@/lib/compose-context"
import { cn } from "@/lib/utils"
import { ComposeWindow } from "@/components/gmail/compose/compose-window"
export function ComposeModalManager() {
const { composeWindows } = useComposeWindows()
const isXs = useIsXs()
const nonMaximized = composeWindows.filter(
(w) => !w.maximized && w.placement !== "inline"
)
const maximized = composeWindows.filter((w) => w.maximized && w.placement !== "inline")
const xsSheetCloseRef = useRef<(() => void) | null>(null)
const bindXsSheetClose = useCallback((fn: (() => void) | null) => {
xsSheetCloseRef.current = fn
}, [])
/** Une seule fenêtre dock visible en xs : la plus récente (comportement type pile). */
const xsActiveDock =
isXs && nonMaximized.length > 0 ? nonMaximized[nonMaximized.length - 1] : null
const handleXsSheetOpenChange = useCallback((open: boolean) => {
if (!open) {
xsSheetCloseRef.current?.()
}
}, [])
const MODAL_WIDTH = 500
const MINIMIZED_WIDTH = 280
const GAP = 12
const RIGHT_OFFSET = 80
const positions = useMemo(() => {
const reversed = [...nonMaximized].reverse()
const result: { id: string; right: number; hidden: boolean }[] = []
let cursor = RIGHT_OFFSET
for (let i = 0; i < reversed.length; i++) {
const w = reversed[i]
const width = w.minimized ? MINIMIZED_WIDTH : MODAL_WIDTH
result.push({
id: w.id,
right: cursor,
hidden: i >= 2 && !w.minimized,
})
cursor += width + GAP
}
return result
}, [nonMaximized])
if (isXs) {
return (
<>
<Sheet open={xsActiveDock != null} onOpenChange={handleXsSheetOpenChange}>
<SheetContent
side="bottom"
hideClose
overlayClassName="z-[60]"
className="z-[61] h-[100dvh] max-h-[100dvh] w-full gap-0 rounded-none border-0 p-0 shadow-none duration-300 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom overflow-hidden pb-[env(safe-area-inset-bottom)]"
>
<SheetTitle className="sr-only">
{(xsActiveDock?.subject ?? "").trim() || "Nouveau message"}
</SheetTitle>
{xsActiveDock ? (
<ComposeWindow
key={xsActiveDock.id}
compose={xsActiveDock}
isXsSheet
bindXsSheetClose={bindXsSheetClose}
/>
) : null}
</SheetContent>
</Sheet>
{maximized.map((compose) => (
<div key={compose.id} className="pointer-events-auto">
<ComposeWindow compose={compose} />
</div>
))}
</>
)
}
return (
<>
{nonMaximized.map((compose) => {
const pos = positions.find((p) => p.id === compose.id)
if (!pos) return null
return (
<div
key={compose.id}
className={cn(
"pointer-events-auto fixed bottom-0 z-50 transition-all duration-300",
pos.hidden && "invisible"
)}
style={{ right: pos.right }}
>
<ComposeWindow compose={compose} />
</div>
)
})}
{maximized.map((compose) => (
<div key={compose.id} className="pointer-events-auto">
<ComposeWindow compose={compose} />
</div>
))}
</>
)
}