Some checks are pending
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Waiting to run
168 lines
5.1 KiB
TypeScript
168 lines
5.1 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useRef, useState } from "react";
|
|
import Image from "next/image";
|
|
import type { LastYearWinner } from "@/content/last-year-winners";
|
|
import { LastYearWinnerTip } from "@/components/home/LastYearWinnerTip";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
const GRV_LOGO = "/branding/logo-icon.png";
|
|
|
|
type Props = {
|
|
company: LastYearWinner;
|
|
tipKey: string;
|
|
isOpen: boolean;
|
|
isPinned: boolean;
|
|
onPin: () => void;
|
|
onUnpin: () => void;
|
|
onHover: (key: string | null) => void;
|
|
variant?: "on-green" | "on-light";
|
|
className?: string;
|
|
};
|
|
|
|
export function LastYearWinnerMark({
|
|
company,
|
|
tipKey,
|
|
isOpen,
|
|
isPinned,
|
|
onPin,
|
|
onUnpin,
|
|
onHover,
|
|
variant = "on-light",
|
|
className,
|
|
}: Props) {
|
|
const leaveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
const [failed, setFailed] = useState(false);
|
|
const src = company.logoSrc ?? GRV_LOGO;
|
|
const showImage = !failed && src;
|
|
const logoOnly = !company.name;
|
|
const onLight = variant === "on-light";
|
|
|
|
const clearLeaveTimer = useCallback(() => {
|
|
if (leaveTimer.current) {
|
|
clearTimeout(leaveTimer.current);
|
|
leaveTimer.current = null;
|
|
}
|
|
}, []);
|
|
|
|
const handleEnter = useCallback(() => {
|
|
clearLeaveTimer();
|
|
onHover(tipKey);
|
|
}, [clearLeaveTimer, onHover, tipKey]);
|
|
|
|
const handleLeave = useCallback(() => {
|
|
if (isPinned) return;
|
|
clearLeaveTimer();
|
|
leaveTimer.current = setTimeout(() => onHover(null), 120);
|
|
}, [clearLeaveTimer, isPinned, onHover]);
|
|
|
|
const handleClick = useCallback(
|
|
(e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
onPin();
|
|
},
|
|
[onPin]
|
|
);
|
|
|
|
const handleOpenChange = useCallback(
|
|
(open: boolean) => {
|
|
if (!open && !isPinned) onHover(null);
|
|
},
|
|
[isPinned, onHover]
|
|
);
|
|
|
|
return (
|
|
<Popover open={isOpen} onOpenChange={handleOpenChange}>
|
|
<PopoverTrigger asChild>
|
|
<button
|
|
type="button"
|
|
data-winner-interactive
|
|
className={cn(
|
|
"shrink-0 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-[#1a5c38]/40",
|
|
isOpen && "relative z-30"
|
|
)}
|
|
aria-label={
|
|
company.name
|
|
? `${company.name} — tap to read impact details`
|
|
: "GRV alumni — tap to read impact details"
|
|
}
|
|
aria-expanded={isOpen}
|
|
onClick={handleClick}
|
|
onMouseEnter={handleEnter}
|
|
onMouseLeave={handleLeave}
|
|
onFocus={handleEnter}
|
|
onBlur={handleLeave}
|
|
>
|
|
<div
|
|
className={cn(
|
|
"flex h-10 shrink-0 items-center gap-2 rounded-lg border px-2.5 shadow-sm transition-colors",
|
|
onLight
|
|
? "border-[#1a5c38]/15 bg-white/95 hover:border-[#1a5c38]/30 hover:bg-white"
|
|
: "border-white/25 bg-white/90 hover:border-white/50 hover:bg-white",
|
|
logoOnly && "px-2",
|
|
isOpen &&
|
|
(onLight
|
|
? "border-[#1a5c38]/40 ring-2 ring-[#1a5c38]/15"
|
|
: "border-white/60 ring-2 ring-white/25"),
|
|
isPinned && "border-[#ffb300]/70 ring-2 ring-[#ffb300]/25",
|
|
className
|
|
)}
|
|
>
|
|
<div className="relative flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-md bg-white">
|
|
{showImage ? (
|
|
<Image
|
|
src={src}
|
|
alt=""
|
|
width={28}
|
|
height={28}
|
|
className="size-7 object-contain p-0.5"
|
|
onError={() => setFailed(true)}
|
|
/>
|
|
) : company.initials ? (
|
|
<span className="text-[10px] font-bold leading-none text-[#1a5c38]">
|
|
{company.initials}
|
|
</span>
|
|
) : (
|
|
<Image
|
|
src={GRV_LOGO}
|
|
alt=""
|
|
width={28}
|
|
height={28}
|
|
className="size-7 object-contain p-0.5"
|
|
/>
|
|
)}
|
|
</div>
|
|
{company.name ? (
|
|
<span className="max-w-[7.5rem] truncate text-[11px] font-medium text-[#1a5c38]">
|
|
{company.name}
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
</button>
|
|
</PopoverTrigger>
|
|
{isOpen ? (
|
|
<PopoverContent
|
|
side="top"
|
|
align="center"
|
|
sideOffset={8}
|
|
collisionPadding={12}
|
|
data-winner-interactive
|
|
className="z-50 w-72 max-w-[calc(100vw-2rem)] shrink-0 overflow-hidden border-[#1a5c38]/15 bg-white p-0 shadow-lg"
|
|
onMouseEnter={handleEnter}
|
|
onMouseLeave={handleLeave}
|
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
onPointerDownOutside={(e) => {
|
|
if (isPinned) e.preventDefault();
|
|
}}
|
|
onInteractOutside={(e) => {
|
|
if (isPinned) e.preventDefault();
|
|
}}
|
|
>
|
|
<LastYearWinnerTip company={company} onClose={onUnpin} showClose={isPinned} />
|
|
</PopoverContent>
|
|
) : null}
|
|
</Popover>
|
|
);
|
|
}
|