mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(docs): stop the pinned sidebar running under the site footer (#6422)
The sidebar and its divider are fixed to the viewport, so at the end of the page the footer was drawn over them and the lower part of the nav list became unreachable. FooterOverlapProbe publishes how far the footer reaches into the viewport as `--docs-footer-overlap`. The sidebar reads it as `bottom`, so it keeps its full height and slides up out of view as the footer arrives; the divider reads it too but is shortened rather than slid, so it terminates on the footer's top border instead of stopping short. Measured against the viewport rather than the document on purpose: the value is a constant 0 while the footer is off screen, so a content-height change higher up the page cannot move the sidebar. Verified with Playwright at 1280x800 and 2000x1100 — expanding/collapsing an FAQ with the footer off screen moves the sidebar 0px/0px and leaves the content column unchanged, and at the page bottom the sidebar's bottom edge lands within ~1px of the footer's top.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
const OVERLAP_PROPERTY = '--docs-footer-overlap'
|
||||
|
||||
/**
|
||||
* Publishes how many pixels of the viewport bottom the footer currently covers.
|
||||
*
|
||||
* The docs sidebar and its divider are pinned to the viewport, so on their own
|
||||
* they would run underneath the footer at the end of the page. Both read this as
|
||||
* their `bottom` and stop at the footer's top edge instead — the sidebar slides
|
||||
* away with the page and the divider meets the footer's border.
|
||||
*
|
||||
* It is deliberately measured against the viewport rather than the document, so
|
||||
* the value only moves while the footer is actually on screen — a content-height
|
||||
* change higher up the page cannot disturb the sidebar at all. That was the
|
||||
* regression #6301 fixed and this must not undo.
|
||||
*/
|
||||
export function FooterOverlapProbe() {
|
||||
const sentinelRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const sentinel = sentinelRef.current
|
||||
if (!sentinel) return
|
||||
|
||||
const root = document.documentElement
|
||||
let frame = 0
|
||||
let published = -1
|
||||
|
||||
const measure = () => {
|
||||
frame = 0
|
||||
const overlap = Math.max(
|
||||
0,
|
||||
Math.round(window.innerHeight - sentinel.getBoundingClientRect().top)
|
||||
)
|
||||
if (overlap === published) return
|
||||
published = overlap
|
||||
root.style.setProperty(OVERLAP_PROPERTY, `${overlap}px`)
|
||||
}
|
||||
|
||||
const schedule = () => {
|
||||
if (frame) return
|
||||
frame = requestAnimationFrame(measure)
|
||||
}
|
||||
|
||||
measure()
|
||||
window.addEventListener('scroll', schedule, { passive: true })
|
||||
window.addEventListener('resize', schedule)
|
||||
|
||||
const observer = new ResizeObserver(schedule)
|
||||
observer.observe(document.body)
|
||||
|
||||
return () => {
|
||||
if (frame) cancelAnimationFrame(frame)
|
||||
window.removeEventListener('scroll', schedule)
|
||||
window.removeEventListener('resize', schedule)
|
||||
observer.disconnect()
|
||||
root.style.removeProperty(OVERLAP_PROPERTY)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div ref={sentinelRef} aria-hidden className='pointer-events-none absolute inset-x-0 top-0' />
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import Link from 'next/link'
|
||||
import { FooterOverlapProbe } from '@/components/footer/footer-overlap'
|
||||
import { SimWordmark } from '@/components/ui/sim-logo'
|
||||
import { SIM_SITE_URL } from '@/lib/urls'
|
||||
|
||||
@@ -133,13 +134,17 @@ function FooterColumn({ title, items }: { title: string; items: FooterItem[] })
|
||||
/**
|
||||
* Site footer.
|
||||
*
|
||||
* `relative z-[22]` stacks it above the docs sidebar (z-20) and that sidebar's
|
||||
* divider (z-21), both of which are pinned to the viewport, so the footer slides
|
||||
* over them at the end of the page instead of being drawn through.
|
||||
* The docs sidebar and its divider are pinned to the viewport, so they would run
|
||||
* underneath a full-bleed footer at the end of the page. `FooterOverlapProbe`
|
||||
* publishes how far the footer reaches into the viewport and both stop there
|
||||
* instead. `relative z-[22]` stacks the footer above the sidebar (z-20) and its
|
||||
* divider (z-21) so that, before the probe's first measurement, the footer covers
|
||||
* them rather than being drawn through.
|
||||
*/
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className='relative z-[22] mt-[120px] w-full border-[var(--border)] border-t bg-[var(--bg)] max-sm:mt-16 max-lg:mt-[88px]'>
|
||||
<FooterOverlapProbe />
|
||||
<div className='mx-auto w-full max-w-[1460px] px-20 pt-16 pb-16 max-sm:px-5 max-lg:px-8 max-lg:pt-12 max-lg:pb-12'>
|
||||
<nav
|
||||
aria-label='Footer navigation'
|
||||
|
||||
Reference in New Issue
Block a user