import { ReactNode } from 'react'; import { BooleanFormatterOptions, CurrencyFormatterOptions, DateFormatterOptions, DateTimeFormatterOptions, NumberFormatterOptions, PercentFormatterOptions, TimeFormatterOptions } from './formatters'; import { SelectMenuOption, SelectMenuGroupByValue } from '../SelectMenu'; import { SelectMenuSyncProps } from '../SelectMenu/SelectMenuSync'; import { SelectMenuProps } from '../SelectMenu/types'; import { MultiSelectMenuOption } from '../MultiSelectMenu'; import { MultiSelectMenuSyncProps } from '../MultiSelectMenu/MultiSelectMenuSync'; import { MultiSelectMenuProps } from '../MultiSelectMenu/types'; /** * Column type literals for display configuration. * Each type maps to a default formatter and alignment. */ export type ColumnType = "text" | "number" | "currency" | "percent" | "date" | "dateTime" | "time" | "boolean" | "markdown" | "html"; /** * Maps column types to their formatter options. * Used for type-safe formatter configuration. */ export type ColumnTypeOptionsMap = { text: undefined; number: NumberFormatterOptions; currency: CurrencyFormatterOptions; percent: PercentFormatterOptions; date: DateFormatterOptions; dateTime: DateTimeFormatterOptions; time: TimeFormatterOptions; boolean: BooleanFormatterOptions; markdown: undefined; html: undefined; }; /** * Column type configuration. * Can be a simple type string (uses defaults) or an object with custom options. * * @example * ```ts * // Simple - uses default formatter options * type: "currency" * * // With options - customizes formatter behavior * type: { type: "currency", options: { currency: "EUR", locale: "de-DE" } } * ``` */ export type ColumnTypeConfig = T | { type: T; options: ColumnTypeOptionsMap[T]; }; /** * Edit mode literals for editable cells. */ export type EditMode = "text" | "number" | "select" | "select-async" | "multiselect" | "multiselect-async" | "boolean"; /** * Base edit configuration shared by all edit modes. * @template T - The row data type * @template K - The column key */ type BaseEditConfig = { /** * Callback fired when the cell value is saved. * @param value - The new value * @param rowId - The id of the row being edited */ onChange: (value: T[K], rowId: string) => void; }; /** * Edit configuration for text mode cells. * @template T - The row data type * @template K - The column key */ export type TextEditConfig = BaseEditConfig & { /** * Text edit mode - renders an inline text input. */ mode: "text"; }; /** * Edit configuration for number mode cells. * Renders an inline number input with increment/decrement support. * * @template T - The row data type * @template K - The column key */ export type NumberEditConfig = BaseEditConfig & { /** * Number edit mode - renders an inline number input with formatting and validation. */ mode: "number"; /** * The minimum value for the number field. * @default 0 */ minValue?: number; /** * The maximum value for the number field. * @default 100000000000000 */ maxValue?: number; /** * The step value for the number field. * @default 1 */ step?: number; /** * The maximum number of decimal places for the number field. * @default 0 */ maximumFractionDigits?: number; /** * The minimum number of decimal places for the number field. * @default 0 */ minimumFractionDigits?: number; }; type SelectMenuCellPassthroughProps = Omit; type MultiSelectMenuCellPassthroughProps = Omit; /** * Edit configuration for select mode cells. * @template T - The row data type * @template K - The column key */ export type SelectEditConfig = Omit, "onChange"> & { /** * Select edit mode - renders a SelectMenu popover. */ mode: "select"; /** * The options available for selection. */ options: SelectMenuOption[]; /** * Callback fired when the selected option changes. * @param option - The newly selected option, or null if cleared * @param rowId - The id of the row being edited */ onChange: (option: SelectMenuOption | null, rowId: string) => void; } & SelectMenuCellPassthroughProps; /** * Edit configuration for multiselect mode cells. * @template T - The row data type * @template K - The column key */ export type MultiselectEditConfig = Omit, "onChange"> & { /** * Multiselect edit mode - renders a searchable MultiSelectMenu popover. */ mode: "multiselect"; /** * The options available for selection. */ options: MultiSelectMenuOption[]; /** * Callback fired when the selected options change. * @param options - The newly selected options * @param rowId - The id of the row being edited */ onChange: (options: MultiSelectMenuOption[], rowId: string) => void; } & MultiSelectMenuCellPassthroughProps; type SelectMenuAsyncCellPassthroughProps = Omit; type MultiSelectMenuAsyncCellPassthroughProps = Omit; /** * Context passed to `loadOptions` for async edit configs. * Provides row data for per-row option loading. * @template T - The row data type * @property row - The full row data object * @property rowId - The string id of the row */ export type AsyncCellContext = { row: T; rowId: string; }; /** * Eager loader for `mode: "select-async"` — returns all matching options at once. * @template T - The row data type */ export type SelectAsyncCellEagerLoader = (searchValue: string, context: AsyncCellContext) => SelectMenuOption[] | Promise; /** * Page-based lazy loader for `mode: "select-async"`. * @template T - The row data type */ export type SelectAsyncCellPageLoader = (searchValue: string, pageNumber: number, pageSize: number, context: AsyncCellContext) => { options: SelectMenuOption[]; hasMore?: boolean; } | Promise<{ options: SelectMenuOption[]; hasMore?: boolean; }>; /** * Offset-based lazy loader for `mode: "select-async"`. * @template T - The row data type */ export type SelectAsyncCellOffsetLoader = (searchValue: string, offset: number, limit: number, context: AsyncCellContext) => { options: SelectMenuOption[]; hasMore?: boolean; } | Promise<{ options: SelectMenuOption[]; hasMore?: boolean; }>; /** * Group-based lazy loader for `mode: "select-async"`. * @template T - The row data type */ export type SelectAsyncCellGroupLoader = (searchValue: string, previousGroupKey: SelectMenuGroupByValue | null, context: AsyncCellContext) => { options: SelectMenuOption[]; hasMore?: boolean; } | Promise<{ options: SelectMenuOption[]; hasMore?: boolean; }>; /** * Eager loader for `mode: "multiselect-async"` — returns all matching options at once. * @template T - The row data type */ export type MultiselectAsyncCellEagerLoader = (searchValue: string, context: AsyncCellContext) => MultiSelectMenuOption[] | Promise; /** * Page-based lazy loader for `mode: "multiselect-async"`. * @template T - The row data type */ export type MultiselectAsyncCellPageLoader = (searchValue: string, pageNumber: number, pageSize: number, context: AsyncCellContext) => { options: MultiSelectMenuOption[]; hasMore?: boolean; } | Promise<{ options: MultiSelectMenuOption[]; hasMore?: boolean; }>; /** * Offset-based lazy loader for `mode: "multiselect-async"`. * @template T - The row data type */ export type MultiselectAsyncCellOffsetLoader = (searchValue: string, offset: number, limit: number, context: AsyncCellContext) => { options: MultiSelectMenuOption[]; hasMore?: boolean; } | Promise<{ options: MultiSelectMenuOption[]; hasMore?: boolean; }>; /** * Group-based lazy loader for `mode: "multiselect-async"`. * @template T - The row data type */ export type MultiselectAsyncCellGroupLoader = (searchValue: string, previousGroupKey: SelectMenuGroupByValue | null, context: AsyncCellContext) => { options: MultiSelectMenuOption[]; hasMore?: boolean; } | Promise<{ options: MultiSelectMenuOption[]; hasMore?: boolean; }>; type SelectAsyncEditConfigBase = { /** * Callback fired when the selected option changes. * @param option - The newly selected option, or null if cleared * @param rowId - The id of the row being edited */ onChange: (option: SelectMenuOption | null, rowId: string) => void; } & SelectMenuAsyncCellPassthroughProps; /** * Edit configuration for async select mode cells. * Uses `SelectMenu` directly (not `SelectMenuSync`) to support server-side option loading. * * The `loadOptions` function receives a `context` argument as its last parameter, * containing the current row data and row id. This enables per-row option loading. * * By default, caching is disabled to prevent cross-row cache pollution when `loadOptions` * uses row context. Pass `cache={{ enabled: true }}` to override. * * Cell value is expected to be a primitive that matches a `SelectMenuOption.id`. * * @template T - The row data type */ export type SelectAsyncEditConfig = (SelectAsyncEditConfigBase & { mode: "select-async"; lazy?: false; loadOptions: SelectAsyncCellEagerLoader; lazyOptions?: never; }) | (SelectAsyncEditConfigBase & { mode: "select-async"; lazy: "page"; loadOptions: SelectAsyncCellPageLoader; lazyOptions?: { pageSize?: number; }; }) | (SelectAsyncEditConfigBase & { mode: "select-async"; lazy: "offset"; loadOptions: SelectAsyncCellOffsetLoader; lazyOptions?: { limit?: number; }; }) | (SelectAsyncEditConfigBase & { mode: "select-async"; lazy: "group"; loadOptions: SelectAsyncCellGroupLoader; lazyOptions?: object; }); type MultiselectAsyncEditConfigBase = { /** * Callback fired when the selected options change. * @param options - The newly selected options (full option objects) * @param rowId - The id of the row being edited */ onChange: (options: MultiSelectMenuOption[], rowId: string) => void; } & MultiSelectMenuAsyncCellPassthroughProps; /** * Edit configuration for async multiselect mode cells. * Uses `MultiSelectMenu` directly (not `MultiSelectMenuSync`) to support server-side option loading. * * The `loadOptions` function receives a `context` argument as its last parameter, * containing the current row data and row id. This enables per-row option loading. * * By default, caching is disabled to prevent cross-row cache pollution when `loadOptions` * uses row context. Pass `cache={{ enabled: true }}` to override. * * **Value contract**: Unlike `mode: "multiselect"` (which stores ID arrays), the cell value * for `mode: "multiselect-async"` must be `MultiSelectMenuOption[]` — full option objects. * The `onChange` callback receives these objects directly and the consumer stores them wholesale. * * @template T - The row data type */ export type MultiselectAsyncEditConfig = (MultiselectAsyncEditConfigBase & { mode: "multiselect-async"; lazy?: false; loadOptions: MultiselectAsyncCellEagerLoader; lazyOptions?: never; }) | (MultiselectAsyncEditConfigBase & { mode: "multiselect-async"; lazy: "page"; loadOptions: MultiselectAsyncCellPageLoader; lazyOptions?: { pageSize?: number; }; }) | (MultiselectAsyncEditConfigBase & { mode: "multiselect-async"; lazy: "offset"; loadOptions: MultiselectAsyncCellOffsetLoader; lazyOptions?: { limit?: number; }; }) | (MultiselectAsyncEditConfigBase & { mode: "multiselect-async"; lazy: "group"; loadOptions: MultiselectAsyncCellGroupLoader; lazyOptions?: object; }); /** * Edit configuration for boolean mode cells. * Renders a dropdown menu with predefined true/false options and an optional null option. * * @template T - The row data type * @template K - The column key */ export type BooleanEditConfig = BaseEditConfig & { /** * Boolean edit mode - renders a dropdown with true/false/null options. */ mode: "boolean"; /** * The label displayed for the `true` option. * @default "True" */ trueLabel?: string; /** * The label displayed for the `false` option. * @default "False" */ falseLabel?: string; /** * Whether to include a null option in the dropdown. * @default false */ allowNull?: boolean; /** * The label displayed for the `null` option. Only used when `allowNull` is true. * @default "Unset" */ nullLabel?: string; }; /** * Union of all edit configurations. * Use with `editConfig` property on column definitions. * * @template T - The row data type * @template K - The column key * * @example * ```ts * // Text editing * editConfig: { mode: "text", onChange: (value, rowId) => save(value, rowId) } * * // Number editing * editConfig: { * mode: "number", * minValue: 0, * maxValue: 1000, * step: 1, * onChange: (value, rowId) => save(value, rowId) * } * * // Select editing * editConfig: { * mode: "select", * options: [{ id: "active", label: "Active" }], * onChange: (option, rowId) => save(option?.id, rowId) * } * * // Boolean editing * editConfig: { * mode: "boolean", * trueLabel: "Yes", * falseLabel: "No", * onChange: (value, rowId) => save(value, rowId) * } * ``` */ export type EditConfig = TextEditConfig | NumberEditConfig | SelectEditConfig | SelectAsyncEditConfig | MultiselectEditConfig | MultiselectAsyncEditConfig | BooleanEditConfig; /** * The type for a table row * @extends T */ export type TableRow = T & { /** * The id of the row */ id: string | number; /** * Row metadata including errors */ meta?: { /** * Cell-level errors keyed by column id. * - `true` marks the cell as having an error * - A string marks the cell as having an error with an accessible error message */ errors?: Partial>; /** * Cell-level warnings keyed by column id. * - `true` marks the cell as having a warning * - A string marks the cell as having a warning with an accessible warning message * - When both an error and warning exist on the same cell, the error takes priority */ warnings?: Partial>; /** * Row-level error displayed in the first column. * - `true` marks the row as having an error * - A string marks the row as having an error with an accessible error message */ rowError?: boolean | string; /** * Row-level warning displayed in the first column. * - `true` marks the row as having a warning * - A string marks the row as having a warning with an accessible warning message * - When both a row error and warning exist, the error takes priority */ rowWarning?: boolean | string; }; /** * The sub component displayed when the row is expanded */ subComponent?: ReactNode; /** * The sub rows of the row when the row is expanded */ subRows?: TableRow[]; }; /** * Column definition type for DataTable columns. * * Use `createColumnHelper()` to create type-safe column definitions with * proper type inference for `type`, `editConfig`, and value types. * * @example * ```ts * type Data = { name: string; amount: number; tags: string[] }; * const createColumn = createColumnHelper(); * * const columns = [ * // Display-only column with type defaults * createColumn("amount", { header: { label: "Amount" }, type: "currency" }), * * // Editable column with editConfig * createColumn("name", { * header: { label: "Name" }, * type: "text", * editConfig: { mode: "text", onChange: (value, rowId) => save(value, rowId) } * }), * * // Multiselect editing * createColumn("tags", { * header: { label: "Tags" }, * editConfig: { * mode: "multiselect", * options: [{ value: "urgent", label: "Urgent" }], * onChange: (value, rowId) => save(value, rowId) * } * }), * ]; * ``` */ export type ColumnHeader = { /** * The visible header label text. */ label: string; /** * Whether the header should show a required indicator. */ required?: boolean; /** * Additional contextual content rendered from a keyboard-accessible more-info trigger. */ moreInfo?: ReactNode; }; export type ColumnHeaderConfig = { /** * The forward-looking header configuration. */ header: ColumnHeader; /** * Legacy header label support during the transition to `header`. * @deprecated Use `header.label` instead. */ headerLabel?: string; } | { /** * The forward-looking header configuration. */ header?: ColumnHeader; /** * Legacy header label support during the transition to `header`. * @deprecated Use `header.label` instead. */ headerLabel: string; }; export type ColumnDef = ColumnHeaderConfig & { /** * The id of the column - must be a key of T */ id: keyof T; /** * The alignment of the header and cell content. * When using `type`, this is set automatically but can be overridden. */ align?: "start" | "center" | "end"; /** * Columns of a group column */ columns?: ColumnDef[]; /** * Configuration for cell editing. Presence enables editing for the column. * Consolidates edit mode, options, and onChange into a single object. * * @example * ```ts * // Text editing * editConfig: { mode: "text", onChange: (value, rowId) => save(value, rowId) } * * // Select editing * editConfig: { * mode: "select", * options: [{ id: "active", label: "Active" }], * onChange: (option, rowId) => save(option?.id, rowId) * } * ``` */ editConfig?: EditConfig; /** * Custom content to display when a cell value is empty (null, undefined, or empty string). * Overrides the default em dash. When set on both the column and the DataTable, * the column-level value takes precedence. */ emptyCellContent?: ReactNode; /** * The content of the footer cell. An array will display multiple footer rows */ footerContent?: ReactNode | ReactNode[]; /** * The maximum width of the column, in pixels */ maxWidth?: number; /** * The minimum width of the column, in pixels */ minWidth?: number; /** * The pinning location of the column */ pinned?: "left" | "right"; /** * The function to customize how the cell content is rendered. * When using `type`, a default renderer is provided but can be overridden. */ renderCell?: (value: T[keyof T], { row, depth }: { row?: T; depth?: number; }) => ReactNode; /** * Whether the column is resizable */ resizable?: boolean; /** * Whether the column is sortable, or a function to customize the sorting logic. * When a function, receives values of the column's value type. */ sortable?: boolean | ((valueA: T[keyof T], valueB: T[keyof T]) => number); /** * The column type for automatic display configuration. * Sets default `renderCell` (formatter) and `align` based on the data type. * * @example * ```ts * // Simple - uses default formatter options * type: "currency" * * // With options - customizes formatter behavior * type: { type: "currency", options: { currency: "EUR", locale: "de-DE" } } * ``` */ type?: ColumnTypeConfig; }; /** * The type for a custom table footer cell props */ export type CustomTableFooterCellProps = { /** * The content of the footer cell */ content: ReactNode; /** * The number of columns the footer cell should span */ colSpan: number; }; export {};