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] {
|
||||
display: none !important;
|
||||
@@ -30,7 +30,8 @@
|
||||
--card-radius: 3.711% / 5.882%; /* 57 px von 1536 x 969, nur in der Vorschau */
|
||||
|
||||
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;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 24px;
|
||||
@@ -494,9 +495,29 @@
|
||||
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 {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
|
||||
@@ -125,6 +125,53 @@ describe('startRouter', () => {
|
||||
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', () => {
|
||||
const render = vi.fn();
|
||||
startRouter(render);
|
||||
|
||||
+11
-1
@@ -9,6 +9,12 @@ export function matchRoute(pathname: string): RouteId {
|
||||
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.
|
||||
let stopCurrent: (() => void) | null = null;
|
||||
let renderCurrent: (() => void) | null = null;
|
||||
@@ -20,6 +26,8 @@ export function navigate(path: string): void {
|
||||
history.pushState(null, '', target);
|
||||
}
|
||||
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 {
|
||||
@@ -33,7 +41,9 @@ export function startRouter(render: Render): void {
|
||||
const renderPage = () => {
|
||||
cleanup?.();
|
||||
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) => {
|
||||
|
||||
Reference in new issue
Block a user