feat(player): polish the description popover (opaque, portaled, opens upward)
The popover bled through (it used the translucent glass surface), was clipped by the modal card's overflow, and ran off the bottom of the viewport. Make it an opaque surface, render it through a portal to <body> with fixed positioning so nothing clips it, and anchor it above the title so it grows upward where there's room. Add a small hover grace so the pointer can travel title → popover.
This commit is contained in:
parent
fcb77ac2e1
commit
195044042f
1 changed files with 44 additions and 8 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Check, CheckCheck, X } from "lucide-react";
|
||||
import { api, type Video } from "../lib/api";
|
||||
|
|
@ -71,8 +72,29 @@ export default function PlayerModal({
|
|||
onState(video.id, next);
|
||||
};
|
||||
|
||||
// Lazy description (fetched only when the title is hovered).
|
||||
// Lazy description (fetched only when the title is hovered). The popover is
|
||||
// portaled to <body> with fixed positioning so the modal card's overflow-y-auto
|
||||
// can't clip it. A small close grace lets the mouse travel title → popover.
|
||||
const [showDesc, setShowDesc] = useState(false);
|
||||
// `bottom` anchors the popover just above the title so it grows upward (over the
|
||||
// player) instead of downward off-screen / behind the OS taskbar.
|
||||
const [descRect, setDescRect] = useState<{ left: number; bottom: number; width: number } | null>(
|
||||
null
|
||||
);
|
||||
const titleRef = useRef<HTMLDivElement | null>(null);
|
||||
const closeTimer = useRef<number | undefined>(undefined);
|
||||
const openDesc = () => {
|
||||
window.clearTimeout(closeTimer.current);
|
||||
const el = titleRef.current;
|
||||
if (el) {
|
||||
const r = el.getBoundingClientRect();
|
||||
setDescRect({ left: r.left, bottom: window.innerHeight - r.top + 8, width: r.width });
|
||||
}
|
||||
setShowDesc(true);
|
||||
};
|
||||
const scheduleCloseDesc = () => {
|
||||
closeTimer.current = window.setTimeout(() => setShowDesc(false), 150);
|
||||
};
|
||||
const detail = useQuery({
|
||||
queryKey: ["video-detail", video.id],
|
||||
queryFn: () => api.videoDetail(video.id),
|
||||
|
|
@ -91,6 +113,7 @@ export default function PlayerModal({
|
|||
return () => {
|
||||
window.removeEventListener("keydown", onKey);
|
||||
document.body.style.overflow = prevOverflow;
|
||||
window.clearTimeout(closeTimer.current);
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
|
|
@ -185,15 +208,28 @@ export default function PlayerModal({
|
|||
{/* Title row — title (with hover description) on the left, Close on the right. */}
|
||||
<div className="flex items-start gap-3">
|
||||
<div
|
||||
className="relative min-w-0 flex-1"
|
||||
onMouseEnter={() => setShowDesc(true)}
|
||||
onMouseLeave={() => setShowDesc(false)}
|
||||
ref={titleRef}
|
||||
className="min-w-0 flex-1"
|
||||
onMouseEnter={openDesc}
|
||||
onMouseLeave={scheduleCloseDesc}
|
||||
>
|
||||
<h2 className="text-lg font-semibold leading-snug cursor-default">
|
||||
{video.title}
|
||||
</h2>
|
||||
{showDesc && (
|
||||
<div className="absolute left-0 top-full mt-2 z-10 w-full max-w-2xl glass-card rounded-xl shadow-2xl p-4">
|
||||
</div>
|
||||
{showDesc &&
|
||||
descRect &&
|
||||
createPortal(
|
||||
<div
|
||||
className="fixed z-[60] bg-surface border border-border rounded-xl shadow-2xl p-4"
|
||||
style={{
|
||||
left: descRect.left,
|
||||
bottom: descRect.bottom,
|
||||
width: descRect.width,
|
||||
}}
|
||||
onMouseEnter={openDesc}
|
||||
onMouseLeave={scheduleCloseDesc}
|
||||
>
|
||||
<div className="text-xs uppercase tracking-wide text-muted mb-2">
|
||||
Description
|
||||
</div>
|
||||
|
|
@ -206,9 +242,9 @@ export default function PlayerModal({
|
|||
) : (
|
||||
<div className="text-sm text-muted">No description.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
title="Close (Esc)"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue