import "./table.css"; import { type ReactNode, type ReactElement } from "react"; import type * as React from "react"; import { useRender } from "@base-ui/react/use-render"; import { type ColorName } from "./colors"; import { type StyleProps } from "./style_props"; import { type SortState, type SortHeaderLabels } from "./sort_header"; import { type TableFitColumn } from "./table_fit"; /** One column of a register — defined ONCE, here, instead of being re-typed in * the header band AND every row. The fit fields live on `TableFitColumn`. */ export interface TableColumn extends TableFitColumn { /** A header that is not a WORD — a ruler of positions. Rendered in place of * `label`, which stays the column's name for the stacked overline and the * sort control. Never a way to restyle a label. */ header?: ReactNode; flex?: number; align?: "left" | "right"; /** The `key` doubles as the `sortKey`. */ sortable?: boolean; /** * THE COLUMN THE REGISTER IS NAMED FOR. ONE per register. Its VALUE is never * given up: the fit sheds the COLUMN like any other, and the register's owner * draws the same cell somewhere the row still has (`shape_frame.tsx`), so the * warning below does not report it. * * It is not {@link TableFitColumn.priority} `"never"`, which says a column may * not LEAVE THE GRID. This says the column may leave the grid but its reading * may not leave the ROW. */ signature?: boolean; /** * HOW MANY LINES THIS COLUMN MAY DRAW — and therefore how many every cell in * it RESERVES. One by default; `2` for a subject over its supporting line. * * The column's, never the cell's: a cell centres inside the row, so a row whose * cell has a caption puts that column's first line above where the rows with * nothing to say put theirs. Declared here, the budget reaches whatever the * cell draws. * * NOT a clamp. `CellStack`/`TextCell`'s own `lines` bounds how far ONE line may * wrap; this is how many lines the column stands. */ lines?: 1 | 2; } /** * IS THE REGISTER A PILE — below the width where a row is a set of columns * (`table_fit.ts`). A CELL asks, because the two modes spend the opposite * resources: a column's width and one row's height against the panel's width and * as much height as it takes. False outside a register. */ export declare function useRegisterStacked(): boolean; /** * DID THIS WIDTH GIVE UP THAT COLUMN — the fit's own answer (`table_fit.ts`), * asked by a cell about ANOTHER column, because one cell may carry a second * column's value. It is the only way a cell can know: the fit is measured inside * the register, long after the shape declared its columns. * * False in a PILE, where nothing was given up, and false outside a register. */ export declare function useRegisterShed(key: string): boolean; /** HOW MANY LINES THE COLUMN THIS CELL IS IN RESERVES * ({@link TableColumn.lines}). ONE outside a register and one in a pile: a cell * that lines up with nobody owes nobody a blank line. */ export declare function useRegisterLines(): 1 | 2; /** * THE BULK-SELECT a register's header carries — DATA, and the register renders * the control, because WHERE its words may appear is the register's own geometry * and a caller handing in a finished checkbox cannot know which fork it is in. A * word printed beside someone else's control would be a second string no markup * ties to the box (WCAG 2.5.3); one `label` answers both. */ export interface TableSelectAll { /** `"mixed"` is the third position — some rows ticked, not all. ONE axis, * because `aria-checked` has exactly these three values and a separate * `indeterminate` flag beside a boolean spells two of them twice. */ checked: boolean | "mixed"; onChange: (checked: boolean) => void; /** * What ticking it SELECTS, in words. ONE string: the control's accessible name * in both forks, and stacked also the VISIBLE text beside the mark. */ label: string; /** Nothing to select — an empty page, a set whose rows are all locked. */ disabled?: boolean; } export interface TableProps extends StyleProps { /** The single source of column layout — shared by the header AND every cell. */ columns: TableColumn[]; sort?: SortState | null; /** Cycles the sort (none→asc→desc→none) — pair with `cycleSort` in the parent. */ onSort?: (key: string) => void; /** Localized a11y strings for the sortable headers. Omit to keep English. */ sortLabels?: SortHeaderLabels; /** The bulk-select in the header's leading gutter, aligned over the rows' * `leading` checkboxes. Set `leading` too, to reserve the gutter width. */ selectAll?: TableSelectAll; /** Reserve a leading gutter (px) for rows that render a `leading` slot (a checkbox). */ leading?: number; /** * Turn the register into a COUNTED one: every row shows its position in a * narrow gutter left of `leading`, and this number — the total — heads it. * * Yours to pass because it is rarely the child count: a paged register renders * a page of it and a filtered one counts what matched. Positions are yours for * the same reason — see `TableRowProps.ordinal`. * * Pass {@link TableProps.counted} INSTEAD when the page already states the * total somewhere the reader will see first. */ count?: number; /** Number the rows WITHOUT heading the gutter with a total, which stops * earning its place the moment the page answers "how many" louder. Ignored * when `count` is given; the gutter is reserved either way. */ counted?: boolean; /** Paint the column-header band with the brand's wash. OPT-IN: it earns its * place on a register that is the page's whole content, and is noise on a * small table inside a card that already has edges. */ headerFill?: boolean; /** Reserve a trailing gutter (px) for rows that render a `trailing` slot (a ⋯ / button). */ trailing?: number; /** * How a row's cells sit against each other VERTICALLY. Default `center`, which * is right while every cell is one line: centring resolves PER CELL, so a cell * that grows a second line moves its content up relative to its siblings. * * `top` puts every cell and both gutters on one top edge, and a single-line row * is PIXEL-IDENTICAL either way. * * **`top` is the fallback, not the fix.** The cause of a ragged register is * UNEQUAL ROWS, so the answer is {@link TableColumn.lines}. Reach for `top` * only where the extra line belongs to the DATA and cannot be budgeted. */ align?: "center" | "top"; /** The `TableRow`s. */ children: ReactNode; testID?: string; ref?: React.Ref; render?: useRender.RenderProp; } export declare function Table(props: TableProps): React.JSX.Element; export interface TableGroupProps extends StyleProps { /** What this band of rows has in common — an ACTION state, not a category. */ label: string; /** How many rows are in it. Shown beside the label; omit only if the band is * never partial (it almost always is — a filter or a page can cut it). */ count?: number; /** A palette family for the band's dot, when the group carries a VALENCE the * label alone understates (a queue that is overdue). Omit for a neutral band — * most bands are neutral, and a dot on every one is decoration. */ color?: ColorName; /** Folded away. Controlled: which bands are open is the register's own state, * and it belongs beside the control that grouped them. The COUNT is what makes * folding safe — a closed band still says how much it is holding. */ collapsed?: boolean; /** Omit for an inert band — a heading with no chevron and no tab stop. A band * that cannot fold must not look like it can. */ onToggleCollapse?: () => void; /** A subtotal row under the band's rows, composed from `TableCell`s so its * figures land under the columns they total. Rendered only while open: a * total under a folded band restates a number the band already carries. */ footer?: ReactNode; children: ReactNode; testID?: string; ref?: React.Ref; render?: useRender.RenderProp; } /** * A titled BAND of rows inside a `Table`. Reach for it when the rows fall into * states that imply DIFFERENT ACTIONS and the reader's real question is which * pile they are in — never to group by a category a column already shows, which * is a sort and costs a band of chrome per value. * * The band separates itself with AIR rather than a rule, because the register * keeps ONE line and a second would compete. A group's rows restart their * `ordinal` numbering inside each band. */ export declare function TableGroup(props: TableGroupProps): ReactElement>; export type TableRowProps = StyleProps & { selected?: boolean; /** Part of a multi-select set — a resting blue tint (the open/hover wash overrides it). */ marked?: boolean; /** * This row LANDED. Pair it with `useJustArrived`, which owns the diff and the * rule that a first render is never an arrival. An OVERLAY, not a fifth * interaction state: `background-color` already has one owner, so an arrival * composes with all of them. */ justArrived?: boolean; /** Outside the door, BEFORE the cells (a selection checkbox) — width = Table `leading`. */ leading?: ReactNode; /** * This row's POSITION, shown left of `leading` when the Table carries `count`. * Yours to compute: a paged register continues (`offset + i + 1`), a grouped * one restarts inside each band. Omit it on a row that has no position. * * A POSITION, never an identity: sort the register and row 12 is a different * record. A stable handle is a column. */ ordinal?: number; /** Outside the door, AFTER the cells: the row's OVERFLOW chrome (a ⋯ `ActionMenu`). * Register mode renders it in the trailing gutter beside `action`; stacked mode keeps * it on the top line. Width = Table `trailing` (shared with `action`). */ trailing?: ReactNode; /** The row's primary action (a CTA `Button`). Register mode: in the trailing gutter, * before `trailing`; stacked mode: the content's bottom line, right-aligned — an action * reads at content scale, not as row chrome. Reserve Table `trailing` for it. */ action?: ReactNode; /** * Min row height — a FLOOR, not a fixed height: a taller cell grows the row. * Stacked rows size to their content and ignore it. **Reach for this only when * a surface genuinely needs more DENSITY than the beat** — if the reason was * "the content did not fit", delete the override. */ minHeight?: number; /** The row's detail, revealed BENEATH it when `expanded`. Reach for it when the * detail is READ or light-edit; a heavy form still deserves its own surface. * Outside the pressable, so a control in it is not swallowed by the press. */ detail?: ReactNode; /** * Whether `detail` is showing. CONTROLLED — who may be open at once is the * caller's rule, and a component-local default would silently pick one. */ expanded?: boolean; children: ReactNode; testID?: string; ref?: React.Ref; render?: useRender.RenderProp; } & ({ /** Opens the record (the drawer). The door + the full-row surface both call it. */ onPress: () => void; /** Accessible name for the door (the keyboard-focusable open target). * REQUIRED: the door is an empty overlay with no text content to derive a * name from. */ accessibilityLabel: string; } | { onPress?: undefined; accessibilityLabel?: undefined; }); /** * A register row: a full-bleed `PressableRow` holding the cells, with a * `RowFocusEntry` as an EMPTY absolutely-positioned SIBLING beneath them, never * their ancestor, because a button must not contain interactive descendants and * cells carry their own controls. The door carries the tab stop, the accessible * name and the focus ring. Cells map to `Table`'s columns by position. * * In STACKED mode the same row renders as a pile — same door, same wash, same * slots. */ export declare function TableRow(props: TableRowProps): React.JSX.Element; export interface TableCellProps extends StyleProps { children: ReactNode; testID?: string; ref?: React.Ref; render?: useRender.RenderProp; /** @internal — injected by `TableRow` from the column at this cell's position. */ _column?: TableColumn; /** @internal — injected by `TableRow`; the Table is in stacked mode. */ _stacked?: boolean; /** @internal — injected by `TableRow`; this is the identity cell (column 0), which * in stacked mode leads the row instead of becoming one of its field lines. */ _primary?: boolean; } /** One cell — its width/align come from the column `TableRow` injects by * position. In stacked mode a FIELD cell is a `DetailRow`, the drawer's own * component rather than a lookalike, so a stacked register and the record behind * its door share one vocabulary. A label-less control column pins right. * * The IDENTITY cell is not a field line and carries no label: it heads the row. * * A stacked field cell with NOTHING IN IT draws nothing — not its label either, * or a lone label is indistinguishable from data that failed to load. A value * that is absent AND meaningful is the caller's to draw. */ export declare function TableCell(props: TableCellProps): React.JSX.Element | null;