--- name: Atomic Component Generator description: Génère des composants React suivant l'Atomic Design avec décomposition maximale et pattern Smart/Dumb. MANDATORY pour tous composants. À utiliser lors de la création de composants, pages, ou quand l'utilisateur mentionne "component", "atomic", "smart", "dumb", "decompose", "React", "UI". allowed-tools: [Read, Write, Edit, Glob, Grep] --- # Atomic Component Generator ## 🎯 Mission Créer des composants React **maximalement décomposés** suivant l'**Atomic Design** et le pattern **Smart/Dumb** pour une **réutilisabilité** et **maintenabilité** maximales. ## ⚛️ Philosophie Atomic Design ### Le Principe **Atomic Design** décompose l'UI en **5 niveaux** : 1. **Atoms** (Atomes) : Plus petites unités (Button, Input, Label) 2. **Molecules** (Molécules) : Groupes d'atomes (FormField = Label + Input + Error) 3. **Organisms** (Organismes) : Groupes de molécules (Header = Logo + Nav + UserMenu) 4. **Templates** : Layouts avec placeholders 5. **Pages** : Templates avec données réelles **Dans ce projet**, nous utilisons principalement **3 niveaux** : - **Atoms** : Composants de base (`components/ui/` - shadcn/ui) - **Smart Components** : Logique + état (`features/*/components/*Form`, `*List`) - **Dumb Components** : Présentation pure (`features/*/components/*Step`, `*Card`) ### Règle d'Or : Décomposition Maximale > **CRITICAL** : Components MUST be maximally decomposed. **Pourquoi** : - ✅ **Réutilisabilité** : Petits composants = réutilisables partout - ✅ **Testabilité** : Petits composants = faciles à tester - ✅ **Maintenabilité** : Un composant = Une responsabilité - ✅ **Lisibilité** : Code clair et compréhensible - ✅ **Collaboration** : Équipe peut travailler en parallèle **Mauvais signe** : - ❌ Fichier > 150 lignes - ❌ Composant fait plusieurs choses - ❌ JSX imbriqué sur 10+ niveaux - ❌ Logique + présentation mélangées ## 🧩 Pattern Server/Client Components (Next.js 16) ### 🎯 Server Components par défaut **IMPORTANT** : Depuis Next.js 16, **TOUS les composants sont Server Components par défaut**. **Server Components** : - ✅ Fetch data server-side (async/await) - ✅ Accès direct à la base de données / APIs backend - ✅ Zero JavaScript client-side pour le data fetching - ✅ Meilleur SEO (contenu pré-rendu) - ✅ Pas besoin de "use client" **Quand utiliser** : - Pages, layouts, templates - Widgets qui affichent des données fetchées - Composants sans interactivité **Exemple** : ```typescript // Server Component (par défaut, pas de "use client") export async function TeamsWidget() { const teams = await getTeams(); // Fetch server-side return
{teams.map(t => )}
; } ``` ### 🖱️ Client Components (uniquement si nécessaire) **Client Components** (avec `"use client"`) : - ✅ Interactivité (onClick, onChange, etc.) - ✅ Hooks React (useState, useEffect, useContext) - ✅ Stores Zustand (state client) - ✅ Browser APIs (localStorage, etc.) **Quand utiliser** : - Formulaires interactifs - Composants avec state UI (modals, dropdowns) - Event handlers - useOptimistic, useTransition **Exemple** : ```typescript "use client"; // Requis pour Client Component export function TeamForm() { const [name, setName] = useState(""); return setName(e.target.value)} />; } ``` ### 📦 Pattern de Composition Server → Client **Règle d'or** : Server Component parent → Client Components enfants ```typescript // ✅ BON - Server Component avec fetch export async function TeamsList() { const teams = await getTeams(); // Server-side return (
{teams.map(team => ( // Client si interactivité ))}
); } // Client Component si besoin d'interactivité "use client"; export function TeamCard({ team }: Props) { const [isExpanded, setIsExpanded] = useState(false); return (
setIsExpanded(!isExpanded)}> {/* ... */}
); } ``` ## ⚠️ CRITICAL: Component Folder Architecture ### ❌ NEVER Create Components in `app/` **RÈGLE ABSOLUE** : Le dossier `app/` est **uniquement pour le routing Next.js**. ``` ❌ MAUVAIS (NEVER DO THIS): app/ └── players/ ├── page.tsx └── components/ ← ❌ JAMAIS DE COMPOSANTS ICI ├── PlayerCard.tsx ← ❌ MAUVAIS ├── PlayersList.tsx ← ❌ MAUVAIS └── ... ✅ BON: app/ └── players/ └── page.tsx ← Routing only (max 50 lignes) features/ └── players/ ← ✅ TOUS les composants vont ici ├── components/ │ ├── PlayerCard.tsx ← ✅ BON │ ├── PlayersList.tsx ← ✅ BON │ └── ... ├── api/ │ └── players.server.ts └── types/ ``` ### Pourquoi Cette Règle Est Critique 1. **Séparation des responsabilités** - `app/` = Routing Next.js (infrastructure) - `features/` = Business logic (métier) 2. **Réutilisabilité** - Composants dans `features/` peuvent être utilisés dans plusieurs routes - Composants dans `app/` sont isolés à une seule route 3. **Maintenabilité** - Structure claire et prévisible - Évite la duplication de code 4. **Best Practices Next.js 16** - Pages dans `app/` doivent être minces (orchestration) - Logique métier dans `features/` ### Examples Réels ```typescript // ❌ MAUVAIS // app/players/components/PlayerCard.tsx export function PlayerCard({ player }) { ... } // ❌ MAUVAIS // app/teams/[id]/components/TeamDetails.tsx export function TeamDetails({ team }) { ... } // ✅ BON // features/players/components/PlayerCard.tsx export function PlayerCard({ player }) { ... } // ✅ BON // features/teams/components/TeamDetails.tsx export function TeamDetails({ team }) { ... } ``` ### Page Structure ```typescript // app/players/page.tsx (Max 50 lignes - Orchestration uniquement) import { PlayersListServer } from "@/features/players/components/PlayersListServer"; import { PlayersStatsGrid } from "@/features/players/components/PlayersStatsGrid"; export default async function PlayersPage() { return (
}> }>
); } ``` ## 🧩 Pattern Smart/Dumb Components (pour Client Components) ### Smart Components (Container Components) **Responsabilités** : - ✅ Gèrent l'état client (useState, Zustand stores) - ✅ Gèrent la logique UI - ✅ Appellent les Server Actions (mutations) - ✅ Orchestrent les Dumb Components - ✅ Gèrent les side effects (useEffect pour polling, refetch, etc.) **Caractéristiques** : - **"use client"** directive - Nom se termine par **Form**, **Manager**, **Container** - Peu ou pas de JSX (délègue aux Dumb Components) - Beaucoup de logique JavaScript - Props minimales (reçoit peu, délègue beaucoup) **Emplacement** : `features/*/components/` (ex: `ClubCreationForm`, `TeamManager`) ### Dumb Components (Presentational Components) **Responsabilités** : - ✅ Affichent l'UI (JSX uniquement) - ✅ Reçoivent des props - ✅ Émettent des events (callbacks) - ✅ Aucun état (ou état UI minimal: hover, focus) - ✅ Aucune logique métier **Caractéristiques** : - Nom descriptif : **Step**, **Card**, **Item**, **Display**, **Section** - Beaucoup de JSX - Peu ou pas de logique JavaScript - Props strictement typées (interfaces) - Pure functions (même props = même output) **Emplacement** : `features/*/components/` ou `components/shared/` si réutilisable ### Exemple de Séparation ```typescript // ❌ MAUVAIS - Tout dans un seul composant (150+ lignes) export function ClubCreation() { const [step, setStep] = useState(1); const [clubData, setClubData] = useState({}); const [isPending, startTransition] = useTransition(); const handleSubmit = async () => { startTransition(async () => { await createClubAction(clubData); }); }; return (
{step === 1 && (

Informations du club

setClubData({...clubData, name: e.target.value})} /> {/* 50 lignes de formulaire */}
)} {step === 2 && (

Choisir un plan

{/* 50 lignes de sélection de plan */}
)} {/* ... */}
); } // ✅ BON - Décomposé en Smart + Dumb // Smart Component (orchestration) export function ClubCreationForm() { const [step, setStep] = useState(1); const { clubData, updateClubData } = useClubCreationStore(); const [isPending, startTransition] = useTransition(); const handleSubmit = async () => { startTransition(async () => { await createClubAction(clubData); }); }; return ( {step === 1 && ( setStep(2)} /> )} {step === 2 && ( updateClubData({ plan })} onNext={handleSubmit} isPending={isPending} /> )} ); } // Dumb Components (présentation) interface ClubInfoStepProps { data: Partial; onChange: (data: Partial) => void; onNext: () => void; } export function ClubInfoStep({ data, onChange, onNext }: ClubInfoStepProps) { return (

