Skip to content

fix: hide feedback button when it overlaps with navbar#735

Open
an1ndra wants to merge 4 commits intoTanStack:mainfrom
an1ndra:add_button_fix
Open

fix: hide feedback button when it overlaps with navbar#735
an1ndra wants to merge 4 commits intoTanStack:mainfrom
an1ndra:add_button_fix

Conversation

@an1ndra
Copy link
Copy Markdown
Contributor

@an1ndra an1ndra commented Feb 26, 2026

Fix: #730

Before:
Screenshot from 2026-02-27 02-11-59

After:
Screenshot from 2026-02-27 02-10-07

Summary by CodeRabbit

  • Bug Fixes
    • Improved floating block button visibility: it now updates during scroll and resize and is hidden when it would overlap the navigation bar, preventing awkward overlap or flicker.

@netlify
Copy link
Copy Markdown

netlify Bot commented Feb 26, 2026

Deploy Preview for tanstack ready!

Name Link
🔨 Latest commit 27c51ca
🔍 Latest deploy log https://app.netlify.com/projects/tanstack/deploys/69ed1a9e9b9eea0008cc9a4a
😎 Deploy Preview https://deploy-preview-735--tanstack.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 29 (🔴 down 33 from production)
Accessibility: 90 (no change from production)
Best Practices: 83 (🔴 down 9 from production)
SEO: 97 (no change from production)
PWA: 70 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 12, 2026

⚠️ No Changeset found

Latest commit: d5e7b82

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

The BlockButton in DocFeedbackProvider.tsx now tracks window.scrollY (and on resize) to force re-renders, reads --navbar-height, measures the block’s viewport position, and hides the floating button when its computed top would overlap the navbar; scrollY is referenced with void to silence unused-state warnings.

Changes

Cohort / File(s) Summary
Button Visibility & Scroll Tracking
src/components/DocFeedbackProvider.tsx
Added scrollY state updated on scroll and resize. During render, synchronously reads --navbar-height, measures target block bounding rect, computes floating-button top (fixed ~24px offset), and returns null to hide the button if it would overlap the navbar. Also references scrollY via void scrollY to avoid unused-state lint warnings. Includes existing gating (mounted, not in editor/note portals, hover/menu-open checks).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
A little plus hid under the bar,
I tracked the scroll near and far,
Measured the block and peeked at the height,
Now the button stays hidden when tucked out of sight,
Hooray — no more overlap in the night! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: hiding the feedback button when it overlaps with the navbar, which directly addresses the primary objective.
Linked Issues check ✅ Passed The code changes implement collision detection between the feedback button and navbar, hiding the button when overlap would occur, which directly addresses issue #730's requirement.
Out of Scope Changes check ✅ Passed All changes are focused on the feedback button visibility logic and navbar overlap detection, staying within the scope of fixing the navbar overlap issue #730.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/components/DocFeedbackProvider.tsx (3)

415-416: Remove void scrollY - the variable is already used in the dependency array implicitly.

The void scrollY statement is a workaround to suppress an unused variable warning, but ESLint and TypeScript typically don't flag state variables as unused when they trigger re-renders. If a linter is flagging this, consider adding a comment explaining the purpose instead.

♻️ Replace with explanatory comment
-  // Suppress unused state warning - scrollY triggers re-renders
-  void scrollY
+  // Note: scrollY state is intentionally "unused" - its updates trigger re-renders
+  // so that getBoundingClientRect() returns fresh values during scroll
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DocFeedbackProvider.tsx` around lines 415 - 416, Remove the
unnecessary "void scrollY" statement in DocFeedbackProvider (the scrollY
state/variable), and instead add a brief explanatory comment near where scrollY
is declared/used that explains it is intentionally included only to trigger
re-renders (and therefore appears in effect dependency arrays); ensure any
useEffect or hook that relies on scrollY includes it in its dependency array so
linters understand the variable is intentionally used.

405-408: Hardcoded button height may become stale if styling changes.

The buttonHeight = 24 assumes w-6 h-6 Tailwind classes (6 × 4px = 24px). If DocFeedbackFloatingButton styling changes, this will silently break the overlap calculation.

♻️ Consider extracting as a shared constant or measuring dynamically

A shared constant near DocFeedbackFloatingButton would be more maintainable:

// In DocFeedbackFloatingButton.tsx or a shared constants file
export const FEEDBACK_BUTTON_HEIGHT = 24

// Then import and use here
import { FEEDBACK_BUTTON_HEIGHT } from './DocFeedbackFloatingButton'
// ...
const buttonTop = blockRect.top - FEEDBACK_BUTTON_HEIGHT
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DocFeedbackProvider.tsx` around lines 405 - 408, The hardcoded
buttonHeight (24) used to compute buttonTop can drift if
DocFeedbackFloatingButton's styling changes; replace the literal with a shared
exported constant (e.g., export FEEDBACK_BUTTON_HEIGHT from
DocFeedbackFloatingButton or a shared constants file) and import it here to
compute buttonTop (replace usage of buttonHeight with the imported
FEEDBACK_BUTTON_HEIGHT), or alternatively measure the actual element height at
runtime via getBoundingClientRect on the DocFeedbackFloatingButton node and use
that value to compute buttonTop so the overlap stays correct.

