Back to skills
SkillHub ClubDesign ProductFull StackFrontendDesigner

atomic-component

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".

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
0
Hot score
74
Updated
March 20, 2026
Overall rating
C2.6
Composite score
2.6
Best-practice grade
D40.3

Install command

npx @skill-hub/cli install romualdp-hoki-atomic-component

Repository

RomualdP/hoki

Skill path: .claude/skills/atomic-component

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".

Open repository

Best for

Primary workflow: Design Product.

Technical facets: Full Stack, Frontend, Designer.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: RomualdP.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install atomic-component into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/RomualdP/hoki before adding atomic-component to shared team environments
  • Use atomic-component for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
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 <div>{teams.map(t => <TeamCard key={t.id} team={t} />)}</div>;
}
```

### 🖱️ 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 <input value={name} onChange={(e) => 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 (
    <div>
      {teams.map(team => (
        <TeamCard key={team.id} team={team} /> // Client si interactivité
      ))}
    </div>
  );
}

// Client Component si besoin d'interactivité
"use client";
export function TeamCard({ team }: Props) {
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <div onClick={() => setIsExpanded(!isExpanded)}>
      {/* ... */}
    </div>
  );
}
```

## ⚠️ 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 (
    <div>
      <Suspense fallback={<StatsGridSkeleton />}>
        <PlayersStatsGrid />
      </Suspense>

      <Suspense fallback={<ListSkeleton />}>
        <PlayersListServer />
      </Suspense>
    </div>
  );
}
```

## 🧩 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 (
    <div>
      {step === 1 && (
        <div>
          <h2>Informations du club</h2>
          <input onChange={(e) => setClubData({...clubData, name: e.target.value})} />
          {/* 50 lignes de formulaire */}
        </div>
      )}
      {step === 2 && (
        <div>
          <h2>Choisir un plan</h2>
          {/* 50 lignes de sélection de plan */}
        </div>
      )}
      {/* ... */}
    </div>
  );
}

// ✅ 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 (
    <FormWizard currentStep={step} onStepChange={setStep}>
      {step === 1 && (
        <ClubInfoStep
          data={clubData}
          onChange={updateClubData}
          onNext={() => setStep(2)}
        />
      )}
      {step === 2 && (
        <PlanSelectionStep
          selectedPlan={clubData.plan}
          onSelectPlan={(plan) => updateClubData({ plan })}
          onNext={handleSubmit}
          isPending={isPending}
        />
      )}
    </FormWizard>
  );
}

// Dumb Components (présentation)
interface ClubInfoStepProps {
  data: Partial<ClubData>;
  onChange: (data: Partial<ClubData>) => void;
  onNext: () => void;
}

export function ClubInfoStep({ data, onChange, onNext }: ClubInfoStepProps) {
  return (
    <div className="space-y-4">
      <h2 className="text-2xl font-bold">Informations du club</h2>
      <FormField
        label="Nom du club"
        value={data.name}
        onChange={(name) => onChange({ name })}
      />
      <FormField
        label="Description"
        value={data.description}
        onChange={(description) => onChange({ description })}
      />
      <Button onClick={onNext}>Suivant</Button>
    </div>
  );
}
```

## 📏 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 (
    <>
      <UserProfileHeader />
      <UserProfileDetails />
      <UserProfileSettings />
    </>
  );
}

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 (
    <FormWizard currentStep={step}>
      {/* Présentation pure */}
    </FormWizard>
  );
}

// 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 (
    <div className="card">
      <div className="avatar">
        <img src={member.avatar} />
      </div>
      <div className="info">
        <h3>{member.name}</h3>
        <p>{member.email}</p>
        <span className="role">{member.role}</span>
      </div>
      <div className="actions">
        <button onClick={onEdit}>Edit</button>
        <button onClick={onDelete}>Delete</button>
      </div>
    </div>
  );
}

// ✅ BON - Composé de petits composants
export function MemberCard({ member, onEdit, onDelete }: Props) {
  return (
    <Card>
      <MemberAvatar src={member.avatar} alt={member.name} />
      <MemberInfo name={member.name} email={member.email} role={member.role} />
      <MemberActions onEdit={onEdit} onDelete={onDelete} />
    </Card>
  );
}

// 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 <EmptyMembersList />;
  }

  // Render (délègue aux Client Components si besoin d'interactivité)
  return (
    <div className="space-y-4">
      <MembersListHeader count={members.length} />
      <div className="grid gap-4">
        {members.map(member => (
          <MemberCard
            key={member.id}
            member={member}
            clubId={clubId}
          />
        ))}
      </div>
    </div>
  );
}
```

### 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 (
    <Card>
      <MemberInfo member={member} />
      <Button onClick={handleRemove} disabled={isPending}>
        {isPending ? "Suppression..." : "Retirer"}
      </Button>
    </Card>
  );
}
```

### 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 (
    <Card className="p-4 flex items-center gap-4">
      <MemberAvatar src={member.avatar} name={member.name} />

      <div className="flex-1">
        <h3 className="font-semibold">{member.name}</h3>
        <p className="text-sm text-muted-foreground">{member.email}</p>
        <span className="text-xs text-primary">{member.role}</span>
      </div>

      <Button
        variant="destructive"
        size="sm"
        onClick={onRemove}
      >
        Retirer
      </Button>
    </Card>
  );
}

// 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 (
      <img
        src={src}
        alt={name}
        className="w-12 h-12 rounded-full object-cover"
      />
    );
  }

  return (
    <div className="w-12 h-12 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-semibold">
      {initials}
    </div>
  );
}
```

### 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 (
    <div className="container py-8">
      <h1 className="text-3xl font-bold mb-6">Membres du club</h1>

      <Suspense fallback={<MembersListSkeleton />}>
        <MembersList clubId={params.id} />
      </Suspense>
    </div>
  );
}
```

## ✅ 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 (
    <>
      <ClubStats />
      <MembersList />
      <ActivitiesFeed />
      <SettingsPanel />
    </>
  );
}
```

### 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 <Card>...</Card>; // Présentation
}

// ✅ BON - Séparation claire
// Smart Component (logique)
export function MembersList() {
  const handleRemove = async (id: string) => {
    await removeMemberAction(id);
  };

  return members.map(m => (
    <MemberCard member={m} onRemove={() => handleRemove(m.id)} />
  ));
}

// Dumb Component (présentation)
export function MemberCard({ member, onRemove }: Props) {
  return <Card onClick={onRemove}>...</Card>;
}
```

### 3. Props Drilling Excessif

```typescript
// ❌ MAUVAIS - Props drilling sur 5 niveaux
<GrandParent data={data}>
  <Parent data={data}>
    <Child data={data}>
      <GrandChild data={data} />
    </Child>
  </Parent>
</GrandParent>

// ✅ 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 <div>{data}</div>;
}
```

## 📚 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é.
atomic-component | SkillHub