SmarMaker - Documentation
Docs» 03_front:composants_et_pages

Composants et pages

Documentation React

Les composants sont les éléments de base d'un projet React. Chacun représente une partie de l'interface utilisateur, réutilisable et maintenable.

Organisation des composants

Structure recommandée

src/components/
├── app/                    # Composants d'infrastructure
│   ├── Provider/
│   │   └── index.jsx
│   └── Router/
│       ├── index.jsx
│       └── Guards/
│           └── index.jsx
├── pages/                  # Pages de l'application
│   ├── public/             # Pages sans authentification
│   │   ├── LoginPage/
│   │   └── WelcomePage/
│   ├── private/            # Pages authentifiées
│   │   ├── HomePage/
│   │   └── SettingsPage/
│   └── errors/             # Pages d'erreur
│       └── Error404Page/
├── forms/                  # Composants de formulaire
│   ├── LoginForm/
│   └── ItemForm/
├── ui/                     # Composants UI réutilisables
│   ├── Card/
│   ├── Modal/
│   └── Header/
└── layouts/                # Layouts de pages
    ├── MainLayout/
    └── AuthLayout/

Convention de nommage

  • Dossier par composant : Chaque composant dans son propre dossier
  • index.jsx : Fichier principal du composant
  • PascalCase : Noms de composants en PascalCase
src/components/ui/Card/
├── index.jsx           # Composant principal
├── Card.module.css     # Styles (optionnel)
└── Card.test.jsx       # Tests (optionnel)

Créer un composant

Composant simple

// src/components/ui/Card/index.jsx

export const Card = ({ children, className = '' }) => {
  return (
    <div className={`bg-white rounded-lg shadow-md p-4 ${className}`}>
      {children}
    </div>
  );
};

Composant avec props

// src/components/ui/Button/index.jsx

export const Button = ({
  children,
  variant = 'primary',
  size = 'md',
  loading = false,
  disabled = false,
  onClick,
  type = 'button',
  className = '',
}) => {
  const variants = {
    primary: 'bg-primary text-white hover:bg-primary-dark',
    secondary: 'bg-secondary text-white hover:bg-secondary-dark',
    outline: 'border-2 border-primary text-primary hover:bg-primary/10',
    ghost: 'text-primary hover:bg-primary/10',
  };

  const sizes = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2',
    lg: 'px-6 py-3 text-lg',
  };

  return (
    <button
      type={type}
      onClick={onClick}
      disabled={disabled || loading}
      className={`
        inline-flex items-center justify-center
        rounded-md font-medium transition-all
        disabled:opacity-50 disabled:cursor-not-allowed
        ${variants[variant]}
        ${sizes[size]}
        ${className}
      `}
    >
      {loading && (
        <span className="mr-2 animate-spin">⟳</span>
      )}
      {children}
    </button>
  );
};

Composant Input personnalisé

// src/components/form/Input/index.jsx

export const Input = (props) => {
  const { label, id, error, ...inputProps } = props;

  return (
    <div className="flex flex-col gap-2">
      {label && (
        <label
          htmlFor={id}
          className="text-sm font-medium text-gray-700"
        >
          {label}
        </label>
      )}
      <input
        id={id}
        className={`
          bg-gray-100 p-4 rounded-lg
          outline-none focus:ring-2 focus:ring-primary
          ${error ? 'ring-2 ring-red-500' : ''}
        `}
        {...inputProps}
      />
      {error && (
        <span className="text-sm text-red-500">{error}</span>
      )}
    </div>
  );
};

Structure d'une page

Page simple

// src/components/pages/private/HomePage/index.jsx

import { useGlobalStates, useNavigation } from '@cap-rel/smartcommon';

export const HomePage = () => {
  const navigate = useNavigation();
  const [session] = useGlobalStates('session');

  return (
    <div className="min-h-screen bg-gray-100">
      {/* Header */}
      <header className="bg-white shadow p-4">
        <h1 className="text-xl font-bold">Accueil</h1>
      </header>

      {/* Content */}
      <main className="p-4">
        <p>Bienvenue {session?.user?.name}</p>
      </main>

      {/* Navigation */}
      <nav className="fixed bottom-0 left-0 right-0 bg-white shadow-lg">
        <div className="flex justify-around p-2">
          <button onClick={() => navigate('/')}>Accueil</button>
          <button onClick={() => navigate('/settings')}>Paramètres</button>
        </div>
      </nav>
    </div>
  );
};

Page avec chargement de données

// src/components/pages/private/ItemsPage/index.jsx

import { useApi, useGlobalStates, useNavigation } from '@cap-rel/smartcommon';
import { useEffect, useState } from 'react';
import { Card } from '../../ui/Card';

