@elabs-ai/components-data 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,15 +6,14 @@
6
6
  > TanStack DataTable, FilterBar, SearchInput, FacetFilter, ColumnPicker.
7
7
 
8
8
  Part of **brand-ui**, a source-owned, token-driven React component system.
9
- These packages are **private** and are not published to any registry they are
10
- consumed from this workspace. See `docs/CONSUMING.md`.
9
+ Published to the **public npm registry** under the `@elabs-ai` scopeit
10
+ installs like any other npm dependency, with no registry configuration and
11
+ no token required. See `docs/CONSUMING.md`.
11
12
 
12
13
  ## Install
13
14
 
14
- Inside this monorepo the packages resolve as workspace dependencies:
15
-
16
- ```json
17
- "@elabs-ai/components-data": "workspace:*"
15
+ ```bash
16
+ pnpm add @elabs-ai/components-tokens @elabs-ai/components-data
18
17
  ```
19
18
 
20
19
  ## Set up styling (do not skip)
@@ -39,7 +38,7 @@ import { ThemeProvider } from "@elabs-ai/components-tokens";
39
38
 
40
39
  ## What's in it
41
40
 
42
- 5 exported components — including `ColumnPicker`, `DataTable`, `FacetFilter`, `FilterBar`, `SearchInput`.
41
+ 6 exported components — including `ColumnPicker`, `DataTable`, `FacetFilter`, `FilterBar`, `FilterChip`.
43
42
 
44
43
  Don't guess the API — ask the CLI:
45
44
 
@@ -69,6 +68,6 @@ prompt for migrating an existing project: `docs/CONSUMING.md`.
69
68
 
70
69
  ## License
71
70
 
72
- UNLICENSED — private.
71
+ MIT
73
72
 
74
73
  <!-- brand-ui:gen:readme:end -->
package/dist/index.d.ts CHANGED
@@ -1,8 +1,30 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, InputHTMLAttributes, ButtonHTMLAttributes, Ref, ReactElement } from 'react';
3
- import { ColumnDef, Table, SortingState, OnChangeFn, VisibilityState, ColumnFiltersState, PaginationState, ColumnPinningState, Row } from '@tanstack/react-table';
4
- export { CellContext, ColumnDef, ColumnPinningState, Row, Table } from '@tanstack/react-table';
3
+ import { RowData, ColumnDef, Table, SortingState, OnChangeFn, VisibilityState, ColumnFiltersState, PaginationState, ColumnPinningState, ColumnSizingState, RowSelectionState, Row } from '@tanstack/react-table';
4
+ export { CellContext, ColumnDef, ColumnMeta, ColumnPinningState, ColumnSizingState, Row, RowSelectionState, Table } from '@tanstack/react-table';
5
+ import { FilterChipProps as FilterChipProps$1 } from '@elabs-ai/components-ui';
5
6
 
7
+ /**
8
+ * `DataTable`'s `columnDef.meta` contract, read by the header/body/skeleton
9
+ * cell renderers. Set `numeric: true` on a column to get `tabular-nums` +
10
+ * end-alignment on both the `<th>` and every `<td>` (including the loading
11
+ * skeleton) for free.
12
+ */
13
+ interface DataTableColumnMeta {
14
+ /** Numeric column: tabular figures + end alignment on header and cells. */
15
+ numeric?: boolean;
16
+ /**
17
+ * Explicit alignment override for when `numeric` isn't the right cue (or
18
+ * to align a non-numeric column). Independent of `numeric` — `numeric`
19
+ * alone still drives `tabular-nums` even when `align` overrides the
20
+ * alignment away from `"end"`.
21
+ */
22
+ align?: "start" | "center" | "end";
23
+ }
24
+ declare module "@tanstack/react-table" {
25
+ interface ColumnMeta<TData extends RowData, TValue> extends DataTableColumnMeta {
26
+ }
27
+ }
6
28
  /** Snapshot of table slice state — used for saved-view serialise/rehydrate. */
7
29
  interface DataTableViewState {
8
30
  sorting: SortingState;
@@ -16,6 +38,17 @@ interface DataTableViewState {
16
38
  * that already constructs a `DataTableViewState` literal.
17
39
  */
18
40
  columnPinning?: ColumnPinningState;
41
+ /**
42
+ * Which rows are checked (#11), keyed by row id — see `getRowId`. OPTIONAL
43
+ * like `columnPinning`, for the same reason: the other members predate it.
44
+ */
45
+ rowSelection?: RowSelectionState;
46
+ /**
47
+ * Per-column widths after resizing (#12), keyed by column id. OPTIONAL like
48
+ * `columnPinning`/`rowSelection`, for the same reason: the other members
49
+ * predate it.
50
+ */
51
+ columnSizing?: ColumnSizingState;
19
52
  }
20
53
  /**
21
54
  * Argument object fired by `onServerChange` whenever a manual slice changes.
@@ -93,6 +126,81 @@ interface DataTableProps<TData, TValue> extends Omit<React.HTMLAttributes<HTMLDi
93
126
  */
94
127
  columnPinning?: ColumnPinningState;
95
128
  onColumnPinningChange?: OnChangeFn<ColumnPinningState>;
129
+ /**
130
+ * Opt in to column resizing (#12): a drag handle renders on every
131
+ * resizable column's trailing edge — pointer-draggable (TanStack's own
132
+ * `header.getResizeHandler()`) and keyboard-operable (ArrowLeft/ArrowRight
133
+ * on the focused handle, per the WAI-ARIA separator-as-slider practice).
134
+ * Default `false` so a table that doesn't opt in renders byte-identical
135
+ * markup to before this feature existed — no handle, no per-cell width
136
+ * styling.
137
+ */
138
+ enableColumnResizing?: boolean;
139
+ /**
140
+ * When `columnSizing` updates: `"onChange"` (default here — TanStack's own
141
+ * default is `"onEnd"`) live-updates while dragging; `"onEnd"` updates once
142
+ * on release. Only meaningful when `enableColumnResizing` is set.
143
+ */
144
+ columnResizeMode?: "onChange" | "onEnd";
145
+ /**
146
+ * Controlled column-widths state (#12), keyed by column id — the SAME
147
+ * controlled/uncontrolled shape as `columnPinning`/`rowSelection`.
148
+ * Uncontrolled sizing can be seeded once via `initialView.columnSizing`.
149
+ *
150
+ * A pinned column's sticky offset (`getStart("left")`/`getAfter("right")`)
151
+ * already sums `column.getSize()`, which folds in a `columnSizing`
152
+ * override automatically — so pinning and resizing compose with no extra
153
+ * wiring once this state reaches the table.
154
+ *
155
+ * Sizing is a LAYOUT concern, like `columnPinning`/`rowSelection` — it is
156
+ * client-only and never joins `DataTableServerArgs` / `onServerChange`.
157
+ */
158
+ columnSizing?: ColumnSizingState;
159
+ onColumnSizingChange?: OnChangeFn<ColumnSizingState>;
160
+ /**
161
+ * Controlled row-selection state (#11) — which rows are checked, keyed by
162
+ * row id (see `getRowId`). When provided the component is
163
+ * selection-controlled; otherwise it manages the slice internally and can
164
+ * be seeded once via `initialView.rowSelection`. Pair it with a selection
165
+ * column built by `createSelectionColumn` (or drive it yourself off the
166
+ * `table` instance handed to `toolbar`).
167
+ *
168
+ * Selection is a LAYOUT/UI concern, not a query concern — like
169
+ * `columnPinning`, it is client-only and never joins `DataTableServerArgs` /
170
+ * `onServerChange`.
171
+ */
172
+ rowSelection?: RowSelectionState;
173
+ onRowSelectionChange?: OnChangeFn<RowSelectionState>;
174
+ /**
175
+ * Which rows can be selected: `true`/`false` for all rows, or a predicate
176
+ * evaluated per row. Passed straight through to `useReactTable`. Default
177
+ * (TanStack's own): `true`.
178
+ */
179
+ enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
180
+ /**
181
+ * Allow more than one row to be selected at once. Default (TanStack's own):
182
+ * `true`. Set `false` for single-select (radio-style) behaviour.
183
+ */
184
+ enableMultiRowSelection?: boolean;
185
+ /**
186
+ * Stable row id, independent of row INDEX. TanStack's default id is set
187
+ * ONCE per row object when the core row model is built, then reused by
188
+ * reference through sorting/filtering — so a client-side sort or filter
189
+ * does NOT disturb selection identity even without this prop. The real
190
+ * hazard is a `data` array replacement: when the app passes NEW object
191
+ * references (a re-fetch, an optimistic update), TanStack rebuilds the
192
+ * core row model from scratch and reassigns default (index-based) ids, so a
193
+ * row that kept its position but got a new object still keeps its
194
+ * selection — but one that MOVED position silently inherits whatever
195
+ * selection belonged to the id now sitting at its old index. This is
196
+ * unavoidable under `manualPagination`: each page IS a fresh `data` array,
197
+ * so the default index-based id restarts at `0` on every page and a
198
+ * selection made on one page can collide with a different record on the
199
+ * next. Supply `getRowId` whenever `data` can be replaced with new object
200
+ * references (including every server-paginated table) so identity survives
201
+ * the replacement instead of falling back to index.
202
+ */
203
+ getRowId?: (row: TData, index: number) => string;
96
204
  /**
97
205
  * One-shot rehydrate for uncontrolled slices only (ignored for any slice
98
206
  * whose corresponding controlled prop is set). Maps to `useReactTable`'s
@@ -158,6 +266,46 @@ interface DataTableProps<TData, TValue> extends Omit<React.HTMLAttributes<HTMLDi
158
266
  * (a `border-border-strong` divider between rows, no stripes).
159
267
  */
160
268
  zebra?: boolean;
269
+ /**
270
+ * Opt-in row drag-reorder. Off by default — an existing table renders
271
+ * byte-identical markup with no extra DOM per row until this is set.
272
+ * Fully controlled like every other slice: the component never mutates
273
+ * `data` itself, it only reports the move via `onRowReorder`; the caller
274
+ * re-orders `data` in response.
275
+ *
276
+ * Keyboard-operable out of the box (`@dnd-kit`'s default keyboard sensor):
277
+ * Space/Enter picks a row up, Arrow Up/Down moves it, Space/Enter drops it,
278
+ * Escape cancels. Every position change is announced through a live region
279
+ * (WCAG 4.1.3).
280
+ *
281
+ * Mutually exclusive with `enableRowVirtualization` — a windowed table
282
+ * can't keep dnd-kit's sortable list and a virtualizer in sync, so reorder
283
+ * is silently disabled (a dev warning fires) when both are set. Combining
284
+ * it with active `sorting` also fires a dev warning (both still work, but
285
+ * a sort re-orders the very rows a drag just moved, which reads as broken).
286
+ */
287
+ enableRowReorder?: boolean;
288
+ /**
289
+ * Fires when a row is dropped in a new position. `from`/`to` are indices
290
+ * into the **`data` array you passed in** — never into the sorted, filtered
291
+ * or paginated view the table renders — so they are safe to use directly
292
+ * with `arrayMove`/`slice`+`splice`/immer against your own `data`, unchanged
293
+ * by an active sort or by client-side pagination (the dragged row's true
294
+ * index in the full array, not its index on the current page). Under
295
+ * `manualPagination`, `data` IS the current page, so `from`/`to` are
296
+ * page-relative — reorder that page's own array with them. `row` is the
297
+ * moved record (`data[from]`).
298
+ */
299
+ onRowReorder?: (from: number, to: number, row: TData) => void;
300
+ /**
301
+ * Where the drag activator lives. `"cell"` (default) renders a dedicated
302
+ * grip-handle column so the rest of the row keeps its ordinary click/
303
+ * keyboard behavior untouched. `"row"` makes the whole row itself the drag
304
+ * activator (no extra column) — reach for this only when the row has no
305
+ * other primary interaction (e.g. no `onRowClick`), since a whole-row
306
+ * activator and a row click target the same surface.
307
+ */
308
+ rowReorderHandle?: "cell" | "row";
161
309
  /**
162
310
  * Fires when a row is activated (#337). Setting it adds ONE activation
163
311
  * target per row: a visually-hidden `<button>` rendered inside the row's
@@ -198,6 +346,19 @@ interface DataTableProps<TData, TValue> extends Omit<React.HTMLAttributes<HTMLDi
198
346
  emptyMessage?: ReactNode;
199
347
  className?: string;
200
348
  }
349
+ /**
350
+ * Ready-made checkbox selection column (#11): header select-all (with a real
351
+ * `indeterminate` state for a partial page selection) + a per-row checkbox,
352
+ * both built on `@elabs-ai/components-ui`'s `Checkbox` — never hand-roll one.
353
+ *
354
+ * Add it to `columns` and pair it with `rowSelection` / `onRowSelectionChange`
355
+ * (or leave both uncontrolled and read `table.getSelectedRowModel()` from a
356
+ * `toolbar` render-prop to build a bulk-action bar).
357
+ *
358
+ * Declares an explicit `size` (40px) so it plays nicely if a caller pins it —
359
+ * every pinned column must declare one (#333) — without the dev warning.
360
+ */
361
+ declare function createSelectionColumn<TData>(): ColumnDef<TData>;
201
362
  declare const DataTableWithRef: <TData, TValue>(props: DataTableProps<TData, TValue> & {
202
363
  ref?: React.Ref<HTMLDivElement>;
203
364
  }) => React.ReactElement | null;
@@ -230,6 +391,34 @@ interface FilterBarProps {
230
391
  /** Horizontal toolbar that groups table filters and actions. */
231
392
  declare function FilterBar({ children, actions, className }: FilterBarProps): react.JSX.Element;
232
393
 
394
+ interface FilterChipProps extends Omit<FilterChipProps$1, "label" | "trailing"> {
395
+ /**
396
+ * Label-in-value text — `"Status: Failed"`, never `"Status = failed"` and
397
+ * never a bare `"Failed"`. Same contract as the base `FilterChip`.
398
+ */
399
+ label: string;
400
+ /**
401
+ * How many records this active filter excluded (or matched) — rendered as a
402
+ * secondary, locale-formatted segment alongside `label`. Omit for a bare
403
+ * chip with no count.
404
+ */
405
+ count?: number;
406
+ /**
407
+ * The word placed before the formatted count, e.g. `"excluded"` →
408
+ * `"excluded 1,204"`. Omitted by default: a bare `count` renders as just the
409
+ * formatted number.
410
+ */
411
+ countLabel?: string;
412
+ }
413
+ /**
414
+ * A removable active-filter chip with an optional secondary count.
415
+ *
416
+ * `onRemove` stays REQUIRED (inherited from the base `FilterChip`, diverging
417
+ * from this item's spec draft) — the whole chip IS the remove control, so a
418
+ * chip with no removal affordance is a plain `Badge`, not this component.
419
+ */
420
+ declare const FilterChip: react.ForwardRefExoticComponent<FilterChipProps & react.RefAttributes<HTMLButtonElement>>;
421
+
233
422
  interface FacetOption {
234
423
  label: string;
235
424
  value: string;
@@ -290,4 +479,4 @@ declare function toCsv<TData extends Record<string, unknown>>(rows: TData[], opt
290
479
  */
291
480
  declare function downloadCsv<TData extends Record<string, unknown>>(rows: TData[], opts?: DownloadCsvOptions<TData>): void;
292
481
 
293
- export { ColumnPicker, type ColumnPickerProps, type CsvColumn, DataTableWithRef as DataTable, type DataTableProps, type DataTableRowClickHandler, type DataTableServerArgs, type DataTableViewState, type DownloadCsvOptions, FacetFilter, type FacetFilterProps, type FacetOption, FilterBar, type FilterBarProps, SearchInput, type SearchInputProps, type ToCsvOptions, downloadCsv, toCsv };
482
+ export { ColumnPicker, type ColumnPickerProps, type CsvColumn, DataTableWithRef as DataTable, type DataTableColumnMeta, type DataTableProps, type DataTableRowClickHandler, type DataTableServerArgs, type DataTableViewState, type DownloadCsvOptions, FacetFilter, type FacetFilterProps, type FacetOption, FilterBar, type FilterBarProps, FilterChip, type FilterChipProps, SearchInput, type SearchInputProps, type ToCsvOptions, createSelectionColumn, downloadCsv, toCsv };