feat(player): download works in fullscreen + de-dup the transport controls (R8 S5)
Two R8 S5 (player big-view control-bar) follow-ups: - Modal now portals into `document.fullscreenElement ?? document.body` (reactive), the same fullscreen-aware root the popover platform uses. A node under <body> is painted beneath whatever holds the fullscreen lock, so the download dialog — a Modal, opened from the player's control bar — would open invisibly in true fullscreen. With that fixed, the `!isFullscreen` guard on the player's DownloadButton is dropped, so download joins add-to-playlist as working in true fullscreen. (Every Modal is now fullscreen- aware; the fix is at the primitive, not the call site.) - The queue transport cluster (prev / counter / next + auto-advance + loop toggles) was duplicated inline in two places — the modal queue bar and the big-view docked control bar — with identical logic and only theme + label-visibility differing. Extracted a `<PlayerTransportControls variant="modal"|"docked">`; a mode/icon change now lands once. Behaviour-preserving (the docked bar's trailing divider and the wrapper rows stay at the call sites). Note the modal variant keeps its `bg-accent/15` active tint, which is a latent no-op under Tailwind v3's hex --accent — left as-is for R10 S0's styling audit. Gate green (tsc/eslint/76 vitest). Device-UAT'd in real Chrome: transport controls render correctly in both the modal queue bar and the docked bar; the download button now shows in true fullscreen and its dialog portals into the fullscreen stage, fully visible and centered. No console errors.
This commit is contained in:
@@ -4,11 +4,14 @@ import { useTranslation } from "react-i18next";
|
||||
import { X } from "lucide-react";
|
||||
import { useBackToClose } from "../lib/history";
|
||||
import { pushLayer, removeLayer, isTopmost } from "../lib/layerStack";
|
||||
import { useFullscreenElement } from "../lib/fullscreen";
|
||||
|
||||
// Small centered modal shell (portaled to <body>): backdrop + ESC + scroll-lock close. Each open
|
||||
// modal is a layer in the shared `layerStack` so ESC only closes the topmost one — e.g. an error
|
||||
// dialog over the tag editor dismisses just the error and returns to the editor underneath, and a
|
||||
// player below defers to it (R8 S3).
|
||||
// Small centered modal shell: backdrop + ESC + scroll-lock close. Each open modal is a layer in the
|
||||
// shared `layerStack` so ESC only closes the topmost one — e.g. an error dialog over the tag editor
|
||||
// dismisses just the error and returns to the editor underneath, and a player below defers to it
|
||||
// (R8 S3). Portals into `document.fullscreenElement ?? document.body`: a node under <body> is painted
|
||||
// beneath whatever holds the fullscreen lock, so a Modal opened from a player in true fullscreen (the
|
||||
// download dialog) would otherwise open invisibly.
|
||||
export default function Modal({
|
||||
title,
|
||||
onClose,
|
||||
@@ -23,6 +26,7 @@ export default function Modal({
|
||||
const { t } = useTranslation();
|
||||
// Browser/mouse Back closes the topmost modal instead of navigating away.
|
||||
useBackToClose(onClose);
|
||||
const fsRoot = useFullscreenElement();
|
||||
|
||||
// Backdrop-click close must only fire when the press STARTED on the backdrop too. Otherwise a
|
||||
// drag that begins inside the dialog (e.g. selecting text in an input) and is released out on
|
||||
@@ -79,6 +83,6 @@ export default function Modal({
|
||||
<div className="px-5 pb-5">{children}</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
fsRoot ?? document.body,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user