From 6eb9ca412007639d3aec5ef9869b080e1ed87b99 Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Thu, 7 May 2026 10:15:44 +0200 Subject: [PATCH] =?UTF-8?q?feat(ui):=20GlossaryTerm=20=E2=80=94=20tooltip?= =?UTF-8?q?=20de=20d=C3=A9finition=20sur=20DSO=20/=20LME=20/=20Mise=20en?= =?UTF-8?q?=20demeure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quand un terme métier apparaît (DSO moyen, LME, mise en demeure…), un petit astérisque rubis à côté indique qu'il est hoverable. Au hover/focus clavier, une popover s'affiche avec la définition courte (qui ce fait, pourquoi ça compte, repère LME 30j). Implémentation : - components/ui/GlossaryTerm.tsx : wrap n'importe quel ReactNode + définition, utilise @radix-ui/react-tooltip (déjà dans la stack pour Dialog), Asterisk Lucide en marker, underline pointillée subtile pour signaler "interactif" - lib/glossary.tsx : définitions centralisées (DSO, LME, mise en demeure, encaissé, rubis) — single source of truth, ton produit cohérent - KpiCard.label / SummaryCard.label passent à React.ReactNode pour accepter le wrapping - Wiring : "DSO moyen" sur dashboard (KpiCard + titre du chart) et /insights (récap + titre du chart). LME aussi taggée dans le sous-titre du DSO chart. Aucun nouveau dep. Co-Authored-By: Claude Opus 4.7 --- apps/web/src/components/dashboard/KpiCard.tsx | 3 +- apps/web/src/components/ui/GlossaryTerm.tsx | 80 +++++++++++++++++++ apps/web/src/lib/glossary.tsx | 46 +++++++++++ apps/web/src/routes/_app/index.tsx | 11 ++- apps/web/src/routes/_app/insights.tsx | 19 ++++- 5 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 apps/web/src/components/ui/GlossaryTerm.tsx create mode 100644 apps/web/src/lib/glossary.tsx diff --git a/apps/web/src/components/dashboard/KpiCard.tsx b/apps/web/src/components/dashboard/KpiCard.tsx index 50ec6c1..3f41caa 100644 --- a/apps/web/src/components/dashboard/KpiCard.tsx +++ b/apps/web/src/components/dashboard/KpiCard.tsx @@ -15,7 +15,8 @@ import { cn } from "@/lib/utils"; * - On laisse l'utilisateur déclarer l'intent. */ type KpiCardProps = { - label: string; + /** Texte ou node — accepte un GlossaryTerm si la métrique a une définition. */ + label: React.ReactNode; value: string; delta?: string; /** Sens du delta affiché (sert juste à colorer subtilement). Default neutral. */ diff --git a/apps/web/src/components/ui/GlossaryTerm.tsx b/apps/web/src/components/ui/GlossaryTerm.tsx new file mode 100644 index 0000000..724a5a1 --- /dev/null +++ b/apps/web/src/components/ui/GlossaryTerm.tsx @@ -0,0 +1,80 @@ +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { Asterisk } from "lucide-react"; +import { cn } from "@/lib/utils"; + +/** + * Term wrapper — affiche un terme avec une petite astérisque cliquable/hoverable + * qui révèle sa définition. Pour le glossaire métier (DSO, LME, mise en demeure…). + * + * Pas de tooltip natif (`title=`) parce qu'il s'affiche en gris système et + * casse la DA. Radix Tooltip est déjà dans la stack pour Dialog, on en + * profite ici. + */ +type GlossaryTermProps = { + /** Le mot/expression visible (ex. "DSO moyen"). */ + children: React.ReactNode; + /** Définition affichée dans le tooltip. */ + definition: React.ReactNode; + /** Côté du tooltip (défaut bottom). */ + side?: "top" | "right" | "bottom" | "left"; + /** Classe sur le wrapper. */ + className?: string; + /** Position de l'astérisque (défaut : after = à droite). */ + marker?: "before" | "after"; +}; + +export function GlossaryTerm({ + children, + definition, + side = "bottom", + className, + marker = "after", +}: GlossaryTermProps) { + const star = ( +