fix(ui): review round 2 — reveal floor, failed next page, count states, row Play
- DataTable: the reveal floor holds only in the view it was made for (Channels never clears its follow, so every later sort mounted rows up to it) - Feed + Plex browse: a failed next page stops the read-ahead and shows a row that retries, instead of re-requesting it once a round-trip - the YouTube Load more click uses nextPageOnClick; a contract pins NEXT_PAGE to the read-aheads and the click sites to nextPageOnClick - ItemCount: a failed count says so; a stale one says updating to a screen reader - the narrow-picture Play lift applies only where a hover row is there to clear - PlayerModal: one description ladder for the popover and the panel, both tested - plexImage contract reads every img src, any attribute order - Tabs comment names who persists the sub-tab
This commit is contained in:
@@ -14,15 +14,26 @@ const OVERFLOW_AT = 1_000_000;
|
||||
// ("999,999 items") and the content is centred within it. A count of a million or more collapses to
|
||||
// "1M+", with the exact figure in the title tooltip. Deliberately generic ("item(s)", dynamic plural)
|
||||
// so any surface can reuse it for whatever it counts. `stale`: the count is the previous filter's,
|
||||
// kept while the new one loads (keepPreviousData) — dimmed and aria-busy, as AdminLogs dims its lines,
|
||||
// so a broad search's slow count ("a": ~3 s) never reads as the new result.
|
||||
export default function ItemCount({ count, stale = false }: { count?: number; stale?: boolean }) {
|
||||
// kept while the new one loads (keepPreviousData) — dimmed, and "updating" for a screen reader (which
|
||||
// does not voice aria-busy on plain text), so a broad search's slow count ("a": ~3 s) never reads as
|
||||
// the new result. `failed`: the count could not be read — a dash that says so, not the icon-only box
|
||||
// of a first load (E31 review 2).
|
||||
export default function ItemCount({
|
||||
count,
|
||||
stale = false,
|
||||
failed = false,
|
||||
}: {
|
||||
count?: number;
|
||||
stale?: boolean;
|
||||
failed?: boolean;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const lang = i18n.language;
|
||||
// `count` is undefined only on a first, un-cached load — keep the fixed box (nothing shifts), just
|
||||
// show the icon until the number arrives.
|
||||
const label =
|
||||
count == null
|
||||
// `count` undefined and not `failed`: a first, un-cached load — keep the fixed box (nothing shifts),
|
||||
// just show the icon until the number arrives.
|
||||
const label = failed
|
||||
? "—"
|
||||
: count == null
|
||||
? ""
|
||||
: t("common.itemCount", {
|
||||
count,
|
||||
@@ -31,7 +42,9 @@ export default function ItemCount({ count, stale = false }: { count?: number; st
|
||||
return (
|
||||
<span
|
||||
data-testid="item-count"
|
||||
title={count == null ? undefined : formatNumber(count, lang)}
|
||||
title={
|
||||
failed ? t("common.countFailed") : count == null ? undefined : formatNumber(count, lang)
|
||||
}
|
||||
aria-busy={stale || undefined}
|
||||
className="inline-flex h-7 min-w-[9rem] shrink-0 items-center justify-center gap-1.5 rounded-full bg-fg/8 px-3 text-sm whitespace-nowrap text-muted"
|
||||
>
|
||||
@@ -39,6 +52,8 @@ export default function ItemCount({ count, stale = false }: { count?: number; st
|
||||
<span className={clsx("tabular-nums transition-opacity", stale && "opacity-60")}>
|
||||
{label}
|
||||
</span>
|
||||
{failed && <span className="sr-only">{t("common.countFailed")}</span>}
|
||||
{stale && !failed && <span className="sr-only">{t("common.countUpdating")}</span>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user