fix(library): render album/artist + popover through a portal#77
Conversation
Follow-up to #75. The z-index fix wasn't enough in practice: the virtualizer rows use `transform: translateY()`, which creates a stacking context, and the popover's `top-full right-0` resolved to "below the entire tile" rather than "below the + button". Some ancestor (likely the page scroller's own transform / will-change set by tanstack-virtual) was clipping or restacking the popover so the following row's avatar/cover still bled through. Switch the popover to a `createPortal(..., document.body)` render path. Each `+` button registers its DOM node in a `Map<id, HTMLButtonElement>` ref; the popover takes that node as `anchorEl` and positions itself via `getBoundingClientRect` + scroll/resize listeners. Now anchored right under the trigger button (not below the full tile), and lives at the document root so stacking contexts no longer apply. TrackTable keeps its current in-flow popover — its z-index fix from #74 works because the row layout is a single flex line, not a CSS grid of multiple cards.
Code Review by Qodo
1. Popover clicks trigger tile
|
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 8 |
| Duplication | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
| <div | ||
| data-add-to-playlist-popover | ||
| role="menu" | ||
| onDoubleClick={(e) => e.stopPropagation()} | ||
| className="absolute top-full right-0 mt-1 z-50 w-56 rounded-xl border border-zinc-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-surface-dark-elevated dark:shadow-black/40 overflow-hidden animate-fade-in" | ||
| style={ | ||
| anchorEl | ||
| ? rect | ||
| ? { | ||
| position: "fixed", | ||
| // Anchor the right edge to the trigger's right edge, | ||
| // matching the in-flow `right-0` behaviour. Drop 4 px | ||
| // below the trigger to match `mt-1`. | ||
| top: rect.bottom + 4, | ||
| left: rect.right - POPOVER_WIDTH, | ||
| width: POPOVER_WIDTH, | ||
| } | ||
| : { position: "fixed", visibility: "hidden" } | ||
| : undefined | ||
| } | ||
| className={`${ | ||
| anchorEl | ||
| ? "z-100" | ||
| : "absolute top-full right-0 mt-1 z-50 w-56" | ||
| } rounded-xl border border-zinc-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-surface-dark-elevated dark:shadow-black/40 overflow-hidden animate-fade-in`} | ||
| > | ||
| <div className="text-[10px] font-bold tracking-widest text-zinc-400 uppercase px-3 pt-3 pb-2"> | ||
| {t("trackActions.addToPlaylist")} |
There was a problem hiding this comment.
1. Popover clicks trigger tile 🐞 Bug ≡ Correctness
AddToPlaylistPopover only stops double-click propagation; in portal mode, React synthetic events still bubble to the originating component tree, so clicking a playlist row can also fire the album/artist tile onClick. This can navigate/open the album/artist while the user is trying to add to a playlist.
Agent Prompt
### Issue description
When the popover is rendered with `createPortal`, React’s event system still bubbles events through the original React tree. Since the popover only stops `onDoubleClick`, normal `click` events from menu items can bubble to the album/artist tile `onClick` handlers and trigger navigation/selection unintentionally.
### Issue Context
- Popover root currently has `onDoubleClick={(e) => e.stopPropagation()}` only.
- Album and artist tiles are clickable (`onClick={() => onAlbumClick(...)}` / `onArtistClick(...)}`).
### Fix Focus Areas
- src/components/views/LibraryView.tsx[1385-1445]
### Implementation notes
- Add `onClick={(e) => e.stopPropagation()}` (and optionally `onMouseDown={(e) => e.stopPropagation()}`) to the popover container (`data-add-to-playlist-popover`).
- Alternatively (or additionally), stop propagation in each playlist row button’s `onClick` before calling `onPick`/`onCreate`.
- Keep the existing outside-click handling working (your `closest([...])` checks will still behave correctly).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary
+button.absolute top-full right-0was resolved relative to the card root (which spans avatar + name + metadata), and the virtualizer'stransform: translateY()rows created stacking contexts that boxed the popover'sz-50inside its row.Fix
createPortal(..., document.body)when ananchorElis supplied.+button registers its DOM node in aMap<id, HTMLButtonElement>ref; the popover consumes that ref's element as anchor, computestop/leftfromgetBoundingClientRect, and keeps it in sync via aResizeObserver+ capture-phase scroll + resize listeners.+button, not under the whole tile.TrackTablekeeps its in-flow popover: its row is a single flex line, its z-index fix from fix(library): hoist track row above siblings while + popover is open #74 works.Test plan
+: popover appears just below the+button (not at the bottom of the title block), is fully opaque, no cover of any other row bleeding through.TrackTable): unchanged, regression check.