/* ============================================================ mobile-forms.jsx Composants de saisie mobile avec contrôle du clavier virtuel. Tous nommés et exposés sur window. ============================================================ */ const { useState: uMF, useRef: rMF } = React; /* ============================================================ FormField — wrapper standard pour un champ Nom système : FormField Affiche : label · description · le champ · message d'erreur/hint ============================================================ */ function FormField({ label, hint, error, required, children }) { return (
{label && ( )} {children} {(error || hint) && (
{error || hint}
)}
); } /* ============================================================ TextInput — champ texte avec contrôle complet du clavier virtuel Nom système : TextInput Props clavier virtuel (mobile uniquement) : keyboard: 'text' | 'numeric' | 'tel' | 'email' | 'url' | 'search' | 'decimal' | 'none' autocomplete: 'name'|'email'|'tel'|'address-line1'|'postal-code'|'country'| 'given-name'|'family-name'|'current-password'|'new-password'| 'one-time-code'|'off'… (Web Authentication API) autocapitalize: 'sentences' | 'words' | 'characters' | 'off' spellCheck: bool enterHint: 'send'|'search'|'go'|'done'|'next'|'previous' (texte de la touche Entrée) pattern: regex de validation ============================================================ */ function TextInput({ value, onChange, placeholder, type = 'text', icon, trailing, keyboard, autocomplete = 'off', autocapitalize = 'sentences', spellCheck = false, enterHint, pattern, maxLength, multiline, rows = 4, error, }) { const C = multiline ? 'textarea' : 'input'; const inputProps = { value, onChange: (e) => onChange(e.target.value), placeholder, inputMode: keyboard, autoComplete: autocomplete, autoCapitalize: autocapitalize, spellCheck, enterKeyHint: enterHint, pattern, maxLength, rows: multiline ? rows : undefined, type: !multiline ? type : undefined, style: { flex: 1, minWidth: 0, background: 'transparent', border: 'none', outline: 'none', color: 'var(--ink-1)', fontFamily: type === 'password' ? 'var(--font-mono)' : 'var(--font-ui)', fontSize: 15, padding: multiline ? '4px 0' : 0, resize: multiline ? 'vertical' : undefined, minHeight: multiline ? rows * 22 : undefined, }, }; return (
{icon && } {trailing}
); } /* ============================================================ DateInput — date picker natif mobile Nom système : DateInput ============================================================ */ function DateInput({ value, onChange, mode = 'date' }) { // mode : 'date' | 'datetime-local' | 'time' | 'month' | 'week' const icons = { date: 'clock', 'datetime-local': 'clock', time: 'clock', month: 'clock', week: 'clock' }; return (
onChange(e.target.value)} style={{ flex: 1, minWidth: 0, background: 'transparent', border: 'none', outline: 'none', color: 'var(--ink-1)', fontFamily: 'var(--font-mono)', fontSize: 15, colorScheme: 'dark', }} />
); } /* ============================================================ Dropdown — select natif stylisé Nom système : Dropdown ============================================================ */ function Dropdown({ value, onChange, options, placeholder = 'Choisir…' }) { return (
); } /* ============================================================ CheckboxItem — case à cocher (style iOS) Nom système : CheckboxItem Cas : oui/non sur une option, sélection multiple dans une liste ============================================================ */ function CheckboxItem({ checked, onChange, label, description }) { return ( ); } /* ============================================================ RadioGroup — groupe d'options exclusives Nom système : RadioGroup ============================================================ */ function RadioGroup({ value, onChange, options }) { return (
{options.map((o, i) => { const v = typeof o === 'string' ? o : o.value; const l = typeof o === 'string' ? o : o.label; const d = typeof o === 'object' ? o.description : null; const active = value === v; return ( ); })}
); } /* ============================================================ MediaInsert — boutons "insérer..." pour image/vidéo/audio/GPS Nom système : MediaInsert Cas : ajouter une pièce jointe dans un formulaire mobile. Note : utilise les API natives via et navigator.geolocation pour le GPS. ============================================================ */ function MediaInsert({ onPick }) { const items = [ { id: 'photo', icon: 'grid', label: 'Photo', hint: 'Appareil photo', accept: 'image/*', capture: 'environment' }, { id: 'image', icon: 'folder', label: 'Image', hint: 'Depuis la galerie', accept: 'image/*' }, { id: 'video', icon: 'play', label: 'Vidéo', hint: 'Caméra ou galerie', accept: 'video/*', capture: 'environment' }, { id: 'audio', icon: 'terminal', label: 'Audio', hint: 'Enregistrement vocal', accept: 'audio/*', capture: 'user' }, { id: 'file', icon: 'download', label: 'Fichier', hint: 'Doc, PDF, autre', accept: '*' }, { id: 'gps', icon: 'network', label: 'Position', hint: 'GPS du téléphone', special: true }, ]; return (
{items.map((it) => ( ))}
); } /* ============================================================ AvatarLogo — gros logo rond pour écran de connexion Nom système : AvatarLogo ============================================================ */ function AvatarLogo({ icon = 'server', size = 80, glow = true }) { return (
); } /* ============================================================ BiometricButton — bouton biométrie (Face ID / Touch ID) Nom système : BiometricButton ============================================================ */ function BiometricButton({ kind = 'face', label, onClick }) { const lbl = label || (kind === 'face' ? 'Face ID' : 'Touch ID'); return ( ); } Object.assign(window, { FormField, TextInput, DateInput, Dropdown, CheckboxItem, RadioGroup, MediaInsert, AvatarLogo, BiometricButton, }); /* ============================================================ CATALOGUE KEYBOARD — pour la doc ============================================================ */ const KEYBOARD_CATALOG = [ { name: 'text', desc: 'Clavier standard (lettres + chiffres).', usage: 'Tout texte libre, noms.' }, { name: 'numeric', desc: 'Pavé numérique sans signe ni virgule.', usage: 'Codes PIN, OTP, références numériques.' }, { name: 'decimal', desc: 'Pavé numérique avec virgule/point.', usage: 'Prix, mesures, montants.' }, { name: 'tel', desc: 'Pavé téléphone avec + et formats.', usage: 'Numéros de téléphone.' }, { name: 'email', desc: 'Clavier texte avec @ et . en accès direct.', usage: 'Adresses email.' }, { name: 'url', desc: 'Clavier texte avec / et .com.', usage: 'URLs, liens.' }, { name: 'search', desc: 'Clavier standard, touche Entrée = "Rechercher".', usage: 'Champs de recherche.' }, { name: 'none', desc: 'Aucun clavier (utile avec un picker custom).', usage: 'Date picker custom, sélecteur de couleur, etc.' }, ]; const AUTOCOMPLETE_CATALOG = [ { name: 'name / given-name / family-name', usage: 'Nom complet, prénom, nom de famille' }, { name: 'email', usage: 'Adresse email (autoremplie depuis le compte iOS/Android)' }, { name: 'tel', usage: 'Numéro de téléphone' }, { name: 'address-line1 / postal-code / country', usage: 'Adresse postale' }, { name: 'current-password', usage: 'Mot de passe existant (Face ID/Touch ID propose le remplissage)' }, { name: 'new-password', usage: 'Nouveau mot de passe (le gestionnaire propose d\'en générer un)' }, { name: 'one-time-code', usage: 'Code OTP reçu par SMS (auto-lu sur iOS / Android)' }, { name: 'off', usage: 'Désactive complètement les suggestions' }, ]; const ENTER_HINT_CATALOG = [ { name: 'send', usage: 'Envoyer un message (chat, email)' }, { name: 'search', usage: 'Rechercher (résultat affiché en bas)' }, { name: 'go', usage: 'Y aller (URL, action de navigation)' }, { name: 'done', usage: 'Terminer la saisie et fermer le clavier' }, { name: 'next', usage: 'Passer au champ suivant du formulaire' }, { name: 'previous', usage: 'Revenir au champ précédent' }, ]; Object.assign(window, { KEYBOARD_CATALOG, AUTOCOMPLETE_CATALOG, ENTER_HINT_CATALOG });