Informations du club

onChange({ name })} /> onChange({ description })} />
); } ``` ## 📏 Règles de Décomposition ### 1. Limite de Lignes - **Page** : MAX 50 lignes (composition uniquement) - **Smart Component** : MAX 100 lignes (logique + orchestration) - **Dumb Component** : MAX 80 lignes (présentation) - **Atomic Component** : MAX 50 lignes (UI basique) **Si dépassé** → Décomposer immédiatement ### 2. Single Responsibility Principle Un composant = **UNE responsabilité** ```typescript // ❌ MAUVAIS - Responsabilités multiples export function UserProfile() { // 1. Fetch data // 2. Display profile // 3. Edit form // 4. Settings panel // ... 200 lignes } // ✅ BON - Responsabilités séparées export function UserProfilePage() { return ( <> ); } export function UserProfileHeader() { /* ... */ } export function UserProfileDetails() { /* ... */ } export function UserProfileSettings() { /* ... */ } ``` ### 3. Extraction de la Logique **Règle** : Si logique > 10 lignes → Extraire dans un **custom hook** ou **util** ```typescript // ❌ MAUVAIS - Logique dans le composant export function ClubCreationForm() { const [step, setStep] = useState(1); const [clubData, setClubData] = useState({}); const validateStep1 = () => { if (!clubData.name || clubData.name.length < 3) return false; if (!clubData.description) return false; return true; }; const validateStep2 = () => { // 20 lignes de validation }; // ... logique complexe } // ✅ BON - Logique extraite export function ClubCreationForm() { const { step, clubData, goToNextStep, goToPreviousStep, updateClubData, validateCurrentStep, } = useClubCreationFlow(); return ( {/* Présentation pure */} ); } // Custom hook (logique extraite) function useClubCreationFlow() { const [step, setStep] = useState(1); const [clubData, setClubData] = useState({}); const validateCurrentStep = () => { return clubCreationValidators[step](clubData); }; // ... logique return { step, clubData, goToNextStep, goToPreviousStep, updateClubData, validateCurrentStep, }; } ``` ### 4. Composition > Monolithic **Privilégier la composition** de petits composants ```typescript // ❌ MAUVAIS - Composant monolithique export function MemberCard({ member }: Props) { return (