export const ItemsPage = () => {
  const api = useApi();
  const navigate = useNavigation();
  const [items, setItems] = useGlobalStates('items');
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchItems = async () => {
      setLoading(true);
      setError(null);

      const response = await api.private.get('items');

      if (response.success) {
        setItems(response.data);
      } else {
        setError(response.error);
      }

      setLoading(false);
    };

    fetchItems();
  }, []);

  if (loading) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <span className="animate-spin text-4xl">⟳</span>
      </div>
    );
  }

  if (error) {
    return (
      <div className="p-4 text-center text-red-500">
        Erreur: {error}
      </div>
    );
  }

  return (
    <div className="min-h-screen bg-gray-100 p-4">
      <h1 className="text-2xl font-bold mb-4">Mes items</h1>

      <div className="space-y-4">
        {items.map((item) => (
          <Card
            key={item.id}
            onClick={() => navigate(`/items/${item.id}`)}
            className="cursor-pointer hover:shadow-lg transition-shadow"
          >
            <h2 className="font-semibold">{item.label}</h2>
            <p className="text-gray-600 text-sm">{item.description}</p>
          </Card>
        ))}
      </div>
    </div>
  );
};

Utiliser les layouts

Créer un layout

// src/components/layouts/MainLayout/index.jsx

import { useNavigation } from '@cap-rel/smartcommon';

export const MainLayout = ({ children, title, showBack = true }) => {
  const navigate = useNavigation();

  return (
    <div className="min-h-screen bg-gray-100 flex flex-col">
      {/* Header */}
      <header className="bg-white shadow p-4 flex items-center gap-4">
        {showBack && (
          <button onClick={() => navigate(-1)} className="text-primary">
            ← Retour
          </button>
        )}
        <h1 className="text-xl font-bold">{title}</h1>
      </header>

      {/* Content */}
      <main className="flex-1 p-4">
        {children}
      </main>

      {/* Bottom navigation */}
      <nav className="bg-white shadow-lg p-2">
        <div className="flex justify-around">
          <button onClick={() => navigate('/')}>🏠</button>
          <button onClick={() => navigate('/search')}>🔍</button>
          <button onClick={() => navigate('/profile')}>👤</button>
        </div>
      </nav>
    </div>
  );
};

Utiliser un layout

// src/components/pages/private/SettingsPage/index.jsx

import { MainLayout } from '../../layouts/MainLayout';
import { useGlobalStates, useIntl } from '@cap-rel/smartcommon';

export const SettingsPage = () => {
  const { t, lng, setLng } = useIntl();
  const [theme, setTheme] = useGlobalStates('settings.theme');

  return (
    <MainLayout title={t('settings.title')}>
      <div className="space-y-6">
        {/* Langue */}
        <section className="bg-white rounded-lg p-4">
          <h2 className="font-semibold mb-2">{t('settings.language')}</h2>
          <select
            value={lng}
            onChange={(e) => setLng(e.target.value)}
            className="w-full p-2 border rounded"
          >
            <option value="fr">Français</option>
            <option value="en">English</option>
          </select>
        </section>

        {/* Thème */}
        <section className="bg-white rounded-lg p-4">
          <h2 className="font-semibold mb-2">{t('settings.theme')}</h2>
          <div className="flex gap-4">
            <button
              onClick={() => setTheme('light')}
              className={`p-4 rounded ${theme === 'light' ? 'ring-2 ring-primary' : ''}`}
            >
              ☀️ Clair
            </button>
            <button
              onClick={() => setTheme('dark')}
              className={`p-4 rounded ${theme === 'dark' ? 'ring-2 ring-primary' : ''}`}
            >
              🌙 Sombre
            </button>
          </div>
        </section>
      </div>
    </MainLayout>
  );
};

Patterns courants

Formulaire avec validation Zod

import { useForm, useApi, useNavigation } from '@cap-rel/smartcommon';
import { Form, Input, Button } from '@cap-rel/smartcommon';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(2, "Minimum 2 caractères"),
  email: z.string().email("Email invalide"),
});

const ContactForm = () => {
  const api = useApi();
  const navigate = useNavigation();
  const form = useForm({ schema });

  const handleSubmit = async (data) => {
    const response = await api.private.post('contacts', { json: data });
    if (response.success) {
      navigate('/contacts');
    }
  };

  return (
    <Form form={form} onSubmit={handleSubmit} className="space-y-4">
      <Input name="name" label="Nom" />
      <Input name="email" type="email" label="Email" />
      <Button type="submit" loading={form.formState.isSubmitting}>
        Envoyer
      </Button>
    </Form>
  );
};

Modal/Drawer avec animation

import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';

const Modal = ({ isOpen, onClose, children }) => {
  return (
    <AnimatePresence>
      {isOpen && (
        <>
          {/* Backdrop */}
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            onClick={onClose}
            className="fixed inset-0 bg-black/50 z-40"
          />

          {/* Content */}
          <motion.div
            initial={{ y: '100%' }}
            animate={{ y: 0 }}
            exit={{ y: '100%' }}
            className="fixed bottom-0 left-0 right-0 bg-white rounded-t-2xl p-4 z-50"
          >
            {children}
          </motion.div>
        </>
      )}
    </AnimatePresence>
  );
};

