Files
notytex/docs/frontend/COMPONENT_BEST_PRACTICES.md

16 KiB

🎨 Guide des Bonnes Pratiques - Composants Frontend

Guide: Design System Notytex
Audience: Développeurs Frontend & UI/UX Designers
Version: 1.0
Mise à jour: 7 août 2025

🎯 Philosophie du Design System

Notytex adopte une approche moderne, cohérente et accessible pour ses composants frontend. Chaque composant doit être réutilisable, maintenable et évolutif.

🏗️ Principes Fondamentaux

  • 🔄 Réutilisabilité : Un composant = plusieurs contextes
  • 🎨 Cohérence : Design tokens centralisés
  • 📱 Responsive-first : Mobile d'abord
  • Accessibilité : WCAG 2.1 AA minimum
  • Performance : Optimisation native

📁 Architecture des Composants

Structure de Répertoires

templates/components/
├── common/
│   ├── macros.html           # Macros réutilisables
│   └── base_components.html  # Composants de base
├── assessment/
│   └── assessment_card.html  # Cartes d'évaluation
├── class/
│   └── class_card.html       # Cartes de classe
├── forms/
│   ├── form_inputs.html      # Champs de formulaire
│   └── form_validation.html  # Validation UI
└── ui/
    ├── buttons.html          # Système de boutons
    ├── modals.html           # Modales
    └── notifications.html    # Toasts/Alerts

Convention de Nommage

Type_Domain_Component.html
│    │      │
│    │      └─ Nom spécifique (card, button, modal...)
│    └──────── Domaine métier (assessment, class, user...)
└─────────────  Type (component, macro, layout...)

📝 Exemples:

  • assessment_card.html
  • class_list_item.html
  • user_profile_widget.html
  • genericCard.html (PascalCase)
  • card.html (trop générique)

🎨 Design Tokens & Cohérence

Palette de Couleurs Système

// Couleurs Primaires
$blue:   from-blue-500 to-blue-600     // Actions principales
$green:  from-green-500 to-green-600   // Succès, validation
$purple: from-purple-500 to-purple-600 // Évaluations, analytique
$orange: from-orange-500 to-orange-600 // Avertissement, en cours
$red:    from-red-500 to-red-600       // Erreur, critique
$pink:   from-pink-500 to-pink-600     // Spécial, mise en avant

// Couleurs Neutres
$gray-50:  #f9fafb    // Fond très clair
$gray-100: #f3f4f6    // Fond clair
$gray-600: #4b5563    // Texte secondaire
$gray-900: #111827    // Texte principal

Typography Scale

// Hiérarchie Textuelle
.text-4xl  { font-size: 2.25rem }  // Titres Hero
.text-3xl  { font-size: 1.875rem } // Titres principaux
.text-2xl  { font-size: 1.5rem }   // Titres sections
.text-xl   { font-size: 1.25rem }  // Sous-titres
.text-lg   { font-size: 1.125rem } // Texte large
.text-base { font-size: 1rem }     // Texte standard
.text-sm   { font-size: 0.875rem } // Texte petit
.text-xs   { font-size: 0.75rem }  // Métadonnées

Spacing System

// Système d'espacement (Tailwind)
space-1: 0.25rem    // 4px
space-2: 0.5rem     // 8px
space-3: 0.75rem    // 12px
space-4: 1rem       // 16px
space-6: 1.5rem     // 24px
space-8: 2rem       // 32px
space-12: 3rem      // 48px

🧩 Anatomie d'un Composant

Template Structure

