petitspas/frontend/lib/widgets/README_FORM_WIDGETS.md
Julien Martin 890619ff59 feat(#78): Créer infrastructure générique pour formulaires multi-modes
Nouvelle architecture centralisée pour tous les formulaires :

**Configuration centrale (display_config.dart):**
- DisplayMode enum (editable/readonly)
- LayoutType enum (mobile/desktop)
- DisplayConfig class pour configuration complète
- LayoutHelper avec utilitaires (détection, spacing, etc.)
- Breakpoint: 600px (mobile < 600px reste toujours vertical)

**Widgets génériques (form_field_wrapper.dart):**
- FormFieldWrapper: champ auto-adaptatif (TextField ou Text readonly)
- FormFieldRow: ligne responsive (horizontal desktop, vertical mobile)

**Structure de page (base_form_screen.dart):**
- BaseFormScreen: layout complet avec carte, boutons, navigation
- Gestion auto des assets carte (horizontal/vertical selon layout)

**Avantages:**
 Code unique pour editable + readonly + mobile + desktop
 Logique centralisée (aucune duplication)
 Héritage automatique via DisplayConfig propagé
 API simple et cohérente

Prochaine étape: Migration des widgets existants

Référence: #78

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 17:33:29 +01:00

5.6 KiB

Infrastructure générique pour les formulaires

📋 Vue d'ensemble

Cette infrastructure permet de créer des formulaires qui s'adaptent automatiquement :

  • Mode éditable (inscription) vs lecture seule (récapitulatif)
  • Layout mobile (vertical, < 600px) vs desktop (horizontal, ≥ 600px)
  • Mobile reste toujours vertical, même en rotation paysage

🏗️ Architecture

1. display_config.dart - Configuration centrale

// Mode d'affichage
enum DisplayMode {
  editable,   // Formulaire éditable
  readonly,   // Récapitulatif
}

// Type de layout
enum LayoutType {
  mobile,     // < 600px, toujours vertical
  desktop,    // ≥ 600px, horizontal
}

// Configuration complète
DisplayConfig config = DisplayConfig.fromContext(
  context,
  mode: DisplayMode.editable,
);

2. form_field_wrapper.dart - Champs génériques

FormFieldWrapper

Widget pour afficher un champ unique qui s'adapte automatiquement.

Mode éditable :

FormFieldWrapper(
  config: config,
  label: 'Prénom',
  value: '',
  controller: firstNameController,
  onChanged: (value) => {},
  hint: 'Entrez votre prénom',
)

Mode readonly :

FormFieldWrapper(
  config: config,
  label: 'Prénom',
  value: 'Jean',
)

FormFieldRow

Widget pour afficher plusieurs champs sur une ligne (desktop) ou en colonne (mobile).

FormFieldRow(
  config: config,
  fields: [
    FormFieldWrapper(...),
    FormFieldWrapper(...),
  ],
)

3. base_form_screen.dart - Structure de page générique

Encapsule toute la structure d'une page de formulaire :

  • En-tête (étape + titre)
  • Carte avec fond adapté (horizontal/vertical)
  • Boutons de navigation
  • Gestion automatique du layout
BaseFormScreen(
  config: DisplayConfig.fromContext(
    context,
    mode: DisplayMode.editable,
  ),
  stepText: 'Étape 1/4',
  title: 'Informations personnelles',
  cardColor: CardColorHorizontal.blue,
  previousRoute: '/previous',
  onSubmit: () => _handleSubmit(),
  content: Column(
    children: [
      FormFieldRow(
        config: config,
        fields: [
          FormFieldWrapper(...),
          FormFieldWrapper(...),
        ],
      ),
    ],
  ),
)

📱 Comportement responsive

Breakpoint : 600px

Largeur écran LayoutType Orientation carte Disposition champs
< 600px mobile Verticale Colonne
≥ 600px desktop Horizontale Ligne

Règle importante

Sur mobile, le layout reste TOUJOURS vertical, même si l'utilisateur tourne son téléphone en mode paysage.

🎨 Utilisation dans un widget de formulaire

Exemple : PersonalInfoFormScreen

class PersonalInfoFormScreen extends StatefulWidget {
  final DisplayMode mode;
  final PersonalInfoData? initialData;
  final Function(PersonalInfoData) onSubmit;
  
  // ...
}

class _PersonalInfoFormScreenState extends State<PersonalInfoFormScreen> {
  late TextEditingController _firstNameController;
  late TextEditingController _lastNameController;
  
  @override
  Widget build(BuildContext context) {
    final config = DisplayConfig.fromContext(
      context,
      mode: widget.mode,
    );
    
    return BaseFormScreen(
      config: config,
      stepText: 'Étape 1/4',
      title: 'Informations personnelles',
      cardColor: CardColorHorizontal.blue,
      previousRoute: '/previous',
      onSubmit: _handleSubmit,
      content: Column(
        children: [
          FormFieldRow(
            config: config,
            fields: [
              FormFieldWrapper(
                config: config,
                label: 'Prénom',
                value: _firstNameController.text,
                controller: config.isEditable ? _firstNameController : null,
                onChanged: config.isEditable ? (v) => setState(() {}) : null,
              ),
              FormFieldWrapper(
                config: config,
                label: 'Nom',
                value: _lastNameController.text,
                controller: config.isEditable ? _lastNameController : null,
                onChanged: config.isEditable ? (v) => setState(() {}) : null,
              ),
            ],
          ),
        ],
      ),
    );
  }
  
  void _handleSubmit() {
    final data = PersonalInfoData(
      firstName: _firstNameController.text,
      lastName: _lastNameController.text,
    );
    widget.onSubmit(data);
  }
}

Avantages

  1. Code unique : Un seul widget pour éditable + readonly + mobile + desktop
  2. Cohérence : Tous les formulaires se comportent de la même façon
  3. Maintenance : Modification centralisée de l'UI
  4. Performance : Pas de rebuild inutile, layout déterminé au build
  5. Simplicité : API claire et prévisible

🔧 Utilitaires disponibles

// Détecter le type de layout
bool isMobile = LayoutHelper.isMobile(context);
bool isDesktop = LayoutHelper.isDesktop(context);

// Espacement adaptatif
double spacing = LayoutHelper.getSpacing(
  context,
  mobileSpacing: 12.0,
  desktopSpacing: 20.0,
);

// Largeur max adaptative
double maxWidth = LayoutHelper.getMaxWidth(context);

🚀 Migration des widgets existants

Pour migrer un widget existant vers cette infrastructure :

  1. Ajouter paramètre DisplayMode mode
  2. Créer DisplayConfig.fromContext(context, mode: widget.mode)
  3. Remplacer la structure Scaffold par BaseFormScreen
  4. Remplacer les champs par FormFieldWrapper
  5. Grouper les champs avec FormFieldRow
  6. Tester en mode editable + readonly + mobile + desktop