mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-06-28 10:03:40 +02:00
5f5c87296e
Triage et intégration des propositions frontend du buffer 95_a_capitaliser.md (lot local RL799_V2/Vue3 + app-alexandrie/RN-Expo, mai-juin 2026). ~73 entrées intégrées sur knowledge/frontend/ + 1 nouveau fichier, dont : - patterns/state.md : race-token partagé latest-wins (fusion 3 props), capture sync anti-race, event bus timestamp, clé cache composite, état dérivé = computed - risques/state.md : 9 risques Zustand/store (fetchId reset, useRef remount, re-fetch infini sur [], flag optimiste écrasé, cache détail/liste stale, latch sans reset, :key index) - patterns/navigation.md : Expo Router (tab bar, useFocusEffect, Href typé, routing pur fusionné) - patterns/general.md : helpers temps purs, composants génériques + skeleton, fail-fast, touch target - risques/general.md : 24 risques (sweep statique, filtre client liste paginée, hooks avant return, a11y VoiceOver/disabled, redirection allowlist, RangeError toISOString, section i18n...) - design-tokens (cluster theming light/dark MD3), tests, performance, react-native, nextjs - NOUVEAU risques/responsive.md (gating par capacité d'input + checklist régressions mobile) - READMEs patterns/risques mis à jour Doublons inter-fichiers évités (vérifié : aucune ancre dupliquée introduite). Rejets (doublons 91/9/87), reciblages workflow (156/257) et bloc 32 (CLAUDE projet) non intégrés ici. Source 95_a_capitaliser.md non purgée (purge en fin de capitalisation complète). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
290 lines
12 KiB
Markdown
290 lines
12 KiB
Markdown
# Frontend — Patterns : Design Tokens
|
|
|
|
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
|
|
|
|
---
|
|
|
|
<a id="pattern-design-tokens-expo-rn"></a>
|
|
## Pattern : Design Tokens natifs TypeScript (Expo / React Native)
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : centraliser les tokens de design sans librairie externe (NativeBase, Tamagui), typés et barrel-exportés.
|
|
- **Contexte** : app Expo / React Native avec un système de design à maintenir.
|
|
- **Quand l'utiliser** : dès le début d'un projet mobile, avant les premiers composants.
|
|
- **Quand l'éviter** : si une librairie UI opinionée est déjà choisie et gère ses propres tokens.
|
|
|
|
### Analyse
|
|
|
|
- **Avantages** :
|
|
- aucune dépendance externe, zéro configuration magique
|
|
- autocomplétion TypeScript exacte via `as const` + types dérivés
|
|
- facile à migrer vers un design system plus élaboré ultérieurement
|
|
- **Limites / vigilance** :
|
|
- les fichiers TTF doivent être présents dans `assets/fonts/` — Google Fonts ne peut pas être téléchargé automatiquement, documenter comme pré-requis dans la story
|
|
- ne pas réutiliser les tokens `spacing` pour les dimensions de composants (voir risques)
|
|
|
|
### Validation
|
|
|
|
- Validé le : 19-03-2026
|
|
- Contexte technique : Expo SDK 52+ / React Native / TypeScript — app-alexandrie story 0.1
|
|
|
|
### Implémentation (exemple minimal)
|
|
|
|
```typescript
|
|
// apps/mobile/src/theme/colors.ts
|
|
export const colors = {
|
|
primary: '#2563EB',
|
|
error: '#DC2626',
|
|
// ...
|
|
} as const;
|
|
export type ColorToken = keyof typeof colors;
|
|
|
|
// apps/mobile/src/theme/spacing.ts
|
|
export const spacing = { xs: 4, sm: 8, md: 12, base: 16, lg: 24 } as const;
|
|
export type SpacingToken = keyof typeof spacing;
|
|
|
|
// apps/mobile/src/theme/index.ts (barrel export)
|
|
export * from './colors';
|
|
export * from './spacing';
|
|
export * from './typography';
|
|
export * from './shadows';
|
|
```
|
|
|
|
### Checklist
|
|
|
|
- [ ] Tous les tokens `as const` pour inférence exacte
|
|
- [ ] Pas de Context React — constantes TypeScript pures
|
|
- [ ] Types dérivés (`ColorToken = keyof typeof colors`) pour l'autocomplétion
|
|
- [ ] `useFonts` dans `_layout.tsx` avec guard `!fontsLoaded`
|
|
- [ ] Fichiers TTF présents dans `assets/fonts/` et documentés dans la story
|
|
|
|
---
|
|
|
|
<a id="pattern-token-typography-semantique"></a>
|
|
## Pattern : Token typography par usage sémantique (React Native)
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : éviter les mauvais usages de tokens typography visuellement proches mais sémantiquement distincts.
|
|
- **Contexte** : fichier `typography.ts` dans un design system React Native.
|
|
- **Quand l'utiliser** : dès que deux tokens partagent la même taille mais un poids différent.
|
|
- **Quand l'éviter** : jamais — les tokens typography doivent toujours refléter l'usage, pas l'apparence.
|
|
|
|
### Analyse
|
|
|
|
- **Avantages** :
|
|
- prévient les "approximations" de tokens en code review
|
|
- changement de style d'usage spécifique sans régression globale
|
|
- **Limites / vigilance** :
|
|
- en review : chercher les usages sans `fontWeight` explicite — c'est souvent le signe que le mauvais token a été choisi
|
|
|
|
### Validation
|
|
|
|
- Validé le : 19-03-2026
|
|
- Contexte technique : React Native / TypeScript — app-alexandrie story 0.4
|
|
|
|
### Implémentation
|
|
|
|
```typescript
|
|
// Bon : nommé par usage sémantique
|
|
listItemTitle: { fontSize: 12, fontWeight: '600' }, // titre d'un item de liste
|
|
caption: { fontSize: 12, fontWeight: '500' }, // info secondaire, hints
|
|
|
|
// Mauvais : nommé par apparence
|
|
mediumText12: { fontSize: 12, fontWeight: '500' }, // ambigu, réutilisé à tort
|
|
```
|
|
|
|
**Règle** : `caption` (Medium) ≠ `listItemTitle` (SemiBold) même si la taille est identique. Ne jamais piocher un token "par approximation".
|
|
|
|
---
|
|
|
|
<a id="pattern-export-styles-composant"></a>
|
|
## Pattern : Export des styles de composant pour réutilisation partielle (React Native)
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : partager les dimensions et formes d'un composant UI vers des éléments custom qui en dérivent, sans dupliquer les valeurs.
|
|
- **Contexte** : app React Native où des screens construisent des éléments qui doivent être "au gabarit" d'un composant existant.
|
|
- **Quand l'utiliser** : bouton custom OAuth, container calqué sur un composant de base, etc.
|
|
- **Quand l'éviter** : si l'écart visuel est intentionnel — dans ce cas, une constante locale est plus claire.
|
|
|
|
### Analyse
|
|
|
|
- **Avantages** :
|
|
- zéro drift silencieux : si les dimensions du composant changent, tous les éléments dérivés suivent
|
|
- tests de styles possibles en dehors du composant
|
|
- **Limites / vigilance** :
|
|
- à n'utiliser que pour des éléments vraiment dérivés, pas comme contournement de design system
|
|
|
|
### Validation
|
|
|
|
- Validé le : 19-03-2026
|
|
- Contexte technique : React Native / StyleSheet — app-alexandrie story 0.3
|
|
|
|
### Implémentation
|
|
|
|
```typescript
|
|
// Button.tsx
|
|
export const buttonStyles = StyleSheet.create({
|
|
base: { borderRadius: 20, height: 57 },
|
|
primary: { backgroundColor: colors.primary },
|
|
});
|
|
export function Button(...) { ... }
|
|
|
|
// login.tsx — bouton OAuth au gabarit du Button
|
|
import { buttonStyles } from '@/components/ui/Button';
|
|
<TouchableOpacity style={[buttonStyles.base, styles.facebookButton]} />
|
|
```
|
|
|
|
<a id="pattern-tests-styles-sans-renderer"></a>
|
|
## Pattern : Tests de styles React Native sans renderer JSX
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : tester les tokens et styles de composants React Native dans un environnement Jest `testEnvironment: node` sans renderer JSX.
|
|
- **Contexte** : config Jest avec `transform: { '^.+\\.ts$': 'ts-jest' }` — les `.tsx` ne sont pas transformés.
|
|
- **Quand l'utiliser** : tokens de thème, logique pure, valeurs de style exportées.
|
|
- **Quand l'éviter** : rendu conditionnel (styles dynamiques inline) — nécessite `@testing-library/react-native`.
|
|
|
|
### Analyse
|
|
|
|
- **Avantages** :
|
|
- teste que le composant utilise les bons tokens, pas seulement que les tokens ont des valeurs
|
|
- détecte les régressions de style sans renderer
|
|
- rapide, aucune config Jest supplémentaire
|
|
- **Limites / vigilance** :
|
|
- ne teste pas le style calculé au runtime (style conditionnel dynamique)
|
|
|
|
### Validation
|
|
|
|
- Validé le : 19-03-2026
|
|
- Contexte technique : React Native / Jest / ts-jest — app-alexandrie story 0.2
|
|
|
|
### Implémentation
|
|
|
|
```typescript
|
|
// Button.tsx — exporter le StyleSheet avec un nom préfixé
|
|
export const buttonStyles = StyleSheet.create({
|
|
base: { borderRadius: 20, height: 57 },
|
|
primary: { backgroundColor: colors.primary },
|
|
});
|
|
export function Button(...) { ... }
|
|
|
|
// ui-components.spec.ts — importer et vérifier les tokens
|
|
import { buttonStyles } from './Button';
|
|
import { colors } from '@/theme';
|
|
|
|
it('variante primary utilise colors.primary', () => {
|
|
expect(buttonStyles.primary.backgroundColor).toBe(colors.primary);
|
|
});
|
|
```
|
|
|
|
### Deux niveaux de tests UI recommandés
|
|
|
|
1. `.spec.ts` (node) : tokens, valeurs, logique pure
|
|
2. `.spec.tsx` (config séparée avec renderer) : rendu visuel, interactions
|
|
|
|
---
|
|
|
|
<a id="pattern-palette-light-dark-md3-usethemedcolors"></a>
|
|
## Pattern : Palette light/dark MD3 + hook `useThemedColors` + dual export
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : poser un theming light/dark/system complet sur une codebase qui n'avait qu'une palette unique, sans casser les imports historiques.
|
|
- **Contexte** : React Native / Expo avec préférence utilisateur light/dark/system.
|
|
- **Quand l'utiliser** : introduction d'un 2ᵉ thème sur une base existante.
|
|
- **Quand l'éviter** : thème unique sans besoin de bascule (le hook serait du sucre sans bénéfice).
|
|
|
|
### Analyse
|
|
|
|
- **Avantages** :
|
|
- compat ascendante : `export const colors = dark` garde les imports `import { colors }` compilants
|
|
- le hook `useThemedColors` rend le reste mécanique (le composant ne code jamais le scheme en dur)
|
|
- dual export pour les styles legacy : `export const fooStyles = makeStyles(colors)` (snapshot dark) reste synchronisé avec la fonction dynamique
|
|
- **Limites / vigilance** :
|
|
- anti-pattern : palette plate sans typage par mode → force à coder le scheme en dur ou à dupliquer les styleSheets
|
|
- tokens "fixed" partagés light/dark (`primaryContainer`, `primaryFixedDim`) pour la continuité du branding ; `inverse-primary` d'un mode = `primary` de l'autre
|
|
- shadow : `#000000` OK en dark, mais en light un tinted neutre (ex. `outline` à 8 %), jamais noir pur
|
|
- validation WCAG : README documentant chaque token + ratio mesuré (texte ≥ 4.5:1, UI ≥ 3:1)
|
|
|
|
### Validation
|
|
|
|
- Validé le : 29-05-2026
|
|
- Contexte technique : React Native / Expo — app-alexandrie (ux-cleanup-8, ~95 fichiers migrés)
|
|
|
|
### Implémentation
|
|
|
|
```ts
|
|
// theme/colors.ts
|
|
const dark = { background: '#131316', primary: '#cdbdff' } as const;
|
|
const light = { background: '#fbf8fe', primary: '#4c00c9' } as const;
|
|
export const colors = dark; // compat ascendante (imports historiques)
|
|
export const colorsLight = light;
|
|
export const colorsDark = dark;
|
|
|
|
// theme/use-themed-colors.ts
|
|
export function useThemedColors() {
|
|
return useEffectiveColorScheme() === 'light' ? colorsLight : colorsDark;
|
|
}
|
|
|
|
// composant
|
|
const themed = useThemedColors();
|
|
const styles = useMemo(() => makeStyles(themed), [themed]);
|
|
function makeStyles(c: ReturnType<typeof useThemedColors>) {
|
|
return StyleSheet.create({ container: { backgroundColor: c.background } });
|
|
}
|
|
|
|
// styles exportés (compat tests) — source unique
|
|
export const fooStyles = makeStyles(colors); // snapshot dark
|
|
```
|
|
|
|
> Les pièges de migration associés (composants tiers, `<Text>` sans `color`, scheme effectif, ThemeProvider) sont documentés dans `risques/design-tokens.md#risque-theming-light-dark-pieges-caches`.
|
|
|
|
---
|
|
|
|
<a id="pattern-map-semantique-slug-token-icone"></a>
|
|
## Pattern : Map sémantique slug → token + icône
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : différencier visuellement des entités d'un même type (forums, packs, badges) via une map slug → token résolue au runtime, sans hex en dur.
|
|
- **Contexte** : entités multiples d'un même type nécessitant un code couleur/icône cohérent light/dark.
|
|
- **Quand l'utiliser** : ≥ 2 entités à différencier visuellement.
|
|
- **Quand l'éviter** : entité unique ou différenciation purement textuelle.
|
|
|
|
### Analyse
|
|
|
|
- **Structure** : `getEntityBadgeTokens(slug) → { bgColorKey, fgColorKey, icon }`, le caller résout via `useThemedColors` (suit le thème).
|
|
- **Règles** : (1) toujours des `ColorKey` de `useThemedColors`, jamais de hex ; (2) toujours un fallback neutre (un slug futur ne casse pas l'UI) ; (3) icônes outline légères ; (4) tests env node sur map + fallback + distinctness.
|
|
- **Avantages** : vs if/else inline → 0 duplication ; vs hex map → suit le thème ; vs composant → reste un helper pur testable env node.
|
|
|
|
### Validation
|
|
|
|
- Validé le : 29-05-2026
|
|
- Contexte technique : React Native — app-alexandrie (`forum-badge.ts`, ux-cleanup-15)
|
|
|
|
---
|
|
|
|
<a id="pattern-migration-tokens-typo-formalises"></a>
|
|
## Pattern : Migration tokens typo formalisés
|
|
|
|
### Synthèse
|
|
|
|
- **Objectif** : formaliser et migrer les valeurs typographiques en dur (`fontSize`, `fontWeight`, `fontFamily`) vers des tokens, comme le sweep tokens couleur.
|
|
- **Contexte** : codebase avec ~100 occurrences de valeurs typo en dur sur des dizaines de fichiers.
|
|
- **Quand l'utiliser** : volumétrie significative de valeurs typo dispersées.
|
|
- **Quand l'éviter** : poignée d'usages localisés.
|
|
|
|
### Analyse
|
|
|
|
- **Méthodologie en 4 étapes** : (1) audit grep + rapport stats ; (2) étendre `typography.ts` SANS renommer les tokens existants (rétro-compat) — ajouter des aliases sémantiques ; (3) migration mécanique (perl/sed) + imports, typecheck après chaque batch ; (4) doc README (table sémantique + anti-patterns).
|
|
- **Cas dégénérés à laisser en dur** : `fontSize: 48/64` sur un emoji/hero = taille d'icône, pas de la typo → ne pas créer un token `iconLarge` (documenter l'exception).
|
|
- **Anti-patterns** : codemod auto sans review visuel (un `15 → 14` casse un layout) ; renommer un token existant (`fontSize.tab` utilisé partout → casse 50+ fichiers) ; créer `useThemedTypography()` si la typo ne varie pas light/dark (sucre sans bénéfice, garder l'import statique).
|
|
|
|
### Validation
|
|
|
|
- Validé le : 30-05-2026
|
|
- Contexte technique : React Native — app-alexandrie (ux-cleanup-12, 110 occurrences / 33 fichiers, 0 régression)
|