"use client"; import type { ReactNode } from "react"; import { useSectionFrame } from "../../widget/components/WidgetSection"; import { LaneRail } from "./LaneRail"; import { LANE_DOT, LANE_TEXT, LANE_WORD, type LaneState, } from "yt-dlp-transcript-common/views/laneState"; import { LaneActionButton, type LaneActionResult } from "./LaneActionButton"; // One lane: its name, its state, its figure and BOTH its switches, in one place. // // The card exists because those four things used to live in four places — a // status word in a grey span after the buttons, the numbers it referred to in // chips several elements away (the dashboard) or in a strip that could be // dragged to another corner entirely (the widget). An operator asking "is the // backfill actually doing anything" had to assemble the answer. // // RESTRAINT: the surface stays `bg-card` in every state. The rail says it and // the dot says it; a third tinted background would be the accessory to remove. export type LaneControl = { key: string; label: string; glyph: string; ariaLabel: string; title?: string; variant: "active" | "idle"; disabled?: boolean; action: () => Promise; onChange?: () => void | Promise; }; export function LaneCard({ name, state, figure, detail, note, controls, }: { name: string; state: LaneState; // The lane's own number, in the house's tabular mono. For a lane with no sweep // this is the sentence saying so. figure: ReactNode; // About two lines under the figure: what the figure is MADE OF. The backfill // lane's is a per-kind breakdown, because its sum is a number in no unit at // all — see the sync payload's `kinds`. // // SUPPRESSED IN THE DENSE FRAME, deliberately, and this is the whole reason it // is a separate prop from `note`. A rail cell is one line; `note` survives // there because it is one short clause ("the sweep is holding") that the rail- // as-edge cannot draw. Two or three lines of breakdown would either wrap the // cell to three times its height or be truncated into a lie. detail?: ReactNode; // Anything the card must keep saying in words — the backfill lane's "the sweep // is holding", which the rail now also draws. note?: ReactNode; controls: LaneControl[]; }) { // The widget provides this per cell; outside the widget the default is // {scroll:false, dense:false}, so the dashboard needs no provider. const { dense } = useSectionFrame(); const head = ( {LANE_WORD[state]} ); if (dense) { // A rail cell has room for one line. The rail becomes the left EDGE — same // reading, turned ninety degrees — and the buttons collapse to glyphs, whose // aria-labels carry the meaning they always did. return (
); } return (

{name}

{head}

{figure}

{detail && (
{detail}
)} {note} {controls.length > 0 && (
{controls.map(({ key, ...c }) => ( ))}
)}
); }