diff --git a/src/app/globals.css b/src/app/globals.css
index 0bf23b1..35bc503 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -411,6 +411,13 @@ body {
min-width: 0 !important;
}
+ /* La colonne centrale est de largeur fixe à l'écran — c'est ce qui rend la
+ grille reconnaissable d'un poste à l'autre. Sur une feuille, cette largeur
+ n'a plus de sens et déborderait de la page. */
+ .w-\[1280px\] {
+ width: auto !important;
+ }
+
[data-print='hide'],
nav,
aside {
diff --git a/src/components/shell/AppShell.tsx b/src/components/shell/AppShell.tsx
index f247234..2e4245b 100644
--- a/src/components/shell/AppShell.tsx
+++ b/src/components/shell/AppShell.tsx
@@ -4,18 +4,15 @@ import Link from 'next/link';
import { usePathname } from 'next/navigation';
import type { ReactNode } from 'react';
+import { SectionTabs } from '@/components/shell/SectionTabs';
import { ThemeToggle } from '@/components/shell/ThemeToggle';
-import { Badge } from '@/components/ui/Badge';
import { Button } from '@/components/ui/Button';
import { cx } from '@/lib/cx';
import { signOutAction } from '@/server/auth/actions';
import {
- isActive,
NAVIGATION,
- placeholderHref,
sectionForPath,
sectionHref,
- type NavItem,
} from '@/components/shell/navigation';
export interface AppShellProps {
@@ -91,64 +88,31 @@ export function AppShell({
-
-
+ {/*
+ Colonne centrale de **largeur fixe**, pas fluide.
- {children}
-
+ Une largeur qui suit la fenêtre fait bouger les colonnes du planning
+ d'un poste à l'autre : la même semaine ne se lit pas au même endroit sur
+ deux écrans, et l'œil doit se réorienter à chaque ouverture. Une largeur
+ arrêtée rend la page reconnaissable, et laisse la fenêtre défiler
+ horizontalement plutôt que de comprimer la grille.
+ */}
+
+
+
+
+
+
+ {children}
+
+
+ Instance auto-hébergée · {accountName}
+
+
+
);
}
-
-function SidebarLink({ item, pathname }: { item: NavItem; pathname: string }) {
- const current = item.href ? isActive(item.href, pathname) : false;
- const className = cx(
- 'flex w-full items-center justify-between gap-2 rounded-2 px-2.5 py-1.5 text-left text-sm',
- 'transition-colors duration-[var(--d-1)] ease-organic',
- current
- ? 'bg-accent-soft font-semibold text-accent-soft-ink'
- : 'text-ink-2 hover:bg-surface-2',
- );
-
- const content = (
- <>
- {item.label}
- {item.badge ? (
-
- {item.badge}
-
- ) : null}
- >
- );
-
- if (!item.href) {
- return (
-
- {content}
-
- );
- }
-
- return (
-
- {content}
-
- );
-}
diff --git a/src/components/shell/SectionTabs.tsx b/src/components/shell/SectionTabs.tsx
new file mode 100644
index 0000000..04371e5
--- /dev/null
+++ b/src/components/shell/SectionTabs.tsx
@@ -0,0 +1,71 @@
+'use client';
+
+import Link from 'next/link';
+
+import { Badge } from '@/components/ui/Badge';
+import {
+ isActive,
+ placeholderHref,
+ type NavSection,
+} from '@/components/shell/navigation';
+import { cx } from '@/lib/cx';
+
+/**
+ * Entrées de la section courante, en onglets.
+ *
+ * Elles vivaient dans une barre latérale. Deux raisons de l'avoir retirée :
+ * elle rognait la largeur utile au moment précis où la grille de planning en
+ * manque, et elle donnait à la navigation deux grammaires — des onglets sur la
+ * fiche salarié, une liste verticale ailleurs. Une seule grammaire se retient ;
+ * deux se cherchent.
+ *
+ * Une entrée sans `href` désigne un écran du périmètre qui n'est pas encore
+ * construit. Elle reste affichée et mène à un écran d'attente qui le dit : la
+ * masquer ferait croire le produit plus étroit qu'il n'est visé, et la laisser
+ * inerte ferait croire la navigation cassée.
+ */
+export function SectionTabs({
+ section,
+ pathname,
+}: {
+ section: NavSection;
+ pathname: string;
+}) {
+ if (section.items.length === 0) return null;
+
+ return (
+
+ );
+}
diff --git a/tests/e2e/apercu.spec.ts b/tests/e2e/apercu.spec.ts
index f76dc25..0fbf1eb 100644
--- a/tests/e2e/apercu.spec.ts
+++ b/tests/e2e/apercu.spec.ts
@@ -12,7 +12,7 @@ test('chaque tuile mène à ses lignes sources', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Aperçu RH' })).toBeVisible();
// Les tuiles sont des liens, pas des chiffres décoratifs. On vise celle du
- // contenu, pas l'entrée homonyme de la barre latérale.
+ // contenu, pas l'onglet de section homonyme.
const tile = page
.locator('main')
.getByRole('link', { name: /Profils incomplets/ })
diff --git a/tests/e2e/navigation.spec.ts b/tests/e2e/navigation.spec.ts
index 166cdf8..abe3d33 100644
--- a/tests/e2e/navigation.spec.ts
+++ b/tests/e2e/navigation.spec.ts
@@ -51,7 +51,8 @@ test('le thème bascule et survit à un rechargement', async ({ page }) => {
test('une entrée de navigation non construite mène à un écran explicite', async ({
page,
}) => {
- // La barre latérale n'affiche que la section courante : on l'ouvre d'abord.
+ // Les onglets n'affichent que les entrées de la section courante : on
+ // l'ouvre d'abord.
await page.goto('/absences/calendrier');
// « Politiques de congés » fait partie du périmètre visé mais n'est pas
// encore construit : l'entrée reste, et mène à un écran qui le dit.
diff --git a/tests/e2e/reglages.spec.ts b/tests/e2e/reglages.spec.ts
index 032b928..c93b6b2 100644
--- a/tests/e2e/reglages.spec.ts
+++ b/tests/e2e/reglages.spec.ts
@@ -23,7 +23,7 @@ test('un manager ne peut ni voir ni modifier les établissements', async ({
}) => {
await signIn(page, 'manager.nantes@example.test');
- // La barre latérale ne propose pas la section, mais c'est un confort :
+ // Les onglets ne proposent pas la section, mais c'est un confort :
// le contrôle qui compte est celui du serveur, testé en accédant à l'URL.
await page.goto('/reglages/etablissements');