From b7fc9e8dd5158ef4983b2ebf8943fa918cbd73ab Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Jul 2026 06:38:45 +0000 Subject: [PATCH 1/2] feat(settings): edge-to-edge mobile chrome + design polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fullscreen settings sheet had broken mobile chrome: the header lived inside the scroll region (so it scrolled away) with a floating close button and no status-bar coverage, letting content bleed up behind the notch; the bottom stopped short of the edge instead of running under the home indicator. - Header: reuse the app's `.edge-glass-header` + `.edge-glass-header-backdrop` scrim as a sticky, full-bleed glass header that covers the status-bar/notch band, holding the title + the (now single, integrated) close button. It hides on scroll-down and returns on scroll-up via `useScrollHideReporter` (phone-gated), so the top runs fully edge-to-edge while scrolling — the same behaviour as the app's search bar. Desktop reverts to a static in-panel header (`lg:` resets); the two-column nav layout is unchanged. - Bottom: drop the panel's mobile `pb-safe` (`max-lg:!pb-0`) and carry a safe-area-aware pad on the content wrapper so content fills edge-to-edge to the physical bottom under the home indicator. - Restructure the mobile scroll region full-bleed (scroll container loses its horizontal padding/max-width; a centred content wrapper insets the sections) so the header scrim spans the true width. - Lighten the repeated "Saved for later — not active yet" markers from a heavy bordered pill to a quiet muted caption (text/id/aria-describedby and per-row placement unchanged, so the inert-preferences contract holds). Preserves the settings test contract: dialog name "Account & app", the four section headings, the row test ids, a single "Close settings" button, Escape-to-close, the 390x820 fullscreen box, and no horizontal overflow — all verified in Chromium. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01RiRMGogS7jxgngFRYEHjw3 --- .../clinical-dashboard/settings-dialog.tsx | 864 +++++++++--------- 1 file changed, 447 insertions(+), 417 deletions(-) diff --git a/src/components/clinical-dashboard/settings-dialog.tsx b/src/components/clinical-dashboard/settings-dialog.tsx index 8c3508517..fdbc9be9d 100644 --- a/src/components/clinical-dashboard/settings-dialog.tsx +++ b/src/components/clinical-dashboard/settings-dialog.tsx @@ -1,6 +1,6 @@ "use client"; -import { type FormEvent, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { type FormEvent, type ReactNode, type UIEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Bell, BookOpen, @@ -42,6 +42,7 @@ import { POPULATION_OPTIONS, useAppPreferences, } from "@/components/clinical-dashboard/use-app-preferences"; +import { useScrollHideReporter } from "@/components/clinical-dashboard/use-hide-on-scroll"; import { clearRecentQueries, countRecentQueries } from "@/lib/recent-query-storage"; import { cn, @@ -118,6 +119,10 @@ export function SettingsDialog({ const { theme, preference: themePreference, setPreference: setThemePreference } = useTheme(); const { preferences, setPreference, resetPreferences } = useAppPreferences(); + // Hide-on-scroll for the mobile glass header (phone-gated inside the hook), so + // the top goes fully edge-to-edge while scrolling — the same behaviour as the + // app's search bar. Desktop keeps a static in-panel header. + const { hidden: headerHidden, reportScroll } = useScrollHideReporter(); const auth = useAuthSession(); const accountData = useAccountData(); @@ -203,6 +208,14 @@ export function SettingsDialog({ [preferences.motion], ); + const handleScroll = useCallback( + (event: UIEvent) => { + const el = event.currentTarget; + reportScroll({ offset: el.scrollTop, maxOffset: el.scrollHeight - el.clientHeight, source: el }); + }, + [reportScroll], + ); + async function submitSettingsEmail(event: FormEvent) { event.preventDefault(); if (!settingsEmail.trim()) return; @@ -256,7 +269,7 @@ export function SettingsDialog({ type="button" onClick={onClose} aria-label="Close settings" - className="absolute right-2.5 top-[max(0.45rem,env(safe-area-inset-top))] z-10 grid h-9 w-9 place-items-center rounded-full text-[color:var(--text-muted)] transition hover:bg-[color:var(--surface)] hover:text-[color:var(--text-heading)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] lg:left-4 lg:right-auto lg:top-4 lg:h-10 lg:w-10" + className="grid h-9 w-9 shrink-0 place-items-center rounded-full border border-[color:var(--border)] bg-[color:var(--surface)]/70 text-[color:var(--text-muted)] shadow-[var(--shadow-inset)] transition hover:bg-[color:var(--surface)] hover:text-[color:var(--text-heading)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] lg:h-10 lg:w-10 lg:border-transparent lg:bg-transparent lg:shadow-none" >