Chapitre 3 : Formulaires
Form
Le composant Form gère la soumission et la validation des formulaires.
Syntaxe de base
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
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
// 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.
import { Textarea } from '@cap-rel/smartcommon';
<Textarea
name="description"
label="Description"
rows={4}
placeholder="Décrivez le produit..."
/>
Select
Liste déroulante.
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.
import { Boolean } from '@cap-rel/smartcommon';
<Boolean
name="isActive"
label="Produit actif"
/>
<Boolean
name="notifications"
label="Recevoir les notifications"
defaultValue={true}
/>
Checker
Case à cocher.
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.
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.
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.
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.
import { ColorPicker } from '@cap-rel/smartcommon';
<ColorPicker
name="color"
label="Couleur du produit"
/>
FilesUploader
Upload de fichiers.
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.
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.
import { SignaturePad } from '@cap-rel/smartcommon';
<SignaturePad
name="signature"
label="Signature"
width={400}
height={200}
/>
Exemple complet : Formulaire produit
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 nav = 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 });
}
nav.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={() => nav.navigate(-1)}
>
Annuler
</Button>
<Button
type="submit"
loading={st.get('submitting')}
>
{isEdit ? 'Enregistrer' : 'Créer'}
</Button>
</div>
</Form>
</Block>
</Page>
);
};
Validation manuelle avec useForm
import { Form, Input, Button } from '@cap-rel/smartcommon';
import { useForm } from '@cap-rel/smartcommon';
function RegisterForm() {
const form = useForm({
defaultValues: { email: '', password: '', confirmPassword: '' }
});
const handleSubmit = async (data) => {
console.log(data);
};
// Validation manuelle via setField
const validateEmail = (value) => {
form.setField({
name: 'email',
value,
errors: {
required: { condition: !value },
format: { condition: value && !value.includes('@') }
}
});
};
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 ->