169 lines
6.2 KiB
TypeScript
169 lines
6.2 KiB
TypeScript
"use client"
|
|
|
|
import type { MouseEvent, ReactNode } from "react"
|
|
import { useEffect, useState } from "react"
|
|
import {
|
|
HoverCard,
|
|
HoverCardContent,
|
|
HoverCardTrigger,
|
|
} from "@/components/ui/hover-card"
|
|
import { Button } from "@/components/ui/button"
|
|
import { cn } from "@/lib/utils"
|
|
import {
|
|
avatarColor,
|
|
cleanSenderName,
|
|
resolveSenderEmail,
|
|
senderInitial,
|
|
} from "@/lib/sender-display"
|
|
import {
|
|
Calendar,
|
|
ExternalLink,
|
|
Mail,
|
|
MessageSquare,
|
|
UserPlus,
|
|
Video,
|
|
} from "lucide-react"
|
|
import { useComposeActions } from "@/lib/compose-context"
|
|
|
|
export interface ContactHoverCardProps {
|
|
/** Champ expéditeur brut (liste, conversation, etc.) */
|
|
displayName: string
|
|
email?: string
|
|
children: ReactNode
|
|
className?: string
|
|
onTriggerClick?: (e: MouseEvent<HTMLSpanElement>) => void
|
|
align?: "start" | "center" | "end"
|
|
side?: "top" | "right" | "bottom" | "left"
|
|
}
|
|
|
|
export function ContactHoverCard({
|
|
displayName,
|
|
email: emailOverride,
|
|
children,
|
|
className,
|
|
onTriggerClick,
|
|
align = "start",
|
|
side = "bottom",
|
|
}: ContactHoverCardProps) {
|
|
const { openComposeWithInitial } = useComposeActions()
|
|
const [open, setOpen] = useState(false)
|
|
const name = cleanSenderName(displayName)
|
|
const email = resolveSenderEmail(displayName, emailOverride)
|
|
const color = avatarColor(name)
|
|
|
|
useEffect(() => {
|
|
if (!open) return
|
|
const close = () => setOpen(false)
|
|
const opts: AddEventListenerOptions = { capture: true, passive: true }
|
|
window.addEventListener("scroll", close, opts)
|
|
window.addEventListener("wheel", close, opts)
|
|
window.addEventListener("touchmove", close, opts)
|
|
return () => {
|
|
window.removeEventListener("scroll", close, opts)
|
|
window.removeEventListener("wheel", close, opts)
|
|
window.removeEventListener("touchmove", close, opts)
|
|
}
|
|
}, [open])
|
|
|
|
return (
|
|
<HoverCard open={open} onOpenChange={setOpen} openDelay={1000} closeDelay={150}>
|
|
<HoverCardTrigger asChild>
|
|
<span
|
|
role="presentation"
|
|
tabIndex={0}
|
|
className={cn(
|
|
"inline min-w-0 max-w-full cursor-default text-inherit outline-none focus-visible:ring-2 focus-visible:ring-[#1a73e8]/30 focus-visible:ring-offset-1 rounded-sm",
|
|
className
|
|
)}
|
|
onClick={(e) => {
|
|
onTriggerClick?.(e)
|
|
}}
|
|
>
|
|
{children}
|
|
</span>
|
|
</HoverCardTrigger>
|
|
<HoverCardContent
|
|
side={side}
|
|
align={align}
|
|
sideOffset={8}
|
|
className={cn(
|
|
"min-w-[380px] w-max max-w-[min(440px,calc(100vw-24px))] rounded-2xl border border-[#e8eaed] bg-white p-0 shadow-lg",
|
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 outline-hidden"
|
|
)}
|
|
>
|
|
<div className="p-4 pb-3">
|
|
<div className="relative flex items-start gap-3">
|
|
<div
|
|
className="flex h-14 w-14 shrink-0 items-center justify-center rounded-full text-lg font-medium text-white"
|
|
style={{ backgroundColor: color }}
|
|
>
|
|
{senderInitial(name)}
|
|
</div>
|
|
<div className="min-w-0 flex-1 pr-8">
|
|
<p className="truncate text-base font-semibold leading-tight text-[#202124]">{name}</p>
|
|
<p className="truncate text-sm leading-tight text-[#5f6368]">{email}</p>
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
className="absolute right-0 top-0 h-8 w-8 shrink-0 text-[#5f6368] hover:bg-[#f1f3f4]"
|
|
aria-label="Ajouter aux contacts"
|
|
>
|
|
<UserPlus className="h-[18px] w-[18px]" strokeWidth={1.5} />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-nowrap items-center gap-2 px-4 pb-4">
|
|
<button
|
|
type="button"
|
|
className="inline-flex h-9 shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-full bg-[#d3e3fd] px-5 text-sm font-medium text-[#001d35] transition-colors hover:bg-[#c4d9fc]"
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
openComposeWithInitial({
|
|
to: [{ name, email }],
|
|
})
|
|
}}
|
|
>
|
|
<Mail className="h-[18px] w-[18px] shrink-0" strokeWidth={1.5} />
|
|
Envoyer un e-mail
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-[#dadce0] bg-white text-[#5f6368] transition-colors hover:bg-[#f1f3f4]"
|
|
aria-label="Message"
|
|
>
|
|
<MessageSquare className="h-[18px] w-[18px]" strokeWidth={1.5} />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-[#dadce0] bg-white text-[#5f6368] transition-colors hover:bg-[#f1f3f4]"
|
|
aria-label="Visioconférence"
|
|
>
|
|
<Video className="h-[18px] w-[18px]" strokeWidth={1.5} />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-[#dadce0] bg-white text-[#5f6368] transition-colors hover:bg-[#f1f3f4]"
|
|
aria-label="Planifier"
|
|
>
|
|
<Calendar className="h-[18px] w-[18px]" strokeWidth={1.5} />
|
|
<span className="absolute right-1 top-1 size-1.5 rounded-full bg-[#1a73e8]" aria-hidden />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="border-t border-[#eceff1] px-3 pb-3 pt-2">
|
|
<button
|
|
type="button"
|
|
className="flex w-full items-center justify-center gap-2 rounded-lg bg-[#f1f3f4] px-3 py-2.5 text-sm font-medium text-[#1a73e8] transition-colors hover:bg-[#e8eaed]"
|
|
>
|
|
Ouvrir la vue détaillée
|
|
<ExternalLink className="h-4 w-4 shrink-0" strokeWidth={1.5} />
|
|
</button>
|
|
</div>
|
|
</HoverCardContent>
|
|
</HoverCard>
|
|
)
|
|
}
|