Skjul flytende "Registrer score" når spillerlisten er nådd/passert
Ny playerListReached-tilstand skjuler den flytende knappen (og dens spacer) fra #140 så snart spillerlisten når headerens høyde -- forblir skjult resten av veien nedover, dukker opp igjen kun ved scroll tilbake opp forbi listen. Første forsøk med IntersectionObserver+rootMargin var feil (fyrer ikke på interne terskler mens allerede overlappende) -- byttet til en scroll/resize-lytter med direkte getBoundingClientRect-sjekk. Se CHANGELOG.md #141. Ikke rullet ut ennå. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
200d00cf5f
commit
76b306cb29
2 changed files with 101 additions and 13 deletions
37
CHANGELOG.md
37
CHANGELOG.md
|
|
@ -13547,3 +13547,40 @@ Neste steg:
|
||||||
**Rullet ut 2026-08-20** -- bruker bekreftet, samlet med #139. Ren
|
**Rullet ut 2026-08-20** -- bruker bekreftet, samlet med #139. Ren
|
||||||
frontend. `docker compose build teecup_frontend && up -d`, rene
|
frontend. `docker compose build teecup_frontend && up -d`, rene
|
||||||
logger, 200 OK.
|
logger, 200 OK.
|
||||||
|
|
||||||
|
141. **Flytende "Registrer score" hang igjen synlig etter at spillerlisten
|
||||||
|
var nådd, 2026-08-20.** Direkte oppfølging av #140 -- bruker viste
|
||||||
|
skjermbilde: selv etter å ha scrollet forbi spillerkortene (helt ned
|
||||||
|
til kommentarfeltet), sto den flytende "Registrer score ↓"-knappen
|
||||||
|
fortsatt og fløt over bunnen av skjermen, uten mening siden
|
||||||
|
spillerlisten allerede var passert (pilen peker "ned til spillerne",
|
||||||
|
men spillerne var nå OVER, ikke under). Bruker spurte om dette burde
|
||||||
|
V0-promptes -- Claude vurderte det til å være ren VIS/SKJUL-LOGIKK
|
||||||
|
rundt et allerede ferdig designet element (fra #140), ikke en ny
|
||||||
|
visuell flate, og løste det direkte.
|
||||||
|
|
||||||
|
`round-detail.tsx`: ny `playerListReached`-tilstand, sant når
|
||||||
|
spillerlistens toppkant har nådd/passert headerens høyde (112px,
|
||||||
|
samme referanse som `scroll-mt-28`). Knappen (og spaceren under den,
|
||||||
|
fra #140) vises nå kun når `mapExpanded && !playerListReached`.
|
||||||
|
|
||||||
|
**Feilslått første forsøk, funnet i scratch:** brukte først
|
||||||
|
`IntersectionObserver` med en krympet `rootMargin` for å fange
|
||||||
|
112px-krysningen. Viste seg feil -- med standard `threshold: [0]`
|
||||||
|
fyrer observatøren KUN ved 0%->0%-kryssinger (elementet helt inn/ut av
|
||||||
|
roten), ikke ved at `boundingClientRect.top` passerer en vilkårlig
|
||||||
|
linje MENS elementet allerede overlapper roten. Spillerlisten ble
|
||||||
|
dermed hengende fast på "ikke nådd" gjennom resten av scrollingen,
|
||||||
|
identisk med bugen selv om koden så riktig ut. Erstattet med en vanlig
|
||||||
|
`scroll`/`resize`-lytter (passive) som leser
|
||||||
|
`getBoundingClientRect().top` direkte -- korrekt, kontinuerlig
|
||||||
|
oppdatering.
|
||||||
|
|
||||||
|
**Verifisert i scratch** (samme mock-oppsett som #139/#140): utvidet
|
||||||
|
banekartet, trykket flytende "Registrer score" -- knappen forsvant nå
|
||||||
|
korrekt idet spillerkort 1 landet synlig, ingen tomt mellomrom stod
|
||||||
|
igjen. Scrollet helt ned til kommentarfeltet -- knappen forble skjult
|
||||||
|
(ikke lenger den opprinnelige bugen). Scrollet tilbake opp forbi
|
||||||
|
spillerlisten -- knappen dukket korrekt opp igjen. `tsc --noEmit` rent.
|
||||||
|
|
||||||
|
**Ikke rullet ut ennå** -- venter på bekreftelse fra bruker.
|
||||||
|
|
|
||||||
|
|
@ -6719,6 +6719,43 @@ function PlayerHoleCards({
|
||||||
const [mapExpanded, setMapExpanded] = useState(false)
|
const [mapExpanded, setMapExpanded] = useState(false)
|
||||||
const [distanceAvailable, setDistanceAvailable] = useState(false)
|
const [distanceAvailable, setDistanceAvailable] = useState(false)
|
||||||
const playerListRef = useRef<HTMLUListElement>(null)
|
const playerListRef = useRef<HTMLUListElement>(null)
|
||||||
|
// Flytende "Registrer score" (2026-08-20) skal kun vises mens
|
||||||
|
// spillerlisten fortsatt er UTENFOR synlig område -- brukerfunn: den
|
||||||
|
// hang igjen synlig (og dekket delvis over spillerkort 1) selv etter at
|
||||||
|
// brukeren allerede hadde scrollet forbi/til spillerlisten, der den ikke
|
||||||
|
// lenger gir mening ("Registrer score ↓" når du allerede ser
|
||||||
|
// spillerne). "Reached" = listens toppkant har nådd/passert headerens
|
||||||
|
// faktiske høyde (samme 112px som scroll-mt-28 andre steder i denne
|
||||||
|
// filen) -- forblir sant BÅDE mens listen vises OG etter at brukeren
|
||||||
|
// har scrollet helt forbi den (listen ligger da over synlig område,
|
||||||
|
// `top` er negativ). Kun scroll TILBAKE OPP forbi listen (top blir
|
||||||
|
// igjen større enn 112) gjør at knappen skal dukke opp igjen.
|
||||||
|
//
|
||||||
|
// Første forsøk brukte IntersectionObserver med en krympet rootMargin
|
||||||
|
// -- viste seg FEIL i scratch-verifisering: med standard threshold=[0]
|
||||||
|
// fyrer den kun ved 0%->>0%-kryssinger (element helt inn/ut av roten),
|
||||||
|
// ikke ved at `boundingClientRect.top` passerer en INTERN linje midt i
|
||||||
|
// et allerede-overlappende element. Listen ble dermed hengende på
|
||||||
|
// "ikke nådd" gjennom hele resten av scrollingen forbi 112px-grensen.
|
||||||
|
// Vanlig scroll-lytter med direkte `getBoundingClientRect()`-sjekk gir
|
||||||
|
// korrekt, kontinuerlig oppdatering.
|
||||||
|
const [playerListReached, setPlayerListReached] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mapExpanded) return
|
||||||
|
const HEADER_HEIGHT_PX = 112
|
||||||
|
function check() {
|
||||||
|
const el = playerListRef.current
|
||||||
|
if (!el) return
|
||||||
|
setPlayerListReached(el.getBoundingClientRect().top <= HEADER_HEIGHT_PX)
|
||||||
|
}
|
||||||
|
check()
|
||||||
|
window.addEventListener("scroll", check, { passive: true })
|
||||||
|
window.addEventListener("resize", check)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("scroll", check)
|
||||||
|
window.removeEventListener("resize", check)
|
||||||
|
}
|
||||||
|
}, [mapExpanded])
|
||||||
const holeMeta: Record<number, { par: number; index: number }> = {}
|
const holeMeta: Record<number, { par: number; index: number }> = {}
|
||||||
for (const list of Object.values(holesByParticipant)) {
|
for (const list of Object.values(holesByParticipant)) {
|
||||||
for (const h of list) {
|
for (const h of list) {
|
||||||
|
|
@ -7102,23 +7139,34 @@ function PlayerHoleCards({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Flytende "Registrer score" (V0-eksport, 2026-08-20) -- kun mens
|
{/* Flytende "Registrer score" (V0-eksport, 2026-08-20) -- mens
|
||||||
banekartet er utvidet. `fixed` posisjonerer mot selve viewporten
|
banekartet er utvidet OG spillerlisten fortsatt er utenfor synlig
|
||||||
(ingen ancestor her setter transform/filter, så det er trygt),
|
område (se `playerListReached` over -- uten denne betingelsen hang
|
||||||
gradient-scrim + skygge løfter den visuelt over innholdet under,
|
knappen igjen synlig, og dekket delvis over spillerkort 1, selv
|
||||||
`env(safe-area-inset-bottom)` unngår at den klemmes helt inntil
|
etter at brukeren allerede hadde scrollet dit). `fixed`
|
||||||
kanten på telefoner med gest-navigasjon. `pointer-events-none` på
|
posisjonerer mot selve viewporten (ingen ancestor her setter
|
||||||
selve scrim-sonen når skjult/i overgang, slik at den aldri
|
transform/filter, så det er trygt), gradient-scrim + skygge
|
||||||
blokkerer klikk på innhold bak. */}
|
løfter den visuelt over innholdet under, `env(safe-area-inset-
|
||||||
|
bottom)` unngår at den klemmes helt inntil kanten på telefoner
|
||||||
|
med gest-navigasjon. `pointer-events-none` på selve scrim-sonen
|
||||||
|
når skjult/i overgang, slik at den aldri blokkerer klikk på
|
||||||
|
innhold bak. */}
|
||||||
<div
|
<div
|
||||||
aria-hidden={!mapExpanded}
|
aria-hidden={!mapExpanded || playerListReached}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-x-0 bottom-0 z-30 px-5 pb-[calc(0.875rem+env(safe-area-inset-bottom))] pt-8 transition-all duration-300 ease-out",
|
"fixed inset-x-0 bottom-0 z-30 px-5 pb-[calc(0.875rem+env(safe-area-inset-bottom))] pt-8 transition-all duration-300 ease-out",
|
||||||
"bg-gradient-to-t from-background via-background/90 to-transparent",
|
"bg-gradient-to-t from-background via-background/90 to-transparent",
|
||||||
mapExpanded ? "translate-y-0 opacity-100" : "pointer-events-none translate-y-full opacity-0",
|
mapExpanded && !playerListReached
|
||||||
|
? "translate-y-0 opacity-100"
|
||||||
|
: "pointer-events-none translate-y-full opacity-0",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={cn("mx-auto w-full max-w-3xl", mapExpanded ? "pointer-events-auto" : "pointer-events-none")}>
|
<div
|
||||||
|
className={cn(
|
||||||
|
"mx-auto w-full max-w-3xl",
|
||||||
|
mapExpanded && !playerListReached ? "pointer-events-auto" : "pointer-events-none",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="rounded-2xl shadow-xl shadow-black/25">
|
<div className="rounded-2xl shadow-xl shadow-black/25">
|
||||||
<RegisterScoreButton
|
<RegisterScoreButton
|
||||||
onClick={() => playerListRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })}
|
onClick={() => playerListRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })}
|
||||||
|
|
@ -7127,8 +7175,11 @@ function PlayerHoleCards({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Gir siste innhold på siden rom til å scrolle klar av den flytende
|
{/* Gir siste innhold på siden rom til å scrolle klar av den flytende
|
||||||
knappen i stedet for å bli permanent skjult bak den. */}
|
knappen i stedet for å bli permanent skjult bak den -- samme
|
||||||
{mapExpanded && <div aria-hidden="true" className="h-20" />}
|
betingelse som selve knappen (2026-08-20-rettelsen), ellers ble
|
||||||
|
det stående et tomt, uforklart mellomrom selv etter at knappen
|
||||||
|
alt var skjult. */}
|
||||||
|
{mapExpanded && !playerListReached && <div aria-hidden="true" className="h-20" />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue