feat(mobile): Wischen vom linken Rand holt die Seitenleiste
Bisher gab es dafuer nur den Knopf oben links - am Telefon die Ecke, die man einhaendig am schlechtesten erreicht. Vier Bedingungen halten die Geste aus dem Weg: nur im 44-px-Streifen am Rand (sonst waeren waagerecht scrollbare Bereiche nicht mehr zu bedienen), mindestens 60 px Weg, doppelt so weit waagerecht wie senkrecht (sonst oeffnet sie beim Blaettern), hoechstens 800 ms. 44 px und nicht 20: Androids Zurueck-Geste belegt die aeussersten ~20 px, was dort beginnt erreicht die Seite nie. Aus einer bildschirmfuellenden Ansicht heraus tut sie nichts - dort wischt man durch Seiten, und die Leiste laege ohnehin dahinter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5ea66ea884
commit
8d95088271
5 files changed
+137
-2
No files matched your search
+25
@@ -1500,6 +1500,31 @@ Ereignisse, `transform: none`.
|
||||
programmgesteuerter Zeiger prüft die Überfahrung nicht bei jedem Bild neu,
|
||||
eine echte Maus schon. Bitte mit der Maus gegenprüfen.
|
||||
|
||||
## Wischen holt die Seitenleiste (25.08.2026)
|
||||
|
||||
Vom linken Rand nach rechts öffnet, nach links schließt – nur an Geräten mit
|
||||
grobem Zeiger (`pointer: coarse`), mit der Maus passiert nichts.
|
||||
|
||||
Gemessen bei 412×900 mit Touch:
|
||||
|
||||
| Geste | Erwartet | Gemessen |
|
||||
|---|---|---|
|
||||
| vom Rand (x=10) 150 px nach rechts | öffnet | ✅ |
|
||||
| offen, 130 px nach links | schließt | ✅ |
|
||||
| aus der Mitte (x=200) nach rechts | nichts | ✅ |
|
||||
| vom Rand, nur 30 px | nichts | ✅ |
|
||||
| vom Rand, 60 px rechts bei 100 px runter | nichts | ✅ |
|
||||
| vom Rand, 150 px in 1,2 s | nichts | ✅ |
|
||||
| in der Vollbild-Vorschau vom Rand | nichts | ✅ |
|
||||
|
||||
Die Grenzen: 44 px Randstreifen, mindestens 60 px Weg, doppelt so weit
|
||||
waagerecht wie senkrecht, höchstens 800 ms.
|
||||
|
||||
- [ ] **Auf Android gegenprüfen.** Die Zurück-Geste des Systems belegt die
|
||||
äußersten ~20 px; was dort beginnt, erreicht die Seite nie. Deshalb 44 px
|
||||
statt 20 – ob das in der Praxis reicht, kann nur ein echtes Gerät sagen.
|
||||
- [ ] Im Querformat gegenprüfen (Aussparung links)
|
||||
|
||||
## Automatisiert geprüft (22.08.2026)
|
||||
|
||||
Diese Punkte hat Claude per API/`curl` gegen Wegwerf-Konten durchgespielt; sie
|
||||
|
||||
@@ -171,6 +171,10 @@ export default function FilePreview({ file, onPrev, onNext, onClose, onSaved, se
|
||||
return (
|
||||
<div
|
||||
onClick={tryClose}
|
||||
/* Marke für die Wischgeste: Aus einer bildschirmfüllenden Ansicht heraus
|
||||
soll der Wisch vom Rand nicht die Seitenleiste holen – die läge
|
||||
dahinter, und hier wischt man durch Seiten. */
|
||||
data-fullscreen=""
|
||||
style={{
|
||||
position: 'fixed', inset: 'var(--honeybar-h) 0 0 0', zIndex: 100,
|
||||
background: 'rgba(0,0,0,0.82)',
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function OfficeEditor({ file, onClose }: {
|
||||
}, [onClose])
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
<div data-fullscreen="" style={{
|
||||
position: 'fixed', inset: 'var(--honeybar-h) 0 0 0', zIndex: 120,
|
||||
background: 'var(--color-background)', display: 'flex', flexDirection: 'column',
|
||||
}}>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { filesApi } from '../api/client'
|
||||
import type { FolderItem, User } from '../api/client'
|
||||
import { useNavHistory, type View } from '../hooks/useNavHistory'
|
||||
import { useEdgeSwipe } from '../hooks/useEdgeSwipe'
|
||||
import Sidebar from './Sidebar'
|
||||
import FileBrowser from '../pages/FileBrowser'
|
||||
import TrashPage from '../pages/TrashPage'
|
||||
@@ -25,6 +26,14 @@ export default function Shell({ user, onLogout, onUserChanged }: Props) {
|
||||
const nav = useNavHistory()
|
||||
const { view, folderPath } = nav
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||
|
||||
// Am Finger: vom linken Rand nach rechts wischen holt die Leiste, nach
|
||||
// links wischen schickt sie zurück. Mit Maus passiert nichts.
|
||||
useEdgeSwipe({
|
||||
open: sidebarOpen,
|
||||
onOpen: useCallback(() => setSidebarOpen(true), []),
|
||||
onClose: useCallback(() => setSidebarOpen(false), []),
|
||||
})
|
||||
// Hochgezählt, wenn etwas auf den Papierkorb gezogen wurde – die Dateiliste
|
||||
// hängt daran und holt sich daraufhin neu.
|
||||
const [trashBump, setTrashBump] = useState(0)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
/**
|
||||
* Wischen vom linken Rand öffnet die Seitenleiste, Wischen nach links
|
||||
* schließt sie wieder.
|
||||
*
|
||||
* Bisher gab es dafür nur den Knopf oben links. Auf einem Telefon ist das die
|
||||
* Ecke, die man einhändig am schlechtesten erreicht – gerade auf einem
|
||||
* aufgeklappten Flip.
|
||||
*
|
||||
* Drei Bedingungen, damit die Geste nicht in die Quere kommt:
|
||||
*
|
||||
* * **Nur am Rand.** Sonst öffnete jedes Wischen über eine Kachel die
|
||||
* Leiste, und waagerecht scrollbare Bereiche (Reiterleisten, breite
|
||||
* Tabellen, der PDF-Betrachter im Zoom) wären nicht mehr zu bedienen.
|
||||
* * **Deutlich waagerecht.** Beim Blättern durch eine lange Liste wandert
|
||||
* der Finger fast nie exakt senkrecht; ohne diese Bedingung öffnete sich
|
||||
* die Leiste beim Scrollen.
|
||||
* * **Zügig.** Ein langsames Ziehen ist meistens etwas anderes – ein
|
||||
* Auswählen, ein Verschieben, ein Halten.
|
||||
*
|
||||
* **Auf Android hat das System das erste Wort:** Die Zurück-Geste belegt die
|
||||
* äußersten ~20 Pixel beider Ränder, und was dort beginnt, erreicht die Seite
|
||||
* gar nicht. Die Zone ist deshalb 44 Pixel breit – ein Wisch, der knapp hinter
|
||||
* dem Systembereich ansetzt, wird noch erkannt.
|
||||
*/
|
||||
|
||||
/** Breite des Streifens am linken Rand, in dem eine Geste beginnen darf. */
|
||||
const edgeZone = 44
|
||||
/** So weit muss der Finger waagerecht wandern. */
|
||||
const minDistance = 60
|
||||
/** Länger gilt als „gezogen", nicht als „gewischt". */
|
||||
const maxDuration = 800
|
||||
|
||||
interface Options {
|
||||
open: boolean
|
||||
onOpen: () => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function useEdgeSwipe({ open, onOpen, onClose }: Options) {
|
||||
useEffect(() => {
|
||||
if (typeof matchMedia !== 'function' || !matchMedia('(pointer: coarse)').matches) return
|
||||
|
||||
let startX = 0
|
||||
let startY = 0
|
||||
let startedAt = 0
|
||||
let valid = false
|
||||
|
||||
function onStart(e: TouchEvent) {
|
||||
valid = false
|
||||
if (e.touches.length !== 1) return // zwei Finger heißt zoomen
|
||||
const t = e.touches[0]
|
||||
|
||||
// Nicht aus einer bildschirmfüllenden Ansicht heraus: Dort läge die
|
||||
// Leiste hinter dem Betrachter, und man wischt dort durch Seiten.
|
||||
const el = e.target instanceof Element ? e.target : null
|
||||
if (el?.closest('[data-fullscreen]')) return
|
||||
|
||||
// Geschlossen: nur am Rand. Offen: überall dort, wo die Leiste und ihr
|
||||
// Schatten liegen – weiter rechts hat ein Wisch nichts mit ihr zu tun.
|
||||
if (!open && t.clientX > edgeZone) return
|
||||
if (open && t.clientX > 320) return
|
||||
|
||||
startX = t.clientX
|
||||
startY = t.clientY
|
||||
startedAt = Date.now()
|
||||
valid = true
|
||||
}
|
||||
|
||||
function onEnd(e: TouchEvent) {
|
||||
if (!valid) return
|
||||
valid = false
|
||||
const t = e.changedTouches[0]
|
||||
if (!t) return
|
||||
if (Date.now() - startedAt > maxDuration) return
|
||||
|
||||
const dx = t.clientX - startX
|
||||
const dy = t.clientY - startY
|
||||
// Doppelt so weit zur Seite wie nach oben oder unten: Das trennt ein
|
||||
// Wischen sauber vom Blättern, ohne einen genauen Winkel zu verlangen.
|
||||
if (Math.abs(dx) < minDistance || Math.abs(dx) < Math.abs(dy) * 2) return
|
||||
|
||||
if (!open && dx > 0) onOpen()
|
||||
else if (open && dx < 0) onClose()
|
||||
}
|
||||
|
||||
// Passiv: Die Geste verhindert nichts, sie sieht nur zu. Ein nicht-passiver
|
||||
// Zuhörer würde das Scrollen der ganzen Anwendung ausbremsen.
|
||||
document.addEventListener('touchstart', onStart, { passive: true })
|
||||
document.addEventListener('touchend', onEnd, { passive: true })
|
||||
return () => {
|
||||
document.removeEventListener('touchstart', onStart)
|
||||
document.removeEventListener('touchend', onEnd)
|
||||
}
|
||||
}, [open, onOpen, onClose])
|
||||
}
|
||||
Reference in new issue
Block a user