mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
feat(ui): introduce the ResponsiveSheet primitive
Shared `<ResponsiveSheet>` flips between a bottom `<Sheet>` on narrow viewports and the existing centred `<Dialog>` at `md+`, with a sticky-pinned footer slot on the Sheet branch so primary CTAs stay reachable when the keyboard collapses the viewport. Picks the breakpoint via a new SSR-safe `useIsMobile` hook (`matchMedia`-driven, default `false` on the server so SSR markup always matches the desktop branch). Adds a `max-h-[calc(100dvh-2rem)] overflow-y-auto` default to `<DialogContent>` so the existing centred-dialog forms also scroll inside the viewport on short / soft-keyboard mobile screens instead of spilling off-screen. Smoke suite locks both branches, the header sr-only escape hatch, sticky-vs-flow footer shape, and controlled-state plumbing.
This commit is contained in:
1 parent
fb6fb4f5e6
commit
65fd0bffdf
4 files changed
+464
-2
No files matched your search
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* SSR-safe viewport-width hook. Returns `true` once the browser
|
||||
* reports a viewport narrower than the Tailwind `md` breakpoint
|
||||
* (768 px). The hook always returns `false` on the server and on
|
||||
* the first client paint so SSR markup matches the desktop branch
|
||||
* of any viewport-conditional render; the hook then flips on the
|
||||
* effect tick once `matchMedia` reports the live value.
|
||||
*
|
||||
* Used by `<ResponsiveSheet>` to switch between bottom-sheet and
|
||||
* centred-dialog mounts. Also consumable by any surface that
|
||||
* needs the same boolean (e.g. the Coach drawer's bottom-sheet
|
||||
* branch on narrow viewports).
|
||||
*/
|
||||
export function useIsMobile(): boolean {
|
||||
const [isMobile, setIsMobile] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
const mql = window.matchMedia("(max-width: 767.98px)");
|
||||
const sync = () => setIsMobile(mql.matches);
|
||||
sync();
|
||||
// `addEventListener` is the modern API; the older `addListener`
|
||||
// is kept around for legacy Safari but every browser we ship to
|
||||
// supports the typed event-listener form.
|
||||
mql.addEventListener("change", sync);
|
||||
return () => mql.removeEventListener("change", sync);
|
||||
}, []);
|
||||
|
||||
return isMobile;
|
||||
}
|
||||
Reference in new issue
Block a user