Liste avec état vide

const ItemsList = ({ items }) => {
  if (items.length === 0) {
    return (
      <div className="text-center py-12 text-gray-500">
        <p className="text-4xl mb-4">📭</p>
        <p>Aucun élément pour le moment</p>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      {items.map(item => (
        <ItemCard key={item.id} item={item} />
      ))}
    </div>
  );
};

Utiliser SmartCommon

Préférez les composants SmartCommon aux composants customs quand c'est possible :

import {
  Form,
  Input,
  Select,
  Checkbox,
  Button,
  Card,
  Modal,
} from '@cap-rel/smartcommon';

Voir SmartCommon pour la liste complète.

Voir aussi

  • SmartCommon - Composants prêts à l'emploi
  • Hooks - Hooks disponibles
  • Routage - Navigation entre pages
  • Animations - Transitions de pages
Previous Next

Made with ❤ by CAP-REL · SmartMaker · GNU AGPL v3+
Code source · Faire un don
SmarMaker - Documentation
Traductions de cette page:
  • Français
  • Deutsch
  • English
  • Español
  • Italiano
  • Nederlands

Table of Contents

Table des matières

  • Composants et pages
    • Organisation des composants
      • Structure recommandée
      • Convention de nommage
    • Créer un composant
      • Composant simple
      • Composant avec props
      • Composant Input personnalisé
    • Structure d'une page
      • Page simple
      • Page avec chargement de données
    • Utiliser les layouts
      • Créer un layout
      • Utiliser un layout
    • Patterns courants
      • Formulaire avec validation Zod
      • Modal/Drawer avec animation
      • Liste avec état vide
    • Utiliser SmartCommon
    • Voir aussi
  • SmartAuth
  • SmartMaker - Back (PHP)
    • Mapping Dolibarr - React
  • SmartMaker - Front (React)
    • Animations de pages
    • Architecture
    • Astuces
    • Calendar
    • Composants et pages
    • Configuration du Provider
    • Debug et Logs
    • Hooks SmartCommon
    • PWA (Progressive Web App)
    • Requêtes API
    • Routage
    • SmartCommon
    • Stockage de données
    • Thèmes
    • Traductions
  • HowTo - Pas à pas - Votre première application
    • Développement PHP (back)
    • Développement React (front)
    • Première étape : Module Builder Dolibarr
    • SmartAuth
    • SmartBoot : Un "squelette" quasiment prêt à l'emploi
  • Formation SmartMaker
    • Module 1 : Fondamentaux JavaScript ES6+
      • Chapitre 1 : Variables et Scope
      • Chapitre 2 : Fonctions
      • Chapitre 3 : Programmation Asynchrone
      • Chapitre 4 : Modules ES6
    • Module 2 : Introduction à React
      • Chapitre 1 : Philosophie React
      • Chapitre 2 : JSX
      • Chapitre 3 : Composants
    • Module 3 : Hooks React Fondamentaux
      • Chapitre 1 : useState
      • Chapitre 2 : useEffect
      • Chapitre 3 : useRef
      • Chapitre 4 : useContext
    • Module 4 : React Avancé
      • Chapitre 1 : useCallback et useMemo
      • Chapitre 2 : Custom Hooks
      • Chapitre 3 : Redux et Redux Toolkit
    • Module 5 : Architecture SmartMaker
      • Chapitre 1 : Structure du projet
      • Chapitre 2 : Configuration
      • Chapitre 3 : Flux de données
    • Module 6 : SmartCommon - Composants
      • Chapitre 1 : Mise en page
      • Chapitre 2 : Navigation
      • Chapitre 3 : Formulaires
      • Chapitre 4 : Affichage
    • Module 7 : SmartCommon - Hooks
      • Chapitre 1 : useApi
      • Chapitre 2 : Gestion d'état
      • Chapitre 3 : Hooks utilitaires
    • Module 8 : Backend API (PHP)
      • Chapitre 1 : Routage
      • Chapitre 2 : Controllers
      • Chapitre 3 : Mappers
      • Extrafields et formulaires dynamiques
    • Module 9 : Intégration complète
      • Chapitre 1 : Backend
      • Chapitre 2 : Frontend
      • Chapitre 3 : Déploiement
    • Module 10 : Fonctionnalités avancées
      • Chapitre 1 : Mode offline
      • Chapitre 2 : Internationalisation (i18n)
      • Chapitre 3 : Autres fonctionnalités
    • Module 11 : Bonnes pratiques
  • Démonstration
  • Start
  • Composants et pages
  • Afficher le texte source
  • Anciennes révisions
  • Liens de retour
  • Haut de page