mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 21:41:42 +02:00
Refonte Structure
This commit is contained in:
16
knowledge/frontend/patterns/README.md
Normal file
16
knowledge/frontend/patterns/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Frontend — Patterns validés — Index
|
||||
|
||||
Patterns frontend/mobile testés et validés en conditions réelles.
|
||||
|
||||
Avant toute proposition frontend, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
|
||||
|
||||
---
|
||||
|
||||
| Fichier | Domaine | Entrées clés |
|
||||
|---------|---------|--------------|
|
||||
| `state.md` | State management, UI states, Zustand, listes paginées | États UI loading/empty/error, séparation server/client state, refresh idempotent, UI admin légère |
|
||||
| `forms.md` | Formulaires, validation, Server Actions, optimistic UI | Formulaire robuste, toggle optimiste rollback, Server Action retourne entité |
|
||||
| `navigation.md` | Navigation, routing, Expo Router, intégrations tierces | Navigation réactive post-action async, link-out page locale canonique |
|
||||
| `design-tokens.md` | Design tokens, typographie, spacing, Tailwind, RN StyleSheet | Tokens TypeScript Expo/RN, typography sémantique, export styles composant, grilles 2 colonnes |
|
||||
| `nextjs.md` | Next.js App Router, embeds, ESLint | Click-to-load embeds tiers, ESLint flat config Next.js |
|
||||
| `tests.md` | Tests styles React Native, Jest node env | Tests de styles sans renderer JSX |
|
||||
186
knowledge/frontend/patterns/design-tokens.md
Normal file
186
knowledge/frontend/patterns/design-tokens.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# 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
|
||||
130
knowledge/frontend/patterns/forms.md
Normal file
130
knowledge/frontend/patterns/forms.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Frontend — Patterns : Forms
|
||||
|
||||
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-formulaire-robuste"></a>
|
||||
## Pattern : Formulaire robuste avec validation et erreurs explicites
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : garantir des formulaires fiables, compréhensibles et maintenables.
|
||||
- **Contexte** : toute interface avec saisie utilisateur et règles métier.
|
||||
- **Quand l'utiliser** : dès qu'un formulaire dépasse un simple champ isolé.
|
||||
- **Quand l'éviter** : formulaires ultra-simples sans validation réelle.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- UX claire (l'utilisateur sait quoi corriger)
|
||||
- Moins d'erreurs silencieuses
|
||||
- Base saine pour tests et accessibilité
|
||||
- **Limites / vigilance** :
|
||||
- Peut sembler verbeux sans discipline
|
||||
- Risque de duplication si mal factorisé
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 25-01-2026
|
||||
- Contexte technique : Front-end agnostique, API HTTP
|
||||
|
||||
### Implémentation (exemple minimal)
|
||||
|
||||
```txt
|
||||
- Validation côté client (format, champs requis)
|
||||
- Validation côté serveur (règles métier)
|
||||
- Mapping explicite des erreurs serveur → champs UI
|
||||
- Aucun submit silencieux
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Messages d'erreur compréhensibles et localisés
|
||||
- [ ] Validation client + serveur cohérente
|
||||
- [ ] Focus automatique sur le champ en erreur
|
||||
- [ ] États loading / disabled gérés
|
||||
- [ ] Tests sur cas valides et invalides
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-toggle-optimiste-rollback"></a>
|
||||
## Pattern : Toggle optimiste avec rollback (React Server Action)
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : masquer la latence serveur sur un toggle boolean en mettant à jour l'UI immédiatement, avec rollback en cas d'erreur.
|
||||
- **Contexte** : toggles boolean (visibilité, disponibilité, settings) où la latence doit être masquée.
|
||||
- **Quand l'utiliser** : toggles sans besoin de re-fetcher l'entité entière après mutation.
|
||||
- **Quand l'éviter** : mutations qui retournent des données complexes → préférer le pattern "Server Action retournant l'entité".
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 21-03-2026
|
||||
- Contexte technique : React / Next.js App Router — app-template-resto
|
||||
|
||||
### Implémentation
|
||||
|
||||
```tsx
|
||||
const [optimistic, setOptimistic] = useState(initialValue);
|
||||
|
||||
async function handleToggle() {
|
||||
const prev = optimistic;
|
||||
setOptimistic(!prev); // update immédiat
|
||||
try {
|
||||
await toggleAction(!prev);
|
||||
router.refresh(); // synchronise le Server Component parent
|
||||
} catch {
|
||||
setOptimistic(prev); // rollback si erreur
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-server-action-retourne-entite"></a>
|
||||
## Pattern : Server Action retournant l'entité — élimination de `router.refresh()` sur create/edit
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : mettre à jour l'état local directement avec les données réelles retournées par le serveur, sans round-trip SSR supplémentaire.
|
||||
- **Contexte** : liste d'items managée côté client (`useState`) avec création et modification via Server Actions.
|
||||
- **Quand l'utiliser** : create et edit d'entités dans une liste. Plus performant que toggle optimiste + `router.refresh()`.
|
||||
- **Quand l'éviter** : simples toggles boolean → le pattern optimiste avec rollback suffit.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages vs toggle optimiste + `router.refresh()` :**
|
||||
- Zéro aller-retour SSR supplémentaire (~500ms–2s économisés sur mobile)
|
||||
- État local garanti cohérent avec la DB (données réelles, pas calculées localement)
|
||||
- Pas de flash de rechargement
|
||||
- **Limites / vigilance** :
|
||||
- `revalidatePath` reste nécessaire pour invalider le cache des pages publiques
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 22-03-2026
|
||||
- Contexte technique : React / Next.js App Router — app-template-resto story 3.8
|
||||
|
||||
### Implémentation
|
||||
|
||||
```typescript
|
||||
// Repository — retourne l'entité complète
|
||||
export async function createItem(tenantId: string, data: Input): Promise<ItemRow> {
|
||||
return prisma.item.create({ data: { tenantId, ...data }, select: { ...fullSelect } });
|
||||
}
|
||||
|
||||
// Action — retourne la donnée au client
|
||||
export async function createItemAction(formData: FormData): Promise<ItemRow> {
|
||||
const actor = await requireOwner();
|
||||
const item = await createItem(actor.tenantId, input);
|
||||
revalidatePath("/dashboard/..."); // invalider cache pages publiques
|
||||
return item; // ← clé : retourner l'entité
|
||||
}
|
||||
|
||||
// Client — mise à jour locale sans round-trip SSR
|
||||
const created = await createItemAction(formData);
|
||||
setItems((prev) => [...prev, created]); // pas de router.refresh()
|
||||
```
|
||||
|
||||
**Pour les entités avec relations :** utiliser un helper `findItemById(tenantId, id)` appelé après la mutation pour retourner la forme complète avec les relations résolues.
|
||||
168
knowledge/frontend/patterns/navigation.md
Normal file
168
knowledge/frontend/patterns/navigation.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Frontend — Patterns : Navigation
|
||||
|
||||
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-navigation-reactive-post-action-async"></a>
|
||||
## Pattern : Navigation réactive post-action async (React / Expo Router)
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : déclencher la navigation après une action asynchrone (login, register, submit) de façon idiomatique et sans bypasser la réactivité React.
|
||||
- **Contexte** : SPA ou app mobile React avec state management (Zustand, Redux, Context) et router déclaratif (React Router, Expo Router, Next.js App Router).
|
||||
- **Quand l'utiliser** : dès qu'une navigation dépend du résultat d'une action async.
|
||||
- **Quand l'éviter** : navigations synchrones sans état async impliqué.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- Respecte le cycle de vie React (pas de lecture de state hors cycle)
|
||||
- Re-render automatique si l'état change entre-temps
|
||||
- Testable : on peut assert sur l'état, pas sur des effets de bord
|
||||
- **Limites / vigilance** :
|
||||
- Ne pas oublier les dépendances du `useEffect` (ESLint react-hooks/exhaustive-deps)
|
||||
- Gérer le cas "composant démonté" si la navigation peut être annulée
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 07-03-2026
|
||||
- Contexte technique : React 18+ / Zustand / Expo Router — pattern applicable sur React Router, Next.js App Router
|
||||
|
||||
### Implémentation (exemple minimal)
|
||||
|
||||
```typescript
|
||||
// ❌ Anti-pattern : lecture de state hors cycle React
|
||||
const handleSubmit = async () => {
|
||||
await login(email, password);
|
||||
const { accessToken } = useAuthStore.getState(); // bypasse la réactivité
|
||||
if (accessToken) router.replace('/(tabs)');
|
||||
};
|
||||
|
||||
// ✅ Pattern correct : useEffect réactif sur le state
|
||||
const { accessToken, isLoading, error } = useAuthStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (accessToken && !isLoading && !error) {
|
||||
router.replace('/(tabs)');
|
||||
}
|
||||
}, [accessToken, isLoading, error]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await login(email, password);
|
||||
// la navigation se déclenche via useEffect quand le store se met à jour
|
||||
};
|
||||
```
|
||||
|
||||
### Pour les callbacks OAuth (ref nécessaire)
|
||||
|
||||
```typescript
|
||||
// Quand un callback externe déclenche la navigation
|
||||
const pendingOAuth = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (pendingOAuth.current && accessToken) {
|
||||
pendingOAuth.current = false;
|
||||
router.replace('/(tabs)');
|
||||
}
|
||||
}, [accessToken]);
|
||||
|
||||
const handleOAuth = async () => {
|
||||
pendingOAuth.current = true;
|
||||
await exchangeWithIdp(token);
|
||||
};
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Aucun `store.getState()` utilisé pour lire l'état post-action dans un handler
|
||||
- [ ] `useEffect` avec dépendances explicites (state pertinent + isLoading + error)
|
||||
- [ ] Cas d'erreur géré (ne pas naviguer si error est défini)
|
||||
- [ ] `useRef` si le trigger vient d'un callback externe (OAuth, deep link)
|
||||
- [ ] Convention documentée dans la story foundations / project-context avant les premiers écrans
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-link-out-page-locale-canonique"></a>
|
||||
## Pattern : Intégration tierce en mode link-out — préférer une page locale canonique
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : éviter les parcours concurrents et centraliser les garde-fous UX quand une fonctionnalité publique dépend d'un service tiers externe.
|
||||
- **Contexte** : site ou webapp avec CTA publics menant vers un tiers de réservation, paiement, prise de rendez-vous ou formulaire externe.
|
||||
- **Quand l'utiliser** : dès qu'une fonctionnalité externe dispose d'une page locale dédiée côté produit (`/reservation`, `/booking`, etc.).
|
||||
- **Quand l'éviter** : si le produit assume volontairement une sortie directe unique vers le tiers, sans page locale intermédiaire ni besoin de contextualisation.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- UX plus cohérente entre home, navigation et pages dédiées
|
||||
- garde-fous, wording et fallbacks centralisés au même endroit
|
||||
- facilite l'évolution future vers embed, click-to-load ou variantes de parcours
|
||||
- **Limites / vigilance** :
|
||||
- ajoute une étape intermédiaire si la page locale n'apporte aucune valeur
|
||||
- demande de maintenir l'alignement entre les CTA internes et le parcours canonique
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 19-03-2026
|
||||
- Contexte technique : site web public / intégration tierce en mode lien externe
|
||||
|
||||
### Implémentation (exemple minimal)
|
||||
|
||||
```txt
|
||||
- faire pointer les CTA internes (home, nav, landing) vers une page locale dédiée
|
||||
- faire de cette page locale le point canonique vers le service tiers externe
|
||||
- centraliser sur cette page le contexte utile, les garde-fous et les fallbacks
|
||||
- éviter les sorties directes concurrentes vers le tiers depuis plusieurs endroits du site
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Un parcours canonique unique est défini
|
||||
- [ ] Les CTA internes convergent vers la page locale dédiée
|
||||
- [ ] Les garde-fous et fallbacks sont centralisés
|
||||
- [ ] Les sorties directes concurrentes vers le tiers sont évitées ou justifiées
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-click-to-load-embeds-tiers"></a>
|
||||
## Pattern : Click-to-load strict pour les embeds tiers (iframe/widget)
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : ne charger aucun service tiers sans action explicite de l'utilisateur (performance + consentement implicite).
|
||||
- **Contexte** : site/webapp avec modules de réservation, map, chat ou tout embed iframe à la demande.
|
||||
- **Quand l'utiliser** : dès qu'un embed tiers est chargé à la demande (pas au premier rendu).
|
||||
- **Quand l'éviter** : si l'embed est central à la page et doit être visible immédiatement.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- LCP non pollué par des tiers (performance-first)
|
||||
- Aucun tiers ne reçoit de données utilisateur sans action volontaire (consentement implicite)
|
||||
- Fallback toujours disponible en cas d'erreur iframe
|
||||
- **Limites / vigilance** :
|
||||
- Le fallback (lien externe + `tel:`) doit être actionnable même si l'embed échoue
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 21-03-2026
|
||||
- Contexte technique : React / Next.js — app-template-resto
|
||||
|
||||
### Implémentation
|
||||
|
||||
```tsx
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [errored, setErrored] = useState(false);
|
||||
|
||||
if (errored) return <a href={url}>Ouvrir {label}</a>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!loaded && <button onClick={() => setLoaded(true)}>Charger {label}</button>}
|
||||
{loaded && <iframe src={url} onError={() => setErrored(true)} />}
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
67
knowledge/frontend/patterns/nextjs.md
Normal file
67
knowledge/frontend/patterns/nextjs.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Frontend — Patterns : Next.js
|
||||
|
||||
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-eslint-flat-config-nextjs"></a>
|
||||
## Pattern : ESLint flat config avec presets Next.js (`eslint.config.mjs`)
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : éviter les bugs de compatibilité de l'ancien `.eslintrc` avec Next.js récent.
|
||||
- **Contexte** : projet Next.js récent utilisant déjà le flat config ESLint.
|
||||
- **Quand l'utiliser** : nouveau projet Next.js ou migration ESLint.
|
||||
- **Quand l'éviter** : si le projet doit rester compatible avec des outils legacy ESLint.
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 16-03-2026
|
||||
- Contexte technique : Next.js 16+ / ESLint flat config — app-template-resto
|
||||
|
||||
### Implémentation
|
||||
|
||||
```javascript
|
||||
// eslint.config.mjs
|
||||
import nextPlugin from "@next/eslint-plugin-next";
|
||||
|
||||
export default [
|
||||
...nextPlugin.configs["core-web-vitals"],
|
||||
...nextPlugin.configs["typescript"],
|
||||
{
|
||||
rules: {
|
||||
// overrides ciblés ici
|
||||
},
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-grilles-2-colonnes-mobile-first"></a>
|
||||
## Pattern : Grilles 2 colonnes FR/EN — mobile-first
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : afficher les champs FR + EN côte à côte sur desktop, en colonne unique sur mobile.
|
||||
- **Contexte** : formulaires dashboard avec champs bilingues FR/EN côte à côte.
|
||||
- **Quand l'utiliser** : tout formulaire avec colonnes parallèles sur un projet mobile-first.
|
||||
- **Quand l'éviter** : si les champs sont indépendants et n'ont pas de relation visuelle FR/EN.
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 22-03-2026
|
||||
- Contexte technique : Tailwind CSS / React — app-template-resto
|
||||
|
||||
### Implémentation
|
||||
|
||||
```html
|
||||
<!-- ✅ Mobile-first — colonne unique sur < 640px, 2 colonnes sur ≥ 640px -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<input placeholder="Nom (FR)" />
|
||||
<input placeholder="Name (EN)" />
|
||||
</div>
|
||||
|
||||
<!-- ❌ À éviter — 2 colonnes trop étroites sur mobile -->
|
||||
<div class="grid grid-cols-2 gap-4">...</div>
|
||||
```
|
||||
189
knowledge/frontend/patterns/state.md
Normal file
189
knowledge/frontend/patterns/state.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Frontend — Patterns : State
|
||||
|
||||
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-etats-ui-loading-empty-error"></a>
|
||||
## Pattern : Gestion explicite des états UI (loading / empty / error)
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : éviter les interfaces ambiguës ou incohérentes en rendant explicites tous les états possibles d'une vue.
|
||||
- **Contexte** : SPA ou webapp consommant des données asynchrones (API, backend, cache).
|
||||
- **Quand l'utiliser** : dès qu'une vue dépend de données externes ou d'un traitement async.
|
||||
- **Quand l'éviter** : vues purement statiques ou synchrones sans dépendance externe.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- UX plus prévisible et compréhensible
|
||||
- Debug facilité (état visible = problème identifiable)
|
||||
- Base saine pour tests et accessibilité
|
||||
- **Limites / vigilance** :
|
||||
- Peut sembler verbeux sur des écrans simples
|
||||
- Nécessite une discipline pour ne pas "court-circuiter" les états
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 25-01-2026
|
||||
- Contexte technique : SPA (React / Vue / Svelte agnostique), API HTTP
|
||||
|
||||
### Implémentation (exemple minimal)
|
||||
|
||||
```txt
|
||||
if (loading) {
|
||||
afficher un skeleton ou spinner
|
||||
} else if (error) {
|
||||
afficher un message clair + action possible
|
||||
} else if (data est vide) {
|
||||
afficher un état empty explicite
|
||||
} else {
|
||||
afficher la vue nominale
|
||||
}
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Aucun écran blanc ou silencieux
|
||||
- [ ] Message d'erreur compréhensible pour l'utilisateur
|
||||
- [ ] États testables individuellement
|
||||
- [ ] Accessibilité respectée (focus, lecture écran)
|
||||
- [ ] Pas de logique métier cachée dans le rendu
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-separation-server-state-client-state"></a>
|
||||
## Pattern : Séparation claire server state / client state
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : éviter le mélange des responsabilités entre données serveur et état local UI.
|
||||
- **Contexte** : SPA ou webapp consommant une API avec interactions utilisateur.
|
||||
- **Quand l'utiliser** : dès que l'application affiche des données distantes modifiables ou synchronisées.
|
||||
- **Quand l'éviter** : applications très simples ou purement statiques.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- Logique plus lisible et testable
|
||||
- Réduction des bugs liés aux états incohérents
|
||||
- Évolutivité facilitée quand l'app grossit
|
||||
- **Limites / vigilance** :
|
||||
- Demande de la rigueur dans le découpage
|
||||
- Peut sembler abstrait au début pour des petits projets
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 25-01-2026
|
||||
- Contexte technique : SPA agnostique (React / Vue / Svelte), API HTTP
|
||||
|
||||
### Implémentation (exemple minimal)
|
||||
|
||||
```txt
|
||||
serverState = données venant du backend (fetch, cache, sync)
|
||||
clientState = état local UI (filtres, onglets, modales, formulaires)
|
||||
|
||||
Ne jamais :
|
||||
- stocker du state UI dans le cache serveur
|
||||
- dériver la logique UI directement des réponses API sans adaptation
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Les données serveur peuvent être invalidées / rechargées
|
||||
- [ ] L'état UI est local et réinitialisable
|
||||
- [ ] Les responsabilités sont lisibles dans le code
|
||||
- [ ] Les tests peuvent cibler chaque type d'état
|
||||
- [ ] Pas de dépendance implicite entre UI et API
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-refresh-idempotent-liste-paginee"></a>
|
||||
## Pattern : Refresh idempotent sur store de liste paginée
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : garantir qu'un pull-to-refresh recharge une liste paginée sans doublons, sans courses réseau et sans état intermédiaire incohérent.
|
||||
- **Contexte** : app mobile ou SPA avec store de domaine (ex. Zustand) et pagination incrémentale.
|
||||
- **Quand l'utiliser** : dès qu'une même liste supporte à la fois `loadMore` et `refresh`.
|
||||
- **Quand l'éviter** : listes purement statiques ou données entièrement remplacées sans pagination.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- évite les doublons lors des refresh concurrents
|
||||
- garde une transition atomique entre ancien et nouvel état
|
||||
- rend le comportement async testable côté store
|
||||
- **Limites / vigilance** :
|
||||
- impose une discipline claire entre `refresh` et `loadMore`
|
||||
- demande une clé d'identité stable pour dédupliquer les items
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 10-03-2026
|
||||
- Contexte technique : React Native / Expo / Zustand / listes paginées
|
||||
|
||||
### Implémentation (exemple minimal)
|
||||
|
||||
```txt
|
||||
- conserver une promesse de refresh partagée tant qu'un refresh est en vol
|
||||
- refuser ou réutiliser tout refresh concurrent au lieu d'en lancer un second
|
||||
- remplacer atomiquement la liste à la fin du refresh
|
||||
- dédupliquer les items par identifiant au merge des pages suivantes
|
||||
- empêcher `loadMore` de fusionner sur un snapshot devenu obsolète
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Une seule promesse de refresh en vol à la fois
|
||||
- [ ] `refresh` et `loadMore` ont des garde-fous explicites
|
||||
- [ ] La liste est remplacée atomiquement après refresh
|
||||
- [ ] Les pages suivantes sont dédupliquées par identifiant stable
|
||||
- [ ] Tests sur refresh concurrent + refresh suivi de pagination
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-ui-admin-legere-domaine-existant"></a>
|
||||
## Pattern : UI admin légère sur domaine existant
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : ajouter une capacité interne simple sans ouvrir trop tôt un back-office séparé ni dupliquer la logique métier.
|
||||
- **Contexte** : app mobile ou SPA avec un domaine métier déjà structuré et quelques actions internes ponctuelles.
|
||||
- **Quand l'utiliser** : publication, activation, modération légère, bascule de statut, action opérateur simple.
|
||||
- **Quand l'éviter** : permissions complexes, workflows multiples, audit riche ou volume d'actions qui justifie un vrai espace d'administration.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- réutilise le service et le store métier existants
|
||||
- limite le coût de structure pour une capacité admin mince
|
||||
- garde les mutations testables et lisibles
|
||||
- **Limites / vigilance** :
|
||||
- ne pas laisser cette approche dériver vers un pseudo back-office implicite
|
||||
- le refresh après mutation doit être explicite sur les vues impactées
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 10-03-2026
|
||||
- Contexte technique : React Native / Expo Router / store de domaine
|
||||
|
||||
### Implémentation (exemple minimal)
|
||||
|
||||
```txt
|
||||
- ajouter une route dédiée minimale pour l'action interne
|
||||
- réutiliser le service/store métier existant au lieu de créer une couche parallèle
|
||||
- afficher le statut courant avant action
|
||||
- bloquer les actions concurrentes avec un flag explicite (`isUpdating*`)
|
||||
- déclencher un refresh explicite des vues impactées après succès
|
||||
- éviter les mutations en fire-and-forget
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Route dédiée minimale, pas de mini back-office générique
|
||||
- [ ] Réutilisation du domaine métier existant
|
||||
- [ ] Garde-fou explicite contre les doubles actions
|
||||
- [ ] Refresh explicite après mutation réussie
|
||||
- [ ] Tests sur succès, erreur et action concurrente
|
||||
53
knowledge/frontend/patterns/tests.md
Normal file
53
knowledge/frontend/patterns/tests.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Frontend — Patterns : Tests
|
||||
|
||||
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
|
||||
|
||||
---
|
||||
|
||||
<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
|
||||
Reference in New Issue
Block a user