358-377: Consider throttling scroll handler to reduce re-render frequency.

Scroll events fire at high frequency (potentially 60+ times per second during active scrolling). Each setScrollY call triggers a re-render that runs getBoundingClientRect() and getComputedStyle() DOM queries. While { passive: true } prevents blocking the main thread, the render volume could still cause jank on lower-powered devices.

♻️ Suggested throttle implementation
   // Track scroll position to trigger re-renders for navbar overlap check
   React.useEffect(() => {
     if (!mounted) return
 
+    let rafId: number | null = null
+
     const handleScroll = () => {
-      setScrollY(window.scrollY)
+      if (rafId === null) {
+        rafId = requestAnimationFrame(() => {
+          setScrollY(window.scrollY)
+          rafId = null
+        })
+      }
     }
 
-    const handleResize = () => {
-      setScrollY(window.scrollY)
-    }
-
     window.addEventListener('scroll', handleScroll, { passive: true })
-    window.addEventListener('resize', handleResize)
+    window.addEventListener('resize', handleScroll)
 
     return () => {
       window.removeEventListener('scroll', handleScroll)
-      window.removeEventListener('resize', handleResize)
+      window.removeEventListener('resize', handleScroll)
+      if (rafId !== null) {
+        cancelAnimationFrame(rafId)
+      }
     }
   }, [mounted])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DocFeedbackProvider.tsx` around lines 358 - 377, The scroll
handler (handleScroll) calls setScrollY on every event causing frequent
re-renders; inside the useEffect that depends on mounted, wrap/debounce the
handler with a throttle mechanism (preferably requestAnimationFrame or a small
lodash.throttle) so setScrollY is only invoked at animation frame rate or
limited frequency, apply the same throttling to handleResize, store any RAF id
or throttle cancel token in a ref, and ensure the cleanup function cancels the
RAF or calls the throttle cancel before removing the event listeners; keep
existing event options (e.g., { passive: true }) and reference the existing
symbols mounted, handleScroll, handleResize, setScrollY, and the useEffect
cleanup to implement this.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/DocFeedbackProvider.tsx`:
- Around line 415-416: Remove the unnecessary "void scrollY" statement in
DocFeedbackProvider (the scrollY state/variable), and instead add a brief
explanatory comment near where scrollY is declared/used that explains it is
intentionally included only to trigger re-renders (and therefore appears in
effect dependency arrays); ensure any useEffect or hook that relies on scrollY
includes it in its dependency array so linters understand the variable is
intentionally used.
- Around line 405-408: The hardcoded buttonHeight (24) used to compute buttonTop
can drift if DocFeedbackFloatingButton's styling changes; replace the literal
with a shared exported constant (e.g., export FEEDBACK_BUTTON_HEIGHT from
DocFeedbackFloatingButton or a shared constants file) and import it here to
compute buttonTop (replace usage of buttonHeight with the imported
FEEDBACK_BUTTON_HEIGHT), or alternatively measure the actual element height at
runtime via getBoundingClientRect on the DocFeedbackFloatingButton node and use
that value to compute buttonTop so the overlap stays correct.
- Around line 358-377: The scroll handler (handleScroll) calls setScrollY on
every event causing frequent re-renders; inside the useEffect that depends on
mounted, wrap/debounce the handler with a throttle mechanism (preferably
requestAnimationFrame or a small lodash.throttle) so setScrollY is only invoked
at animation frame rate or limited frequency, apply the same throttling to
handleResize, store any RAF id or throttle cancel token in a ref, and ensure the
cleanup function cancels the RAF or calls the throttle cancel before removing
the event listeners; keep existing event options (e.g., { passive: true }) and
reference the existing symbols mounted, handleScroll, handleResize, setScrollY,
and the useEffect cleanup to implement this.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 770047cf-68db-4e2a-a362-2d7049e1a0c2

