fix(web): Editor-Layout ab 1024 px und Scroll-Reset beim Seitenwechsel
Editor: Seitenspalten geben nach (minmax), die Vorschau bleibt ab 1024 px mindestens 360 px breit, einspaltig erst unter 1024 px. Router: navigate() springt nach dem Rendern nach oben, Dokumenttitel je Route. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
09832c2cf4
commit
09de06e797
3 files changed
+83
-5
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
/* Editor: drei Spalten (Vorlagen | Vorschau | Optionen), unter 900 px einspaltig. */
|
/* Editor: drei Spalten (Vorlagen | Vorschau | Optionen), unter 1024 px einspaltig. */
|
||||||
|
|
||||||
.editor [hidden] {
|
.editor [hidden] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
@@ -30,7 +30,8 @@
|
|||||||
--card-radius: 3.711% / 5.882%; /* 57 px von 1536 x 969, nur in der Vorschau */
|
--card-radius: 3.711% / 5.882%; /* 57 px von 1536 x 969, nur in der Vorschau */
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 300px 1fr 400px;
|
/* Die Seitenspalten geben nach, damit die Vorschau in der Mitte ab 1024 px mindestens 360 px breit bleibt. */
|
||||||
|
grid-template-columns: minmax(210px, 280px) minmax(470px, 1fr) minmax(250px, 360px);
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
@@ -494,9 +495,29 @@
|
|||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Unter 900 px: Vorschau, Vorlagen als waagerechte Reihe, Optionen */
|
/* Ab 1024 px: schmale Optionsspalte → zwei statt drei Netzwerk-Kacheln pro Reihe, damit Beschriftungen nicht mitten im Wort brechen */
|
||||||
|
|
||||||
@media (max-width: 899px) {
|
@media (min-width: 1024px) {
|
||||||
|
.mark-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1024 bis 1199 px: drei Spalten, aber die Mitte bekommt kompaktere Knöpfe, damit sie in einer Reihe bleiben */
|
||||||
|
|
||||||
|
@media (min-width: 1024px) and (max-width: 1199px) {
|
||||||
|
.editor-center {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-center .btn {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unter 1024 px: Vorschau, Vorlagen als waagerechte Reihe, Optionen */
|
||||||
|
|
||||||
|
@media (max-width: 1023px) {
|
||||||
.editor {
|
.editor {
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|||||||
@@ -125,6 +125,53 @@ describe('startRouter', () => {
|
|||||||
expect(location.pathname).toBe('/');
|
expect(location.pathname).toBe('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Scrollposition und Titel', () => {
|
||||||
|
it('navigate springt nach dem Rendern an den Seitenanfang', () => {
|
||||||
|
const order: string[] = [];
|
||||||
|
const scrollTo = vi.spyOn(window, 'scrollTo').mockImplementation(() => order.push('scroll'));
|
||||||
|
try {
|
||||||
|
startRouter(() => {
|
||||||
|
order.push('render');
|
||||||
|
});
|
||||||
|
order.length = 0;
|
||||||
|
navigate('/karten');
|
||||||
|
expect(scrollTo).toHaveBeenCalledTimes(1);
|
||||||
|
expect(scrollTo).toHaveBeenCalledWith(0, 0);
|
||||||
|
expect(order).toEqual(['render', 'scroll']);
|
||||||
|
} finally {
|
||||||
|
scrollTo.mockRestore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('beim Start und bei popstate wird nicht gescrollt (das übernimmt der Browser)', () => {
|
||||||
|
const scrollTo = vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
|
||||||
|
try {
|
||||||
|
startRouter(vi.fn());
|
||||||
|
history.pushState(null, '', '/karten');
|
||||||
|
window.dispatchEvent(new PopStateEvent('popstate'));
|
||||||
|
expect(scrollTo).not.toHaveBeenCalled();
|
||||||
|
} finally {
|
||||||
|
scrollTo.mockRestore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setzt den Dokumenttitel je Route', () => {
|
||||||
|
vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
|
||||||
|
try {
|
||||||
|
startRouter(vi.fn());
|
||||||
|
expect(document.title).toBe('AirCard-IOS – Kartenmotiv gestalten');
|
||||||
|
navigate('/karten');
|
||||||
|
expect(document.title).toBe('Meine Karten – AirCard-IOS');
|
||||||
|
navigate('/passwort-neu');
|
||||||
|
expect(document.title).toBe('Passwort neu setzen – AirCard-IOS');
|
||||||
|
navigate('/');
|
||||||
|
expect(document.title).toBe('AirCard-IOS – Kartenmotiv gestalten');
|
||||||
|
} finally {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('rendert bei popstate die dann aktuelle Route', () => {
|
it('rendert bei popstate die dann aktuelle Route', () => {
|
||||||
const render = vi.fn();
|
const render = vi.fn();
|
||||||
startRouter(render);
|
startRouter(render);
|
||||||
|
|||||||
+11
-1
@@ -9,6 +9,12 @@ export function matchRoute(pathname: string): RouteId {
|
|||||||
return 'editor';
|
return 'editor';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TITLES: Record<RouteId, string> = {
|
||||||
|
editor: 'AirCard-IOS – Kartenmotiv gestalten',
|
||||||
|
cards: 'Meine Karten – AirCard-IOS',
|
||||||
|
reset: 'Passwort neu setzen – AirCard-IOS',
|
||||||
|
};
|
||||||
|
|
||||||
// Beendet den laufenden Router (Seiten-Cleanup, Listener); null, solange keiner läuft.
|
// Beendet den laufenden Router (Seiten-Cleanup, Listener); null, solange keiner läuft.
|
||||||
let stopCurrent: (() => void) | null = null;
|
let stopCurrent: (() => void) | null = null;
|
||||||
let renderCurrent: (() => void) | null = null;
|
let renderCurrent: (() => void) | null = null;
|
||||||
@@ -20,6 +26,8 @@ export function navigate(path: string): void {
|
|||||||
history.pushState(null, '', target);
|
history.pushState(null, '', target);
|
||||||
}
|
}
|
||||||
renderCurrent?.();
|
renderCurrent?.();
|
||||||
|
// Eine neue Seite beginnt oben (pushState setzt die Scrollposition nicht zurück); bei popstate bleibt es dem Browser überlassen.
|
||||||
|
if (renderCurrent) window.scrollTo(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startRouter(render: Render): void {
|
export function startRouter(render: Render): void {
|
||||||
@@ -33,7 +41,9 @@ export function startRouter(render: Render): void {
|
|||||||
const renderPage = () => {
|
const renderPage = () => {
|
||||||
cleanup?.();
|
cleanup?.();
|
||||||
cleanup = undefined;
|
cleanup = undefined;
|
||||||
cleanup = render(matchRoute(location.pathname), root);
|
const route = matchRoute(location.pathname);
|
||||||
|
document.title = TITLES[route];
|
||||||
|
cleanup = render(route, root);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClick = (event: MouseEvent) => {
|
const onClick = (event: MouseEvent) => {
|
||||||
|
|||||||
Reference in new issue
Block a user