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).
35 lines
2.3 KiB
HTML
35 lines
2.3 KiB
HTML
<div class="container mx-auto p-4 sm:p-6">
|
|
<h2 class="mb-6 border-l-4 border-red-500 pl-4 text-2xl font-bold text-slate-100 sm:text-3xl">Sessions</h2>
|
|
|
|
<div *ngIf="error()" class="mb-4 rounded border border-amber-600 bg-amber-900/40 p-3 text-amber-200">{{ error() }}</div>
|
|
|
|
<div class="mb-4 flex flex-col gap-3 sm:flex-row">
|
|
<button type="button" class="chip w-full rounded bg-slate-700 px-4 py-3 text-white hover:bg-slate-600 sm:w-auto sm:py-2" (click)="reload()" [disabled]="loading()">Reload</button>
|
|
<button type="button" class="chip w-full rounded border border-red-500/40 bg-red-600/20 px-4 py-3 text-red-200 hover:bg-red-600/40 sm:w-auto sm:py-2" (click)="logoutAll()" [disabled]="loading()">Logout all devices</button>
|
|
</div>
|
|
|
|
<div *ngIf="loading()" class="text-slate-400">Loading...</div>
|
|
|
|
<div *ngIf="!loading() && items().length === 0" class="text-slate-400">No active sessions.</div>
|
|
|
|
<ul class="space-y-3">
|
|
<li *ngFor="let s of items()" class="flex flex-col gap-3 rounded bg-slate-800 p-4 sm:flex-row sm:items-start sm:justify-between">
|
|
<!-- UUIDs and User-Agent strings cannot wrap on their own: force it. -->
|
|
<div class="min-w-0 flex-1 break-anywhere text-sm text-slate-200">
|
|
<div><span class="text-slate-400">Session ID:</span> <code class="break-all">{{ s.id }}</code></div>
|
|
<div><span class="text-slate-400">User Agent:</span> {{ s.userAgent || '—' }}</div>
|
|
<div><span class="text-slate-400">Device:</span> {{ s.deviceInfo || '—' }}</div>
|
|
<div><span class="text-slate-400">IP:</span> {{ s.ip || '—' }}</div>
|
|
<div><span class="text-slate-400">Remember me:</span> {{ s.isRemember ? 'Yes' : 'No' }}</div>
|
|
<div><span class="text-slate-400">Created:</span> {{ s.createdAt || '—' }}</div>
|
|
<div><span class="text-slate-400">Last seen:</span> {{ s.lastSeenAt || '—' }}</div>
|
|
<div><span class="text-slate-400">Expires:</span> {{ s.expiresAt || '—' }}</div>
|
|
<div *ngIf="s.revokedAt"><span class="text-slate-400">Revoked:</span> {{ s.revokedAt }}</div>
|
|
</div>
|
|
<div class="shrink-0 sm:pl-4">
|
|
<button type="button" class="chip w-full rounded bg-slate-700 px-4 py-3 text-white hover:bg-slate-600 sm:py-2" (click)="revoke(s.id)">Revoke</button>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|