feat(design): the channel menu's Tags and Priority submenus (C1, E28 S3e3b)

Tags A-Z as in-place checks (the menu stays open for the next); Priority
as the fixed steps 10 5 3 2 1 0, an off-scale current value ticked in its
place. A pick re-sorts the list at once, moving only that row
(placeByPriority, the server's order), and the table / card pager and
the focus follow it (DataTable and useCardPager take a reveal seq,
listNav.focusNavItem lands on the row's own "…", a virtualized card
revealed first). ActionMenu rows take check: many | one. The menu reads
the row fresh from the cache and keys its target by id.
This commit is contained in:
2026-09-26 17:34:47 +02:00
parent 2b3094ffc5
commit 5e5c8a42a9
15 changed files with 467 additions and 28 deletions
+22 -1
View File
@@ -6,7 +6,7 @@
import { useRef, useState } from "react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { navItemAttrs, useListNav } from "./listNav";
import { focusNavItem, navItemAttrs, useListNav } from "./listNav";
import { pushLayer, removeLayer } from "./layerStack";
afterEach(cleanup);
@@ -615,3 +615,24 @@ describe("useListNav · Delete (E28 S3e2: a playlist row's X became the Delete k
expect(gone("d0")).toBe(false);
});
});
describe("focusNavItem", () => {
it("hands the focus to the control `pick` finds in the item, else to the item", () => {
Element.prototype.scrollIntoView ??= () => {};
const { container } = render(<List id="a" count={3} />);
act(() => focusNavItem(container, 2, (item) => item.querySelector<HTMLElement>("button")));
expect(focused()).toBe("a2-action");
act(() => focusNavItem(container, 1));
expect(focused()).toBe("a1");
});
it("brings a virtualized item in first, then focuses its control", async () => {
Element.prototype.scrollIntoView ??= () => {};
const reveal = vi.fn();
const { container, rerender } = render(<List id="a" count={5} mounted={2} reveal={reveal} />);
act(() => focusNavItem(container, 4, (item) => item.querySelector<HTMLElement>("button")));
expect(reveal).toHaveBeenCalledWith(4);
rerender(<List id="a" count={5} reveal={reveal} />);
await waitFor(() => expect(focused()).toBe("a4-action"));
});
});