Files
nav-carte/utils/echelles.ts
T
Jules NenyandClaude Opus 5.5 7762b31f1d feat(nav): onglets S / M / XL et echelleDeRoute dans utils/echelles.ts
Chaque échelle porte ses onglets (libellé, cible, actif(route)) ; echelleDeRoute
rend le groupe courant ou null. Source unique de la nav B10. Test node :
scripts/test-echelles-nav.mjs (23/23). B10 M1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBu7CSLy7PJgL4HJaV5oHP
2026-10-02 16:48:28 +02:00

118 lines
4.2 KiB
TypeScript

/**
* Les trois échelles du deck du 23/09/2026 (conférence AEP), slides 15 à 18.
* Texte repris mot pour mot : c'est la tête de chaque onglet (directive Jules du 25/09, point 2).
* Source unique : la nav S / M / XL (B10 : NavEchelles, NavTiroir, FilEchelle) lit aussi ce fichier.
* Aucun libellé d'échelle ni d'onglet ne se recopie ailleurs.
*/
export type EchelleId = 'S' | 'M' | 'XL'
/** Ce que la nav lit d'une route : compatible avec `useRoute()` sans en dépendre (testable en node). */
export interface RouteNav {
path: string
query?: Record<string, unknown>
}
export interface OngletNav {
libelle: string
to: string
actif: (route: RouteNav) => boolean
}
export interface Echelle {
id: EchelleId
titre: string
sousTitre?: string
mots: string[]
phrase: string
/** Catégorie de /proposer présélectionnée par le lien de contribution. */
categorieProposer: 'ecosysteme' | 'reseau' | 'jobs' | 'outils' | 'references'
/** Onglets du groupe, dans l'ordre de la barre, du menu et du fil d'échelle (B10, directive Jules du 29/09 + arbitrage 1A du 26/09). */
onglets: OngletNav[]
}
const sousChemin = (base: string) => (r: RouteNav) => r.path === base || r.path.startsWith(`${base}/`)
const surPublications = (r: RouteNav) => r.query?.tab === 'projets'
export const ECHELLES: Record<EchelleId, Echelle> = {
S: {
id: 'S',
titre: 'Partir de soi',
sousTitre: "pour s'émanciper",
mots: [
'quête de sens',
'où habiter, avec qui',
'gagner sa vie',
'santé mentale',
'mener ses projets',
'naviguer avec soi',
'les compétences psychosociales',
],
phrase: 'Des ressources, des méthodes, des pairs : matériel et mutualisé.',
categorieProposer: 'outils',
onglets: [
{ libelle: 'Jobs', to: '/trouver-du-taf', actif: sousChemin('/trouver-du-taf') },
{ libelle: 'Outils', to: '/outils', actif: sousChemin('/outils') },
],
},
M: {
id: 'M',
titre: 'La communauté',
mots: [
'entraide',
'mutualisation',
'appartenance',
'être vu, soutenu',
'traverser les creux',
'accomplissement',
'le codev',
],
phrase: "C'est le groupe qui remet un individu debout. L'affinité fait tenir, la confrontation fait avancer.",
categorieProposer: 'ecosysteme',
onglets: [
{ libelle: "Écosystème d'entraide", to: '/', actif: (r) => r.path === '/' },
{ libelle: 'Réseaux AEP', to: '/agences', actif: sousChemin('/agences') },
{ libelle: 'Codev', to: '/codev', actif: sousChemin('/codev') },
],
},
XL: {
id: 'XL',
titre: 'Contribuer',
mots: [
'contribution',
'affiliation, filiation',
'identité',
'sens',
'de nouveaux récits',
'un nouveau contrat social',
'la médecine du corps social',
],
phrase: 'La bibliothèque, les récits, la filiation entre projets.',
categorieProposer: 'references',
onglets: [
{ libelle: 'Recherche média', to: '/media', actif: (r) => sousChemin('/media')(r) && !surPublications(r) },
{ libelle: 'Publications', to: '/media?tab=projets', actif: (r) => sousChemin('/media')(r) && surPublications(r) },
],
},
}
/** Ordre des groupes dans la barre et le menu. */
export const ORDRE_ECHELLES: EchelleId[] = ['S', 'M', 'XL']
/** Slide 15, commune aux trois échelles. */
export const ECHELLES_CADRE = "Trois échelles, imbriquées. Pas un escalier : ça circule dans les deux sens. Entrez par celle qui presse."
/** Version courte de la slide 15 (tête du menu) : première et dernière phrase, tirées du texte ci-dessus. */
export const ECHELLES_CADRE_COURT = (() => {
const phrases = ECHELLES_CADRE.match(/[^.]+\./g) ?? [ECHELLES_CADRE]
return phrases.length > 1 ? `${phrases[0].trim()} ${phrases[phrases.length - 1].trim()}` : ECHELLES_CADRE
})()
/** Groupe de la page courante et son onglet actif, ou null hors groupe (Manifeste, À propos, Proposer, fiche…). */
export function echelleDeRoute(route: RouteNav): { echelle: EchelleId; onglet: OngletNav } | null {
for (const id of ORDRE_ECHELLES) {
const onglet = ECHELLES[id].onglets.find((o) => o.actif(route))
if (onglet) return { echelle: id, onglet }
}
return null
}