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');