Skip to main content

Table

A feature-rich data table with sorting, filtering, row selection, column reordering/visibility, and context menus. All table operations (sorting, filtering, reordering) are handled C++-side by Dear ImGui — the JS event callbacks are informational.

Basic usage

import { useRef, useEffect } from "react";
import { TableImperativeHandle } from "@xframes/common";

const columns = [
{ heading: "Name", fieldId: "name" },
{ heading: "Age", fieldId: "age", type: "number" as const },
];

const App = () => {
const tableRef = useRef<TableImperativeHandle>(null);

useEffect(() => {
tableRef.current?.setTableData([
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
]);
}, []);

return (
<XFrames.Node root style={{ height: "100%" }}>
<XFrames.Table
ref={tableRef}
columns={columns}
style={{ width: "100%", flex: 1 }}
/>
</XFrames.Node>
);
};

Props

PropTypeDescription
columnsColumnConfig[]Required. Column definitions
clipRowsnumberNumber of visible rows before scrolling
filterablebooleanShow per-column filter inputs
reorderablebooleanAllow column drag-to-reorder
hideablebooleanAllow column visibility toggles
contextMenuItems{ id: string; label: string }[]Right-click menu options for rows
onSort(event: TableSortEvent) => voidFires when a column is sorted
onFilter(event: TableFilterEvent) => voidFires when a column filter changes
onRowClick(event: TableRowClickEvent) => voidFires when a row is clicked
onItemAction(event: TableItemActionEvent) => voidFires when a context menu item is clicked

All four style variants are supported (style, hoverStyle, activeStyle, disabledStyle).

Column configuration

Each entry in the columns array configures one column:

FieldTypeDefaultDescription
headingstringRequired. Column header label
fieldIdstringKey matching row data objects
type"string" | "number" | "boolean""string"Data type — affects display and filtering
defaultHidebooleanfalseHidden by default (requires hideable on table)
defaultSortbooleanfalseSorted by default on first render
widthFixedbooleanfalseFixed column width
noSortbooleanfalseDisable sorting on this column
noResizebooleanfalseDisable column resize
noReorderbooleanfalseDisable column reorder
noHidebooleanfalsePin column — cannot be hidden
const columns = [
{ heading: "City", fieldId: "city", noHide: true },
{ heading: "Country", fieldId: "country" },
{ heading: "Population", fieldId: "population", type: "number" as const },
{ heading: "Area (km²)", fieldId: "area", type: "number" as const, defaultHide: true },
{ heading: "Megacity", fieldId: "megacity", type: "boolean" as const },
];

Data format

Data is an array of objects where keys match column fieldId values:

const data = [
{ city: "Tokyo", country: "Japan", population: 37400068, area: 2191, megacity: true },
{ city: "Delhi", country: "India", population: 30290936, area: 1484, megacity: true },
{ city: "Zurich", country: "Switzerland", population: 421878, area: 88, megacity: false },
];

tableRef.current?.setTableData(data);

Typed cells

The type field on a column controls how values are displayed and filtered:

TypeDisplayFilter
"string"Plain textText input — comma-separated includes, - prefix excludes
"number"Formatted numberText input — matches against the formatted display value
"boolean"Font Awesome icons (check/xmark)Dropdown — All / Yes / No

Sorting and filtering

Sorting and filtering are handled entirely C++-side. The onSort and onFilter callbacks are informational — use them for logging or syncing external state, not for performing the sort/filter yourself.

import { TableSortEvent, TableFilterEvent } from "@xframes/common";

const handleSort = useCallback((event: TableSortEvent) => {
const { columnIndex, sortDirection } = event.nativeEvent;
console.log(`Sorted column ${columnIndex}, direction: ${sortDirection}`);
}, []);

const handleFilter = useCallback((event: TableFilterEvent) => {
const { columnIndex, filterText } = event.nativeEvent;
console.log(`Filtered column ${columnIndex}: "${filterText}"`);
}, []);
note

Enable filtering with the filterable prop on the Table. Each column automatically gets the appropriate filter UI based on its type.

Row selection

Click a row to select it. The onRowClick callback provides the row index:

import { TableRowClickEvent } from "@xframes/common";

