import { useTranslation } from "@browser-skill/i18n/react"; import { RiCloseLine } from "@remixicon/react"; import { type TransitionEvent, useEffect, useRef, useState } from "react"; import logoUrl from "../../assets/logo.png"; export interface BorrowRequestData { id: string; isActiveTab: boolean; tabTitle: string; timeoutMs: number; onAllow: () => void; onDeny: () => void; } interface Props { requests: BorrowRequestData[]; } const EXIT_ANIMATION_MS = 150; export function BorrowConfirmationOverlay({ requests }: Props) { const activeRequest = requests.find((r) => r.isActiveTab); const inactiveRequests = requests.filter((r) => !r.isActiveTab); return ( <> {activeRequest && ( )} {inactiveRequests.length > 0 && ( {inactiveRequests.map((req) => ( ))} )} ); } function BorrowBackdrop({ children }: { children: React.ReactNode }) { return (
{children}
); } function BorrowToastStack({ children }: { children: React.ReactNode }) { return (
{children}
); } /** Matches BorrowProgressBar / CountdownRing transition duration (duration-1000). */ const PROGRESS_TRANSITION_MS = 1000; function BorrowRequestItem({ request, isModal }: { request: BorrowRequestData; isModal: boolean }) { const { t } = useTranslation("extension"); const totalSeconds = Math.ceil(request.timeoutMs / 1000); const [secondsLeft, setSecondsLeft] = useState(totalSeconds); const [exiting, setExiting] = useState(false); const [awaitingAutoDeny, setAwaitingAutoDeny] = useState(false); const settledRef = useRef(false); const onAllowRef = useRef(request.onAllow); const onDenyRef = useRef(request.onDeny); onAllowRef.current = request.onAllow; onDenyRef.current = request.onDeny; function triggerAllow() { if (settledRef.current) return; settledRef.current = true; setAwaitingAutoDeny(false); setExiting(true); setTimeout(() => onAllowRef.current(), EXIT_ANIMATION_MS); } function handleAllow() { triggerAllow(); } function handleDeny() { if (settledRef.current) return; settledRef.current = true; setAwaitingAutoDeny(false); setExiting(true); setTimeout(() => onDenyRef.current(), EXIT_ANIMATION_MS); } useEffect(() => { if (secondsLeft <= 0) { if (!settledRef.current) { setAwaitingAutoDeny(true); } return; } const id = setTimeout(() => setSecondsLeft((s) => s - 1), 1000); return () => clearTimeout(id); }, [secondsLeft]); // Prefer transitionend for visual sync, but fall back to a timer so // background tabs / suppressed CSS transitions still auto-deny. useEffect(() => { if (!awaitingAutoDeny || settledRef.current) return; const id = setTimeout(() => { handleDeny(); }, PROGRESS_TRANSITION_MS); return () => clearTimeout(id); }, [awaitingAutoDeny]); const progress = secondsLeft / totalSeconds; const truncatedTitle = request.tabTitle.length > 30 ? `${request.tabTitle.slice(0, 30)}…` : request.tabTitle; const cardClass = `transition-opacity duration-150 ease-out ${exiting ? "opacity-0" : "opacity-100"}`; if (isModal) { return (
browser-skill {t("borrowConfirmation.title")}

{t("borrowConfirmation.body")}

{ if (!awaitingAutoDeny || secondsLeft !== 0) return; if (propertyName !== "stroke-dashoffset") return; handleDeny(); }} /> {t("borrowConfirmation.autoDeny", { count: secondsLeft })}
); } return (

{t("borrowConfirmation.targetTab")}
"{truncatedTitle}"

{ if (!awaitingAutoDeny || secondsLeft !== 0) return; if (event.propertyName !== "width") return; handleDeny(); }} />
); } function BorrowToastHeader({ onDeny }: { onDeny: () => void }) { const { t } = useTranslation("extension"); return (
browser-skill {t("borrowConfirmation.titleInactive")}
); } function BorrowProgressBar({ progress, onProgressTransitionEnd, }: { progress: number; onProgressTransitionEnd?: (event: TransitionEvent) => void; }) { return (
); } function ActionButtons({ onDeny, onAllow, gapClass = "gap-2.5", }: { onDeny: () => void; onAllow: () => void; gapClass?: string; }) { const { t } = useTranslation("extension"); return (
); } function CountdownRing({ progress, seconds, onProgressTransitionEnd, }: { progress: number; seconds: number; onProgressTransitionEnd?: (propertyName: string) => void; }) { const r = 28; const circ = 2 * Math.PI * r; const offset = circ * (1 - progress); return ( onProgressTransitionEnd?.(event.propertyName)} /> {seconds} ); }