Omega-Izhevsk

@omega/table (0.5.2)

Published 2026-09-29 23:14:50 +03:00 by Roman

Installation

@omega:registry=
npm install @omega/table@0.5.2
"@omega/table": "0.5.2"

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,
  }}
/>
  • getRowHasChildren shows an expander for nodes whose children are not loaded yet; onRowExpandedChange is 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 for loading, an inline error row with a Retry button for error, and treats success with no subRows as a leaf (the expander disappears until the state is reset). Without childrenLoading the 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 childrenById map or TanStack Query queryKey: ['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, because onRowExpandedChange only 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 expanded state).
  • guidelines: true renders 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.tree exposes expandAll / 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, default true). Changing the page size keeps the current top row in view.
  • Pagination is mutually exclusive with virtualization (a page is bounded by pageSize; virtualization is ignored with a dev warning).
  • In tree mode pageSize/rowCount count 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 TanStack paginateExpandedRows: false (expand after page slice). Server mode uses paginateExpandedRows: true because manualPagination skips the pagination row model that would otherwise perform the deferred expand.
  • pagination.loading blocks the pagination controls independently of the general loading, 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: none on tbody, aria-busy on 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.pagination exposes first / previous / next / last / goToPage / setPageSize for 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

  1. CSS variables and density for visual customization.
  2. Column definitions, metadata, class/style resolvers, and locale text.
  3. Slots and column-menu command customization for structural replacement.
  4. useDataTableInstance, tableRef, and apiRef for 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
Details
npm
2026-09-29 23:14:50 +03:00
1
latest
132 KiB
Assets (1)
table-0.5.2.tgz 132 KiB
Versions (11) View all
0.5.2 2026-09-29
0.5.1 2026-09-29
0.5.0 2026-09-29
0.4.0 2026-09-25
0.3.0 2026-09-08