feat(mobile): refonte responsive complète (header, drawer, dvh, cibles tactiles)
Le header était en grid-cols-[auto_1fr_auto] avec une barre de recherche non-wrappable (7 chips + input + 2 boutons, aucun min-w-0) : ~1000px de largeur intrinsèque dans un viewport de 375px. Toutes les routes débordaient horizontalement et les boutons Connexion/Inscription partaient hors écran. Socle - --header-h / --header-offset / --app-vh centralisent la métrique du header : supprime les top-16 codés en dur (channel-tabs était en top-0 et glissait sous le header au scroll). - Safe-area iOS, viewport-fit=cover, color-scheme/theme-color. - dvh partout (100vh > hauteur visible sur iOS), overflow-x: clip au lieu de hidden (hidden cassait silencieusement tous les sticky). - Cible tactile 44px sur pointeur grossier, skip link, focus-ring clavier, scroll-rail pour les tab strips. Header - Barre mobile (burger, logo, recherche, menu ⋮) + barre desktop inchangée. - Le menu ⋮ expose ce qui disparaissait : bande des thèmes, préférences, historique, favoris, téléchargements, thème, connexion/inscription. - Recherche en feuille sous le header avec focus automatique. - Échap / clic extérieur / navigation ferment les panneaux transitoires. Drawer - Partait à inset-y-0 : les 56 premiers px du menu étaient derrière le header. Descend sous le header, bouton fermer intégré, verrouille le scroll, se ferme à Échap. Barre de recherche - Chips masquées sous lg, remplacées par un bouton résumé qui ouvre le sélecteur de fournisseurs. Suggestions en dvh, menu Ctrl+K plein écran sur mobile. Pages - Shorts : cadre 9:16 contraint par dvh ET vw (il débordait en paysage), Préc./Suiv. qui étaient hors du conteneur height-capped. - Watch : barre d'actions qui wrappe, mode cinéma en opacity-0 group-hover donc invisible au doigt, thumbnails fluides, playsinline sur les iframes. - Historique, thèmes, chaîne, playlists, préférences, sessions : rails swipables, min-w-0, truncate, en-têtes qui wrappent. Vérifié : audit navigateur (CDP) sur 17 routes x 7 largeurs (320→1440), zéro débordement horizontal ; 19 tests d'interaction mobile ; suites de tests du dépôt vertes (api 40/40, transcript 27/27, search, suggest, playlists, preferences, subscriptions, highlight, telemetry).
This commit is contained in:
@@ -26,10 +26,10 @@
|
||||
}
|
||||
|
||||
@if (hasQuery() && availableTags().length > 0) {
|
||||
<div class="mb-4 flex items-center gap-2 overflow-x-auto pb-1">
|
||||
<div class="scroll-rail no-scrollbar mb-4 flex items-center gap-2 pb-1">
|
||||
@for (tag of availableTags(); track tag.key) {
|
||||
<button
|
||||
class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
class="chip shrink-0 px-3 py-2 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="{
|
||||
'bg-slate-200 text-slate-900 border-slate-200': filterTag() === tag.key,
|
||||
'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700': filterTag() !== tag.key
|
||||
@@ -43,35 +43,35 @@
|
||||
|
||||
@if (hasQuery()) {
|
||||
<!-- Pills de type façon directory Twitch : Directs / Vidéos / Clips / Chaînes -->
|
||||
<div class="flex items-center gap-2 overflow-x-auto pb-1" role="tablist" aria-label="Filtrer par type de contenu">
|
||||
<button class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
<div class="scroll-rail no-scrollbar flex items-center gap-2 pb-1" role="tablist" aria-label="Filtrer par type de contenu">
|
||||
<button class="chip shrink-0 px-3 py-2 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="typePill() === 'all' ? 'bg-slate-200 text-slate-900 border-slate-200' : 'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700'"
|
||||
[attr.aria-pressed]="typePill() === 'all'" (click)="setTypePill('all')">
|
||||
Tout ({{ typeCounts().all }})
|
||||
</button>
|
||||
<button *ngIf="typeCounts().live > 0 || typePill() === 'live'" class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
<button *ngIf="typeCounts().live > 0 || typePill() === 'live'" class="chip shrink-0 px-3 py-2 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="typePill() === 'live' ? 'bg-purple-600 text-white border-purple-500' : 'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700'"
|
||||
[attr.aria-pressed]="typePill() === 'live'" (click)="setTypePill('live')">
|
||||
Directs ({{ typeCounts().live }})
|
||||
</button>
|
||||
<button *ngIf="typeCounts().video > 0 || typePill() === 'video'" class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
<button *ngIf="typeCounts().video > 0 || typePill() === 'video'" class="chip shrink-0 px-3 py-2 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="typePill() === 'video' ? 'bg-slate-200 text-slate-900 border-slate-200' : 'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700'"
|
||||
[attr.aria-pressed]="typePill() === 'video'" (click)="setTypePill('video')">
|
||||
Vidéos ({{ typeCounts().video }})
|
||||
</button>
|
||||
<button *ngIf="typeCounts().clip > 0 || typePill() === 'short'" class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
<button *ngIf="typeCounts().clip > 0 || typePill() === 'short'" class="chip shrink-0 px-3 py-2 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="typePill() === 'short' ? 'bg-red-600 text-white border-red-500' : 'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700'"
|
||||
[attr.aria-pressed]="typePill() === 'short'" (click)="setTypePill('short')">
|
||||
Clips ({{ typeCounts().clip }})
|
||||
</button>
|
||||
<button *ngIf="typeCounts().channel > 0 || typePill() === 'channel'" class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
<button *ngIf="typeCounts().channel > 0 || typePill() === 'channel'" class="chip shrink-0 px-3 py-2 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="typePill() === 'channel' ? 'bg-slate-200 text-slate-900 border-slate-200' : 'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700'"
|
||||
[attr.aria-pressed]="typePill() === 'channel'" (click)="setTypePill('channel')">
|
||||
Chaînes ({{ typeCounts().channel }})
|
||||
</button>
|
||||
</div>
|
||||
<!-- Barre de filtrage (même UX que la page thème / fournisseur) -->
|
||||
<div class="bg-slate-800/50 p-4 rounded-xl border border-slate-700 my-4">
|
||||
<div class="rounded-xl border border-slate-700 bg-slate-800/50 p-4 my-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-5 gap-4">
|
||||
<div>
|
||||
<label for="sortSelect" class="block text-sm font-medium text-slate-300 mb-1">{{ 'filter.sort' | t }}</label>
|
||||
|
||||
Reference in New Issue
Block a user