const handleRowClick = useCallback((event: TableRowClickEvent) => {
console.log(`Clicked row: ${event.nativeEvent.rowIndex}`);
}, []);

<XFrames.Table ref={tableRef} columns={columns} onRowClick={handleRowClick} />

Column reordering and visibility

  • reorderable — Users can drag column headers to reorder them.
  • hideable — Users can toggle column visibility via the column context menu.

When hideable is set, all columns become hideable by default. Use noHide: true on individual columns to pin them:

const columns = [
{ heading: "ID", fieldId: "id", noHide: true }, // Always visible
{ heading: "Name", fieldId: "name" }, // Can be hidden
{ heading: "Notes", fieldId: "notes", defaultHide: true }, // Hidden by default
];

<XFrames.Table ref={tableRef} columns={columns} hideable reorderable />

Context menus

Set contextMenuItems to show a right-click menu on table rows. The onItemAction callback fires with the row index and the selected action ID:

import { TableItemActionEvent } from "@xframes/common";

const handleItemAction = useCallback((event: TableItemActionEvent) => {
const { rowIndex, actionId } = event.nativeEvent;
console.log(`Action "${actionId}" on row ${rowIndex}`);
}, []);

<XFrames.Table
ref={tableRef}
columns={columns}
contextMenuItems={[
{ id: "view", label: "View Details" },
{ id: "delete", label: "Delete" },
]}
onItemAction={handleItemAction}
/>

Imperative handle

Load and manage table data programmatically via the ref:

MethodSignatureDescription
setTableData(data: any[]) => voidReplace the entire dataset
appendDataToTable(data: any[]) => voidAppend rows to existing data
setColumnFilter(columnIndex: number, filterText: string) => voidSet a column filter programmatically
clearFilters() => voidClear all active filters
import { useRef } from "react";
import { TableImperativeHandle } from "@xframes/common";

const tableRef = useRef<TableImperativeHandle>(null);

// Replace all data
tableRef.current?.setTableData(newData);

// Append rows
tableRef.current?.appendDataToTable(additionalRows);

// Filter column 2 to "USA"
tableRef.current?.setColumnFilter(2, "USA");

// Clear all filters
tableRef.current?.clearFilters();

Events reference

All event types are imported from @xframes/common. Payloads are accessed via event.nativeEvent.

Event TypePayload
TableSortEvent{ columnIndex: number; sortDirection: number }
TableFilterEvent{ columnIndex: number; filterText: string }
TableRowClickEvent{ rowIndex: number }
TableItemActionEvent{ rowIndex: number; actionId: string }

Complete example

import { useRef, useEffect, useCallback } from "react";
import {
TableImperativeHandle,
TableRowClickEvent,
TableItemActionEvent,
RWStyleSheet,
} from "@xframes/common";

const columns = [
{ heading: "City", fieldId: "city", noHide: true },
{ heading: "Country", fieldId: "country" },
{ heading: "Population", fieldId: "population", type: "number" as const },
{ heading: "Megacity", fieldId: "megacity", type: "boolean" as const },
];

const cities = [
{ city: "Tokyo", country: "Japan", population: 37400068, megacity: true },
{ city: "Delhi", country: "India", population: 30290936, megacity: true },
{ city: "Zurich", country: "Switzerland", population: 421878, megacity: false },
];

const styles = RWStyleSheet.create({
table: { width: "100%", flex: 1 },
});

const App = () => {
const tableRef = useRef<TableImperativeHandle>(null);

useEffect(() => {
tableRef.current?.setTableData(cities);
}, []);

const handleRowClick = useCallback((event: TableRowClickEvent) => {
console.log(`Selected row: ${event.nativeEvent.rowIndex}`);
}, []);

const handleAction = useCallback((event: TableItemActionEvent) => {
const { rowIndex, actionId } = event.nativeEvent;
console.log(`Action "${actionId}" on row ${rowIndex}`);
}, []);

return (
<XFrames.Node root style={{ height: "100%" }}>
<XFrames.Table
ref={tableRef}
columns={columns}
clipRows={20}
filterable
reorderable
hideable
onRowClick={handleRowClick}
contextMenuItems={[
{ id: "view", label: "View Details" },
{ id: "delete", label: "Delete" },
]}
onItemAction={handleAction}
style={styles.table}
/>
</XFrames.Node>
);
};