fix(ui): eine Breite fuer Seitenleiste und Kopfleiste
Kopfleiste blendete den Menue-Knopf ab 720px aus, die Seitenleiste stand aber erst ab 768px fest im Bild. Dazwischen lag ein Band von 48 Pixeln ohne Menue und ohne Knopf dorthin -- ein aufgeklapptes Fold landet mit rund 757px genau darin. Die Grenze steht jetzt einmal in src/breakpoints.ts. Dazu zwei Folgefehler derselben Sache: * Der abdunkelnde Grund lag mit z-index 40 ueber der fest stehenden Leiste (die hat dort keinen). Per Randwisch aufgeschoben, war die Leiste danach sichtbar, aber taub. Er blendet sich ab 720px aus. * Der Randwisch loest breit gar nicht mehr aus, und Aufklappen bei aufgeschobener Leiste setzt sie zurueck -- sonst schluckte die Zurueck-Geste einmal ins Leere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fbaf24bdbd
commit
8e27c703d2
6 files changed
+66
-4
No files matched your search
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Die eine Breite, ab der die Bedienung „breit" ist.
|
||||||
|
*
|
||||||
|
* Sie entscheidet **zwei** Dinge, und beide müssen zusammen umschalten:
|
||||||
|
* die Seitenleiste steht fest im Fluss statt über dem Inhalt zu liegen, und
|
||||||
|
* die Kopfleiste kommt in eine Zeile – der Knopf zum Aufschieben verschwindet
|
||||||
|
* dabei, weil es nichts mehr aufzuschieben gibt.
|
||||||
|
*
|
||||||
|
* Vorher standen dafür zwei Zahlen an zwei Stellen: 720px in der Kopfleiste,
|
||||||
|
* 767px in der Seitenleiste. Dazwischen lag ein Band von 48 Pixeln, in dem der
|
||||||
|
* Knopf schon weg war, die Leiste aber noch nach links aus dem Bild geschoben –
|
||||||
|
* kein Menü, kein Weg dorthin. Ein aufgeklapptes Fold landet mit rund 757 px
|
||||||
|
* genau darin.
|
||||||
|
*
|
||||||
|
* Also: eine Zahl, ein Ort. Wer sie ändert, ändert beides zugleich.
|
||||||
|
*/
|
||||||
|
export const wideEnough = '(min-width: 720px)'
|
||||||
|
|
||||||
|
/** Dieselbe Grenze zur Laufzeit – für Fälle, die kein CSS lösen kann. */
|
||||||
|
export function isWide(): boolean {
|
||||||
|
return typeof matchMedia === 'function' && matchMedia(wideEnough).matches
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import UserMenu, { useSidebarToggle } from './UserMenu'
|
import UserMenu, { useSidebarToggle } from './UserMenu'
|
||||||
|
import { wideEnough } from '../breakpoints'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Kopfleiste jeder Ansicht.
|
* Die Kopfleiste jeder Ansicht.
|
||||||
@@ -23,8 +24,12 @@ export const headerHeight = 57
|
|||||||
* Ansichts-Umschalter war ganz verschwunden und der Nutzerbereich am rechten
|
* Ansichts-Umschalter war ganz verschwunden und der Nutzerbereich am rechten
|
||||||
* Rand abgeschnitten. Eine Zeile, die nicht umbrechen kann, quetscht – und
|
* Rand abgeschnitten. Eine Zeile, die nicht umbrechen kann, quetscht – und
|
||||||
* ausgerechnet auf dem Gerät, mit dem man unterwegs einen Beleg sucht.
|
* ausgerechnet auf dem Gerät, mit dem man unterwegs einen Beleg sucht.
|
||||||
|
*
|
||||||
|
* Dieselbe Grenze schaltet die Seitenleiste um; sie steht deshalb in
|
||||||
|
* `breakpoints.ts` und nicht mehr hier. Re-exportiert, weil sie hier gesucht
|
||||||
|
* wird.
|
||||||
*/
|
*/
|
||||||
export const wideEnough = '(min-width: 720px)'
|
export { wideEnough }
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** Strichsymbol, 18 × 18 – dasselbe wie in der Seitenleiste. */
|
/** Strichsymbol, 18 × 18 – dasselbe wie in der Seitenleiste. */
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { filesApi } from '../api/client'
|
import { filesApi } from '../api/client'
|
||||||
import type { FolderItem, User } from '../api/client'
|
import type { FolderItem, User } from '../api/client'
|
||||||
import { useNavHistory, type View } from '../hooks/useNavHistory'
|
import { useNavHistory, type View } from '../hooks/useNavHistory'
|
||||||
@@ -14,6 +14,7 @@ import ProfilePage from '../pages/ProfilePage'
|
|||||||
import TagsPage from '../pages/TagsPage'
|
import TagsPage from '../pages/TagsPage'
|
||||||
import CalendarPage from '../pages/CalendarPage'
|
import CalendarPage from '../pages/CalendarPage'
|
||||||
import { UserMenuProvider } from './UserMenu'
|
import { UserMenuProvider } from './UserMenu'
|
||||||
|
import { wideEnough } from '../breakpoints'
|
||||||
|
|
||||||
export type { View }
|
export type { View }
|
||||||
|
|
||||||
@@ -44,6 +45,19 @@ export default function Shell({ user, onLogout, onUserChanged }: Props) {
|
|||||||
onClose: useCallback(() => { setSidebarOpen(false) }, []),
|
onClose: useCallback(() => { setSidebarOpen(false) }, []),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Wird das Gerät aufgeklappt, während die Leiste aufgeschoben war, steht sie
|
||||||
|
// ab da ohnehin fest im Bild. Bliebe „aufgeschoben" dabei stehen, schluckte
|
||||||
|
// die Zurück-Geste einmal ins Leere – sie zielte auf etwas, das nichts mehr
|
||||||
|
// verdeckt.
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof matchMedia !== 'function') return
|
||||||
|
const mq = matchMedia(wideEnough)
|
||||||
|
const auf = () => { if (mq.matches) setSidebarOpen(false) }
|
||||||
|
auf()
|
||||||
|
mq.addEventListener('change', auf)
|
||||||
|
return () => mq.removeEventListener('change', auf)
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Hochgezählt, wenn etwas auf den Papierkorb gezogen wurde – die Dateiliste
|
// Hochgezählt, wenn etwas auf den Papierkorb gezogen wurde – die Dateiliste
|
||||||
// hängt daran und holt sich daraufhin neu.
|
// hängt daran und holt sich daraufhin neu.
|
||||||
const [trashBump, setTrashBump] = useState(0)
|
const [trashBump, setTrashBump] = useState(0)
|
||||||
@@ -81,9 +95,13 @@ export default function Shell({ user, onLogout, onUserChanged }: Props) {
|
|||||||
onOpenSidebar: () => setSidebarOpen(true),
|
onOpenSidebar: () => setSidebarOpen(true),
|
||||||
}}>
|
}}>
|
||||||
<div className="aether-vh" style={{ display: 'flex', overflow: 'hidden' }}>
|
<div className="aether-vh" style={{ display: 'flex', overflow: 'hidden' }}>
|
||||||
{/* Mobile overlay */}
|
{/* Der abdunkelnde Grund hinter der aufgeschobenen Leiste.
|
||||||
|
Die Klasse blendet ihn aus, sobald die Leiste fest im Bild steht:
|
||||||
|
Dort liegt sie im Fluss und **ohne** z-index, ein Grund darüber
|
||||||
|
machte sie unanklickbar – sichtbar, aber taub. */}
|
||||||
{sidebarOpen && (
|
{sidebarOpen && (
|
||||||
<div
|
<div
|
||||||
|
className="aether-scrim"
|
||||||
style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 40 }}
|
style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 40 }}
|
||||||
onClick={() => setSidebarOpen(false)}
|
onClick={() => setSidebarOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { RenameIcon, TrashIcon } from './icons'
|
|||||||
import type { User } from '../api/client'
|
import type { User } from '../api/client'
|
||||||
import type { View } from './Shell'
|
import type { View } from './Shell'
|
||||||
import { useAppSettings } from '../hooks/useAppSettings'
|
import { useAppSettings } from '../hooks/useAppSettings'
|
||||||
|
import { wideEnough } from '../breakpoints'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: User
|
user: User
|
||||||
@@ -775,7 +776,11 @@ export default function Sidebar({ user, view, activeTagId, onOpenTag, onTrashDro
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
@media (max-width: 767px) {
|
/* Schmal: Die Leiste liegt über dem Inhalt statt neben ihm.
|
||||||
|
Die Grenze ist dieselbe, die in der Kopfleiste den Knopf zum
|
||||||
|
Aufschieben zeigt – hätte sie eine eigene, gäbe es dazwischen einen
|
||||||
|
Streifen ohne Menü und ohne Knopf dorthin. */
|
||||||
|
@media not all and ${wideEnough} {
|
||||||
.aether-sidebar {
|
.aether-sidebar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: var(--honeybar-h); left: 0;
|
top: var(--honeybar-h); left: 0;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
import { isWide } from '../breakpoints'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wischen vom linken Rand öffnet die Seitenleiste, Wischen nach links
|
* Wischen vom linken Rand öffnet die Seitenleiste, Wischen nach links
|
||||||
@@ -50,6 +51,10 @@ export function useEdgeSwipe({ open, onOpen, onClose }: Options) {
|
|||||||
function onStart(e: TouchEvent) {
|
function onStart(e: TouchEvent) {
|
||||||
valid = false
|
valid = false
|
||||||
if (e.touches.length !== 1) return // zwei Finger heißt zoomen
|
if (e.touches.length !== 1) return // zwei Finger heißt zoomen
|
||||||
|
// Steht die Leiste ohnehin fest im Bild – aufgeklapptes Faltgerät,
|
||||||
|
// Querformat –, gibt es nichts aufzuschieben. Bei jeder Geste neu
|
||||||
|
// gefragt, weil sich die Breite hier im Betrieb ändert.
|
||||||
|
if (isWide()) return
|
||||||
const t = e.touches[0]
|
const t = e.touches[0]
|
||||||
|
|
||||||
// Nicht aus einer bildschirmfüllenden Ansicht heraus: Dort läge die
|
// Nicht aus einer bildschirmfüllenden Ansicht heraus: Dort läge die
|
||||||
|
|||||||
@@ -126,3 +126,10 @@ body {
|
|||||||
an der Kante kleben zu lassen. */
|
an der Kante kleben zu lassen. */
|
||||||
.aether-content { padding: 12px; }
|
.aether-content { padding: 12px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Der abgedunkelte Grund hinter der aufgeschobenen Seitenleiste.
|
||||||
|
Steht die Leiste fest im Bild, gibt es nichts abzudunkeln – und ein Grund
|
||||||
|
darüber würde sie unbedienbar machen, ohne dass man ihm das ansieht. */
|
||||||
|
@media (min-width: 720px) {
|
||||||
|
.aether-scrim { display: none; }
|
||||||
|
}
|
||||||
Reference in new issue
Block a user