The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").
Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.
Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).
One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
import { useId, type ReactNode } from "react";
|
|
|
|
// Shared MATCH / segmented primitive — a rounded-full pill track where exactly ONE option is active
|
|
// (radio-like). Backs the feed any/all + or/and "Match" toggles and the feed Mine/Library scope bar,
|
|
// and the Plex any/all combine toggles. The active paint comes entirely from the
|
|
// `.pill[aria-pressed="true"]` recipe (index.css) — do NOT add bg/text/border-colour utilities on the
|
|
// buttons here or a utility-layer class would beat the pressed fill.
|
|
|
|
export interface SegmentedOption<T extends string> {
|
|
value: T;
|
|
label: string;
|
|
icon?: ReactNode;
|
|
title?: string; // optional per-option tooltip
|
|
}
|
|
|
|
export default function SegmentedToggle<T extends string>({
|
|
options,
|
|
value,
|
|
onChange,
|
|
ariaLabel,
|
|
label,
|
|
title,
|
|
fullWidth = false,
|
|
size = "md",
|
|
className,
|
|
}: {
|
|
options: readonly SegmentedOption<T>[];
|
|
value: T;
|
|
onChange: (v: T) => void;
|
|
ariaLabel: string;
|
|
// Optional left-hand caption; when set the control renders as a justify-between row (the feed/Plex
|
|
// "Match" toggles). Omit for a standalone bar (the Mine/Library scope switch).
|
|
label?: ReactNode;
|
|
// What the choice means, beyond its name: the row's tooltip for the pointer, and the group's
|
|
// accessible description for the keyboard and a screen reader — a `title` on a non-focusable box
|
|
// alone reaches neither (it had only ever been the pointer's).
|
|
title?: string;
|
|
// Full-width track with flex-1 buttons (the scope bar); default is intrinsic width.
|
|
fullWidth?: boolean;
|
|
// md (28, text-xs) is the toolbar rung: it lines up with the search pill, the triggers and an `sm`
|
|
// Button (DESIGN B1). sm (24, text-3xs) is only a side panel's captioned match toggle. md is the
|
|
// default because the old sm default slid a pager and a toolbar count onto 10px type.
|
|
size?: "sm" | "md";
|
|
className?: string;
|
|
}) {
|
|
const hintId = useId();
|
|
const track = (
|
|
<div
|
|
className={`inline-flex items-center rounded-full border border-border bg-card p-px ${
|
|
size === "md" ? "text-xs" : "text-3xs"
|
|
}${fullWidth ? " w-full" : ""}`}
|
|
role="group"
|
|
aria-label={ariaLabel}
|
|
aria-describedby={title ? hintId : undefined}
|
|
title={label == null ? title : undefined}
|
|
>
|
|
{title && (
|
|
<span id={hintId} hidden>
|
|
{title}
|
|
</span>
|
|
)}
|
|
{options.map((o) => (
|
|
<button
|
|
key={o.value}
|
|
onClick={() => onChange(o.value)}
|
|
title={o.title}
|
|
aria-pressed={value === o.value}
|
|
// A segment is the target: 24 (sm) / 28 (md), so the track lands on the ladder's 28 / 32
|
|
// with its 1px padding and edge (B1); 44 on a coarse pointer (A3).
|
|
className={`pill touch-target inline-flex items-center justify-center gap-1 rounded-full ${
|
|
size === "md" ? "h-7 px-2.5" : "h-6 px-2"
|
|
}${fullWidth ? " flex-1" : ""}`}
|
|
>
|
|
{o.icon}
|
|
<span>{o.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
|
|
if (label == null) {
|
|
return className ? <div className={className}>{track}</div> : track;
|
|
}
|
|
return (
|
|
<div className={`flex items-center justify-between ${className ?? "mb-1.5"}`} title={title}>
|
|
<span className="text-3xs uppercase tracking-wide text-muted">{label}</span>
|
|
{track}
|
|
</div>
|
|
);
|
|
}
|