SmarMaker - Documentation
Docs» front» 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

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


  • SmartMaker
    • SmartAuth
    • Back (PHP)
      • Mapping Dolibarr
    • Front (React)
      • Configuration
      • SmartCommon
      • Hooks
      • Architecture
      • Composants et pages
      • Routage
      • Requêtes Api
      • Stockage de données
      • Animations
      • Traductions
      • Thèmes
      • PWA
      • Debug
      • Astuces
    • HowTo first app
  • Formations
  • Démonstration
  • Afficher le texte source
  • Anciennes révisions
  • Liens de retour
  • Haut de page
  • S'identifier
front/composants_et_pages.txt · Dernière modification : 2026/01/11 22:50 de 127.0.0.1