/* ============================================================ exemple-mobile-apps-app.jsx 10 écrans navigables : Onboarding, Accueil, Chat (liste+détail), Calendrier, Maps, Recherche, Scanner QR, Caméra, Fichiers, Paramètres. Avatar+menu présents sur tous les écrans en haut à droite. ============================================================ */ const { useState: uX, useEffect: eX } = React; /* ---------- DONNÉES MOCK ---------- */ const CHATS = [ { id: 1, name: 'Marc Dubois', avatar: 'var(--accent)', last: 'Tu peux jeter un œil au scan ?', time: '14:02', unread: 2 }, { id: 2, name: 'Équipe Ops', avatar: 'var(--blue)', last: 'Léa : déploiement OK 🚀', time: '13:14', unread: 0 }, { id: 3, name: 'Sophie', avatar: 'var(--purple)', last: 'On se voit ce soir ?', time: '11:30', unread: 1 }, { id: 4, name: 'Antoine', avatar: 'var(--ok)', last: 'Merci pour le tuyau !', time: 'hier', unread: 0 }, { id: 5, name: 'Notifs syst.', avatar: 'var(--warn)', last: 'Backup horaire OK · 812 Mo', time: 'hier', unread: 0 }, ]; const CHAT_MESSAGES = [ { id: 1, me: false, text: 'Salut ! Tu as eu le temps de regarder le scan d\'hier soir ?', time: '13:58' }, { id: 2, me: true, text: 'Oui, je viens de finir. Le node-03 a un soucis de latence', time: '13:59', status: 'read' }, { id: 3, me: false, text: 'Aïe. Tu peux me partager les détails ?', time: '14:00' }, { id: 4, me: true, text: 'Je t\'envoie le rapport dans 5 min', time: '14:01', status: 'read' }, { id: 5, me: false, text: 'Top, merci 🙏', time: '14:02' }, ]; const FILES = [ { name: 'Documents', type: 'folder', date: '21 mai' }, { name: 'Photos 2026', type: 'folder', date: '12 mai' }, { name: 'rapport-scan.pdf', type: 'pdf', date: '21 mai 14:02', size: 384000 }, { name: 'capture.png', type: 'image', date: '21 mai 13:58', size: 1280000 }, { name: 'meeting.mp4', type: 'video', date: '20 mai', size: 124000000 }, { name: 'notes.md', type: 'code', date: '20 mai', size: 4800 }, { name: 'audio-memo.m4a',type: 'audio', date: '19 mai', size: 820000 }, { name: 'backup.zip', type: 'archive', date: '19 mai 03:00', size: 850000000 }, ]; const ONBOARDING_SLIDES = [ { icon: 'server', color: 'var(--accent)', title: 'Bienvenue 👋', desc: 'Cette app te permet de superviser tes serveurs et capteurs depuis ton téléphone.' }, { icon: 'bell', color: 'var(--blue)', title: 'Tu es alerté en temps réel', desc: 'Reçois une notification dès qu\'un service tombe ou qu\'une métrique sort des seuils.' }, { icon: 'network', color: 'var(--purple)', title: 'Maîtrise ton réseau', desc: 'Lance un scan, repère les nouveaux hôtes, ouvre une session SSH depuis le mobile.' }, ]; const NOW = new Date(2026, 4, 21); // 21 mai 2026 const EVENTS = new Set([3, 7, 12, 14, 21, 24, 28]); /* ============================================================ HeaderAvatar — header partagé (titre + avatar rond) Le bouton Avatar ouvre AvatarMenu (popup). ============================================================ */ function HeaderAvatar({ title, large, subtitle, onBack, onAvatar, avatarColor = 'var(--accent)' }) { return ( } /> ); } /* ============================================================ ÉCRANS ============================================================ */ /* --- Accueil : grille d'apps --- */ function ScreenHome({ goto, onAvatar }) { const apps = [ { id: 'chat', icon: 'bell', color: 'var(--accent)', title: 'Messages', subtitle: '3 non lus', badge: 3 }, { id: 'calendar', icon: 'clock', color: 'var(--blue)', title: 'Calendrier', subtitle: 'mai 2026' }, { id: 'maps', icon: 'network', color: 'var(--ok)', title: 'Carte', subtitle: '8 pins' }, { id: 'search', icon: 'search', color: 'var(--info)', title: 'Recherche', subtitle: 'global' }, { id: 'scanner', icon: 'grid', color: 'var(--purple)', title: 'Scanner QR', subtitle: 'caméra' }, { id: 'camera', icon: 'memory', color: 'var(--warn)', title: 'Appareil photo', subtitle: 'caméra' }, { id: 'files', icon: 'folder', color: 'var(--info)', title: 'Fichiers', subtitle: '124 \u00e9l\u00e9ments' }, { id: 'settings', icon: 'cog', color: 'var(--ink-3)', title: 'Paramètres', subtitle: 'préférences' }, { id: 'onboard', icon: 'play', color: 'var(--accent-soft)', title: 'Onboarding', subtitle: 'revoir l\'intro' }, ]; return (
{}} placeholder="Rechercher dans tout…" />
{apps.map((a) => ( goto(a.id)} /> ))}
); } /* --- Chat list --- */ function ScreenChatList({ onAvatar, open }) { return (
a + c.unread, 0)} non lus`} onAvatar={onAvatar} />
{}} placeholder="Rechercher une conversation" />
{CHATS.map((c) => ( open(c)} rightActions={[ { label: 'Lu', icon: 'play', color: 'var(--info)' }, { label: 'Épingl.', icon: 'bell', color: 'var(--accent)' }, ]} leftActions={[ { label: 'Archiv.', icon: 'folder', color: 'var(--ok)' }, { label: 'Suppr.', icon: 'close', color: 'var(--err)' }, ]}>
{c.name} {c.time}
{c.last}
{c.unread > 0 && ( {c.unread} )}
))}
); } /* --- Chat detail --- */ function ScreenChatDetail({ chat, onBack, onAvatar }) { const [msgs, setMsgs] = uX(CHAT_MESSAGES); const send = (text) => setMsgs([...msgs, { id: Date.now(), me: true, text, time: 'maint.', status: 'sent' }]); return (
{chat.name}
● en ligne
{msgs.map((m) => )}
); } /* --- Calendar --- */ function ScreenCalendar({ onAvatar, showToast }) { const [sel, setSel] = uX(NOW); return (
{ setSel(d); showToast(`${d.getDate()} mai sélectionné`); }} events={EVENTS} /> {}} /> {}} /> {}} />
showToast('Nouvel évènement')} />
); } /* --- Maps --- */ function ScreenMaps({ onAvatar }) { const [active, setActive] = uX(0); const pins = [ { x: 30, y: 35, color: 'var(--accent)', icon: 'server', label: 'Bureau' }, { x: 60, y: 50, color: 'var(--ok)', icon: 'grid', label: 'Maison' }, { x: 75, y: 25, color: 'var(--blue)', icon: 'bell', label: 'Café' }, { x: 22, y: 70, color: 'var(--err)', icon: 'power', label: 'Atelier' }, { x: 50, y: 80, color: 'var(--warn)', icon: 'clock' }, { x: 80, y: 65, color: 'var(--purple)', icon: 'memory' }, ]; return (
} />
{}} placeholder="Adresse, lieu…" />
{/* contrôles flottants */}
{/* bottom info */}
{pins[active].label || 'Lieu sans nom'}
48.8566° N · 2.3522° E
); } /* --- Search avec filtres --- */ function ScreenSearch({ onBack, onAvatar }) { const [q, setQ] = uX(''); const [filters, setFilters] = uX(['all']); const allResults = [ { id: 1, type: 'people', title: 'Sophie Martin', meta: 'sophie@exemple.com' }, { id: 2, type: 'message', title: 'Tu peux jeter un œil…', meta: 'Marc · hier 14:02' }, { id: 3, type: 'file', title: 'rapport-scan.pdf', meta: '21 mai · 384 Ko' }, { id: 4, type: 'event', title: 'Standup équipe', meta: 'mardi 09:00' }, { id: 5, type: 'place', title: 'Bureau Paris 11e', meta: '48.8566° N · 2.3522° E' }, { id: 6, type: 'file', title: 'capture.png', meta: '21 mai · 1.2 Mo' }, ]; const results = allResults.filter((r) => { if (q && !r.title.toLowerCase().includes(q.toLowerCase())) return false; if (filters.includes('all')) return true; return filters.includes(r.type); }); const iconFor = (t) => ({ people: 'user', message: 'bell', file: 'folder', event: 'clock', place: 'network' })[t]; const colorFor = (t) => ({ people: 'var(--accent)', message: 'var(--blue)', file: 'var(--info)', event: 'var(--purple)', place: 'var(--ok)' })[t]; return (
} />
setFilters(v.length ? v : ['all'])} options={[ { value: 'all', label: 'Tout', icon: 'grid' }, { value: 'people', label: 'Personnes', icon: 'user' }, { value: 'message', label: 'Messages', icon: 'bell' }, { value: 'file', label: 'Fichiers', icon: 'folder' }, { value: 'event', label: 'Agenda', icon: 'clock' }, { value: 'place', label: 'Lieux', icon: 'network' }, ]} />
{results.length} résultat{results.length > 1 ? 's' : ''}
{results.map((r) => ( {}} /> ))} {results.length === 0 && (
Aucun résultat pour "{q}"
)}
); } /* --- QR Scanner --- */ function ScreenScanner({ onBack, showToast }) { return (
Scanner QR / code-barres
showToast('Code détecté : https://exemple.com')} />
); } /* --- Caméra --- */ function ScreenCamera({ onBack, showToast }) { return (
showToast('Photo prise')} />
); } /* --- Fichiers --- */ function ScreenFiles({ onBack, onAvatar, showToast }) { return (
} />
{}} placeholder="Rechercher un fichier" />
Mes fichiers / Espace personnel
showToast(`Ouvrir : ${it.name}`)} onAction={(act, it) => showToast(`${act} · ${it.name}`)} />
showToast('Importer un fichier')} />
); } /* --- Settings --- */ function ScreenSettings({ onBack, onAvatar, showToast, openSheet }) { const [notif, setNotif] = uX(true); const [bio, setBio] = uX(false); const [sync, setSync] = uX(true); return (
showToast('Profil')} /> {}} /> openSheet('theme')} /> openSheet('lang')} /> } /> } /> } /> {}} /> {}} /> showToast('Cache effacé')} /> {}} /> {}} /> {}} /> showToast('Déconnexion')} />
); } Object.assign(window, { ScreenHome, ScreenChatList, ScreenChatDetail, ScreenCalendar, ScreenMaps, ScreenSearch, ScreenScanner, ScreenCamera, ScreenFiles, ScreenSettings, ONBOARDING_SLIDES, CHATS, CHAT_MESSAGES, FILES, EVENTS, }); /* ============================================================ exemple-mobile-apps-doc.jsx Doc panneau droite (catalogue commenté avec visuels) + ROOT. ============================================================ */ const { useState: uAD, useEffect: eAD } = React; /* ============================================================ ScreenVisual — mini SVG de chaque écran (pour la doc) ============================================================ */ function ScreenVisual({ type }) { const frame = (inner, bg) => ( {inner} ); if (type === 'onboarding') return frame( {/* dots */} {/* button */} ); if (type === 'home') return frame( {/* 9 grid */} {[0,1,2,0,1,2,0,1,2].map((c, i) => { const r = Math.floor(i / 3); return ; })} ); if (type === 'chat-list') return frame( {[0,1,2,3].map((i) => ( {i === 0 && } ))} ); if (type === 'chat-detail') return frame( {/* messages */} {/* composer */} ); if (type === 'calendar') return frame( {/* day labels */} {[0,1,2,3,4,5,6].map((d) => ( {['L','M','M','J','V','S','D'][d]} ))} {/* days */} {Array.from({length:28}).map((_, i) => { const x = 8 + (i % 7) * 12; const y = 52 + Math.floor(i / 7) * 13; const sel = i === 20; const today = i === 7; return ( {i + 1} {[2,6,11,13,20].includes(i) && } ); })} ); if (type === 'maps') return frame( {/* routes */} {/* pins */} {[[30,55,'var(--accent)'],[60,80,'var(--ok)'],[75,45,'var(--blue)'],[22,110,'var(--err)']].map(([x,y,c], i) => ( ))} {/* bottom card */} ); if (type === 'search') return frame( {/* chips */} {/* results */} {[0,1,2,3,4].map((i) => ( ))} ); if (type === 'scanner') return frame( {/* coins */} {/* scan line */} {/* shutter */} , '#000' ); if (type === 'camera') return frame( {/* règle des tiers */} {/* shutter */} , '#000' ); if (type === 'files') return frame( {[ ['folder','var(--accent)','Documents'], ['folder','var(--accent)','Photos'], ['list','var(--err)','rapport.pdf'], ['grid','var(--blue)','capture.png'], ['play','var(--purple)','meeting.mp4'], ['terminal','var(--ok)','memo.m4a'], ].map((it, i) => ( ))} ); if (type === 'settings') return frame( {[ ['user','var(--blue)','Compte'], ['cog','var(--ink-3)','Préférences'], ['bell','var(--accent)','Notifications', true], ['refresh','var(--ok)','Sync', true], ['folder','var(--purple)','Données'], ['power','var(--err)','Se déconnecter', null, true], ].map((it, i) => ( {it[3] && } ))} ); if (type === 'avatar-menu') return frame( M {/* écran assombri derrière */} {/* popup menu */} {/* items */} {[0,1,2,3].map((i) => ( ))} ); return frame(null); } /* ============================================================ ScreenCard — carte de présentation d'un écran ============================================================ */ function ScreenCard({ type, name, when, why, components, gestures }) { return (
Écran {name}
Quand{when}
Pourquoi{why}
{components &&
Composants{components}
} {gestures &&
Gestes{gestures}
}
); } /* ============================================================ NamedItem (avec preview live) ============================================================ */ function NamedItem({ name, desc, location, preview }) { return (
<{name}/> {location && 📍 {location}}
{desc}
{preview && (
{preview}
)}
); } /* ============================================================ DOC PANEL ============================================================ */ function Doc() { return (

Patterns d'app courants

Ensemble de patterns que toute app mobile moderne utilise : onboarding, chat, calendrier, maps, recherche, scanner, caméra, fichiers, paramètres, plus le bouton avatar en haut à droite qui ouvre un menu utilisateur. Chaque écran est navigable dans le smartphone à gauche.

10 écrans modèles

Bascule entre eux depuis l'Accueil du smartphone (grille d'icônes). L'écran Onboarding se lance automatiquement au premier passage.

Bouton Avatar + menu utilisateur

Pattern récurrent : un bouton rond avec initiales en haut à droite de chaque écran. Au tap, il ouvre un menu déroulant avec accès rapide à Profil, Paramètres, Aide et Déconnexion. Pour le tester : tape sur le rond orange en haut à droite de n'importe quel écran (sauf Onboarding/Scanner/Caméra).

} />
Marc Dupont
marc@exemple.com
{[ { icon: 'user', label: 'Mon profil' }, { icon: 'cog', label: 'Paramètres' }, { icon: 'bell', label: 'Notifications' }, { icon: 'power', label: 'Se déconnecter', danger: true }, ].map((it, i) => (
0 ? '1px solid var(--border-1)' : 'none', color: it.danger ? 'var(--err)' : 'var(--ink-1)', fontSize: 13, }}> {it.label}
))}
} />

Nouveaux composants

Tous accessibles globalement après chargement de mobile-apps.jsx.

{[0,1,2].map((i) => ( ))} } /> } /> {}} />} /> {}} events={new Set([3, 7, 14, 21])} /> } /> } /> {}} options={[ { value: 'a', label: 'Tout', icon: 'grid' }, { value: 'b', label: 'Personnes', icon: 'user' }, { value: 'c', label: 'Fichiers', icon: 'folder' }, { value: 'd', label: 'Messages', icon: 'bell' }, ]} />} /> {}} /> } /> {}} /> } /> {}} /> } />

Antisèche — pattern par cas

Premier lancementOnboardingSlider en plein écran, store flag "vu" en localStorage
Liste à actionsSwipeableRow avec leftActions (destructives) + rightActions (utiles)
Identité utilisateurAvatar en haut à droite + AvatarMenu au tap → profil/réglages/déconnexion
Recherche multi-typesSearchBar + FilterChips + ListRow (un seul container pour tous types)
Caméra/ScannerPlein écran sombre, header floating semi-transparent, contrôles flottants en bas
CarteMapView + boutons flottants (zoom, position) + BottomSheet/glass-strong pour les détails du pin
RéglagesEmpilement de ListSection avec en bas un ListRow danger "Se déconnecter"
); } /* ============================================================ APP ROOT ============================================================ */ function PhoneAppApps({ theme }) { const [screen, setScreen] = uAD('home'); const [openedChat, setOpenedChat] = uAD(null); const [toast, setToast] = uAD(null); const [avatarOpen, setAvatarOpen] = uAD(false); const [sheet, setSheet] = uAD(null); const showToast = (msg) => setToast(msg); const goto = (s) => { setScreen(s); setOpenedChat(null); }; const onAvatar = () => setAvatarOpen(true); return (
{screen === 'onboard' && ( goto('home')} /> )} {screen === 'home' && } {screen === 'chat' && !openedChat && } {screen === 'chat' && openedChat && setOpenedChat(null)} onAvatar={onAvatar} />} {screen === 'calendar' && } {screen === 'maps' && } {screen === 'search' && goto('home')} onAvatar={onAvatar} />} {screen === 'scanner' && goto('home')} showToast={showToast} />} {screen === 'camera' && goto('home')} showToast={showToast} />} {screen === 'files' && goto('home')} onAvatar={onAvatar} showToast={showToast} />} {screen === 'settings' && goto('home')} onAvatar={onAvatar} showToast={showToast} openSheet={setSheet} />}
{screen !== 'onboard' && screen !== 'scanner' && screen !== 'camera' && ( { goto(id); }} items={[ { id: 'home', icon: 'grid', label: 'accueil' }, { id: 'chat', icon: 'bell', label: 'chat' }, { id: 'calendar', icon: 'clock', label: 'agenda' }, { id: 'maps', icon: 'network', label: 'carte' }, ]} /> )} setAvatarOpen(false)} name="Marc Dupont" email="marc@exemple.com" items={[ { icon: 'user', label: 'Mon profil', onClick: () => showToast('Profil') }, { icon: 'cog', label: 'Paramètres', onClick: () => goto('settings') }, { icon: 'bell', label: 'Notifications', onClick: () => showToast('Notifications') }, { icon: 'folder', label: 'Mes fichiers', onClick: () => goto('files') }, { icon: 'list', label: "Centre d'aide", onClick: () => showToast('Aide') }, { icon: 'power', label: 'Se déconnecter', danger: true, onClick: () => goto('onboard') }, ]} /> setSheet(null)} title="Choisir le thème"> {}} options={[ { value: 'dark', label: 'Sombre', icon: 'moon' }, { value: 'light', label: 'Clair', icon: 'sun' }, ]} /> setSheet(null)} title="Langue"> {}} options={[ { value: 'fr', label: 'Français' }, { value: 'en', label: 'English' }, { value: 'de', label: 'Deutsch' }, ]} /> setToast(null)} message={toast} variant="ok" />
); } function App() { const [theme, setTheme] = uAD('dark'); const [device, setDevice] = uAD('ios'); eAD(() => { document.documentElement.dataset.theme = theme; }, [theme]); return (

Exemple mobile · patterns chat · calendrier · maps · scanner · caméra · fichiers · avatar menu

exemple saisie
{device === 'ios' &&
}
↑ commence par l'accueil, puis explore les 9 autres écrans
); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render();