/* ============================================================
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 (
);
}
/* --- 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 (
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 lancement OnboardingSlider en plein écran, store flag "vu" en localStorage
Liste à actions SwipeableRow avec leftActions (destructives) + rightActions (utiles)
Identité utilisateur Avatar en haut à droite + AvatarMenu au tap → profil/réglages/déconnexion
Recherche multi-types SearchBar + FilterChips + ListRow (un seul container pour tous types)
Caméra/Scanner Plein écran sombre, header floating semi-transparent, contrôles flottants en bas
Carte MapView + boutons flottants (zoom, position) + BottomSheet/glass-strong pour les détails du pin
Réglages Empilement 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
setDevice('ios')}>iOS
setDevice('android')}>Android
setTheme('dark')}>Sombre
setTheme('light')}>Clair
↑ commence par l'accueil, puis explore les 9 autres écrans
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render( );