Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions apps/web/src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,44 @@ const theme = createTheme({
},
},
},
// The installed app draws the page under the status and navigation bars
// (`viewport-fit=cover`), so every dialog and drawer keeps its content out
// of them. In a plain browser tab the insets are 0.
MuiDialog: {
styleOverrides: {
// A floating dialog is centred and sized within the safe area.
container: ({ ownerState }) =>
ownerState.fullScreen
? {}
: {
boxSizing: "border-box",
paddingTop: "var(--omx-safe-top)",
paddingBottom: "var(--omx-safe-bottom)",
paddingLeft: "var(--omx-safe-left)",
paddingRight: "var(--omx-safe-right)",
},
// A full-screen dialog still covers the bars, with its content inset.
paperFullScreen: {
boxSizing: "border-box",
paddingTop: "var(--omx-safe-top)",
paddingBottom: "var(--omx-safe-bottom)",
paddingLeft: "var(--omx-safe-left)",
paddingRight: "var(--omx-safe-right)",
},
},
},
MuiDrawer: {
styleOverrides: {
// The screen edge the drawer is attached to and the two beside it;
// its open side faces the page, not a system bar.
paper: ({ ownerState }) => ({
...(ownerState.anchor !== "bottom" && { paddingTop: "var(--omx-safe-top)" }),
...(ownerState.anchor !== "top" && { paddingBottom: "var(--omx-safe-bottom)" }),
...(ownerState.anchor !== "right" && { paddingLeft: "var(--omx-safe-left)" }),
...(ownerState.anchor !== "left" && { paddingRight: "var(--omx-safe-right)" }),
}),
},
},
},
});

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/admin/AdminLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function AdminLayout({ children, user, selfHosted = false }: AdminLayoutP
<Box
sx={{
flexGrow: 1,
mt: `${TOPBAR_HEIGHT}px`,
mt: `calc(${TOPBAR_HEIGHT}px + var(--omx-safe-top))`,
overflowY: "auto",
}}
>
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/admin/AdminTopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function AdminTopBar({ user, sidebarOpen, onToggleSidebar }: AdminTopBarP
borderBottom: "1px solid",
borderColor: "divider",
color: "text.primary",
pt: "var(--omx-safe-top)",
width: { md: sidebarOpen ? `calc(100% - ${SIDEBAR_WIDTH}px)` : "100%" },
ml: { md: sidebarOpen ? `${SIDEBAR_WIDTH}px` : 0 },
transition:
Expand Down
6 changes: 4 additions & 2 deletions apps/web/src/components/admin/ImpersonationBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ export function ImpersonationBanner() {
left: 0,
right: 0,
zIndex: 9999,
height: 48,
boxSizing: "border-box",
height: "calc(48px + var(--omx-safe-top))",
pt: "calc(6px + var(--omx-safe-top))",
"& .MuiAlert-message": {
display: "flex",
alignItems: "center",
Expand All @@ -61,7 +63,7 @@ export function ImpersonationBanner() {
Impersonating <strong>{session.user.name}</strong> ({session.user.email})
</Alert>
{/* Spacer to push page content below the fixed banner */}
<div style={{ height: 48 }} />
<div style={{ height: "calc(48px + var(--omx-safe-top))" }} />
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useEffect, useRef, useState } from "react";
import { AttributionStrip } from "@/components/ui/AttributionStrip";
import { AttributionText } from "@/components/ui/AttributionText";
import { useMap } from "@/integration-api/map/MapContext";
import { SEARCH_BAR_CLEARANCE } from "@/lib/layout";

/** Capture month and year in the active locale, or null when unknown. */
export function formatCaptureDate(capturedAt: string | undefined, locale: string): string | null {
Expand Down Expand Up @@ -98,14 +99,20 @@ export function StreetLevelInfoCard({
<Box
sx={{
position: "absolute",
top: 80,
left: 12,
// Below the viewer's search bar, which already clears the status bar.
top: SEARCH_BAR_CLEARANCE,
left: "calc(12px + var(--omx-safe-left))",
zIndex: 10,
bgcolor: "rgba(30,30,30,0.9)",
borderRadius: "12px",
p: "12px 14px",
minWidth: 260,
maxWidth: 340,
// On a phone the search bar spans the width, so the viewer's close
// button sits beside this card and needs its room.
maxWidth: {
xs: "calc(100% - 92px - var(--omx-safe-left) - var(--omx-safe-right))",
sm: 340,
},
color: "#fff",
backdropFilter: "blur(4px)",
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { LocationMinimap } from "@/integration-api/components/LocationMinimap";
import { useStreetLevelProviders } from "@/integration-api/components/useStreetLevelProviders";
import { useMap } from "@/integration-api/map/MapContext";
import { useEnv } from "@/integration-api/runtime/EnvProvider";
import { SEARCH_BAR_CLEARANCE } from "@/lib/layout";
import { StreetLevelFlatImage } from "./StreetLevelFlatImage";
import { StreetLevelInfoCard } from "./StreetLevelInfoCard";
import { fetchStreetLevelNode, pickPanoramaUrl, type StreetLevelNode } from "./useStreetLevelNode";
Expand Down Expand Up @@ -300,8 +301,8 @@ export default function StreetLevelViewerInner() {
<Box
sx={{
position: "absolute",
right: 12,
bottom: 28,
right: "calc(12px + var(--omx-safe-right))",
bottom: "calc(28px + var(--omx-safe-bottom))",
width: { xs: 132, sm: 196 },
zIndex: 10,
}}
Expand Down Expand Up @@ -349,8 +350,10 @@ export default function StreetLevelViewerInner() {
aria-label={tc("close")}
sx={{
position: "absolute",
top: 8,
right: 8,
// On a phone the search bar spans the top, so the button goes below
// it; beside it on wider screens. Both clear the status bar.
top: { xs: SEARCH_BAR_CLEARANCE, sm: "calc(8px + var(--omx-safe-top))" },
right: "calc(8px + var(--omx-safe-right))",
bgcolor: "rgba(0,0,0,0.5)",
color: "#fff",
borderRadius: "50%",
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/components/panels/place/PlacePhotoGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { MapCredits } from "@/components/map/MapCredits";
import { LocationMinimap } from "@/integration-api/components/LocationMinimap";
import { useMap } from "@/integration-api/map/MapContext";
import { useDateTimeFormat } from "@/integration-api/runtime/useDateTimeFormat";
import { SAFE_AREA_PADDING } from "@/lib/layout";
import { useBackDismiss } from "@/lib/useBackDismiss";
import { PhotoAttribution } from "./PhotoAttribution";

Expand Down Expand Up @@ -99,6 +100,8 @@ function PlacePhotoGalleryInner({ open, onClose, placeName, placeId, lat, lng }:
display: "flex",
flexDirection: "column",
zIndex: 1400,
boxSizing: "border-box",
...SAFE_AREA_PADDING,
}}
>
{/* Top bar */}
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/components/panels/sheet/detents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export interface SnapSlot {
detent: "top" | Detent;
}

const MAX_HEIGHT = "94dvh";
// Never under the status bar, whose height `dvh` does not know about when the
// page is drawn beneath it.
const MAX_HEIGHT = "min(94dvh, calc(100dvh - var(--omx-safe-top) - 8px))";

export const PLACE_DETENTS: DetentConfig = {
peek: "180px",
Expand Down
12 changes: 4 additions & 8 deletions apps/web/src/integration-api/runtime/useFullScreenOnMobile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import type { SystemStyleObject } from "@mui/system/styleFunctionSx";
* slotProps={{ paper: { sx: mobileFullScreenDialogPaperSx } }}
* />
*
* The shared paper sx squares off the corners on mobile, keeps the content
* clear of the system bars, and vertically centers DialogContent when its children don't fill the viewport (using
* `safe center` so long, scrollable content still starts at the top).
* The shared paper sx squares off the corners on mobile and vertically centers
* DialogContent when its children don't fill the viewport (using `safe center`
* so long, scrollable content still starts at the top). The theme keeps every
* full-screen dialog's content clear of the system bars.
*/
export function useFullScreenOnMobile(): boolean {
const theme = useTheme();
Expand All @@ -26,11 +27,6 @@ export function useFullScreenOnMobile(): boolean {

export const mobileFullScreenDialogPaperSx: SystemStyleObject<Theme> = {
borderRadius: { xs: 0, sm: "12px" },
// A full-screen dialog covers the status bar and home indicator wherever the
// page draws edge to edge (an installed PWA, the installed app), so its
// content keeps clear of the system bars there.
paddingTop: { xs: "var(--omx-safe-top)", sm: 0 },
paddingBottom: { xs: "var(--omx-safe-bottom)", sm: 0 },
"& .MuiDialogContent-root": {
display: { xs: "flex", sm: "block" },
flexDirection: "column",
Expand Down
13 changes: 13 additions & 0 deletions apps/web/src/lib/layout.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
/** Shared layout constants for the sidebar panels. */

/**
* Padding that keeps a full-screen surface's content out of the system bars
* and display cutouts. The installed app and an installed web app draw the
* page under them (`viewport-fit=cover`); in a plain browser tab the insets
* are 0, so this changes nothing there.
*/
export const SAFE_AREA_PADDING = {
pt: "var(--omx-safe-top)",
pb: "var(--omx-safe-bottom)",
pl: "var(--omx-safe-left)",
pr: "var(--omx-safe-right)",
} as const;
export const PANEL_WIDTH = 400;

/** Offset of the floating search bar from the top and left edges of the map. */
Expand Down
Loading