import type { ReactNode } from "react"; import type { BoardLane } from "./BoardLaneList.tsx"; export type BoardMove = { /** Where the card was, in `fromLane.items`. */ fromIndex: number; fromLaneKey: string; itemKey: string; /** * Where it goes, **already corrected for its own removal** — so a * consumer's handler is `splice(fromIndex, 1)` then * `splice(toIndex, 0, item)` with no arithmetic of its own. A * within-lane move quoted against the un-spliced list is one too * high for every position below the origin, and that off-by-one is * the single most common bug in a hand-rolled board. */ toIndex: number; toLaneKey: string; }; export type BoardProps = { className?: string; /** * What level the lane headings are. The caller's, because only the * caller knows what the board sits under: a board directly below a * page `

` wants `2`, and one inside an already-deep section * wants `4`. Getting it wrong is an axe `heading-order` failure and * a broken screen-reader outline. */ headingLevel?: 2 | 3 | 4 | 5; /** The board's accessible name. Required — it is a landmark. */ label: string; /** * How the lanes themselves are arranged. * * `columns` is the workflow shape: lanes sit side by side above * `cq-lg`, and the Narrow View selects one lane. `rows` is the * project-pool shape: every lane is one horizontal band, its cards * form a responsive grid inside that band, and every lane stays on * screen at every width. */ laneLayout?: "columns" | "rows"; lanes: readonly BoardLane[]; /** * What the move handle shows **while the lanes are side by side**. * Below `cq-lg` the handle says "Move" whatever this is, because * there is nowhere to drag to — see `BoardCard`. * * The library ships no icons and no symbol glyphs in a default — * they render as nothing where the font lacks them, which * includes this sandbox's chromium, the kiosk image and the * ePaper build — so the default is a word and an app that owns a * glyph set passes one. On a dense board it is worth ~35px a row. */ moveIcon?: ReactNode; /** * Which lane is on screen below `cq-lg`. **Initial only** — the * segmented control owns it from then on, matching every other * uncontrolled control in this library. */ narrowLaneKey?: string; /** * The one thing that makes a board a board. **Absent means * read-only**: no handles, no menus, no drag, and nothing * announcing itself. A board with no `onMove` is a legitimate * thing — a dashboard, an ePaper render — and it should not be * carrying affordances that do nothing. */ onMove?: (move: BoardMove) => void; }; /** * Lanes of cards, sized by their **container**, moved by keyboard or * by pointer. * * ### The container rule, which is the whole point * * There is no media query in this component, and adding one would be * a defect rather than a shortcut. Two facts make window width * useless here and they compound: * * - **A lane in a three-up board is narrow on any monitor.** Three * lanes across 1600px is ~500px each, which is a phone's worth of * room, on a 4K display, in a maximised window. * - **The owner browses zoomed in.** A 1500px window at 175% zoom * is ~860 effective CSS pixels, so the number a media query reads * is not the number of pixels anything actually has. * * So there are **two nested containers**, and they answer two * different questions: * * - The board's own box decides *how workflow lanes are shown* — * side by side at `cq-lg` and wider, one lane plus a segmented * control below it. A project-pool board with * `laneLayout="rows"` keeps every lane on screen and grids the * cards inside each horizontal band instead. * - Each lane's list decides *what shape a card is* — two lines, * one line, or its own card. See `BoardCard`. * * Both are `@container`, and neither is queried by the element that * declares it, because a container query matches descendants only. * * ### There is no horizontal scroll, at any width * * *"No horizontal scroll, ever. The board becomes a segmented * control, never a pan surface."* A board that pans sideways hides * lanes behind a gesture with no affordance, and on a trackpad it * fights the browser's own back-navigation. So the narrow workflow * layout shows one lane at a time and names the others in a * `radiogroup` that says how many cards each holds. A row layout * wraps its cards inside each lane and also never pans. * * ### Moving a card is a first-class operation, and it is not a drag * * The move is a **`Menu` on every card**, listing the other lanes. * That is the primary path, not an accessibility fallback: it is the * only one that works from the keyboard, the only one a screen * reader can drive, and the only one available in the Narrow View * where the other lanes are not on screen to drop onto. Pointer * dragging is layered on the same handle and commits through the * same callback. * * **No drag-and-drop dependency is taken.** The reasoning — licence, * provenance, bundle cost, and what a general DnD library would * actually be buying for one card going into one lane — is in * [the decision](../../../docs/decisions/2026-08-19-the-board-owns-the-move-and-takes-no-drag-and-drop-dependency.md). * * ### What this component deliberately does not do * * Lanes, counts, truncation, empty states and the move; **not** * data, not virtualisation, and not the cross-lane "needs attention" * summary. A board is a view of a list the consumer owns, the lane * truncates by construction so there is nothing to virtualise, and a * panel that reaches across every lane is not a lane — it is an * `Alert` above the board, which is how the docs page composes it. */ export declare const Board: ({ className, headingLevel, label, laneLayout, lanes, moveIcon, narrowLaneKey, onMove, }: BoardProps) => ReactNode; //# sourceMappingURL=Board.d.ts.map