fix(web): lecteur mobile — compacter la barre du lecteur sur téléphone (wrap flex, libellés secondaires masqués, navigation/plein écran en icônes, select de mode réductible) pour éliminer le rognage à droite (bug #40)

This commit is contained in:
Git Agent
2026-08-26 13:12:05 +02:00
parent edaad8f667
commit b26116fbe3
3 changed files with 91 additions and 9 deletions

View File

@ -1,4 +1,4 @@
import { ArrowLeft, ArrowRight } from "lucide-react"; import { ArrowLeft, ArrowRight, Maximize2, Minimize2 } from "lucide-react";
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
import { ErrorRibbon } from "../components/ui"; import { ErrorRibbon } from "../components/ui";
import { navigate } from "../router"; import { navigate } from "../router";
@ -189,9 +189,9 @@ export function ReaderShell({ title, backHref, error, onRetry, controls, zoomCon
setHeaderVisible(true); setHeaderVisible(true);
}} }}
> >
<button className="ghost-button" onClick={() => navigate(backHref)}> <button className="ghost-button reader-back-button" onClick={() => navigate(backHref)} aria-label="Retour">
<ArrowLeft size={17} /> <ArrowLeft size={17} />
Retour <span>Retour</span>
</button> </button>
<strong className="reader-title">{title}</strong> <strong className="reader-title">{title}</strong>
<span className="reader-position">{controls.positionLabel}</span> <span className="reader-position">{controls.positionLabel}</span>
@ -204,12 +204,12 @@ export function ReaderShell({ title, backHref, error, onRetry, controls, zoomCon
</select> </select>
</label> </label>
)} )}
<button className="ghost-button" onClick={controls.onPrevious} disabled={!controls.canPrevious}> <button className="ghost-button reader-nav-button reader-previous-button" onClick={controls.onPrevious} disabled={!controls.canPrevious} aria-label="Page precedente">
<ArrowLeft size={16} /> <ArrowLeft size={16} />
Precedent <span>Precedent</span>
</button> </button>
<button className="ghost-button" onClick={controls.onNext} disabled={!controls.canNext}> <button className="ghost-button reader-nav-button reader-next-button" onClick={controls.onNext} disabled={!controls.canNext} aria-label="Page suivante">
Suivant <span>Suivant</span>
<ArrowRight size={16} /> <ArrowRight size={16} />
</button> </button>
{zoomControls && ( {zoomControls && (
@ -226,8 +226,9 @@ export function ReaderShell({ title, backHref, error, onRetry, controls, zoomCon
</button> </button>
</div> </div>
)} )}
<button className="ghost-button reader-fullscreen-toggle" onClick={() => void toggleFullscreen()} aria-pressed={fullscreenActive}> <button className="ghost-button reader-fullscreen-toggle" onClick={() => void toggleFullscreen()} aria-pressed={fullscreenActive} aria-label={fullscreenActive ? "Quitter le plein écran" : "Plein écran"}>
{fullscreenActive ? "Quitter le plein écran" : "Plein écran"} {fullscreenActive ? <Minimize2 size={16} /> : <Maximize2 size={16} />}
<span>{fullscreenActive ? "Quitter le plein écran" : "Plein écran"}</span>
</button> </button>
</header> </header>
{error && ( {error && (

View File

@ -222,6 +222,15 @@ describe("reader runtime helpers", () => {
expect(verticalContent).toContain("align-self: start"); expect(verticalContent).toContain("align-self: start");
}); });
it("lets the mobile reader toolbar wrap instead of overflowing horizontally", () => {
const styles = readFileSync(new URL("../styles/app.css", import.meta.url), "utf8");
expect(styles).toContain(".reader-topbar {\n gap: 8px;\n flex-wrap: wrap;");
expect(styles).toContain("overflow: hidden");
expect(styles).toContain(".reader-back-button span,\n .reader-nav-button span,\n .reader-fullscreen-toggle span,\n .reader-mode-control > span");
expect(styles).toContain(".reader-mode-control select {\n width: 100%;");
});
it("detects fullscreen support and active fullscreen element", () => { it("detects fullscreen support and active fullscreen element", () => {
const fullscreenElement = { requestFullscreen: () => Promise.resolve() } as unknown as HTMLElement; const fullscreenElement = { requestFullscreen: () => Promise.resolve() } as unknown as HTMLElement;
const regularElement = {} as HTMLElement; const regularElement = {} as HTMLElement;

View File

@ -1282,6 +1282,78 @@ main.app-main-reader .reader-page {
.reader-topbar { .reader-topbar {
gap: 8px; gap: 8px;
flex-wrap: wrap;
align-content: start;
overflow: hidden;
}
.reader-back-button,
.reader-nav-button,
.reader-fullscreen-toggle {
width: 34px;
min-width: 34px;
padding: 0;
}
.reader-back-button {
order: 1;
}
.reader-title {
order: 2;
flex: 1 1 120px;
font-size: 0.9rem;
}
.reader-position {
order: 3;
min-width: 78px;
font-size: 0.78rem;
}
.reader-fullscreen-toggle {
order: 4;
margin-left: 0;
}
.reader-mode-control {
order: 5;
flex: 1 1 132px;
min-width: 0;
}
.reader-mode-control select {
width: 100%;
min-width: 0;
max-width: 100%;
font-size: 0.78rem;
}
.reader-previous-button {
order: 6;
}
.reader-next-button {
order: 7;
}
.reader-zoom-controls {
order: 8;
flex: 0 1 auto;
min-width: 0;
gap: 4px;
}
.reader-zoom-label {
min-width: 46px;
font-size: 0.78rem;
}
.reader-back-button span,
.reader-nav-button span,
.reader-fullscreen-toggle span,
.reader-mode-control > span {
display: none;
} }
.reader-zoom-reset { .reader-zoom-reset {