---
title: "Chapitre 3 : Formulaires"
weight: 550
---

# Chapitre 3 : Formulaires

## Form

Le composant `Form` gère la soumission et la validation des formulaires.

### Syntaxe de base

```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

```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

```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.

```javascript
import { Textarea } from '@cap-rel/smartcommon';

<Textarea
    name="description"
    label="Description"
    rows={4}
    placeholder="Décrivez le produit..."
/>
```

## Select

Liste déroulante.

```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.

```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.

```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.

```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.

```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.

```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.

```javascript
import { ColorPicker } from '@cap-rel/smartcommon';

<ColorPicker
    name="color"
    label="Couleur du produit"
/>
```

## FilesUploader

Upload de fichiers.

```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.

```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.

```javascript
import { SignaturePad } from '@cap-rel/smartcommon';

<SignaturePad
    name="signature"
    label="Signature"
    width={400}
    height={200}
/>
```

## Exemple complet : Formulaire produit

```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 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

```javascript
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

1. **Form** gère la soumission et collecte les valeurs
2. **name** est obligatoire sur chaque champ
3. **defaultValues** pour pré-remplir (édition)
4. **required** pour les champs obligatoires
5. **Zod** pour la validation avancée

[← Chapitre précédent](/training/module6-smartcommon-composants/navigation) | [Retour au module](/training/module6-smartcommon-composants) | [Chapitre suivant : Affichage ->](/training/module6-smartcommon-composants/affichage)
