"use client"; import { useState, useRef, useCallback } from "react"; import { createPortal } from "react-dom"; import { AlertTriangle } from "lucide-react"; import type { DriverData } from "@/lib/types"; interface Props { drivers: DriverData } function fmt(v: number | null, dec: number, unit = "") { if (v === null) return "—"; return `${v.toLocaleString("fr-FR", { minimumFractionDigits: dec, maximumFractionDigits: dec })}${unit}`; } interface TooltipState { x: number; y: number } function Tile({ label, value, dec = 2, unit = "", delta, deltaPct, deltaDec, tooltip, accent }: { label: string; value: number | null; dec?: number; unit?: string; delta?: number | null; deltaPct?: boolean; deltaDec?: number; tooltip?: string; accent?: "red" | "green" | "amber"; }) { const [tip, setTip] = useState(null); const ref = useRef(null); const show = useCallback(() => { if (!ref.current) return; const r = ref.current.getBoundingClientRect(); setTip({ x: r.left + r.width / 2, y: r.top }); }, []); const hide = useCallback(() => setTip(null), []); const pos = delta != null && delta > 0; const neg = delta != null && delta < 0; const dColor = pos ? "text-emerald-400" : neg ? "text-red-400" : "text-slate-500"; const dArrow = pos ? "▲" : neg ? "▼" : ""; const dFmt = delta != null ? `${Math.abs(delta).toFixed(deltaDec ?? dec)}${deltaPct ? "%" : ""}` : null; const borderCls = accent === "red" ? "border-red-500/30 bg-red-500/5" : accent === "green" ? "border-emerald-500/30 bg-emerald-500/5" : accent === "amber" ? "border-amber-500/30 bg-amber-500/5" : "border-slate-800/60 bg-slate-900/40"; return ( <>
{label}
{fmt(value, dec, unit)} {dFmt && ( {dArrow}{dFmt} )}
{tip && typeof document !== "undefined" && createPortal(
{tooltip}
, document.body, )} ); } function GroupLabel({ label }: { label: string }) { return ( {label} ); } export default function DriversBar({ drivers }: Props) { const { vix, vixDelta, sp500, sp500ChangePct, btc, btcDeltaPct, btcChange24h, hySpread, igSpread, us10y, us2y, curveSlope, gold, goldDeltaPct, silver, silverDeltaPct, brent, brentDelta, brentDeltaPct, wti, wtiDelta, wtiDeltaPct, } = drivers; const riskOff = (vix ?? 0) > 25 || (hySpread ?? 0) > 500; return (
{/* Ligne titre + alerte */}
Drivers Globaux {riskOff && (
Risk-Off
)}
{/* Grille responsive — 2 lignes sur desktop, s'adapte sur mobile */}
{/* ── Sentiment ─────────────────────────────────────────── */} 25 ? "red" : undefined} tooltip="Clôture actuelle − clôture précédente (Yahoo Finance)." /> {/* ── Crédit ────────────────────────────────────────────── */}
500 ? "red" : (hySpread ?? 0) > 400 ? "amber" : undefined} tooltip="High Yield spread vs Treasuries US. >500 bps = risk-off fort." /> {/* ── Taux ──────────────────────────────────────────────── */}
{/* ── Commodités ────────────────────────────────────────── */}
0 ? "+" : ""}${brentDelta.toFixed(2)} $` : ""}`} /> 0 ? "+" : ""}${wtiDelta.toFixed(2)} $` : ""}`} />
); }