import type { ReactNode } from "react"; import { OptionProp } from "../Select/SelectProps"; /** Extra string keys render as label:value rows on the card; number values (also allowed here) are silently never shown. */ export type Account = { /** Used as the React key and the dnd draggableId — must be unique and stable across renders. */ id: string; /** Fully replaces AccountCard for this card — onDelete/onOpen/isDeletable and expand/collapse never apply when set. */ customNode?: () => ReactNode; /** Excluded from the generic row list; shown as separate description text, only when expanded. */ details?: string; /** No special rendering — just another label:value row like any extra string key. */ name?: string; /** Same generic row treatment as `name` — not validated or used for an avatar. */ email?: string; /** Same generic row treatment as `name`/`email` — plain string, not a number. */ rating?: string; [key: string]: string | number | null | undefined | (() => ReactNode); }; /** One Kanban column: id, title, accent color, and its `Account` items. */ export type ColumnType = { /** Not read by the board; the key in the `data` record is the column's identity for drag and callbacks. */ id: string; /** Column header text; also used in the reorder-handle and delete-all aria-labels. */ title: string; /** Falls back to `colors.primary[500]` when omitted — used for the accent dot/border. */ color: string; /** Cards in display order; a drag reorders them in the board's internal state. */ items: Account[]; /** Column-level slot (e.g. totals); shown below the title expanded, and rotated in the collapsed rail. */ customNode?: () => ReactNode; }; /** Fired after a column rail is collapsed or expanded via the board UI. */ export type ColumnCollapseChangePayload = { /** Key of the toggled column in the `data` record. */ colId: string; /** True when the column is in the narrow collapsed rail. */ collapsed: boolean; /** Full collapsed map for all columns (same shape as `collapsedColumns` initial prop). */ collapsedColumns: Record; }; /** Payload of `onDrag` — the moved card, its source/target column ids and indexes, and the next column map. */ export type KanbanDragPayload = { /** Source column id (its key in the `data` record). */ from: string; /** Destination column id; equals `from` when reordered within one column. */ to: string; /** The dragged card. */ item: Account; /** Next column map with the move already applied, keyed by column id. */ columns: Record; /** Zero-based index of the card in the source column before the move. */ fromIndex: number; /** Zero-based index of the card in the destination column after the move. */ toIndex: number; }; /** Board permission gates; every flag defaults to true when omitted. */ export type KanbanPermissions = { /** Also gates the `kanbanEdit` button and card drag; column-reorder drag ignores this. */ isEditable?: boolean; /** Also gates the `kanbanCreate` button. */ isCreatable?: boolean; /** Also hides the column-delete trash icon on column-header hover, not just per-card delete. */ isDeletable?: boolean; /** false returns null for the whole board — nothing renders, not even header actions. */ isViewable?: boolean; }; /** Props for KanbanBoard — keyed columns (`data`) of draggable `Account` cards. */ export type KanbanBoardProps = { /** Base columns; `customColumns` below is merged on top, not into, this. */ data: Record; /** Merged on top of `data` — a matching column id in both silently overwrites the `data` entry, not merges its fields. */ customColumns?: Record; /** `isVisible` renders an inline dashed column when others exist, or a centered empty-state overlay when there are none. */ customizeColumn?: { isVisible?: boolean; /** Defaults to "Add column". */ buttonText?: string; /** Fires from both the inline dashed column and the empty-board overlay variant. */ onAddColumn?: () => void; }; /** Never fires for a `customNode` card — it owns its own delete UI, and is not gated by `permissions.isDeletable`. */ onDelete?: (account: Account) => void; /** Same `customNode` exemption as `onDelete`; unlike delete, `onOpen` has no permission gate at all. */ onOpen?: (account: Account) => void; /** Receives the deleted column's item ids, not the column's own id — the column identity itself is not passed. */ onColumnDelete?: (colId: (string | number)[]) => void; /** Fires after `columns` state already updated internally — a notification, not a gate; it cannot veto or revert the move. */ onDrag?: (payload: KanbanDragPayload) => void; /** Also true (skeletons show) whenever `preferences.baseurl` is set and its own fetch is still loading. */ isLoading?: boolean; /** Header filter select; renders only when `isSelect` is true. */ kanbanSelect?: { /** Gates the whole select — unlike `kanbanEdit`/`kanbanCreate`, never checked against `permissions`. */ isSelect?: boolean; options?: OptionProp[]; /** Never fires with undefined — picking the placeholder row again fires nothing. */ onSelectChange?: (option?: OptionProp) => void; selectPlaceholderText?: string; /** Defaults to `"12.5rem"` when omitted. */ width?: string; /** Controlled value passed straight to the underlying Select. */ value?: string; /** False renders the themed popup, which looks identical on Windows and macOS; a native select's list is OS-drawn. */ native?: boolean; }; /** Button shows only when this `isEditable` AND `permissions.isEditable` (default true) both allow it. */ kanbanEdit?: { isEditable?: boolean; onEdit?: () => void; /** Defaults to "Edit Layout". */ label?: string; }; /** Same `permissions.isCreatable` AND-gate as `kanbanEdit`. */ kanbanCreate?: { isCreatable?: boolean; onCreate?: () => void; /** Defaults to "Create New". */ label?: string; }; /** Renders cards via a virtualized react-window list; omitted, it auto-enables above 30 cards in that column — true/false always overrides. */ virtualization?: boolean; /** Gates edit, create, delete and view actions; each flag defaults to true when omitted. */ permissions?: KanbanPermissions; /** Per-column empty state for columns with no items. */ noItems?: { /** Also requires the column to have zero items — evaluated per column, not board-wide. */ isVisible?: boolean; /** Defaults to "No items in this column". */ text?: string; /** Defaults to "Add Item"; hidden entirely when `isEmptyTextOnly` is true. */ buttonText?: string; /** Receives the empty column the button was clicked in. */ onClick?: (column: ColumnType) => void; /** When true, the button never renders — only the text line shows. */ isEmptyTextOnly?: boolean; }; /** When true, columns can collapse to a narrow horizontal rail (full height). */ enableColumnCollapse?: boolean; /** Seeds collapsed state once on mount only — later prop changes are ignored unless `preferences.baseurl` is set to sync from saved prefs. */ collapsedColumns?: Record; /** Called when the user collapses or expands a column (after internal state updates). */ onColumnCollapseChange?: (payload: ColumnCollapseChangePayload) => void; /** Column drag ignores `permissions.isEditable` — reordering stays possible even when item drag is permission-locked. */ enableColumnReorder?: boolean; /** Called after column order changes (same shape as `data` keys order). */ onColumnReorder?: (columns: Record) => void; /** Board height (CSS length, e.g. `"100%"` to fill a sized parent); columns fill it. Defaults to the viewport minus 100px, min 500px. */ height?: string | number; /** Caps the board height; columns shrink to fit. */ maxH?: string | number; /** Same shape as Table's `preferences`; collapse state loads/saves to `json.collapsedColumns` only when `baseurl` is set. */ preferences?: { /** Must be non-empty to activate preferences loading/saving at all. */ baseurl: string; /** Sent verbatim as the `Authorization` header — include any `Bearer ` prefix yourself. */ token: string; /** The load fetch re-runs only when `baseurl` changes — changing key/name/page/orgId alone never refetches. */ key?: string; name?: string; page?: string; orgId?: string; }; };