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

221 lines
5.6 KiB
Markdown

# 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
```dart
// 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 :**
```dart
FormFieldWrapper(
config: config,
label: 'Prénom',
value: '',
controller: firstNameController,
onChanged: (value) => {},
hint: 'Entrez votre prénom',
)
```
**Mode readonly :**
```dart
FormFieldWrapper(
config: config,
label: 'Prénom',
value: 'Jean',
)
```
#### FormFieldRow
Widget pour afficher plusieurs champs sur une ligne (desktop) ou en colonne (mobile).
```dart
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
```dart
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
```dart
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
```dart
// 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