Documentation Index
Fetch the complete documentation index at: https://snakysec.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
UI patterns — règles d’application
Ce document tranche les conventions UI récurrentes pour éviter le drift visuel entre pages. Toute nouvelle page DOIT suivre ces règles.Card-as-link (REC-IMP-05)
Règle absolue : une<Card> ne doit jamais être un lien si elle contient
elle-même un bouton d’action.
Pattern A — Card cliquable (cards de listing / hub)
La card entière est un<Link>, aucune action interne.
<Link> parent
(<div className="absolute right-2 top-2 z-10">) et l’icône MoreHorizontal
fait e.preventDefault(); e.stopPropagation() sur le clic pour ne pas
naviguer la card.
Voir client-card-kebab.tsx pour le pattern de référence.
Pattern B — Card informative (avec actions internes)
La card n’est pas un lien ; chaque action porte son propre<Link> ou
<Button>. Le <Link> “View all” dans le <CardHeader> est une action
secondaire, pas le primary action de la card.
<Card> dans un <Link> quand la card contient
un <Button> actionnable. La zone du bouton devient ambiguë (clique-t-on le
bouton ou la card ?), et certains navigateurs propagent le clic.
Sidebar active state (REC-MIN-09)
Toute sidebar (global, portal, DR runbook, settings) utilise la classe canonique exportée parlib/sidebar-active.ts :
Breadcrumb obligatoire (REC-IMP-01)
Toute page de profondeur ≥2 (i.e. accessible par >1 click depuis le hub) DOIT rendre un<PageBreadcrumb> (components/page-breadcrumb.tsx).
Le back arrow seul est interdit comme nav primaire — les rares cas où il
reste utile (e.g. retour au stepper sur une étape onboarding) le font en
plus du breadcrumb, pas à la place.
Filtres URL-synced (REC-IMP-06)
Toute page avec des filtres utilise<UrlFilters> (components/url-filters.tsx)
plutôt qu’une implémentation maison. La définition est déclarative :
usePathname() (ne hardcode jamais le path), URL-sync, sentinel all,
et reset auto de ?page=1 sur changement de filtre.
Modales destructives (REC-CRIT-07)
Aucunwindow.confirm() pour une action destructive. Utiliser
<DestructiveDialog> (components/destructive-dialog.tsx) qui standardise :
disclosures “irréversible” et “journalisé”, bouton destructive rouge,
async-aware (spinner pendant le traitement).
EmptyState (REC-MIN-11)
Toute liste vide rend un<EmptyState> (components/empty-state.tsx).
Quand la page a des filtres actifs et que la liste est vide à cause
d’eux, passer resetFiltersHref pour faire apparaître un bouton standard
“Réinitialiser les filtres”.
Hiérarchie d’actions de page (P1)
Règle : une page expose 1 action primaire + au plus 3 actions secondaires + un menu kebab “Plus” pour le reste. Au-delà, refondre la page (cf. REC-CRIT-02 sur la fiche client).| Type | Style | Exemple |
|---|---|---|
| Primaire | <Button> plein, à droite | ”Lancer un audit” sur fiche client |
| Secondaire | <Button variant="outline"> | ”Comparer”, “Exporter” |
| Tertiaire | menu kebab <MoreHorizontal> | Edit, Credentials, Trajectory, CISO, Guide |
dashboard/clients/[id]/page.tsx (TriggerAuditButton primaire +
ClientActionsMenu kebab) et audits/[id]/page.tsx (AuditActions buttons +
AuditReportsPanel pour générations).
Profondeur navigation 2 niveaux (P2)
Règle : ne jamais dépasser 2 niveaux de profondeur de navigation au sens “menu” (sidebar entries × sub-pages). Au-delà, surfacer la 3ème profondeur via :- des tabs URL-synced dans la page parente (P3), OU
- un secondary sidebar dans le layout (DR runbook, Settings hub).
- Sidebar à 3 niveaux d’accordéon → utiliser secondary sidebar à la place
- Pages de hub uniquement composées de cards de hub → applatir la hiérarchie
Tabs URL-synced (P3, REC-CRIT-02)
Règle : toute UI à tabs au sein d’une route SaaS doit refléter l’onglet actif dans l’URL via?tab=.... Reload ou deep-link préserve la sélection,
crucial pour partage email / Teams.
Composant de référence : dashboard/clients/[id]/url-synced-tabs.tsx
(wrap Radix Tabs, sync via router.replace() — pas push, pour ne pas
polluer l’historique).
<Tabs defaultValue> shadcn standard est OK.
Réutilisation MSSP/portal via variant prop (P10, REC-CRIT-05)
Règle : quand un composant fait sens des deux côtés (MSSP + portail client), un seul composant + une propvariant qui ferme l’écart.
Pas de duplication.
Exemples :
ReportDownloadMenuavecformats={[...]}filtré par contexte. Le portail passe["pdf-executive"](A2 : portail = exécutif seul), le MSSP passe["pdf-executive", "pdf-technical", "excel", "html", "json"].<EmptyState>partagé : propresetFiltersHrefoptionnelle.<UrlFilters>(P9) avec définition déclarative — le call site adapte les options sans refactor.
- ❌
DownloadReportButton+AuditActions(downloads)séparés → ✅ unifiés - ❌
DashboardFilters+AuditFiltersdivergents → ✅ wrappers thin sur UrlFilters - ❌
documentContextsrecalculé dashboard + portal → ✅lib/document-contexts.ts