fix(design): the keyboard ring of a flush row stays visible (E28)

A list item's ring sat 2 px outside it. On a table row it ran under the
sticky head and past the table's sides; on a Plex playlist show header, flush
in its clipping card, it was cut away and the focus was invisible. A table row
(by its tag), a log line and the Plex show header and episode rows now ring
inside themselves, the show header with its card's corners.

The sticky header's fade-below was drawn at rest, over the first row's top
16 px, hiding the ring's top edge: it now fades in with the first 1rem of
scroll (a scroll-driven animation; no fade where unsupported).
This commit is contained in:
2026-09-26 20:01:04 +02:00
parent 47c12f9405
commit 3e7c79fa69
7 changed files with 116 additions and 6 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ export default function LogLine({
return (
<div
className="border-b border-border/50 last:border-b-0"
{...navItemAttrs(navIndex, `${event} ${msg}`)}
{...navItemAttrs(navIndex, `${event} ${msg}`, { inset: true })}
>
<Pressable
selectable
@@ -265,6 +265,9 @@ describe("PlexPlaylistView · the keyboard", () => {
expect(document.activeElement).toBe(rowOf("The Show"));
pressKey("j");
expect(document.activeElement).toBe(rowOf("Ep e1"));
// Both sit flush in the show's clipping card: an outside ring was cut away (index.css).
expect(rowOf("The Show").hasAttribute("data-nav-inset")).toBe(true);
expect(rowOf("Ep e1").hasAttribute("data-nav-inset")).toBe(true);
pressKey("k");
const bulk = vi.spyOn(api, "plexPlaylistRemoveBulk").mockResolvedValue({} as never);
pressKey("Delete");
+7 -3
View File
@@ -178,7 +178,7 @@ function EpisodeRow({ ep, ctx, dense }: { ep: PlexCard; ctx: Ctx; dense: boolean
ref={setNodeRef}
style={style}
{...ctx.trigger(target)}
{...navItemAttrs(ctx.navIndex(ep.id), ep.title)}
{...navItemAttrs(ctx.navIndex(ep.id), ep.title, { inset: true })}
className={`group flex items-center gap-2.5 ${dense ? "rounded-lg px-1.5 py-1" : `${SURFACE.card} p-1.5`}`}
>
<DragHandle
@@ -292,8 +292,12 @@ function ShowGroup({ block, ctx }: { block: ShowBlock; ctx: Ctx }) {
{/* Group header — drag moves the whole show; the chevron collapses; its "…" holds the rest. */}
<div
{...ctx.trigger(target)}
{...navItemAttrs(ctx.navIndex(block.id), block.title)}
className="group flex items-center gap-2.5 p-2"
{...navItemAttrs(ctx.navIndex(block.id), block.title, { inset: true })}
// The card's corners minus its 1 px edge, so the inside keyboard ring follows the card's
// curve instead of cutting across it; square at the bottom while the seasons hang below.
className={`group flex items-center gap-2.5 p-2 rounded-t-[calc(var(--radius-xl)-1px)] ${
open ? "" : "rounded-b-[calc(var(--radius-xl)-1px)]"
}`}
>
<DragHandle
label={t("plex.playlist.reorderItem", { title: block.title })}