Skip to content

Commit fda2b25

Browse files
committed
Update status banner height
1 parent 2f26d3f commit fda2b25

3 files changed

Lines changed: 42 additions & 17 deletions

File tree

src/components/StatusBanner/StatusBannerBar.tsx

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,46 @@
11
"use client";
22

3+
import { useLayoutEffect, useRef } from "react";
34
import StatusBannerClient from "./StatusBannerClient";
45
import { useStatusBanner } from "./StatusBannerProvider";
56

67
/**
7-
* Header slot: renders the full-width status strip once the async fetch in
8-
* StatusBannerProvider resolves, sliding it in from the top. Renders nothing
9-
* while there's no active banner, so it never blocks the initial paint.
8+
* Renders the full-width status strip once the async fetch in StatusBannerProvider resolves.
109
*/
1110
export default function StatusBannerBar() {
12-
const { banner, href, mounted, shown } = useStatusBanner();
11+
const { banner, href, mounted, shown, setContentHeight } = useStatusBanner();
12+
const contentRef = useRef<HTMLDivElement>(null);
13+
14+
useLayoutEffect(() => {
15+
const el = contentRef.current;
16+
if (!el) {
17+
return;
18+
}
19+
const update = () =>
20+
setContentHeight(Math.ceil(el.getBoundingClientRect().height));
21+
update();
22+
const observer = new ResizeObserver(update);
23+
observer.observe(el);
24+
return () => observer.disconnect();
25+
}, [banner, shown, setContentHeight]);
26+
1327
if (!mounted || !banner) {
1428
return null;
1529
}
1630
return (
1731
<div
18-
className={`overflow-hidden transition-[height] duration-300 ease-out ${
19-
shown ? "h-11" : "h-0"
20-
}`}
32+
className="grid transition-[grid-template-rows] duration-300 ease-out"
33+
style={{ gridTemplateRows: shown ? "1fr" : "0fr" }}
2134
>
22-
<div
23-
className={`transition-all duration-300 ease-out ${
24-
shown ? "translate-y-0 opacity-100" : "-translate-y-1 opacity-0"
25-
}`}
26-
>
27-
<StatusBannerClient banner={banner} href={href} variant="bar" />
35+
<div className="min-h-0 overflow-hidden">
36+
<div
37+
ref={contentRef}
38+
className={`transition-opacity duration-300 ease-out ${
39+
shown ? "opacity-100" : "opacity-0"
40+
}`}
41+
>
42+
<StatusBannerClient banner={banner} href={href} variant="bar" />
43+
</div>
2844
</div>
2945
</div>
3046
);

src/components/StatusBanner/StatusBannerProvider.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,25 @@ import type { StatusBanner } from "@/lib/status-banner";
1313
// - `banner`/`href`: the resolved banner (null when there's nothing to show).
1414
// - `mounted`: keep the DOM present through the slide-out, not just while active.
1515
// - `shown`: the transition target — false = collapsed (height 0), true = open.
16+
// - `contentHeight`/`setContentHeight`: (estimated) height of the banner content.
1617
export type StatusBannerContextValue = {
1718
banner: StatusBanner | null;
1819
href: string | null;
1920
mounted: boolean;
2021
shown: boolean;
22+
contentHeight: number;
23+
setContentHeight: (height: number) => void;
2124
};
2225

26+
const DEFAULT_CONTENT_HEIGHT = 44;
27+
2328
const StatusBannerContext = createContext<StatusBannerContextValue>({
2429
banner: null,
2530
href: null,
2631
mounted: false,
2732
shown: false,
33+
contentHeight: DEFAULT_CONTENT_HEIGHT,
34+
setContentHeight: () => {},
2835
});
2936

3037
export function useStatusBanner(): StatusBannerContextValue {
@@ -58,6 +65,7 @@ export default function StatusBannerProvider({
5865
} | null>(null);
5966
const [mounted, setMounted] = useState(false);
6067
const [shown, setShown] = useState(false);
68+
const [contentHeight, setContentHeight] = useState(DEFAULT_CONTENT_HEIGHT);
6169

6270
useEffect(() => {
6371
let cancelled = false;
@@ -114,6 +122,8 @@ export default function StatusBannerProvider({
114122
href: data?.href ?? null,
115123
mounted,
116124
shown,
125+
contentHeight,
126+
setContentHeight,
117127
};
118128

119129
return (

src/components/StatusBanner/StatusBannerSpacer.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,15 @@ import { useStatusBanner } from "./StatusBannerProvider";
99
* duration) so content slides down smoothly instead of jumping.
1010
*/
1111
export default function StatusBannerSpacer() {
12-
const { mounted, shown } = useStatusBanner();
12+
const { mounted, shown, contentHeight } = useStatusBanner();
1313
if (!mounted) {
1414
return null;
1515
}
1616
return (
1717
<div
1818
aria-hidden
19-
className={`transition-[height] duration-300 ease-out ${
20-
shown ? "h-11" : "h-0"
21-
}`}
19+
className="transition-[height] duration-300 ease-out"
20+
style={{ height: shown ? contentHeight : 0 }}
2221
/>
2322
);
2423
}

0 commit comments

Comments
 (0)