Menu en haut, retour explicite, et bibliographie qui ne rappelle plus la BnF

Navigation
- La barre d'onglets du bas devient un menu en haut, sur le repli du gabarit Blazor :
  bascule sur téléphone, rangée sur PC. Cela renverse la décision du 2026-08-18 (le
  pouce atteint le bas de l'écran) : le raisonnement reste juste, mais il ne vaut rien
  face à une barre qui s'affiche mal. Le point tenu est qu'il existe désormais un état
  de repli — sans feuille de style, il reste une liste de liens lisible.
- Bouton « retour » dans le bandeau, sur tous les écrans, appuyé sur l'historique du
  navigateur, avec repli sur le catalogue quand la pile est vide (PWA ouverte depuis
  l'écran d'accueil).
- Le pictogramme de la marque passe en SVG en ligne : le fichier logo-bandeau.svg
  pouvait manquer sur un appareil dont le cache précédait son ajout, d'où l'icône cassée.

Bibliographie
- Case à cocher à gauche du titre, ligne centrée verticalement, boutons groupés à droite.
- Barre de sélection collante EN HAUT, avec masquage groupé et « tout décocher ».
- « Nouveautés » et « Ses livres chez vous » remontent à côté du filtre.
- ⚠️ « Je le veux » et « Masquer » ne rechargent plus rien : ils basculent un drapeau
  personnel que le client connaît déjà. Auparavant chaque clic vidait le cache des
  bibliographies et relançait la BnF (une seconde par page, jusqu'à dix pages pour les
  nouveautés, qui ne sont pas mises en cache) puis relisait tout le catalogue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-20 20:35:39 +02:00
co-authored by Claude Opus 5
parent c256394c77
commit 83089d7479
9 changed files with 645 additions and 246 deletions
+92 -26
View File
@@ -2,13 +2,43 @@
@implements IDisposable
@inject ServiceLivresApi Api
@inject EtatReseau Reseau
@inject NavigationManager Navigation
@inject IJSRuntime JS
@* Mise en page pensée mobile d'abord : un bandeau, une colonne, rien à gauche.
Le PC hérite de la même colonne, simplement centrée et limitée en largeur. *@
@*
Mise en page pensée mobile d'abord : un bandeau, une colonne, rien à gauche.
Le PC hérite de la même colonne, simplement centrée et limitée en largeur.
⚠️ La navigation est passée EN HAUT le 2026-08-20, à la demande de l'utilisateur, après
que la barre d'onglets du bas se soit affichée « toujours très mal » sur son téléphone.
Cela renverse la décision du 2026-08-18 (« en bas, le pouce atteint le bas de l'écran ») :
le raisonnement sur le pouce reste juste, mais il ne vaut rien face à une barre qui ne
s'affiche pas correctement. Le repli retenu est celui du gabarit Blazor par défaut — un
bouton bascule et une liste qui se déploie — parce qu'il ne dépend d'AUCUNE mise en page
exotique : sans la moindre feuille de style, il reste une suite de liens lisibles les uns
sous les autres, au lieu d'une rangée écrasée.
*@
<header class="bandeau">
@* Le déplacement entre écrans était jugé « foireux » : d'un écran profond (bibliographie,
fiche de tome, ajout d'envie) il fallait deviner quel onglet ramenait en arrière. Un
retour explicite, au même endroit sur tous les écrans, répond à la question sans
obliger à connaître l'arborescence. *@
<button type="button" class="bandeau-icone" @onclick="RetourAsync"
title="Revenir à l'écran précédent" aria-label="Revenir à l'écran précédent">
<span aria-hidden="true">&#8592;</span>
</button>
<a class="marque" href="">
<img src="logo-bandeau.svg" alt="" class="marque-logo" />
MaBibli
@* Pictogramme dessiné en ligne plutôt que chargé depuis /logo-bandeau.svg : un fichier
séparé se sert (ou non) indépendamment de l'application, et c'est précisément ce qui
produisait l'icône cassée en haut à gauche — un appareil dont le cache datait d'avant
l'ajout du fichier recevait un 404. Un SVG en ligne ne peut pas manquer. *@
<svg class="marque-logo" viewBox="0 0 64 64" aria-hidden="true" focusable="false">
<polygon points="32,15 10,20 10,47 32,44" fill="currentColor" />
<polygon points="32,15 54,20 54,47 32,44" fill="currentColor" />
<line x1="32" y1="15" x2="32" y2="44" stroke="#1b3a5c" stroke-width="1.5" />
</svg>
<span class="marque-nom">MaBibli</span>
</a>
@if (!Reseau.EnLigne)
@@ -24,8 +54,30 @@
@_utilisateur.Affichage@(_utilisateur.Simule ? " (dev)" : "")
</span>
}
<button type="button" class="bandeau-icone bandeau-bascule" @onclick="() => _menuOuvert = !_menuOuvert"
aria-expanded="@_menuOuvert" aria-controls="menu-principal"
aria-label="@(_menuOuvert ? "Fermer le menu" : "Ouvrir le menu")">
<span aria-hidden="true">@(_menuOuvert ? "\u2715" : "\u2630")</span>
</button>
</header>
@*
Les six destinations de l'application. Sur PC elles tiennent en une rangée sous le
bandeau ; sur téléphone elles se déploient en liste au clic sur la bascule.
Les entrées restent actives hors-ligne : les six écrans se consultent depuis leurs
instantanés. Ce sont les écritures qui se désactivent, pas la navigation.
*@
<nav id="menu-principal" class="menu @(_menuOuvert ? "menu-ouvert" : null)" aria-label="Navigation principale">
<NavLink class="menu-lien" href="" Match="NavLinkMatch.All">Catalogue</NavLink>
<NavLink class="menu-lien" href="auteurs">Auteurs</NavLink>
<NavLink class="menu-lien" href="series">Séries</NavLink>
<NavLink class="menu-lien" href="revues">Revues</NavLink>
<NavLink class="menu-lien" href="prets">Prêts</NavLink>
<NavLink class="menu-lien" href="souhaits">Envies</NavLink>
</nav>
@if (!Reseau.EnLigne)
{
@*
@@ -46,38 +98,24 @@
@Body
</main>
@*
Navigation permanente, en bas de l'écran : six onglets, toujours au même endroit.
Elle remplace les listes de liens que chaque page portait dans sa barre d'actions — cinq
boutons y passaient sur deux ou trois lignes sur un téléphone, et l'entrée « Envies » ne
figurait pas partout. Les barres d'actions restantes ne portent plus que des ACTIONS.
⚠️ Le pouce atteint le bas de l'écran, pas le haut : c'est ce qui justifie le bas plutôt
qu'un rang d'onglets sous le bandeau. Les actions de page se placent juste au-dessus
(variable --mb-onglets-hauteur, partagée avec .actions-flottantes).
Les onglets restent actifs hors-ligne : les six écrans se consultent depuis leurs
instantanés. Ce sont les écritures qui se désactivent, pas la navigation.
*@
<nav class="onglets" aria-label="Navigation principale">
<NavLink class="onglet" href="" Match="NavLinkMatch.All">Catalogue</NavLink>
<NavLink class="onglet" href="auteurs">Auteurs</NavLink>
<NavLink class="onglet" href="series">Séries</NavLink>
<NavLink class="onglet" href="revues">Revues</NavLink>
<NavLink class="onglet" href="prets">Prêts</NavLink>
<NavLink class="onglet" href="souhaits">Envies</NavLink>
</nav>
@code {
private UtilisateurCourant? _utilisateur;
private bool _etaitEnLigne = true;
private bool _synchroEnCours;
/// <summary>Menu déployé (téléphone). Sur PC la rangée est visible en permanence.</summary>
private bool _menuOuvert;
protected override async Task OnInitializedAsync()
{
Reseau.Change += SurChangementReseau;
Reseau.SynchroChange += SurSynchro;
// ⚠️ Sans cela, le menu resterait déployé par-dessus l'écran qu'on vient d'atteindre :
// NavLink ne referme rien de lui-même, et un clic sur « Auteurs » laisserait les six
// entrées empilées au-dessus de la liste des auteurs.
Navigation.LocationChanged += SurNavigation;
// Écoute des bascules online/offline avant tout appel : un démarrage hors-ligne doit
// aller directement au cache, sans attendre l'échec d'une requête.
await Reseau.DemarrerAsync();
@@ -144,9 +182,37 @@
/// <summary>La date affichée vient de changer : rien à recharger, juste à redessiner.</summary>
private void SurSynchro() => _ = InvokeAsync(StateHasChanged);
private void SurNavigation(object? _, LocationChangedEventArgs __)
{
if (!_menuOuvert)
{
return;
}
_menuOuvert = false;
_ = InvokeAsync(StateHasChanged);
}
/// <summary>
/// Revient à l'écran précédent, en repliant le menu au passage.
/// </summary>
/// <remarks>
/// Le retour passe par l'<b>historique du navigateur</b>, pas par une destination calculée :
/// c'est le seul moyen de ramener là d'où l'on vient réellement, un même écran étant
/// atteignable par plusieurs chemins (une fiche livre s'ouvre depuis le catalogue, une
/// bibliographie, une série ou une recherche). Le repli sur le catalogue quand la pile est
/// vide est traité en JavaScript, <c>history.length</c> n'ayant pas d'équivalent côté C#.
/// </remarks>
private async Task RetourAsync()
{
_menuOuvert = false;
await JS.InvokeVoidAsync("mabibliRetour");
}
public void Dispose()
{
Reseau.Change -= SurChangementReseau;
Reseau.SynchroChange -= SurSynchro;
Navigation.LocationChanged -= SurNavigation;
}
}
+110 -39
View File
@@ -4,9 +4,8 @@
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
padding: 0.75rem 1rem;
gap: 0.5rem;
padding: 0.6rem 0.75rem;
background: #1b3a5c;
color: #fff;
}
@@ -19,21 +18,59 @@
display: inline-flex;
align-items: center;
gap: 0.4rem;
/* La marque prend la place restante : c'est elle qui repousse la bascule à droite,
sans dépendre d'un justify-content que le nombre d'éléments du bandeau ferait varier
(la pastille hors-ligne et le nom d'utilisateur vont et viennent). */
flex: 1 1 auto;
min-width: 0;
}
.marque-nom {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.marque-logo {
width: 1.3rem;
height: 1.3rem;
flex: 0 0 auto;
color: #fff;
}
/* Boutons du bandeau (retour, bascule du menu). Cibles tactiles de 2.25rem : on les touche
au pouce, souvent en marchant. */
.bandeau-icone {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
padding: 0;
font-size: 1.15rem;
line-height: 1;
color: #fff;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 6px;
cursor: pointer;
}
.bandeau-icone:hover,
.bandeau-icone:focus-visible {
background: rgba(255, 255, 255, 0.15);
}
.utilisateur {
flex: 0 1 auto;
font-size: 0.8rem;
opacity: 0.85;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 55%;
max-width: 40%;
}
/* L'état du réseau change ce que l'application permet : il se voit dans le bandeau, en
@@ -67,52 +104,86 @@
width: 100%;
max-width: 46rem;
margin: 0 auto;
/* Marge basse généreuse : ni les onglets ni les actions flottantes ne doivent masquer
le dernier livre de la liste. */
padding: 1rem 1rem 9rem;
/* Marge basse généreuse : la barre d'actions flottantes ne doit pas masquer le dernier
livre de la liste. */
padding: 1rem 1rem 6rem;
}
/* --- Onglets de navigation --- */
/* --- Menu principal --- */
.onglets {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
/*
Repli du gabarit Blazor par défaut : sur téléphone, la liste est masquée et se déploie
au clic sur la bascule ; sur PC elle devient une rangée sous le bandeau.
⚠️ Le défaut de la version précédente (six onglets en `flex: 1 1 0` sur une barre fixe)
était qu'elle n'avait AUCUN état de repli : dès que la place manquait — ou que la feuille
de style scopée n'était pas celle attendue — les six libellés se retrouvaient collés,
tronqués ou empilés sans mise en forme. Ici, l'état sans style est une liste de liens.
*/
.menu {
display: none;
flex-direction: column;
background: #16304c;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}
.menu-ouvert {
display: flex;
background: #fff;
border-top: 1px solid var(--mb-bord);
padding-bottom: env(safe-area-inset-bottom);
/* Hauteur figée et partagée : .actions-flottantes s'en sert pour se poser juste au-dessus. */
height: calc(var(--mb-onglets-hauteur) + env(safe-area-inset-bottom));
}
.onglet {
flex: 1 1 0;
padding: 0.4rem 0.25rem;
line-height: 1.4;
text-align: center;
.menu-lien {
display: block;
padding: 0.7rem 1rem;
color: #e8edf3;
text-decoration: none;
font-size: 0.8rem;
color: var(--mb-texte-doux);
font-size: 0.95rem;
border-left: 4px solid transparent;
}
/* Le trait actif est posé en haut de l'onglet, du côté du contenu qu'il désigne. */
border-top: 3px solid transparent;
/* Le libellé ne doit jamais passer sur deux lignes. Cinq onglets tiennent sur 320 px à
cette taille ; c'est la raison du 0.8rem plutôt que du 0.85rem d'avant « Séries ». */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.menu-lien:hover,
.menu-lien:focus-visible {
background: rgba(255, 255, 255, 0.1);
color: #fff;
}
/* NavLink pose « active » tout seul ; « Catalogue » utilise NavLinkMatch.All, sans quoi
il resterait allumé sur toutes les pages — son href est la racine. */
.onglet.active {
color: var(--mb-accent);
.menu-lien.active {
color: #fff;
font-weight: 600;
border-top-color: var(--mb-accent);
background: var(--mb-fond-doux);
background: rgba(255, 255, 255, 0.16);
border-left-color: #f5d76e;
}
/*
À partir de 40rem la rangée tient sans se comprimer, et la bascule n'a plus de raison
d'être : le menu est toujours visible, l'état déployé n'a plus d'effet.
Le point de rupture est à 40rem plutôt qu'aux 48rem du reste de la feuille : six libellés
courts tiennent bien avant que la grille de cartes ne s'élargisse, et les garder cachés
derrière une bascule sur une tablette serait un clic de trop.
*/
@media (min-width: 40rem) {
.menu {
display: flex;
flex-direction: row;
justify-content: center;
gap: 0.25rem;
padding: 0 0.5rem;
}
.bandeau-bascule {
display: none;
}
.menu-lien {
padding: 0.6rem 0.9rem;
border-left: 0;
border-bottom: 3px solid transparent;
}
.menu-lien.active {
background: rgba(255, 255, 255, 0.12);
border-bottom-color: #f5d76e;
}
}