# Ticket #03 — 3/4 — Barre de sélection de site (navbar horizontale) ## 0. Pivots post-implémentation (2026-04-20) Écarts assumés entre la spec initiale (écrite avant exploration de la lib) et le code livré après implémentation et test visuel. À lire en premier pour comprendre les divergences lors de la relecture. 1. **Contraste texte auto supprimé, texte blanc forcé conforme Figma.** La spec (sections 5, 6, 10) prévoyait un calcul de luminance WCAG pour décider entre texte noir et blanc sur chaque tile. Après test visuel, le choix design retenu est d'imposer **texte blanc partout** (default Malio `text-white font-bold uppercase tracking-wide`). Conséquence : charge à l'admin de choisir des couleurs de site suffisamment foncées pour que le blanc reste lisible. Les utilitaires `parseHex`, `getRelativeLuminance`, `getReadableTextColor` ont été supprimés comme code mort. Seul `isValidSiteColor(hex)` reste dans `shared/utils/color.ts` (consommé par `SiteDrawer`). 2. **Taille texte explicite `text-2xl` (24 px) appliquée via `labelClass`.** Malio applique `font-bold uppercase tracking-wide` sans taille explicite. Le wrapper `SiteSelector.vue` passe `labelClass="text-2xl"` pour garantir les 24 px de la maquette Figma. 3. **A11y : `ariaGroupLabel` au niveau radiogroup** au lieu de `ariaLabelActive` / `ariaLabelInactive` par tile. La raison : Malio rend déjà un `role="radio"` avec `aria-checked` par tile — le lecteur d'écran annonce "bouton radio coché/non coché" + le nom visible. Ajouter un `aria-label` par tile aurait dupliqué l'info et alourdi sans bénéfice. Le seul ajout nécessaire était un label au groupe, fait via `:aria-label="t('sites.selector.ariaGroupLabel')"` sur `MalioSiteSelector`. 4. **Auto-détection composables des layers dans `nuxt.config.ts`.** Pas prévu dans la spec. Ajouté car `imports.dirs` explicite override les auto-imports par défaut de Nuxt pour les composables de layer. Sans ça, `useCurrentSite` n'est pas résolu par Nuxt. Scan dynamique aligné sur le pattern `moduleLayers` existant. 5. **Couleurs fixtures finales :** `#056CF2` (Châtellerault), `#F3CB00` (Saint-Jean), `#74BF04` (Pommevic). Choix client post-maquette. ## 1. Objectif Ce ticket livre l'UI de consommation du module Sites pour l'utilisateur final : une barre horizontale en haut de l'application qui liste les sites autorises de l'utilisateur connecte, met en avant le site courant et permet de basculer d'un site a l'autre en un clic. Le ticket consomme la donnee posee par le ticket 2 (`/api/me` expose `sites` et `currentSite`, `PATCH /api/me/current-site` permet le switch) et s'appuie sur un nouveau composant `MalioSiteSelector` fourni par la version a jour de `@malio/layer-ui`. Resultat attendu : apres merge, un user avec ≥ 1 site voit une barre sous la navbar horizontale ; un clic sur un site non actif le rend actif, change l'etat global, et est persiste cote serveur. ## 2. Périmètre ### IN - **Upgrade** de `@malio/layer-ui` (actuellement `^1.3.0`) vers la version contenant `MalioSiteSelector`. La signature exacte du composant (props, slots, events) doit etre lue dans `node_modules/@malio/layer-ui/COMPONENTS.md` apres installation — la spec decrit le contrat attendu, le developpeur adapte selon l'API reelle (cf. Risque 1). - Ajouter les champs `sites: Site[]` et `currentSite: Site | null` dans le type `UserData` (`frontend/shared/types/user-data.ts`) pour refleter le payload `/api/me` enrichi au ticket 2. - Ajouter le type partage `Site` dans `frontend/shared/types/sites.ts` (deja cree au ticket 2, sinon a creer). - Creer le composable `useCurrentSite()` dans `frontend/modules/sites/composables/` qui expose `currentSite`, `availableSites`, `switchSite(site)`, `resetCurrentSite()`. Pattern aligne sur `useSidebar()`. - Creer le composable `useModules()` dans `frontend/shared/composables/` qui consomme `/api/modules` et expose `isModuleActive(id: string)`. Necessaire car `isModuleActive` est requis par le ticket mais n'existe pas encore cote front. - Creer `SiteSelector.vue` dans `frontend/modules/sites/components/` : wrapper fin autour de `MalioSiteSelector` qui branche le composable `useCurrentSite()`, gere l'optimistic update avec rollback, emet un toast de succes/erreur. - Integrer le selecteur dans `frontend/app/layouts/default.vue` — render conditionnel sur `isModuleActive('sites') && user.sites.length > 0`. - Appeler `resetCurrentSite()` au logout (`frontend/modules/core/pages/logout.vue`), aligne sur `resetSidebar()` deja present. - Gestion du **contraste automatique** : le texte du bloc passe en noir ou en blanc selon la luminance de `site.color`. Fonction utilitaire `getReadableTextColor(hex: string): 'black' | 'white'` dans `frontend/shared/utils/color.ts` (nouveau fichier utilitaire partage). - Accessibilite : chaque bloc est un `