Chapitre 3 : Formulaires
Form
Le composant Form gère la soumission et la validation des formulaires.
Syntaxe de base
- snippet.javascript
import { Form, Input, Button } from '@cap-rel/smartcommon'; function ContactForm() { const handleSubmit = (values) => { console.log(values); // { name: 'Jean', email: 'jean@example.com' } }; return ( <Form onSubmit={handleSubmit}> <Input name="name" label="Nom" required /> <Input name="email" label="Email" type="email" required /> <Button type="submit">Envoyer</Button> </Form> ); }
Avec valeurs initiales
- snippet.javascript
function EditProductForm({ product }) { const handleSubmit = async (values) => { await api.private.put(`products/${product.id}`, { json: values }); }; return ( <Form onSubmit={handleSubmit} defaultValues={{ label: product.label, price: product.price, description: product.description }} > <Input name="label" label="Nom du produit" /> <Input name="price" label="Prix" type="number" /> <Input name="description" label="Description" /> <Button type="submit">Enregistrer</Button> </Form> ); }
Input
Champ de saisie texte polyvalent.
Props principales
| Prop | Type | Description |
|---|---|---|
| name | string | Nom du champ (obligatoire) |
| label | string | Libellé affiché |
| type | string | 'text', 'email', 'password', 'number', 'tel' |
| placeholder | string | Texte indicatif |
| required | boolean | Champ obligatoire |
| disabled | boolean | Champ désactivé |
| error | string | Message d'erreur |
Exemples
- snippet.javascript
// Texte simple <Input name="firstName" label="Prénom" /> // Email avec validation <Input name="email" label="Email" type="email" required /> // Mot de passe <Input name="password" label="Mot de passe" type="password" /> // Nombre <Input name="quantity" label="Quantité" type="number" min={0} max={100} /> // Téléphone <Input name="phone" label="Téléphone" type="tel" /> // Avec placeholder <Input name="search" placeholder="Rechercher..." type="text" /> // Désactivé <Input name="ref" label="Référence" disabled value="PRD-001" />
Textarea
Zone de texte multiligne.
- snippet.javascript
import { Textarea } from '@cap-rel/smartcommon'; <Textarea name="description" label="Description" rows={4} placeholder="Décrivez le produit..." />
Select
Liste déroulante.
- snippet.javascript
import { Select } from '@cap-rel/smartcommon'; // Options simples <Select name="category" label="Catégorie" options={[ { value: 'electronics', label: 'Électronique' }, { value: 'clothing', label: 'Vêtements' }, { value: 'food', label: 'Alimentation' } ]} /> // Avec valeur vide <Select name="status" label="Statut" placeholder="Sélectionnez..." options={[ { value: 'draft', label: 'Brouillon' }, { value: 'published', label: 'Publié' }, { value: 'archived', label: 'Archivé' } ]} /> // Options depuis une API function ProductForm() { const [categories, setCategories] = useState([]); useEffect(() => { api.private.get('categories').json().then(data => { setCategories(data.map(c => ({ value: c.id, label: c.name }))); }); }, []); return ( <Form> <Select name="category_id" label="Catégorie" options={categories} /> </Form> ); }
Boolean
Interrupteur on/off.
- snippet.javascript
import { Boolean } from '@cap-rel/smartcommon'; <Boolean name="isActive" label="Produit actif" /> <Boolean name="notifications" label="Recevoir les notifications" defaultValue={true} />
Checker
Case à cocher.
- snippet.javascript
import { Checker } from '@cap-rel/smartcommon'; <Checker name="acceptTerms" label="J'accepte les conditions générales" required /> // Groupe de cases <div> <Checker name="options.express" label="Livraison express" /> <Checker name="options.gift" label="Emballage cadeau" /> <Checker name="options.insurance" label="Assurance" /> </div>
Calendar
Sélecteur de date.
- snippet.javascript
import { Calendar } from '@cap-rel/smartcommon'; // Date simple <Calendar name="birthdate" label="Date de naissance" /> // Avec contraintes <Calendar name="startDate" label="Date de début" minDate={new Date()} maxDate={new Date(2025, 11, 31)} /> // Date et heure <Calendar name="appointment" label="Rendez-vous" showTime={true} />
Timer
Sélecteur d'heure.
- snippet.javascript
import { Timer } from '@cap-rel/smartcommon'; <Timer name="startTime" label="Heure de début" /> <Timer name="duration" label="Durée" format="HH:mm" />
Range
Slider de valeur.
- snippet.javascript
import { Range } from '@cap-rel/smartcommon'; <Range name="price" label="Prix maximum" min={0} max={1000} step={10} /> <Range name="rating" label="Note minimale" min={1} max={5} step={0.5} />
ColorPicker
Sélecteur de couleur.
- snippet.javascript
import { ColorPicker } from '@cap-rel/smartcommon'; <ColorPicker name="color" label="Couleur du produit" />
FilesUploader
Upload de fichiers.
- snippet.javascript
import { FilesUploader } from '@cap-rel/smartcommon'; <FilesUploader name="documents" label="Documents" accept=".pdf,.doc,.docx" multiple maxFiles={5} />
PhotosUploader
Upload de photos avec prévisualisation.
- snippet.javascript
import { PhotosUploader } from '@cap-rel/smartcommon'; <PhotosUploader name="photos" label="Photos du produit" maxFiles={10} maxSize={5 * 1024 * 1024} // 5 Mo />
SignaturePad
Zone de signature manuscrite.
- snippet.javascript
import { SignaturePad } from '@cap-rel/smartcommon'; <SignaturePad name="signature" label="Signature" width={400} height={200} />
Exemple complet : Formulaire produit
- snippet.javascript
import { useEffect } from 'react'; import { Page, Block, Form, Input, Textarea, Select, Boolean, Calendar, PhotosUploader, Button } from '@cap-rel/smartcommon'; import { useApi, useNavigation, useStates } from '@cap-rel/smartcommon'; export const ProductFormPage = ({ productId }) => { const api = useApi(); const navigate = useNavigation(); const isEdit = !!productId; const st = useStates({ initialStates: { product: null, categories: [], loading: isEdit, submitting: false } }); // Charger les catégories useEffect(() => { api.private.get('categories').json().then(data => { st.set('categories', data.map(c => ({ value: c.id, label: c.name }))); }); }, []); // Charger le produit si édition useEffect(() => { if (isEdit) { api.private.get(`products/${productId}`).json().then(data => { st.set('product', data); st.set('loading', false); }); } }, [productId]); const handleSubmit = async (values) => { st.set('submitting', true); try { if (isEdit) { await api.private.put(`products/${productId}`, { json: values }); } else { await api.private.post('products', { json: values }); } navigate('/products'); } catch (err) { alert('Erreur : ' + err.message); } finally { st.set('submitting', false); } }; if (st.get('loading')) { return <Page title="Chargement..."><Spinner /></Page>; } return ( <Page title={isEdit ? 'Modifier le produit' : 'Nouveau produit'}> <Block> <Form onSubmit={handleSubmit} defaultValues={st.get('product') || { label: '', description: '', price: 0, category_id: null, isActive: true, availableFrom: null, photos: [] }} > {/* Informations de base */} <Input name="label" label="Nom du produit" required /> <Textarea name="description" label="Description" rows={4} /> <Input name="price" label="Prix (€)" type="number" min={0} step={0.01} required /> <Select name="category_id" label="Catégorie" options={st.get('categories')} required /> {/* Options */} <Boolean name="isActive" label="Produit actif" /> <Calendar name="availableFrom" label="Disponible à partir de" /> {/* Photos */} <PhotosUploader name="photos" label="Photos" maxFiles={5} /> {/* Actions */} <div className="flex gap-2 mt-4"> <Button type="button" variant="outline" onClick={() => navigate(-1)} > Annuler </Button> <Button type="submit" loading={st.get('submitting')} > {isEdit ? 'Enregistrer' : 'Créer'} </Button> </div> </Form> </Block> </Page> ); };
Validation avec Zod
- snippet.javascript
import { z } from 'zod'; import { Form, Input, Button } from '@cap-rel/smartcommon'; import { useForm } from '@cap-rel/smartcommon'; const schema = z.object({ email: z.string().email('Email invalide'), password: z.string().min(8, 'Minimum 8 caractères'), confirmPassword: z.string() }).refine(data => data.password === data.confirmPassword, { message: 'Les mots de passe ne correspondent pas', path: ['confirmPassword'] }); function RegisterForm() { const form = useForm({ schema }); const handleSubmit = async (data) => { // data est validé console.log(data); }; return ( <Form form={form} onSubmit={handleSubmit}> <Input name="email" label="Email" type="email" /> <Input name="password" label="Mot de passe" type="password" /> <Input name="confirmPassword" label="Confirmer" type="password" /> <Button type="submit">S'inscrire</Button> </Form> ); }
Points clés à retenir
- Form gère la soumission et collecte les valeurs
- name est obligatoire sur chaque champ
- defaultValues pour pré-remplir (édition)
- required pour les champs obligatoires
- Zod pour la validation avancée
← Chapitre précédent | Retour au module | Chapitre suivant : Affichage →