Centre the offline banner over the content column
Docs / docs (push) Canceled after 0s
Playwright Tests / test-playwright (1, 2) (push) Canceled after 0s
Playwright Tests / test-playwright (2, 2) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Test Backend / test-backend (push) Canceled after 0s
Compose Smoke Test / test-compose (push) Canceled after 0s
Playwright Tests / merge-reports (push) Canceled after 0s
Docs / docs (push) Canceled after 0s
Playwright Tests / test-playwright (1, 2) (push) Canceled after 0s
Playwright Tests / test-playwright (2, 2) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Test Backend / test-backend (push) Canceled after 0s
Compose Smoke Test / test-compose (push) Canceled after 0s
Playwright Tests / merge-reports (push) Canceled after 0s
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { WifiOff } from "lucide-react"
|
||||
import { AnimatePresence, motion } from "motion/react"
|
||||
import { useSyncExternalStore } from "react"
|
||||
import { createPortal } from "react-dom"
|
||||
|
||||
import { ago } from "@/components/Health/queries"
|
||||
import { connectionStore } from "@/lib/connectionStore"
|
||||
@@ -17,10 +16,11 @@ import { isPortal } from "@/lib/portal"
|
||||
* stale readings with a timestamp are more use than an empty page, which is
|
||||
* why the banner leads with when we last heard anything.
|
||||
*
|
||||
* Drawn on `document.body` rather than where it is mounted. `position: fixed`
|
||||
* resolves against the nearest ancestor carrying a transform, a filter or a
|
||||
* `backdrop-filter`, and the shell floats several of those — inside them the
|
||||
* banner centres on the content column instead of the viewport.
|
||||
* Centred over the content column rather than the window: it belongs to what
|
||||
* is being read, not to the shell around it. That is what `sticky` inside the
|
||||
* content column buys — no sidebar width to track and nothing to recompute
|
||||
* when the sidebar collapses — and `h-0` keeps it out of the flow, so nothing
|
||||
* below it moves when the banner appears.
|
||||
*/
|
||||
export function ConnectionBanner() {
|
||||
const connection = useSyncExternalStore(
|
||||
@@ -30,19 +30,19 @@ export function ConnectionBanner() {
|
||||
)
|
||||
if (!isPortal()) return null
|
||||
|
||||
return createPortal(
|
||||
<AnimatePresence>
|
||||
{connection.offline && (
|
||||
<motion.div
|
||||
variants={fadeIn}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="hidden"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className="pointer-events-none fixed inset-x-0 top-4 z-50 flex justify-center px-4"
|
||||
>
|
||||
<div className="flex items-center gap-2 rounded-full border border-border bg-card/80 px-4 py-2 text-sm shadow-e2 backdrop-blur-md">
|
||||
return (
|
||||
<div className="pointer-events-none sticky top-4 z-50 flex h-0 justify-center px-4">
|
||||
<AnimatePresence>
|
||||
{connection.offline && (
|
||||
<motion.div
|
||||
variants={fadeIn}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="hidden"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className="flex items-center gap-2 rounded-full border border-border bg-card/80 px-4 py-2 text-sm shadow-e2 backdrop-blur-md"
|
||||
>
|
||||
<WifiOff className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="font-medium">Installation offline</span>
|
||||
<span className="text-muted-foreground">
|
||||
@@ -50,10 +50,9 @@ export function ConnectionBanner() {
|
||||
? `last seen ${ago(connection.lastSeen / 1000)} — reconnecting…`
|
||||
: "reconnecting…"}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>,
|
||||
document.body,
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,10 +29,11 @@ function Layout() {
|
||||
|
||||
return (
|
||||
<SidebarProvider className="bg-card">
|
||||
{/* Renders nothing unless this page is served through a portal. */}
|
||||
<ConnectionBanner />
|
||||
<AppSidebar />
|
||||
<SidebarInset className="bg-card">
|
||||
{/* Renders nothing unless this page is served through a portal.
|
||||
Inside the inset so it centres over the content, not the window. */}
|
||||
<ConnectionBanner />
|
||||
{/* The sidebar carries its own collapse control; a phone has no
|
||||
sidebar on screen to carry it. */}
|
||||
<header className="sticky top-0 z-10 flex h-16 shrink-0 items-center gap-2 px-4 md:hidden">
|
||||
|
||||
Reference in New Issue
Block a user