From 7f5ef9346981faa758b361793af61fd9169d8812 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 08:51:48 +0000 Subject: [PATCH] =?UTF-8?q?fix(grille):=20d=C3=A9filement=20horizontal=20v?= =?UTF-8?q?isible=20et=20barre=20du=20bas=20dans=20le=20flux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les deux symptômes n'en faisaient qu'un. L'ascenseur horizontal EXISTAIT déjà (conteneur en overflow-auto, table en min-width ~2600 px), mais il était caché sous la carte de synthèse. Celle-ci était en `position: fixed` et ne réservait donc aucune hauteur : seul un `pb-12` posé sur la page compensait, trop court d'une quarantaine de pixels — exactement la bande où se trouvent la barre de défilement et la dernière ligne. Son `left-[264px]` était en prime calé sur une barre latérale dépliée, alors qu'elle est repliée par défaut : 200 px de vide à gauche, qui écrasaient ses statistiques en hauteur. Elle est remise dans le flux : la grille lui cède exactement la place nécessaire, et il n'y a plus de valeur magique à tenir en phase avec la sidebar. L'ascenseur devient visible : la règle générale (8 px, rail transparent, pouce à 12 % d'opacité) convient aux panneaux où le défilement est accessoire, pas à la Grille où il est l'outil de navigation principal. Classe `.grid-scroll` dédiée, avec rail dessiné, pouce saisissable, et prise en charge de Firefox que les sélecteurs -webkit- ignorent. Corrigé aussi : - totalWidth lisait `columnDef.size` au lieu de `getSize()`. Le redimensionnement de colonnes étant actif et persisté, la largeur plancher restait figée sur l'ancienne somme : dernières colonnes rognées, course de défilement trop courte. - Conteneur non focusable : flèches, Origine et Fin ne défilaient pas la grille. - Barre de filtres qui passait sur deux lignes sous ~1200 px, volant 48 px au tableau : largeurs réduites en dessous de xl. - Barre d'actions groupées sans flex-wrap, qui s'écrasait sur petit écran. - Double croix dans la recherche : `type="search"` ajoutait la croix native du navigateur par-dessus le bouton maison. Masquage local — la recherche de l'en-tête n'a pas de bouton propre et garde donc le sien. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675 --- src/app/globals.css | 41 +++++++++++++++++++ .../grid/components/bulk-action-toolbar.tsx | 2 +- .../grid/components/floating-summary-bar.tsx | 24 +++++++++-- src/features/grid/components/grid-client.tsx | 2 +- .../grid/components/grid-filter-bar.tsx | 13 ++++-- src/features/grid/components/heatmap-grid.tsx | 12 +++++- 6 files changed, 83 insertions(+), 11 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index c93cee5..9946d08 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -238,6 +238,47 @@ background: rgba(0, 0, 0, 0.18); } +/* ── Défilement de la Grille ── + La règle ci-dessus (8 px, rail transparent, pouce très pâle) convient aux + panneaux où l'ascenseur est accessoire. Dans la Grille, le tableau fait + ~2600 px de large : l'ascenseur HORIZONTAL est l'outil de navigation + principal, et un rail invisible ne dit pas qu'il reste des colonnes à + droite. On lui donne donc un rail dessiné et un pouce saisissable. + `scrollbar-*` couvre Firefox, que les sélecteurs -webkit- ignorent. */ +.grid-scroll { + scrollbar-width: auto; + scrollbar-color: var(--text-muted) var(--bg-elevated); +} + +.grid-scroll::-webkit-scrollbar { + width: 10px; + height: 14px; +} + +.grid-scroll::-webkit-scrollbar-track:horizontal { + background: var(--bg-elevated); + border-top: 1px solid var(--border); +} + +.grid-scroll::-webkit-scrollbar-thumb { + background: var(--text-muted); + border-radius: 99px; + /* Bordure transparente + background-clip : le pouce garde de l'air dans son + rail sans qu'on ait à jouer sur des marges, impossibles ici. */ + border: 3px solid transparent; + background-clip: padding-box; +} + +.grid-scroll::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); + background-clip: padding-box; +} + +/* Le coin entre les deux ascenseurs, sinon laissé en blanc par le navigateur. */ +.grid-scroll::-webkit-scrollbar-corner { + background: var(--bg-elevated); +} + /* ── Heatmap cells ── */ .heatmap-zero { background: var(--bg-elevated); diff --git a/src/features/grid/components/bulk-action-toolbar.tsx b/src/features/grid/components/bulk-action-toolbar.tsx index c2d3cdd..7515515 100644 --- a/src/features/grid/components/bulk-action-toolbar.tsx +++ b/src/features/grid/components/bulk-action-toolbar.tsx @@ -32,7 +32,7 @@ export function BulkActionToolbar({ selectedCodeins, onClearSelection }: BulkAct }; return ( -
+
{selectedCodeins.length} sélectionné{selectedCodeins.length > 1 ? "s" : ""} diff --git a/src/features/grid/components/floating-summary-bar.tsx b/src/features/grid/components/floating-summary-bar.tsx index dbf5b7d..1cd521f 100644 --- a/src/features/grid/components/floating-summary-bar.tsx +++ b/src/features/grid/components/floating-summary-bar.tsx @@ -19,9 +19,12 @@ function Stat({ label, value, sub }: { label: string; value: string; sub?: strin // Determine color based on label to match the prototype const valColor = label.includes("CA") || label.includes("Marge") ? "text-emerald-600 dark:text-emerald-400" : "text-slate-900 dark:text-white"; return ( -
- {label} - {value} + // Barre maintenant dans le flux : chaque pixel de hauteur est pris au + // tableau. Ces chiffres sont déjà repris en gros dans l'en-tête de page, + // ils n'ont pas besoin d'y être une seconde fois en 20 px. +
+ {label} + {value} {sub && {sub}}
); @@ -153,7 +156,20 @@ export function FloatingSummaryBar() { return (
m.code === magasin)?.nom || "National (Total)"; return ( -
+
{/* Header — chrome raffiné (eyebrow teal + fournisseur héro + pastilles métriques) */}
diff --git a/src/features/grid/components/grid-filter-bar.tsx b/src/features/grid/components/grid-filter-bar.tsx index 38f2942..bc5cdbf 100644 --- a/src/features/grid/components/grid-filter-bar.tsx +++ b/src/features/grid/components/grid-filter-bar.tsx @@ -134,7 +134,7 @@ export function GridFilterBar({ fournisseurs, magasins }: GridFilterBarProps) { fournisseurs={fournisseurs} selectedCode={searchParams.get("fournisseur")} onSelect={handleSupplierSelect} - className="w-[280px]" + className="w-[200px] xl:w-[280px]" /> {/* Store Selector */} @@ -142,7 +142,7 @@ export function GridFilterBar({ fournisseurs, magasins }: GridFilterBarProps) { magasins={magasins} selectedCode={activeMagasin === "TOTAL" ? null : activeMagasin} onSelect={handleStoreSelect} - className="w-[220px]" + className="w-[170px] xl:w-[220px]" /> {/* Search */} @@ -156,12 +156,19 @@ export function GridFilterBar({ fournisseurs, magasins }: GridFilterBarProps) { placeholder="Rechercher (code, désignation...)" value={searchValue} onChange={(e) => setSearchValue(e.target.value)} - className="apple-input w-72 pr-8" + // La croix native de `type="search"` (WebKit/Blink) est masquée : + // ce champ a déjà son propre bouton d'effacement juste à droite, + // et les deux apparaissaient côte à côte. On garde `type="search"` + // pour la sémantique ; le masquage est local, la recherche de + // l'en-tête n'ayant pas de bouton maison, elle garde le sien. + className="apple-input w-52 xl:w-72 pr-8 [&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-cancel-button]:hidden" style={{ paddingLeft: "32px" }} /> {searchValue && (