16 KiB
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
- TailwindCSS : tailwindcss.com
- WCAG Guidelines : w3.org/WAI/WCAG21/quickref
- Jinja2 Templates : jinja.palletsprojects.com
- Design Tokens : design-tokens.github.io
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 multipleshero_sectionmacro - Section d'en-tête avec gradientsimple_filter_section- Filtres interactifs compactsprogress_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 !