{member.name}

{member.email}

{member.role}
); } // ✅ BON - Composé de petits composants export function MemberCard({ member, onEdit, onDelete }: Props) { return ( ); } // Composants réutilisables function MemberAvatar({ src, alt }: AvatarProps) { /* ... */ } function MemberInfo({ name, email, role }: InfoProps) { /* ... */ } function MemberActions({ onEdit, onDelete }: ActionsProps) { /* ... */ } ``` ## 🏗️ Structure des Composants ### Organisation des Dossiers ``` features/ └── club-management/ └── components/ ├── ClubCreationForm.tsx # Smart (orchestration) ├── ClubInfoStep.tsx # Dumb (présentation step 1) ├── PlanSelectionStep.tsx # Dumb (présentation step 2) ├── MembersList.tsx # Smart (fetch + state) ├── MemberCard.tsx # Dumb (présentation membre) ├── MemberAvatar.tsx # Dumb (avatar) ├── MemberActions.tsx # Dumb (boutons actions) └── shared/ ├── FormWizard.tsx # Réutilisable (wizard) └── StepIndicator.tsx # Réutilisable (steps) components/ └── ui/ # Atomic components (shadcn/ui) ├── button.tsx ├── input.tsx ├── card.tsx └── ... ``` ### Template Server Component (preferred) ```typescript // features/club-management/components/MembersList.tsx // Server Component (pas de "use client") import { getMembers } from '../api/members.server'; import { MemberCard } from './MemberCard'; import { MembersListSkeleton } from './MembersListSkeleton'; interface MembersListProps { clubId: string; } export async function MembersList({ clubId }: MembersListProps) { // ✅ Fetch server-side const members = await getMembers(clubId); // Empty state if (members.length === 0) { return ; } // Render (délègue aux Client Components si besoin d'interactivité) return (
{members.map(member => ( ))}
); } ``` ### Template Client Component (si interactivité nécessaire) ```typescript // features/club-management/components/MemberCard.tsx "use client"; import { useTransition } from 'react'; import { removeMemberAction } from '../actions/remove-member.action'; interface MemberCardProps { member: Member; clubId: string; } export function MemberCard({ member, clubId }: MemberCardProps) { const [isPending, startTransition] = useTransition(); // Event handler (mutation) const handleRemove = () => { startTransition(async () => { await removeMemberAction(clubId, member.id); }); }; return ( ); } ``` ### Template Dumb Component ```typescript // features/club-management/components/MemberCard.tsx import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { MemberAvatar } from './MemberAvatar'; interface MemberCardProps { member: { id: string; name: string; email: string; role: string; avatar?: string; }; onRemove: () => void; } export function MemberCard({ member, onRemove }: MemberCardProps) { return (