📥 Commits

Reviewing files that changed from the base of the PR and between 202961c and 784a7b1.

📒 Files selected for processing (1)
  • src/components/DocFeedbackProvider.tsx

tannerlinsley pushed a commit that referenced this pull request Apr 4, 2026
Closes 8 items: automated spam (#775), contribution farming (#780),
incorrect Sentry bot fixes (#728, #724, #737, #722), unused-dep PR (#720),
and over-engineered scroll listener (#735). Each with a polite explanatory
comment.

https://claude.ai/code/session_012aRt7qrUbktsyuz85BwXJz
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/DocFeedbackProvider.tsx (1)

386-435: ⚠️ Potential issue | 🟠 Major

Per-block scroll listeners create unnecessary performance overhead—consider lifting to provider or evaluating the z-index alternative from #730.

Two actionable issues:

  1. Performance concern (major). Each BlockButton instance attaches its own scroll and resize listeners (lines 398–403), which on a typical docs page with dozens of blocks means dozens of listeners firing on every scroll tick. Each fires setScrollY, triggering a re-render that calls getComputedStyle(...) and block.getBoundingClientRect() synchronously—an O(N) cost per scroll frame.

    Fix: Lift the scroll/resize subscription to DocFeedbackProvider as a single listener broadcasting via context, or use requestAnimationFrame throttling so updates fire at most once per frame.

  2. Code smell: scrollY state as a re-render trigger. Lines 380–438 declare scrollY state purely to force re-renders when scroll changes, then suppress the unused variable warning with void scrollY. A cleaner pattern: use a counter (const [tick, setTick] = useState(0)) and call setTick(t => t+1) on scroll to explicitly signal intent for re-renders.

Minor: Line 429 hardcodes buttonHeight = 24 based on the Tailwind w-6 h-6 class, but the actual rendered DocFeedbackFloatingButton height is not enforced anywhere. Consider measuring via a ref or exporting a constant.

Note on alternatives: Both navbar and button carry z-[100]. A simpler CSS-only fix—lowering the button's z-index below the navbar (e.g., z-[60])—would let the navbar paint over it with zero runtime cost. Verify against issue #730's actual requirements to determine if this approach is sufficient.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DocFeedbackProvider.tsx` around lines 386 - 435, This
per-block effect in DocFeedbackProvider.tsx (the useEffect that adds 'scroll'
and 'resize' listeners and uses setScrollY/scrollY) creates many listeners and
costly synchronous layout checks; move the scroll/resize subscription out of the
per-block scope into the provider level (DocFeedbackProvider) and publish a
single scroll tick via context, or throttle updates with requestAnimationFrame
so updates happen at most once per frame; replace the scrollY state used purely
to force renders with a simple counter state (e.g., tick and setTick(t => t+1))
so intent is explicit, and when deciding button overlap use that single
tick-driven re-render to run getComputedStyle and block.getBoundingClientRect();
finally, stop hardcoding buttonHeight (currently buttonHeight = 24) by measuring
the floating button via a ref or exposing a constant used by
DocFeedbackFloatingButton so overlap calculation is accurate.
🧹 Nitpick comments (2)
src/components/DocFeedbackProvider.tsx (2)

427-435: Hardcoded buttonHeight = 24 can desync from the actual button.

The check assumes DocFeedbackFloatingButton renders at exactly 24px tall (w-6 h-6). If that component's size ever changes (padding, icon swap, hover scale, etc.), the overlap math silently goes wrong and the button will either flash through the navbar or hide too eagerly.

Prefer measuring the rendered portal container with a ref / getBoundingClientRect(), or at least extract a named constant near the DocFeedbackFloatingButton definition so the two stay coupled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DocFeedbackProvider.tsx` around lines 427 - 435, The hardcoded
buttonHeight (24) used to compute buttonTop in DocFeedbackProvider can drift
from the actual DocFeedbackFloatingButton size; replace the magic number by
measuring the rendered button container (e.g., use a ref on the portal/root
element for DocFeedbackFloatingButton and call getBoundingClientRect() to get
its height) and compute buttonTop from that measured height, or alternatively
export a shared constant from the DocFeedbackFloatingButton module and import it
into DocFeedbackProvider; ensure the navbar overlap check uses the
measuredHeight (or shared constant) instead of buttonHeight and keep the
existing navbarHeight and isMenuOpen logic intact.

380-380: Replace scrollY + void scrollY with a render-tick state to drop the lint workaround.

scrollY's value is never actually consumed (getBoundingClientRect() already reflects current scroll), it exists solely to trigger a re-render, hence the void scrollY escape hatch. A nameless tick state expresses that intent without needing a void reference:

♻️ Proposed change
-  const [scrollY, setScrollY] = React.useState(0)
+  const [, forceTick] = React.useState(0)
-    const handleScroll = () => {
-      setScrollY(window.scrollY)
-    }
-
-    const handleResize = () => {
-      setScrollY(window.scrollY)
-    }
+    const tick = () => forceTick((n) => n + 1)
+    window.addEventListener('scroll', tick, { passive: true })
+    window.addEventListener('resize', tick)
+
+    return () => {
+      window.removeEventListener('scroll', tick)
+      window.removeEventListener('resize', tick)
+    }
-  // Suppress unused state warning - scrollY triggers re-renders
-  void scrollY
-

Also applies to: 437-438

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DocFeedbackProvider.tsx` at line 380, The component
DocFeedbackProvider currently introduces state named scrollY (const [scrollY,
setScrollY]) solely to force re-renders and uses a `void scrollY` lint
workaround; replace that with a dedicated render-tick state (e.g., renderTick
and setRenderTick) and remove any `void scrollY` usages. Change every place that
calls setScrollY(...) (including the code around lines 437-438) to instead
increment the tick via setRenderTick(t => t + 1) so the component re-renders
without storing a faux scroll value, and remove any remaining references to the
scrollY symbol and the lint escape hatch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/components/DocFeedbackProvider.tsx`:
- Around line 386-435: This per-block effect in DocFeedbackProvider.tsx (the
useEffect that adds 'scroll' and 'resize' listeners and uses setScrollY/scrollY)
creates many listeners and costly synchronous layout checks; move the
scroll/resize subscription out of the per-block scope into the provider level
(DocFeedbackProvider) and publish a single scroll tick via context, or throttle
updates with requestAnimationFrame so updates happen at most once per frame;
replace the scrollY state used purely to force renders with a simple counter
state (e.g., tick and setTick(t => t+1)) so intent is explicit, and when
deciding button overlap use that single tick-driven re-render to run
getComputedStyle and block.getBoundingClientRect(); finally, stop hardcoding
buttonHeight (currently buttonHeight = 24) by measuring the floating button via
a ref or exposing a constant used by DocFeedbackFloatingButton so overlap
calculation is accurate.

---

Nitpick comments:
In `@src/components/DocFeedbackProvider.tsx`:
- Around line 427-435: The hardcoded buttonHeight (24) used to compute buttonTop
in DocFeedbackProvider can drift from the actual DocFeedbackFloatingButton size;
replace the magic number by measuring the rendered button container (e.g., use a
ref on the portal/root element for DocFeedbackFloatingButton and call
getBoundingClientRect() to get its height) and compute buttonTop from that
measured height, or alternatively export a shared constant from the
DocFeedbackFloatingButton module and import it into DocFeedbackProvider; ensure
the navbar overlap check uses the measuredHeight (or shared constant) instead of
buttonHeight and keep the existing navbarHeight and isMenuOpen logic intact.
- Line 380: The component DocFeedbackProvider currently introduces state named
scrollY (const [scrollY, setScrollY]) solely to force re-renders and uses a
`void scrollY` lint workaround; replace that with a dedicated render-tick state
(e.g., renderTick and setRenderTick) and remove any `void scrollY` usages.
Change every place that calls setScrollY(...) (including the code around lines
437-438) to instead increment the tick via setRenderTick(t => t + 1) so the
component re-renders without storing a faux scroll value, and remove any
remaining references to the scrollY symbol and the lint escape hatch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d62612c-3b6c-48ac-b49e-904edda01523

📥 Commits

Reviewing files that changed from the base of the PR and between 784a7b1 and 27c51ca.

📒 Files selected for processing (1)
  • src/components/DocFeedbackProvider.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"+" icon in the docs is going over the Navbar

2 participants