{# 1. Commentaire de description #}
{# Composant pour [FONCTION] dans [CONTEXTE] #}

{# 2. Import des dépendances #}
{% from 'components/common/macros.html' import helper_macro %}

{# 3. Définition du macro principal #}
{% macro component_name(required_param, optional_param=default) %}

{# 4. Logique de configuration #}
{% set config = {
    'variant': optional_param,
    'classes': 'base-classes ' + (additional_classes if condition else '')
} %}

{# 5. Structure HTML avec données dynamiques #}
<div class="{{ config.classes }}" data-component="component-name">
    <!-- Contenu structuré -->
    {% if caller %}
        {{ caller() }}
    {% else %}
        <!-- Contenu par défaut -->
    {% endif %}
</div>

{# 6. Fin du macro #}
{% endmacro %}

Exemple Concret : Button Component

{# Composant bouton avec variants et états #}

{% macro button(
    text, 
    url=None, 
    type="primary", 
    size="md", 
    icon=None,
    disabled=False,
    classes=""
) %}

{# Configuration des variants #}
{% set variants = {
    'primary': 'bg-gradient-to-r from-blue-500 to-blue-600 text-white',
    'secondary': 'bg-gray-50 text-gray-700 border border-gray-300',
    'danger': 'bg-gradient-to-r from-red-500 to-red-600 text-white'
} %}

{% set sizes = {
    'sm': 'px-3 py-2 text-sm',
    'md': 'px-4 py-2 text-base', 
    'lg': 'px-6 py-3 text-lg'
} %}

{# Classes finales #}
{% set button_classes = [
    'inline-flex items-center justify-center',
    'rounded-xl font-semibold transition-all duration-300',
    'hover:shadow-lg transform hover:scale-105',
    variants[type],
    sizes[size],
    'opacity-50 cursor-not-allowed' if disabled else '',
    classes
]|join(' ') %}

{# Rendu conditionnel (lien vs bouton) #}
{% if url and not disabled %}
    <a href="{{ url }}" class="{{ button_classes }}">
{% else %}
    <button type="button" class="{{ button_classes }}"{% if disabled %} disabled{% endif %}>
{% endif %}
    {% if icon %}
        <span class="w-5 h-5 mr-2">{{ icon|safe }}</span>
    {% endif %}
    {{ text }}
{% if url and not disabled %}</a>{% else %}</button>{% endif %}

{% endmacro %}

🎛️ API des Composants

Paramètres Standards

Chaque composant doit exposer une API cohérente :

{% macro component_name(
    # === OBLIGATOIRES ===
    data,              # Données principales
    title,             # Titre/Label
    
    # === OPTIONNELS ===
    variant="default", # Type/Style (primary, secondary, danger...)
    size="md",         # Taille (sm, md, lg, xl)
    classes="",        # Classes CSS additionnelles
    
    # === COMPORTEMENT ===
    clickable=True,    # Interactif ou non
    disabled=False,    # État désactivé
    loading=False,     # État de chargement
    
    # === CONTENU ===
    icon=None,         # Icône SVG
    badge=None,        # Badge/Compteur
    actions=[],        # Liste d'actions
    
    # === TECHNIQUE ===
    id=None,          # ID HTML personnalisé
    attrs={}          # Attributs HTML additionnels
) %}

Exemple d'Utilisation

{# Usage simple #}
{{ button("Sauvegarder") }}

{# Usage avancé #}
{{ button(
    text="Créer évaluation",
    url=url_for('assessments.new'),
    type="primary",
    size="lg", 
    icon='<svg>...</svg>',
    classes="w-full md:w-auto"
) }}

🔄 États et Variants

États Standardisés

Tous les composants interactifs doivent gérer ces états :

// États de base
.default     // État normal
.hover       // Survol
.active      // Actif/Pressé  
.focus       // Focus clavier
.disabled    // Désactivé

// États applicatifs
.loading     // Chargement en cours
.success     // Succès/Validation
.error       // Erreur
.warning     // Avertissement

Variants de Style

// Variants visuels
.primary     // Action principale (bleu)
.secondary   // Action secondaire (gris)
.success     // Succès (vert)
.warning     // Attention (orange) 
.danger      // Danger (rouge)
.ghost       // Transparent
.outline     // Contour seulement

Exemple : Card States

{# États d'une carte #}
{% set card_states = {
    'default': 'bg-white shadow-lg hover:shadow-xl',
    'active': 'bg-blue-50 border-2 border-blue-200 shadow-xl',
    'disabled': 'bg-gray-50 opacity-50 cursor-not-allowed',
    'loading': 'bg-gray-50 animate-pulse'
} %}

<div class="rounded-xl transition-all duration-300 {{ card_states[state] }}">
    {% if state == 'loading' %}
        <div class="p-6">
            <div class="animate-pulse">
                <div class="h-4 bg-gray-300 rounded w-3/4 mb-2"></div>
                <div class="h-3 bg-gray-300 rounded w-1/2"></div>
            </div>
        </div>
    {% else %}
        <!-- Contenu normal -->
    {% endif %}
</div>

📱 Responsive Design

Breakpoints Standard

// Mobile First approach
sm:  640px   // Téléphone large / Tablette portrait
md:  768px   // Tablette
lg:  1024px  // Desktop
xl:  1280px  // Large desktop
2xl: 1536px  // Ultra wide

Grilles Responsives

{# Grille adaptative standard #}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
    {% for item in items %}
        {{ item_card(item) }}
    {% endfor %}
</div>

{# Navigation responsive #}
<nav class="flex flex-col md:flex-row md:items-center space-y-4 md:space-y-0 md:space-x-6">
    <!-- Items navigation -->
</nav>

Typography Responsive

// Échelle typographique adaptative
.hero-title {
    @apply text-2xl md:text-4xl lg:text-5xl;
}

.section-title {
    @apply text-lg md:text-xl lg:text-2xl;
}

.body-text {
    @apply text-sm md:text-base;
}

Accessibilité

Checklist Obligatoire

  • Contraste ≥ 4.5:1 pour texte normal
  • Contraste ≥ 3:1 pour texte large
  • Navigation clavier complète
  • ARIA labels sur éléments interactifs
  • Focus indicators visibles
  • Screen reader compatible

Implémentation

{# Bouton accessible #}
<button
    type="button"
    class="focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
    aria-label="{{ accessible_label }}"
    {% if disabled %}aria-disabled="true"{% endif %}
    {% if has_popup %}aria-haspopup="true"{% endif %}
>
    {% if icon %}
        <span aria-hidden="true">{{ icon|safe }}</span>
    {% endif %}
    {{ text }}
</button>

{# Card interactive accessible #}
<div
    role="button"
    tabindex="0"
    class="cursor-pointer focus:ring-2 focus:ring-blue-500"
    aria-label="Voir détails de {{ item.name }}"
    onkeydown="if(event.key==='Enter'||event.key===' ') this.click()"
>
    <!-- Contenu carte -->
</div>

ARIA Patterns

<!-- Menu dropdown -->
<button aria-expanded="false" aria-haspopup="true" aria-controls="menu-1">
<ul id="menu-1" role="menu" aria-labelledby="menu-button-1">

<!-- Tabs -->
<div role="tablist" aria-label="Sections">
    <button role="tab" aria-selected="true" aria-controls="panel-1">
<div id="panel-1" role="tabpanel" aria-labelledby="tab-1">

<!-- Progress -->
<div role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">

Performance

Optimisations Obligatoires

{# 1. Lazy loading d'images #}
<img 
    src="{{ image.url }}" 
    loading="lazy"
    alt="{{ image.alt_text }}"
    class="w-full h-auto"
>

{# 2. SVG inline pour icônes #}
{% set icon_check %}
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
    <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
{% endset %}

{# 3. Classes CSS optimisées #}
{% set optimized_classes = [
    'base-class',
    'conditional-class' if condition else '',
    extra_classes
]|select|join(' ') %}

Métriques Cibles

  • Temps de rendu : <10ms par composant
  • Taille HTML : <5KB par composant complexe
  • CSS utilisé : >80% des classes appliquées
  • JavaScript : Minimal (interactions natives CSS)

🧪 Tests & Validation

Tests Automatisés

# test_components.py
def test_component_renders_without_error():
    """Test que le composant se rend sans erreur"""
    with app.app_context():
        template = app.jinja_env.get_template('components/class/class_card.html')
        # Test avec données minimales
        result = template.render(class=mock_class_data)
        assert len(result) > 0
        assert 'class_card' in result  # Vérifier présence indicateur

def test_component_handles_missing_data():
    """Test gestion des données manquantes"""
    with app.app_context():
        template = app.jinja_env.get_template('components/class/class_card.html')
        # Test avec données None/vides
        result = template.render(class=ClassGroup(name=None, students=[]))
        assert 'Aucune description' in result  # Message par défaut
        assert 'bg-gray-100' in result  # État "Vide"

Tests Visuels

# Validation syntaxe tous templates
find templates/components -name "*.html" -exec echo "Testing {}" \; -exec uv run python -c "
from app import create_app
app = create_app()
with app.app_context():
    try:
        template = app.jinja_env.get_template('{}')
        print('✅ Syntaxe valide')
    except Exception as e:
        print(f'❌ Erreur: {e}')
        exit(1)
" \;

# Test responsive (manuel avec DevTools)
# 1. Mobile (375px)
# 2. Tablette (768px) 
# 3. Desktop (1024px)
# 4. Large screen (1440px)

Validation Accessibilité

# Outils recommandés
axe-core        # Test automatisé
WAVE            # Extension navigateur
Lighthouse      # Audit complet
Screen Reader   # Test manuel (NVDA/JAWS/VoiceOver)

🔧 Maintenance & Évolution

Versioning des Composants

{# Header de composant avec version #}
{# 
Component: class_card
Version: 1.2.0
Last Modified: 2025-08-07
Breaking Changes: None
Dependencies: macros.html v2.1+
#}

Changelog

## class_card v1.2.0 (2025-08-07)
### Added
- Support pour classes sans description
- Badge "Vide" pour classes sans élèves
- Extraction intelligente niveau de classe

### Changed
- Couleurs selon niveau au lieu d'année scolaire
- Actions regroupées en grid 2 colonnes

### Fixed
- Gestion cas classe.name None
- Responsive sur petits écrans

### Breaking Changes
- Paramètre `year` remplacé par extraction automatique

Dépréciation

{# Composant déprécié - Prévoir migration #}
{% macro old_class_card(class) %}
{# 
⚠️  DEPRECATED: Ce composant sera supprimé en v3.0
    Utiliser class_card de components/class/class_card.html
    Migration guide: docs/frontend/MIGRATION_v2_to_v3.md
#}
<!-- Implementation dépréciée -->
{% endmacro %}

📚 Ressources & Références

Documentation Externe

Outils Recommandés

# Développement
Live Server        # Preview temps réel
DevTools           # Inspect & debug
ColorZilla         # Extraction couleurs
Figma/Adobe XD     # Design source

# Tests
axe DevTools       # Accessibilité
Lighthouse         # Performance & SEO
Browser Stack      # Tests cross-browser
Percy/Chromatic    # Visual regression

Templates de Référence

  • assessment_card.html - Carte complexe avec états multiples
  • hero_section macro - Section d'en-tête avec gradient
  • simple_filter_section - Filtres interactifs compacts
  • progress_indicator - Indicateur de progression dynamique

Checklist Validation Composant

Avant de valider un nouveau composant :

📝 Code Quality

  • Syntaxe Jinja2 valide
  • Paramètres documentés dans commentaires
  • Gestion d'erreurs (None, empty, invalid data)
  • Performance optimisée (classes, rendering)
  • Sécurité (XSS prevention, safe filters)

🎨 Design System

  • Couleurs du design system utilisées
  • Typography scale respectée
  • Spacing cohérent (multiples de 4px)
  • Animations fluides (300ms transitions)
  • États visuels définis (hover, focus, disabled)

📱 Responsive & Accessibility

  • Mobile-first approche
  • Breakpoints standards utilisés
  • Contraste suffisant (4.5:1 minimum)
  • Focus indicators visibles
  • Screen reader compatible

🧪 Tests

  • Template rendering sans erreur
  • Données variées testées (vide, None, long text)
  • Navigation clavier fonctionnelle
  • Multi-browser compatible
  • Visual regression validé

📖 Documentation

  • Usage examples fournis
  • API parameters documentés
  • Dependencies listées
  • Migration guide si breaking changes

🎯 En suivant ce guide, tous les composants Notytex maintiendront un niveau de qualité et de cohérence exceptionnels !