mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-05-18 08:18:15 +02:00
Triage du 95_a_capitaliser.md (~75 propositions) : - 60 entrées intégrées dans knowledge/ (backend, frontend, workflow) - 4 nouveaux fichiers : backend/patterns/tests.md, backend/risques/tests.md, frontend/patterns/general.md, workflow/patterns/general.md - 6 doublons rejetés - Mise à jour des READMEs index pour refléter les nouvelles entrées - 95_a_capitaliser.md restauré à sa structure initiale - 40_decisions_et_archi.md : décision mono-tenant déployable vs SaaS multi-tenant - 90_debug_et_postmortem.md : sub-agents Write indisponible, effet iceberg CI, prisma migrate diffs cosmétiques Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
378 lines
14 KiB
Markdown
378 lines
14 KiB
Markdown
# Frontend — Patterns : Général
|
|
|
|
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
|
|
|
|
---
|
|
|
|
<a id="pattern-focus-visible-interne-overflow-clip"></a>
|
|
## Pattern : Focus visible interne pour champs sous overflow clip
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : préserver le focus visible des champs `<input>` / `<select>` quand l'app applique `overflow-x: clip` sur ses conteneurs (PageShell, panel, layout) — le focus outline natif est dessiné **hors** de la box du champ et se fait clipper.
|
|
- **Contexte** : app mobile-first où `overflow-x: clip` (ou `hidden`) est fréquent sur les conteneurs pour empêcher le débordement horizontal.
|
|
- **Quand l'utiliser** : tout projet avec une chaîne de parents en `overflow: hidden|clip` autour des champs de saisie.
|
|
- **Quand l'éviter** : si la chaîne de parents n'a pas d'`overflow: hidden|clip` — l'outline natif suffit.
|
|
|
|
### Analyse
|
|
|
|
- **Avantages** :
|
|
- aucun pixel ne sort de la box, donc aucun clip possible
|
|
- `box-shadow: inset` + `border-color` est portable Chrome/Firefox/Safari
|
|
- **Limites / vigilance** :
|
|
- `outline-offset: -2px` marche sur Chrome mais le rendu varie : Firefox/Safari peuvent ignorer selon la combinaison `outline-style`
|
|
|
|
### Validation
|
|
|
|
- Validé le : 27-04-2026
|
|
- Contexte technique : CSS / mobile-first — RL799_V2
|
|
|
|
### Pattern correctif (par composant)
|
|
|
|
```css
|
|
.my-input:focus-visible,
|
|
.my-select:focus-visible {
|
|
outline: none;
|
|
border-color: var(--color-accent);
|
|
box-shadow: inset 0 0 0 1px var(--color-accent);
|
|
}
|
|
```
|
|
|
|
### Application globale au thème (recommandée pour mobile-first)
|
|
|
|
Plutôt que de répéter le pattern dans chaque composant, le pousser dans le fichier de thème global. Tous les inputs/selects/textareas en bénéficient automatiquement.
|
|
|
|
```css
|
|
/* Dans theme/<theme>.css ou globals.css, hors :root */
|
|
input[type='text']:focus-visible,
|
|
input[type='search']:focus-visible,
|
|
input[type='email']:focus-visible,
|
|
input[type='tel']:focus-visible,
|
|
input[type='url']:focus-visible,
|
|
input[type='number']:focus-visible,
|
|
input[type='password']:focus-visible,
|
|
input[type='date']:focus-visible,
|
|
input[type='datetime-local']:focus-visible,
|
|
input[type='time']:focus-visible,
|
|
input[type='month']:focus-visible,
|
|
input[type='week']:focus-visible,
|
|
select:focus-visible,
|
|
textarea:focus-visible {
|
|
outline: none;
|
|
border-color: var(--color-accent);
|
|
box-shadow: inset 0 0 0 1px var(--color-accent);
|
|
}
|
|
```
|
|
|
|
### Pourquoi sélecteurs d'attribut explicites et pas `input:focus-visible`
|
|
|
|
`input` couvre aussi `type='checkbox'`, `type='radio'`, `type='file'`, `type='range'`, `type='color'` qui ont leur propre design natif. La règle pourrait casser leur rendu (carrés or autour des cases à cocher, etc.). On liste explicitement les types texte/saisie.
|
|
|
|
### Conflits avec composants ayant leur propre `:focus`
|
|
|
|
Un composant qui a déjà `.my-input:focus { ... }` (sans `-visible`) gardera la priorité par spécificité (classe > tag). La règle globale ne joue que pour les composants qui n'ont rien défini → sûr à introduire en mid-projet.
|
|
|
|
---
|
|
|
|
<a id="pattern-restyle-input-date-sans-wrapper-js"></a>
|
|
## Pattern : Restyle global de `<input type="date">` sans wrapper JS
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : aligner les inputs date HTML5 sur l'identité visuelle du thème via une règle CSS globale, sans wrapper JS custom.
|
|
- **Contexte** : projet avec un thème dark/light custom où les inputs date natifs (icône calendrier blanche, placeholder gris OS, popover light par défaut) cassent le design.
|
|
- **Quand l'utiliser** : 80 % des cas (audit log, formulaires admin, profils) où la datepicker custom serait du sur-engineering.
|
|
- **Quand l'éviter** :
|
|
- validation custom synchrone (range, dates blackout, format spécifique)
|
|
- format d'affichage différent (DD/MM vs MM/DD vs ISO)
|
|
- intégration profonde dans un design system
|
|
|
|
### Analyse
|
|
|
|
- **Avantages** :
|
|
- `color-scheme` + `accent-color` donnent un popover natif cohérent gratuitement
|
|
- filtre SVG pour teinter l'icône calendrier
|
|
- zéro JavaScript, zéro bundle additionnel
|
|
- **Limites / vigilance** :
|
|
- **Firefox** : `accent-color` respecté, mais pas de pseudo-element pour customiser le placeholder
|
|
- **Safari iOS** : popover sheet OS, peu personnalisable. Acceptable car cohérent avec le reste des UI iOS natives
|
|
- filtre `filter()` à calibrer pour matcher la couleur du thème — chaque thème nécessite son tuning
|
|
|
|
### Validation
|
|
|
|
- Validé le : 27-04-2026
|
|
- Contexte technique : CSS / inputs date HTML5 — RL799_V2
|
|
|
|
### Pattern minimal
|
|
|
|
```css
|
|
/* Toutes les variantes de pickers HTML5 */
|
|
input[type='date'],
|
|
input[type='datetime-local'],
|
|
input[type='time'],
|
|
input[type='month'],
|
|
input[type='week'] {
|
|
color-scheme: dark; /* ou 'light' selon le thème de l'app */
|
|
accent-color: var(--color-accent);
|
|
}
|
|
|
|
/* Place-holder OS (jj/mm/aaaa) — couleur soft */
|
|
input[type='date']::-webkit-datetime-edit-fields-wrapper,
|
|
input[type='datetime-local']::-webkit-datetime-edit-fields-wrapper {
|
|
color: var(--color-text-soft);
|
|
}
|
|
|
|
/* Une fois saisie, couleur normale */
|
|
input[type='date']:not(:placeholder-shown)::-webkit-datetime-edit-fields-wrapper {
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
/* Icône calendrier teintée (filtre SVG noir → couleur d'accent soft) */
|
|
input[type='date']::-webkit-calendar-picker-indicator,
|
|
input[type='datetime-local']::-webkit-calendar-picker-indicator,
|
|
input[type='time']::-webkit-calendar-picker-indicator,
|
|
input[type='month']::-webkit-calendar-picker-indicator,
|
|
input[type='week']::-webkit-calendar-picker-indicator {
|
|
/* Calibrer pour matcher la couleur du thème */
|
|
filter: invert(70%) sepia(40%) saturate(450%) hue-rotate(5deg) brightness(95%);
|
|
cursor: pointer;
|
|
opacity: 0.85;
|
|
transition: opacity 0.15s ease;
|
|
}
|
|
```
|
|
|
|
### Anti-patterns
|
|
|
|
- Construire un mini calendrier JS custom pour gagner 5 % d'esthétique → effort énorme (a11y clavier, focus management, mobile, edge cases), bénéfice marginal
|
|
- Hardcoder les couleurs `filter()` au lieu d'utiliser des tokens du thème
|
|
- Restyler sans `color-scheme: dark/light` → le popover natif reste en mode clair sur thème sombre
|
|
|
|
---
|
|
|
|
<a id="pattern-ui-journaux-audit-logs"></a>
|
|
## Pattern : UI pour journaux / audit logs / timelines
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : passer d'un rendu naïf en cards uniformes "acteur · code · cible (uuid) · metadata" à une lecture rapide pour l'admin en surveillance, sans toucher au backend.
|
|
- **Contexte** : tout projet finit par afficher un journal d'événements (audit, activité, historique, timeline) avec metadata variable.
|
|
- **Quand l'utiliser** : journal avec ≥ 10 types d'actions et besoin de scanner rapidement.
|
|
- **Quand l'éviter** : log technique brut destiné aux devs (un `<pre>` peut suffire).
|
|
|
|
### Analyse
|
|
|
|
- **Avantages** :
|
|
- 5 patterns combinables qui améliorent radicalement la scanabilité
|
|
- aucun changement backend (le DTO reste plat)
|
|
- **Limites / vigilance** :
|
|
- reset de l'état d'expansion à chaque rechargement (l'expansion est éphémère, pas une préférence durable)
|
|
|
|
### Validation
|
|
|
|
- Validé le : 27-04-2026
|
|
- Contexte technique : Vue 3 / CSS — RL799_V2 (Journal d'audit admin, 45+ types d'actions)
|
|
|
|
### 1. `<optgroup>` dérivé du préfixe label
|
|
|
|
Quand l'API retourne un catalogue d'actions avec convention `Catégorie — Libellé` (ex : `Soirée — annulation`, `Tenue — création`), dériver les groupes côté front au lieu de modifier le DTO.
|
|
|
|
```typescript
|
|
const groupsFromLabels = computed(() => {
|
|
const groups = new Map<string, Entry[]>();
|
|
for (const opt of catalog.value) {
|
|
const sep = opt.label.indexOf(' — ');
|
|
const category = sep > 0 ? opt.label.slice(0, sep) : 'Divers';
|
|
groups.set(category, [...(groups.get(category) ?? []), opt]);
|
|
}
|
|
return Array.from(groups.entries())
|
|
.sort(([a], [b]) => a.localeCompare(b, 'fr', { sensitivity: 'base' }));
|
|
});
|
|
```
|
|
|
|
```html
|
|
<select>
|
|
<option value="">Toutes les actions</option>
|
|
<optgroup v-for="[cat, opts] in groupsFromLabels" :label="cat" :key="cat">
|
|
<option v-for="o in opts" :value="o.value" :key="o.value">{{ o.label }}</option>
|
|
</optgroup>
|
|
</select>
|
|
```
|
|
|
|
Bénéfice : 1 seul clic pour filtrer (vs cascade 2 selects), accessible natif, zéro modif backend.
|
|
|
|
### 2. Code couleur sémantique par catégorie
|
|
|
|
Barre de couleur de 3 px à gauche de chaque card de la liste, mappée sur la catégorie de l'événement. Transforme un mur de cards uniformes en lecture instantanée.
|
|
|
|
```css
|
|
.log-item {
|
|
border-left: 3px solid var(--log-cat-color, var(--color-border));
|
|
}
|
|
.log-item--cat-soiree { --log-cat-color: var(--color-accent-primary); }
|
|
.log-item--cat-rgpd { --log-cat-color: var(--color-accent-danger); }
|
|
```
|
|
|
|
Pourquoi pas le fond complet : trop bruyant, perd la sobriété d'un journal admin. La barre latérale signale sans crier.
|
|
|
|
### 3. UUIDs rétrogradés en monospace soft
|
|
|
|
Les UUIDs / IDs techniques affichés en plein texte cassent la lecture humaine. Les détecter via regex et les rendre en font monospace + couleur soft + taille réduite, sans les masquer (utiles pour forensics).
|
|
|
|
```typescript
|
|
const UUID_RE = /[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}/gi;
|
|
```
|
|
|
|
```css
|
|
.uuid {
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
font-size: 0.78em;
|
|
color: var(--color-text-soft);
|
|
word-break: break-all;
|
|
}
|
|
```
|
|
|
|
### 4. Date relative + absolue en tooltip
|
|
|
|
Pour la lecture humaine, "il y a 5 min" / "hier" / "il y a 3 j" bat toujours "27 avril 2026 à 08:37". La date absolue reste accessible en `title` du `<time>` pour les forensics.
|
|
|
|
```typescript
|
|
const formatRelative = (iso: string) => {
|
|
const diff = (Date.now() - new Date(iso).getTime()) / 1000;
|
|
if (diff < 60) return 'à l\'instant';
|
|
if (diff < 3600) return `il y a ${Math.round(diff / 60)} min`;
|
|
if (diff < 86400) return `il y a ${Math.round(diff / 3600)} h`;
|
|
if (diff < 172800) return 'hier';
|
|
if (diff < 604800) return `il y a ${Math.round(diff / 86400)} j`;
|
|
return new Date(iso).toLocaleDateString('fr-FR', {
|
|
day: 'numeric', month: 'short', year: 'numeric',
|
|
});
|
|
};
|
|
```
|
|
|
|
### 5. Détails techniques repliables
|
|
|
|
La metadata détaillée (clés/valeurs verboses, IDs internes, raisons null) sert à l'investigation, pas à la lecture courante. La masquer derrière un `Voir les détails / Masquer les détails`, et reset l'état d'expansion à chaque rechargement.
|
|
|
|
```typescript
|
|
const expanded = ref<Set<string>>(new Set());
|
|
const toggle = (id: string) => {
|
|
const next = new Set(expanded.value);
|
|
next.has(id) ? next.delete(id) : next.add(id);
|
|
expanded.value = next;
|
|
};
|
|
// Reset après chaque load
|
|
const loadList = async () => {
|
|
/* … */
|
|
expanded.value = new Set();
|
|
};
|
|
```
|
|
|
|
Le bouton "voir détails" n'apparaît **que si** la card a effectivement de la metadata. Un toggle vide pollue la grille visuelle.
|
|
|
|
### Anti-patterns à éviter
|
|
|
|
- Cards uniformes en couleur/border quel que soit le type d'événement → tue la scanabilité
|
|
- Date absolue toujours visible (`27 avril 2026 à 08:37`) sur des cards serrées → bruit cognitif inutile
|
|
- UUIDs en plein texte sans rétrogradation visuelle
|
|
- Cascade 2 selects quand un `<optgroup>` natif suffit
|
|
- Bouton "voir détails" affiché même sans détails à voir
|
|
- Persister l'expansion entre navigations / paginations → état orphelin
|
|
|
|
---
|
|
|
|
<a id="pattern-structuration-pages-admin"></a>
|
|
## Pattern : Structuration de pages admin (eyebrows + grille filtres + variante danger)
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : poser une grammaire visuelle commune sur les écrans admin (filtres + liste/form + sections multiples) sans framework lourd.
|
|
- **Contexte** : module Admin avec plusieurs panels (Audit, Utilisateurs, Corbeille, etc.) qui partagent la même structure.
|
|
- **Quand l'utiliser** : ≥ 3 panels admin avec structure similaire.
|
|
- **Quand l'éviter** : page admin unique sans cohérence inter-panels à maintenir.
|
|
|
|
### Validation
|
|
|
|
- Validé le : 27-04-2026
|
|
- Contexte technique : Vue 3 / CSS — RL799_V2 (5 panels admin)
|
|
|
|
### 1. Eyebrows de section
|
|
|
|
Au lieu de titres H2/H3 trop forts, utiliser des "eyebrows" : mini-labels uppercase, letter-spacing élargi, taille `caption`, couleur d'accent.
|
|
|
|
```css
|
|
.eyebrow {
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.2em;
|
|
font-size: var(--font-size-caption);
|
|
color: var(--color-accent);
|
|
margin: 0;
|
|
}
|
|
```
|
|
|
|
```html
|
|
<p class="eyebrow">Filtres</p>
|
|
<div class="my-filters"><!-- … --></div>
|
|
|
|
<p class="eyebrow">Résultats <span class="eyebrow-count">· {{ total }} entrées</span></p>
|
|
<div class="my-list"><!-- … --></div>
|
|
```
|
|
|
|
Le compteur (`· N entrées`) est en `font-weight: normal`, sans uppercase, en `color-text-secondary` — typographie en cascade.
|
|
|
|
Bénéfice : structure visuelle sans concurrencer un éventuel titre de page. Cohérence inter-écrans dans tout un module si l'eyebrow est utilisé partout.
|
|
|
|
Quand ne pas afficher l'eyebrow `Résultats` : sur loading / error / empty state. Le `StateBlock` (ou équivalent) prend le relais.
|
|
|
|
### 2. Grille de filtres hiérarchique
|
|
|
|
Quand 3+ filtres dont l'un est dominant (typiquement une recherche), au lieu d'un flex-wrap chaotique, utiliser une grille avec le filtre primaire en pleine largeur :
|
|
|
|
```html
|
|
<div class="filters">
|
|
<label class="filters__label filters__label--full">Recherche
|
|
<input type="search">
|
|
</label>
|
|
<label class="filters__label">Statut <select><!-- … --></select></label>
|
|
<label class="filters__label">Rôle <select><!-- … --></select></label>
|
|
</div>
|
|
```
|
|
|
|
```css
|
|
.filters {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
align-items: end;
|
|
gap: var(--space-2);
|
|
}
|
|
.filters__label--full {
|
|
grid-column: 1 / -1;
|
|
}
|
|
```
|
|
|
|
### 3. Variante `danger` pour actions destructives
|
|
|
|
Sur un écran qui mélange actions constructives et destructives (ex : saisie initiale + bypass admin) :
|
|
|
|
```css
|
|
.btn--danger {
|
|
border-color: color-mix(in srgb, var(--color-danger) 48%, transparent);
|
|
background: color-mix(in srgb, var(--color-danger) 12%, transparent);
|
|
color: var(--color-danger);
|
|
}
|
|
|
|
.card--danger {
|
|
border-left: 3px solid var(--color-danger);
|
|
}
|
|
```
|
|
|
|
`color-mix` produit un rouge **soft** (12-20 % saturation), pas un flash rouge violent.
|
|
|
|
**Anti-pattern** : `background: red` / `color: red` directs ou hex hardcodés (`#ef4444`). Toujours via tokens du thème.
|
|
|
|
### Anti-patterns à éviter
|
|
|
|
- Filtres en `flex-wrap` qui produisent 3 lignes asymétriques selon le viewport
|
|
- Hint général qui répète ce que l'empty state dit déjà → 1 message, 1 niveau d'info
|
|
- Confondre "titre de page" (l'onglet actif suffit souvent) et "structure de section" (eyebrows)
|
|
- Action destructive en variant primary or → danger explicite
|