Web frontend: touch layout for phones
Phones got the kiosk's keyboard-first layout squeezed down: shelves cut off by the tab rail and a header taking a fifth of the screen. They now get their own layout, chosen by a media query (narrow, or short landscape on a touch screen); tablets and desktop are unchanged. - Pick a group (Musik/Hörbücher/Podcasts), then a category, then scroll through a grid of smaller albums. - A floating glass dock: mini player, Musik/Zimmer tabs (no typing tab - a phone has no keyboard for it) and a search button that turns into a real search field, raised above the on-screen keyboard. - Album sheet and room page get compact phone variants. Browse state stays in the shared UiState, so resizing or rotating keeps your place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
26
web/src/hooks/useKeyboardInset.ts
Normal file
26
web/src/hooks/useKeyboardInset.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/** How much of the bottom of the layout viewport the on-screen keyboard covers, in px.
|
||||
*
|
||||
* iOS Safari slides the keyboard over the page without resizing it - `100dvh` does not
|
||||
* follow it - so a bar pinned to the bottom ends up hidden behind the very keyboard it
|
||||
* is typing with. The visual viewport does shrink, and the difference is the inset. */
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useKeyboardInset(): number {
|
||||
const [inset, setInset] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const viewport = window.visualViewport;
|
||||
if (!viewport) return;
|
||||
const measure = () =>
|
||||
setInset(Math.max(0, Math.round(window.innerHeight - viewport.height - viewport.offsetTop)));
|
||||
viewport.addEventListener("resize", measure);
|
||||
viewport.addEventListener("scroll", measure);
|
||||
return () => {
|
||||
viewport.removeEventListener("resize", measure);
|
||||
viewport.removeEventListener("scroll", measure);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return inset;
|
||||
}
|
||||
23
web/src/hooks/usePhoneLayout.ts
Normal file
23
web/src/hooks/usePhoneLayout.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/** Whether the screen is a phone, which gets its own touch-first layout (see
|
||||
* `components/mobile/`) instead of the kiosk's keyboard-first one.
|
||||
*
|
||||
* Narrow portrait, or short landscape on a touch screen - a phone turned sideways is
|
||||
* wide enough for the kiosk layout's columns but nowhere near tall enough for its
|
||||
* header, shelves and player bar. A tablet is neither and keeps the kiosk layout. */
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const PHONE_QUERY = "(max-width: 600px), (max-height: 500px) and (pointer: coarse)";
|
||||
|
||||
export function usePhoneLayout(): boolean {
|
||||
const [phone, setPhone] = useState(() => matchMedia(PHONE_QUERY).matches);
|
||||
|
||||
useEffect(() => {
|
||||
const media = matchMedia(PHONE_QUERY);
|
||||
const onChange = () => setPhone(media.matches);
|
||||
media.addEventListener("change", onChange);
|
||||
return () => media.removeEventListener("change", onChange);
|
||||
}, []);
|
||||
|
||||
return phone;
|
||||
}
|
||||
Reference in New Issue
Block a user