116 lines
3.6 KiB
TypeScript
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>
|
|
))}
|
|
</>
|
|
)
|
|
}
|