Navigation
Une route par fichier
Avec expo-router, il n'y a pas de fichier qui liste les routes : chaque fichier de src/app/ est une route.
| Fichier | Route |
|---|---|
src/app/sign-in.tsx | /sign-in |
src/app/reset-password.tsx | /reset-password |
src/app/(tabs)/index.tsx | / (le fichier index est la racine de son dossier) |
src/app/(tabs)/team.tsx | /team |
src/app/(tabs)/member.tsx | /member |
Les fichiers _layout.tsx ne sont pas des routes : ils entourent les écrans de leur dossier. Les dossiers entre parenthèses, comme (tabs), regroupent des écrans sans apparaître dans l'adresse.
Deux niveaux de navigation
Le Stack racine
src/app/_layout.tsx utilise un Stack : les écrans s'empilent comme des cartes, et on revient en arrière en dépilant. Il contient les écrans publics et le groupe (tabs). Les gardes Stack.Protected décident lesquels sont accessibles (voir Démarrage).
Les onglets
src/app/(tabs)/_layout.tsx utilise Tabs, avec une barre dessinée à la main :
import { Tabs } from 'expo-router/js-tabs';
export default function TabsLayout() {
return (
<Tabs screenOptions={{ headerShown: false }} tabBar={(props) => <TabBar {...props} />}>
<Tabs.Screen name="index" />
<Tabs.Screen name="dashboard" />
{/* ... les 24 écrans */}
</Tabs>
);
}Tous les écrans de l'utilisateur connecté sont déclarés ici, même ceux qui ne sont pas des onglets (fiche d'un membre, changement de mot de passe...). Ainsi la barre d'onglets reste visible partout, comme dans la maquette.
headerShown: false retire l'en-tête automatique : chaque écran dessine son propre AppBar.
La barre d'onglets : src/components/navigation/tab-bar.tsx
Les onglets selon le rôle
export const tabsByRole: Record<Role, Tab[]> = {
employee: [clock, dashboard, times, account],
manager: [clock, dashboard, times, team, account],
general_manager: [clock, dashboard, times, team, more],
};| Rôle | Onglets |
|---|---|
| Employé | Clock · Dashboard · Times · Account |
| Manager | Clock · Dashboard · Times · Team · Account |
| General manager | Clock · Dashboard · Times · Team · More |
Le GM a un onglet de plus à afficher (Utilisateurs), mais cinq onglets est un maximum confortable. Son onglet More regroupe donc Administration (Utilisateurs) et Compte.
Quel onglet allumer
Quand on est sur un écran qui n'est pas un onglet (par exemple review), il faut quand même allumer le bon onglet. C'est le rôle de parentTab :
const parentTab: Record<string, string> = {
today: 'index',
'request-correction': 'times',
member: 'team',
'member-dashboard': 'team',
'edit-working-time': 'team',
'team-detail': 'team',
'new-team': 'team',
requests: 'team',
review: 'team',
'add-absence': 'team',
'change-password': 'account',
'change-email': 'account',
changes: 'account',
notifications: 'account',
'your-data': 'account',
users: 'more',
invite: 'more',
user: 'more',
};const route = state.routes[state.index]?.name ?? '';
const mapped = parentTab[route] ?? route;
const current = tabs.some((tab) => tab.name === mapped) ? mapped : 'more';Si l'onglet parent n'existe pas pour ce rôle (par exemple account pour le GM, qui n'a pas d'onglet Account), on allume More.
Accessibilité
Chaque onglet a accessibilityRole="tab" et accessibilityState={{ selected }} : VoiceOver annonce « Équipe, onglet, sélectionné ».
Aller d'un écran à l'autre
import { router } from 'expo-router';
router.push('/requests'); // empile un écran (on peut revenir)
router.back(); // revient à l'écran précédent
router.replace('/account-deleted'); // remplace l'écran actuel (pas de retour)
router.navigate('/times'); // va vers un écran, sans empiler s'il est déjà ouvert
// avec des paramètres (src/components/team/team-detail.tsx)
router.push({ pathname: '/member', params: { id: member.id, team: team.id } });L'écran d'arrivée lit les paramètres de l'adresse. Ils arrivent toujours sous forme de texte :
// src/app/(tabs)/member.tsx
const { id, team: teamId } = useLocalSearchParams<{ id: string; team?: string }>();| Écran | Paramètres |
|---|---|
member | id (la personne), team (optionnel) |
member-dashboard | id |
edit-working-time | userId, id (absent pour une création) |
add-absence | userId |
team-detail | id (l'équipe) |
review | id (la demande) |
user | id |
reset-password, set-password, confirm-email | token |
sign-in | reset (affiche « mot de passe changé ») |
Routes typées
Avec typedRoutes activé dans app.json, TypeScript connaît toutes les routes. Une faute de frappe dans router.push('/memberr') est soulignée. Les types sont générés dans .expo/types/ au lancement de npx expo start : après avoir créé un écran, lance-le une fois.
Le bouton retour
AppBar affiche une flèche quand on lui passe back :
<AppBar title={t('settings.password.title')} back bordered />La flèche appelle router.back(). Sur Android, le bouton retour du système fait la même chose.