// ───────────────────────────────────────────────────────────────────────────── // Generic DataTable — shared types // // Pure type module. Re-exports a couple of `table-properties` aliases so // consumers only need to import from `@exxatdesignux/ui/components/data-table/types` // when authoring column defs or conditional rules. // ───────────────────────────────────────────────────────────────────────────── import type * as React from "react" import type { ConditionalRule, FilterOperator, FilterTextMask, } from "../../lib/table-properties-types" import type { ColumnCellKind } from "../../lib/column-cell-kind" import type { StatusBadgeTone } from "../../lib/status-badge-tints" export type { ConditionalRule, FilterTextMask, ColumnCellKind, StatusBadgeTone } export type SortDir = "asc" | "desc" export interface ColumnDef { /** Unique key — must match a key of TData or be synthetic (e.g. "select", "actions") */ key: string /** Header label */ label: string /** Default width in px */ width?: number minWidth?: number /** * Set `false` for fixed utility columns (select checkbox, row-actions kebab). * Hides the resize handle and pins the rendered width to `width`, ignoring * any persisted/resized value — stale localStorage can never inflate them. */ resizable?: boolean /** Whether this column can be sorted */ sortable?: boolean /** * Key of TData used for sorting comparisons. * If omitted but sortable=true, falls back to `key`. */ sortKey?: keyof TData & string /** Pin to left or right by default */ defaultPin?: "left" | "right" /** If true, user cannot unpin this column */ lockPin?: boolean /** Render the cell content. If omitted, renders String(row[key]). */ cell?: (row: TData, ctx: CellContext) => React.ReactNode /** Custom header renderer — overrides the default label text */ header?: () => React.ReactNode /** * Semantic cell kind — sets default filter icon, type, and static options when * `filter` is omitted or partial. Pair with named cells from `table-cells.tsx`. */ cellKind?: ColumnCellKind /** * When the column embeds a favorite/star control, expose a companion **Favorite** * filter (`isStarred` by default). Filter-only — does not add a table column. */ favoriteFilter?: boolean | { fieldKey?: string; label?: string } /** Filter-only synthetic columns — hidden from the grid, used in filter menus only. */ filterOnly?: boolean /** Filter config — drives per-column "Filter by this column" option */ filter?: { type?: "select" | "text" | "date" | "date-range" | "range" /** icon class for filter pills, e.g. "fa-circle-dot" */ icon?: string options?: { value: string label: string /** * Optional rich rendering for this option in filter dropdowns, the * Properties drawer, and the group divider when the table is grouped by * this column (e.g. status chip, colored swatch). Falls back to `label` * plain text when omitted. */ node?: React.ReactNode /** * Font Awesome suffix for this value (e.g. `fa-circle-check`). Used when * the group divider rebuilds a status or pill badge and in filter menus * that render from metadata instead of `node`. */ icon?: string /** * Semantic tone behind this value. Declared, not inferred: the DataTable * cannot read a tone out of a rendered chip, and grouping by the column * tints each divider with the matching `--status-badge-*-fill` so a group * and its rows' chips read as one colour. Omit and the divider stays the * neutral grey. */ tone?: StatusBadgeTone }[] operators?: FilterOperator[] /** Avatar list picker for person columns — usually from `cellKind: "person"`. */ selectVariant?: "default" | "person" /** When `type` is `text`, optional mask for filter popover + drawer. */ textMask?: FilterTextMask /** When `type` is `range` and min/max omitted — derive from dataset. */ dataBounds?: boolean /** When `type` is `range` — defaults from `cellKind: "progress"` (0–100%). */ rangeMin?: number rangeMax?: number rangeStep?: number rangeUnit?: string } } // `TData` is part of the public surface so callers can write // `CellContext` for symmetry with column-def renderers, even // though the interface body doesn't currently reference it. export interface CellContext<_TData> { rowIndex: number selected: boolean onSelect: (selected: boolean) => void } export interface DataTableProps> { /** Row data */ data: TData[] /** Column definitions */ columns: ColumnDef[] /** Returns a stable unique ID for each row (used for selection keys) */ getRowId?: (row: TData, index: number) => string | number /** * Accessible name for each row’s selection checkbox (e.g. primary column value). * If omitted, a generic label is used. */ getRowSelectionLabel?: (row: TData, rowIndex: number) => string /** Enable row selection checkboxes */ selectable?: boolean /** Enable global search */ searchable?: boolean /** Enable "Group by" feature */ groupable?: boolean /** Custom empty state */ emptyState?: React.ReactNode /** Called when a row is clicked */ onRowClick?: (row: TData) => void /** * Id of the row a detail rail is currently showing (`null` when none is). * * Setting this — including to `null` — declares that rows open a rail, which * changes two things. The open row is marked, so the user can see which of * fifty rows the rail beside them is about. And every row becomes a * {@link railTriggerProps} target, so clicking a second row retargets the * rail instead of dismissing it and opening it again a frame later. * * The rail state lives with the consumer (usually a URL param, so a peeked * record survives a reload and can be shared); the table only reflects it. * Leave undefined on hubs whose rows navigate to a route. */ openRowId?: string | number | null /** Default sort */ defaultSort?: { key: string; dir: SortDir } /** Conditional formatting rules — apply bg color to cells based on value */ conditionalRules?: ConditionalRule[] } export interface PaginationConfig { /** Rows per page. Default 10. */ pageSize?: number /** Options shown in the page-size selector. Default [10, 25, 50, 100]. */ pageSizeOptions?: number[] }