From dd01fb58f4eff1f13a1fbbe3ffbf00a992b5a1e7 Mon Sep 17 00:00:00 2001 From: R0m1k3 Date: Tue, 11 Aug 2026 15:50:38 +0200 Subject: [PATCH] =?UTF-8?q?Supprimer=20l'ascenseur=20des=20onglets,=20et?= =?UTF-8?q?=20rendre=20les=20r=C3=A9glages=20=C3=A0=20un=20rail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les onglets de section défilaient horizontalement dès que la liste dépassait la largeur. Une barre de défilement cache la moitié des entrées derrière un geste que rien n'annonce : on ne sait pas ce qu'on ne voit pas. Ils passent à la ligne — deux rangées coûtent seize pixels et montrent tout. Les réglages sortent des onglets pour un rail vertical, et eux seuls. Ce n'est pas un retour de la barre latérale : partout ailleurs la section tient en quelques entrées et le contenu garde toute la largeur. Les réglages en comptent une vingtaine ; en onglets, ils occuperaient trois rangées au-dessus de chaque écran et changeraient de forme d'une page à l'autre selon la longueur des libellés. Le rail les groupe dans l'ordre d'un paramétrage — société, organisation, travail, paie, accès, conformité — et non par ordre alphabétique : quelqu'un qui installe l'instance descend la liste. Une entrée ajoutée sans groupe reste visible en fin de rail plutôt que de disparaître. Co-Authored-By: Claude Opus 5 --- src/components/shell/AppShell.tsx | 31 +++++++- src/components/shell/SectionTabs.tsx | 6 +- src/components/shell/SettingsRail.tsx | 106 ++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 src/components/shell/SettingsRail.tsx diff --git a/src/components/shell/AppShell.tsx b/src/components/shell/AppShell.tsx index 2a7fca1..08c6949 100644 --- a/src/components/shell/AppShell.tsx +++ b/src/components/shell/AppShell.tsx @@ -5,6 +5,7 @@ import { usePathname } from 'next/navigation'; import type { ReactNode } from 'react'; import { SectionTabs } from '@/components/shell/SectionTabs'; +import { SettingsRail } from '@/components/shell/SettingsRail'; import { ThemeToggle } from '@/components/shell/ThemeToggle'; import { Button } from '@/components/ui/Button'; import { cx } from '@/lib/cx'; @@ -32,6 +33,7 @@ export function AppShell({ }: AppShellProps) { const pathname = usePathname(); const section = sectionForPath(pathname); + const isSettings = section.id === 'reglages'; return (
@@ -127,11 +129,32 @@ export function AppShell({ */}
-
- -
+ {/* + Deux navigations de section, pour deux usages. - {children} + Partout, des onglets : la section tient en quelques entrées et le + contenu garde toute la largeur. Aux réglages, un rail vertical : une + vingtaine d'entrées en onglets prendraient trois rangées au-dessus de + chaque écran, et changeraient de forme d'une page à l'autre selon la + longueur des libellés. On traverse les réglages, on ne fait pas + d'aller-retour entre eux pendant qu'on travaille. + */} + {isSettings ? ( + // Le rail porte la marge gauche ; le contenu garde la sienne, celle + // de `PageBody`. Les additionner ferait dériver la colonne des + // réglages par rapport à toutes les autres pages. +
+ +
{children}
+
+ ) : ( + <> +
+ +
+ {children} + + )}

{section.items.map((item) => { const current = item.href ? isActive(item.href, pathname) : false; diff --git a/src/components/shell/SettingsRail.tsx b/src/components/shell/SettingsRail.tsx new file mode 100644 index 0000000..fa7e13f --- /dev/null +++ b/src/components/shell/SettingsRail.tsx @@ -0,0 +1,106 @@ +'use client'; + +import Link from 'next/link'; + +import { + isActive, + placeholderHref, + type NavSection, +} from '@/components/shell/navigation'; +import { cx } from '@/lib/cx'; + +/** + * Rail latéral des réglages — et **seulement** des réglages. + * + * Partout ailleurs, la section tient en une poignée d'entrées et se présente en + * onglets, qui laissent toute la largeur au contenu. Les réglages en comptent + * une vingtaine : en onglets, ils occuperaient trois rangées au-dessus de + * chaque écran, et la liste changerait de forme d'une page à l'autre selon la + * longueur des libellés. + * + * Une liste verticale les tient dans un ordre stable, groupés par domaine. Ce + * n'est pas la même navigation qu'ailleurs parce que ce n'est pas le même + * usage : on traverse les réglages, on ne fait pas d'aller-retour entre eux + * pendant qu'on travaille. + */ + +/** + * Regroupement des entrées, dans l'ordre d'un paramétrage. + * + * L'identité de l'entreprise, puis ce qui organise le travail, puis ce qui le + * paie, puis ce qui l'encadre. Un client qui installe l'instance descend la + * liste ; c'est cet ordre-là, pas l'alphabet. + */ +const GROUPS: ReadonlyArray<{ label: string; ids: readonly string[] }> = [ + { label: 'Société', ids: ['compte', 'sites', 'convention'] }, + { label: 'Organisation', ids: ['emplois', 'etiquettes', 'types-absence'] }, + { label: 'Travail', ids: ['preferences', 'impression', 'productivite'] }, + { label: 'Paie', ids: ['paie-reglages'] }, + { label: 'Accès', ids: ['roles', 'securite', 'email'] }, + { + label: 'Conformité', + ids: ['registre', 'conservation', 'rgpd', 'modeles'], + }, +]; + +export function SettingsRail({ + section, + pathname, +}: { + section: NavSection; + pathname: string; +}) { + const byId = new Map(section.items.map((item) => [item.id, item])); + + // Une entrée ajoutée à la navigation sans être rangée dans un groupe reste + // visible, en fin de liste : la perdre serait pire que de mal la classer. + const grouped = new Set(GROUPS.flatMap((group) => group.ids)); + const rest = section.items.filter((item) => !grouped.has(item.id)); + + const groups = [ + ...GROUPS.map((group) => ({ + label: group.label, + items: group.ids + .map((id) => byId.get(id)) + .filter((item) => item !== undefined), + })), + ...(rest.length > 0 ? [{ label: 'Autres', items: rest }] : []), + ].filter((group) => group.items.length > 0); + + return ( +

+ ); +}