fix(ui): E31 S2 — the tab names the object, the channel header wraps, the player fits a short window
- The browser tab names the place and the object: PageHeading reports the view's own title (lib/documentTitle), so a channel, playlist or any Plex view reads "<object> · <module> · Siftlode" however it was opened; the Feed is named too; a season adds its show (tabTitle). - The channel page's tab + action row wraps and lets the tag lane fold into "+N": at 1024 it ran past the edge and the page scrolled sideways. - The YouTube player card's width is capped from the window height less the chrome measured under the video: at 133% zoom (685px) it cut its channel row. - Plex certificate words (Not Rated, Approved, Passed) are named through the vocabulary on the info badge, show page, collection, filter and chips. - DESIGN.md: the zoom target is 125% and 133%, not 200% (decision log).
This commit is contained in:
@@ -1,4 +1,12 @@
|
||||
import { useCallback, useEffect, useEffectEvent, useId, useRef, useState } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useEffectEvent,
|
||||
useId,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useNavigationActions } from "./navigationContext";
|
||||
@@ -181,6 +189,25 @@ export default function PlayerModal({
|
||||
// useYtNativeControls, useYtBigView) where a fresh identity each render would spread into their
|
||||
// dep lists too.
|
||||
const focusModal = useCallback(() => cardRef.current?.focus(), []);
|
||||
// The card fits the window, video AND what sits under it (the queue strip, title, channel row): the
|
||||
// video is 16:9 at the card's width, so on a short window (133% zoom on a 1080p screen: 685px) a
|
||||
// full-width card ran past the bottom and cut its channel row (E31 S1). `below` is that chrome's
|
||||
// height, measured (it wraps with the title and the text size), and caps the card's width below.
|
||||
const [below, setBelow] = useState(0);
|
||||
useLayoutEffect(() => {
|
||||
const card = cardRef.current;
|
||||
const stage = stageRef.current;
|
||||
if (!card || !stage) return;
|
||||
const measure = () => {
|
||||
// A maximised stage is fixed to the viewport, out of the card's flow: keep the last value.
|
||||
if (getComputedStyle(stage).position === "fixed") return;
|
||||
setBelow(card.scrollHeight - stage.offsetHeight);
|
||||
};
|
||||
measure();
|
||||
const ro = new ResizeObserver(measure);
|
||||
for (const child of card.children) ro.observe(child);
|
||||
return () => ro.disconnect();
|
||||
}, []);
|
||||
// The dialog contract every other `aria-modal` shell shares (ui/dialogFocus): the focus goes back
|
||||
// to whatever opened the player, and the page lock is the one counted lock. It opens focused on the
|
||||
// CARD, not its first button, so Space/F/arrows reach the shortcuts. Until E11 this was the one
|
||||
@@ -579,7 +606,14 @@ export default function PlayerModal({
|
||||
it and a portrait tablet lost it). stopPropagation so a click steps instead of closing.
|
||||
Hidden on narrow screens where there's no room beside the card. */}
|
||||
<div
|
||||
className={`relative flex w-full max-h-full ${hasQueue ? "max-w-[63rem] sm:px-14" : "max-w-4xl"}`}
|
||||
className={`relative flex w-full max-h-full ${hasQueue ? "sm:px-14" : ""}`}
|
||||
// The design width (63rem with the side strips, else 56rem), narrowed so the 16:9 video plus the
|
||||
// chrome under it fits the window's height less the backdrop's padding (`p-6` = 3rem).
|
||||
style={{
|
||||
maxWidth: `min(${hasQueue ? "63rem" : "56rem"}, calc((100dvh - 3rem - ${below}px) * 16 / 9${
|
||||
hasQueue ? " + 7rem" : ""
|
||||
}))`,
|
||||
}}
|
||||
>
|
||||
{hasQueue && (
|
||||
<Pressable
|
||||
|
||||
Reference in New Issue
Block a user