{member.name}

{member.email}

{member.role}
); } // Sous-composant (si réutilisable ailleurs) interface MemberAvatarProps { src?: string; name: string; } function MemberAvatar({ src, name }: MemberAvatarProps) { const initials = name .split(' ') .map(n => n[0]) .join('') .toUpperCase(); if (src) { return ( {name} ); } return (
{initials}
); } ``` ### Template Page (Ultra-thin) ```typescript // app/(dashboard)/coach/clubs/[id]/members/page.tsx import { Suspense } from 'react'; import { MembersList } from '@/features/club-management/components/MembersList'; import { MembersListSkeleton } from '@/features/club-management/components/MembersListSkeleton'; interface PageProps { params: { id: string }; } export default function ClubMembersPage({ params }: PageProps) { return (

Membres du club

}>
); } ``` ## ✅ Checklist Atomic Design ### Avant de Créer un Composant - [ ] Le composant a-t-il UNE seule responsabilité ? - [ ] Est-il Smart (logique) ou Dumb (présentation) ? - [ ] Peut-il être décomposé davantage ? - [ ] Est-il réutilisable ailleurs ? - [ ] Les props sont-elles strictement typées ? - [ ] Le nom est-il descriptif ? ### Pendant la Création - [ ] Logique extraite dans hooks/utils si > 10 lignes - [ ] JSX imbrication < 5 niveaux - [ ] Composant < 150 lignes - [ ] Aucun state dans Dumb Component (sauf UI: hover, focus) - [ ] Aucune logique métier dans Dumb Component - [ ] Composition privilégiée ### Après la Création - [ ] Composant testé (si Smart) - [ ] Props documentées (JSDoc si nécessaire) - [ ] Accessible (ARIA, keyboard navigation) - [ ] Responsive (mobile-first) - [ ] Pas de warnings ESLint/TypeScript ## 🚨 Erreurs Courantes à Éviter ### 1. Composant Monolithique ```typescript // ❌ MAUVAIS - 300 lignes, fait tout export function ClubDashboard() { // Fetch data // Display stats // Display members list // Display latest activities // Display settings panel // ... 300 lignes } // ✅ BON - Décomposé export function ClubDashboard() { return ( <> ); } ``` ### 2. Mélange Smart/Dumb ```typescript // ❌ MAUVAIS - Mélange logique + présentation export function MemberCard({ member }: Props) { const [isPending, startTransition] = useTransition(); const handleRemove = async () => { startTransition(async () => { await removeMemberAction(member.id); }); }; return ...; // Présentation } // ✅ BON - Séparation claire // Smart Component (logique) export function MembersList() { const handleRemove = async (id: string) => { await removeMemberAction(id); }; return members.map(m => ( handleRemove(m.id)} /> )); } // Dumb Component (présentation) export function MemberCard({ member, onRemove }: Props) { return ...; } ``` ### 3. Props Drilling Excessif ```typescript // ❌ MAUVAIS - Props drilling sur 5 niveaux // ✅ BON - Context ou Zustand store const useDataStore = create((set) => ({ data: null, setData: (data) => set({ data }), })); // Composants consomment directement export function GrandChild() { const data = useDataStore(state => state.data); return
{data}
; } ``` ## 📚 Skills Complémentaires Pour aller plus loin : - **mobile-first** : Design mobile-first principles - **react-state-management** : State management patterns - **zero-warnings** : Quality standards --- **Rappel** : **Décomposition maximale** = Code maintenable, réutilisable et testable. Un composant = Une responsabilité.