"use client"; import { useState, useRef, useCallback } from "react"; import { createPortal } from "react-dom"; import type { DriverData } from "@/lib/types"; interface Props { drivers: DriverData } // ── Helpers ─────────────────────────────────────────────────────────────────── function fmt(v: number | null, dec: number, unit = "") { if (v === null) return "—"; return `${v.toLocaleString("fr-FR", { minimumFractionDigits: dec, maximumFractionDigits: dec })}${unit}`; } function DeltaTag({ delta, pct = false, dec = 2 }: { delta: number | null; pct?: boolean; dec?: number }) { if (delta === null) return null; const pos = delta > 0; const neg = delta < 0; const color = pos ? "text-emerald-600" : neg ? "text-red-500" : "text-gray-400"; const arrow = pos ? "▲" : neg ? "▼" : "▬"; return ( {arrow}{Math.abs(delta).toFixed(dec)}{pct ? "%" : ""} ); } // ── Tooltip via portal (évite le clipping de overflow-x-auto) ───────────────── interface TooltipState { x: number; y: number } function TooltipLabel({ label, tooltip }: { label: string; tooltip: string }) { 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), []); return ( <> {label} i {tip && typeof document !== "undefined" && createPortal(
{tooltip} {/* Flèche en bas */}
, document.body, )} ); } /** Un indicateur : label · valeur · delta — hauteur fixe garantie */ function M({ label, value, dec = 2, unit = "", delta, deltaPct, deltaDec, tooltip, }: { label: string; value: number | null; dec?: number; unit?: string; delta?: number | null; deltaPct?: boolean; deltaDec?: number; tooltip?: string; }) { return (
{/* Ligne 1 — label */}
{tooltip ? : {label} }
{/* Ligne 2 — valeur */}
{fmt(value, dec, unit)}
{/* Ligne 3 — delta (slot réservé = alignement garanti) */}
{delta !== undefined && delta !== null && ( )}
); } /** Séparateur vertical */ function VSep() { return
; } // ── Composant principal ─────────────────────────────────────────────────────── export default function DriversBar({ drivers }: Props) { const { vix, vixDelta, sp500, sp500ChangePct, btc, btcChange24h, hySpread, igSpread, us10y, us2y, curveSlope, gold, goldDelta, silver, silverDelta, brent, brentDelta, wti, wtiDelta, } = drivers as DriverData & { dxy?: number | null }; const dxy = (drivers as DriverData & { dxy?: number | null }).dxy ?? null; const riskOff = (vix ?? 0) > 25 || (hySpread ?? 0) > 500; return (
{/* En-tête */}
Drivers globaux {riskOff && ( ⚠ Risk-Off )}
{/* ── Ligne unique — tous les indicateurs ── */}
{/* Sentiment / Risk-On */} {/* Crédit */} {/* Taux & FX */} {/* Commodités */}
); }