@omega/table (0.5.0)
Installation
@omega:registry=npm install @omega/table@0.5.0"@omega/table": "0.5.0"About this package
@omega/table
DataTable is the styled table facade over TanStack Table. Applications own
data loading and business behavior; the package owns table state, rendering,
accessibility, selection, filtering, column layout, and virtualization.
Styling contract
Import @omega/table/styles.css. Package styles consume the shared
--spp-ui-* theme tokens and expose stable semantic --spp-ui-table-*
properties. Add an application class through className and override tokens on
the same element:
<DataTable className="orders-table" data={rows} columns={columns} />
.orders-table {
--spp-ui-table-cell-px: 8px;
--spp-ui-table-cell-py: 4px;
--spp-ui-table-header-height: 36px;
--spp-ui-table-font-size: 13px;
--spp-ui-table-border-radius: 4px;
}
The defaults use :where(.spp-ui-table-root, .spp-ui-table__popover), so an
application class on the table root wins without !important. Portaled column
menu and filter popovers also carry the token defaults (and data-density)
because they render outside the table tree.
Public semantic tokens
- Surfaces:
--spp-ui-table-bg-surface,--spp-ui-table-bg-header,--spp-ui-table-bg-row,--spp-ui-table-bg-row-hover,--spp-ui-table-bg-row-selected,--spp-ui-table-bg-row-selected-hover,--spp-ui-table-bg-pinned. - Borders and typography:
--spp-ui-table-border-color,--spp-ui-table-border-radius,--spp-ui-table-font-size,--spp-ui-table-header-font-weight. - Density and spacing:
--spp-ui-table-row-height,--spp-ui-table-header-height,--spp-ui-table-cell-px,--spp-ui-table-cell-py,--spp-ui-table-header-cell-px,--spp-ui-table-floating-filter-px,--spp-ui-table-floating-filter-py. - Controls and menus:
--spp-ui-table-header-action-size,--spp-ui-table-control-radius,--spp-ui-table-control-hover-bg,--spp-ui-table-menu-min-width,--spp-ui-table-menu-item-min-height,--spp-ui-table-menu-item-padding. - Feedback:
--spp-ui-table-highlight-duration,--spp-ui-table-highlight-added-bg,--spp-ui-table-highlight-updated-bg,--spp-ui-table-highlight-deleted-bg,--spp-ui-table-row-disabled-opacity,--spp-ui-table-row-removing-opacity,--spp-ui-table-row-placeholder-opacity. - Overlays and popovers:
--spp-ui-table-overlay-min-height,--spp-ui-table-overlay-padding,--spp-ui-table-popover-min-width,--spp-ui-table-popover-bg,--spp-ui-table-popover-shadow,--spp-ui-table-popover-padding,--spp-ui-table-popover-gap.
Sticky positioning, z-indexes, table layout, and virtualization geometry are implementation details and are intentionally not theme tokens.
Layout and sizing
DataTable fills the size of its container (the AG Grid / MUI DataGrid model):
the root is a flex column with flex: 1 1 0% and height: 100%, only the body
scrolls, and the header, footer, and state overlays (loading / empty / error)
stay pinned within the same region. This avoids layout shift when rows arrive
or the table becomes empty.
The container must provide a definite size — typically a flex chain with
min-height: 0 at every level:
.screen-root {
display: flex;
flex-direction: column;
height: 100%; /* not `max-height`: the table fills the screen, it does not grow with content */
}
If the container has no definite height, the table degrades gracefully to
content height (embedded scenarios). To keep a content-sized table inside a
flex container, opt out explicitly via className:
.embedded-table {
flex: 0 0 auto;
height: auto;
}
Density
density provides coherent compact, standard, and comfortable presets.
It changes semantic row/header tokens and the default virtualization estimate.
Header vertical size is owned by --spp-ui-table-header-height (not padding).
Explicit CSS token values can refine a preset; an explicit
virtualization.estimateRowHeight takes precedence over its estimate.
Locale
Package defaults are Russian (defaultDataTableLocaleText /
ruDataTableLocaleText). For English UI pass the EN pack:
import { DataTable, enDataTableLocaleText } from '@omega/table';
<DataTable data={rows} columns={columns} localeText={enDataTableLocaleText} />;
Partial overrides still work; nested filterOperators merges correctly via
resolveDataTableLocaleText(overrides) (or with an explicit base).
Tree Data
treeData turns the table into a treegrid: hierarchical rows expand in place,
compose with sorting, filtering, pinning, virtualization, and column management.
The hierarchy source is a discriminated union — exactly one of getSubRows
(nested data) or getParentId (flat relational data). In tree mode getRowId
is required at the type level, because expansion state is keyed by row id.
<DataTable
data={flatRows}
columns={columns}
getRowId={(row) => row.id}
treeData={{
column: 'name',
getParentId: (row) => row.parentId,
defaultExpandedDepth: 1,
}}
/>
getRowHasChildrenshows an expander for nodes whose children are not loaded yet;onRowExpandedChangeis the seam for lazy level loading.- Lazy trees must also pass
childrenLoading = { getState, onRetry }— the explicit per-node state machine (idle | loading | success | error). The table renders a loading row forloading, an inline error row with a Retry button forerror, and treatssuccesswith nosubRowsas a leaf (the expander disappears until the state is reset). WithoutchildrenLoadingthe table shows no loading/error rows at all: it cannot distinguish "loading" from "loaded empty" or "failed" on its own. The application owns the request, cache, and data merge; the table owns only presentation. - Lazy children + page/data changes: cache loaded children by parent id
(e.g. a
childrenByIdmap or TanStack QueryqueryKey: ['children', id]) and merge the cache into the current page's roots. Expansion state survives page turns, so an addressed cache makes late responses safe and shows children instantly on return. Do not abort in-flight child requests on page change — the expanded node would otherwise wait forever, becauseonRowExpandedChangeonly fires on expansion changes. Abort on component unmount only, if at all. - Filtering follows the AG Grid default: a row is visible when it matches, any
ancestor matches, or any descendant matches — matching parents keep their
children, matching leaves keep their ancestor chain, and branches with
matches are derived-expanded while a filter is active (without overwriting
the user's
expandedstate). guidelines: truerenders opt-in hierarchy guides (VSCode / MUI X style indent lines with ├/└ connectors, terminated on last children). Styled via--spp-ui-table-tree-guide-color,--spp-ui-table-tree-guide-width, and--spp-ui-table-tree-guide-style.- Alt/Ctrl-click on an expander toggles the whole loaded branch; the tree column
menu adds "Expand all" / "Collapse all", and
apiRef.treeexposesexpandAll/collapseAll/expandRow/collapseRow/expandToDepth. - The tree column cannot be hidden; indentation is themed via
--spp-ui-table-tree-indent.
Pagination
pagination adds an AG Grid-style footer panel: page-size select, range
label ("1–50 из 8 618"), first/prev/next/last navigation and an arbitrary
page input. State is the TanStack PaginationState (pageIndex is 0-based)
and follows the table's controlled/uncontrolled pattern:
initialState.pagination, state.pagination, onPaginationChange.
// Client mode: all rows are already in `data`, the table slices pages itself.
<DataTable data={rows} columns={columns} pagination />
// Server mode: the host loads pages; the table counts pages by `rowCount`.
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 });
<DataTable
data={page.items}
columns={columns}
loading={query.isFetching}
pagination={{ mode: 'server', rowCount: page.total }}
state={{ pagination }}
onPaginationChange={setPagination}
/>;
The backend contract may be 1-based — adapt in the query layer, not in the
table: page: pagination.pageIndex + 1.
- Sorting/filtering resets to the first page (
autoResetPageIndex, defaulttrue). Changing the page size keeps the current top row in view. - Pagination is mutually exclusive with
virtualization(a page is bounded bypageSize; virtualization is ignored with a dev warning). - In tree mode
pageSize/rowCountcount top-level rows; expanded (including lazily loaded) children stay on their parent's page — the AG Grid "Paginate Only Top Level Rows" model. Client mode uses TanStackpaginateExpandedRows: false(expand after page slice). Server mode usespaginateExpandedRows: truebecausemanualPaginationskips the pagination row model that would otherwise perform the deferred expand. pagination.loadingblocks the pagination controls independently of the generalloading, so lazy children loading does not block page turns.- While a server page loads, the previous rows stay visible
(keepPreviousData style): row numbers keep the offset of the displayed
page until the new data arrives, and the body is non-interactive
(
pointer-events: noneontbody,aria-busyon the root), so rows and tree expanders of the outgoing page cannot be clicked. - Row numbering is continuous across pages; in tree mode only root rows are numbered.
apiRef.paginationexposesfirst/previous/next/last/goToPage/setPageSizefor custom external controls.
Footer composition (MUI DataGrid-style structural slots): slots.footer
replaces the whole footer, slots.pagination replaces only the pagination
panel inside the default footer. The deprecated slots.slotBelow /
slots.slotAbove aliases still work; prefer footer / toolbar.
Selection
Selection is keyed by stable getRowId and survives data updates (the AG
Grid / MUI DataGrid model): refetches with the same ids, lazy children
loads, and filtering never clear it. When rows are removed from the
data, only their ids are pruned from the selection — added rows (lazy
children, pagination loads) never reset it. In server-pagination mode the
selection lives within the current page: rows that leave the model on a
page turn are pruned (cross-page selection is a separate future feature).
Customization levels
- CSS variables and
densityfor visual customization. - Column definitions, metadata, class/style resolvers, and locale text.
- Slots and column-menu command customization for structural replacement.
useDataTableInstance,tableRef, andapiReffor advanced integrations.
Dependencies
Dependencies
| ID | Version |
|---|---|
| @dnd-kit/core | ^6.3.1 |
| @dnd-kit/sortable | ^10.0.0 |
| @dnd-kit/utilities | ^3.2.2 |
| @omega/icons | 0.2.0 |
| @omega/ui | 0.3.0 |
| classnames | 2.5.1 |
Development Dependencies
| ID | Version |
|---|---|
| @tanstack/react-table | 8.21.3 |
| @tanstack/react-virtual | 3.14.6 |
| react-aria-components | 1.19.0 |
Peer Dependencies
| ID | Version |
|---|---|
| @tanstack/react-table | >=8.20.0 |
| @tanstack/react-virtual | >=3.13.0 |
| react | >=18.3.0 |
| react-aria-components | >=1.19.0 |
| react-dom | >=18.3.0 |
Optional Dependencies
| ID | Version |
|---|---|
| @tanstack/react-table | 8.21.3 |
| @tanstack/react-virtual | 3.14.6 |