@elabs-ai/components-data 4.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Manuel Reimitz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ <!-- brand-ui:gen:readme:start -->
2
+ <!-- Generated by scripts/gen-package-readmes.mjs — do not edit inside these markers. -->
3
+
4
+ # `@elabs-ai/components-data`
5
+
6
+ > TanStack DataTable, FilterBar, SearchInput, FacetFilter, ColumnPicker.
7
+
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`.
11
+
12
+ ## Install
13
+
14
+ Inside this monorepo the packages resolve as workspace dependencies:
15
+
16
+ ```json
17
+ "@elabs-ai/components-data": "workspace:*"
18
+ ```
19
+
20
+ ## Set up styling (do not skip)
21
+
22
+ Components are Tailwind v4 classes backed by semantic tokens. Two lines in
23
+ your CSS entry, or **everything renders unstyled** — the single most common
24
+ mistake:
25
+
26
+ ```css
27
+ @import "@elabs-ai/components-tokens/styles.css";
28
+ @source "../node_modules/@elabs-ai/components-data/dist";
29
+ ```
30
+
31
+ The `@source` line is required because Tailwind ignores `node_modules`. Add
32
+ one per brand-ui package you render. Then wrap your app once:
33
+
34
+ ```tsx
35
+ import { ThemeProvider } from "@elabs-ai/components-tokens";
36
+
37
+ <ThemeProvider defaultTheme="light">{children}</ThemeProvider>;
38
+ ```
39
+
40
+ ## What's in it
41
+
42
+ 5 exported components — including `ColumnPicker`, `DataTable`, `FacetFilter`, `FilterBar`, `SearchInput`.
43
+
44
+ Don't guess the API — ask the CLI:
45
+
46
+ ```bash
47
+ pnpm add -D @elabs-ai/components-cli
48
+ pnpm exec brand-ui search <query> # find a component
49
+ pnpm exec brand-ui docs <Name> # its real props, from source
50
+ ```
51
+
52
+ ## Using an AI coding agent?
53
+
54
+ For Claude Code, install the plugin from this repo's checkout:
55
+
56
+ ```
57
+ /plugin marketplace add .
58
+ /plugin install brand-ui
59
+ ```
60
+
61
+ `brand-ui docs <Name>` returns intent, composition, state→token mappings and
62
+ anti-patterns — tell your agent to run it instead of guessing a prop. The CLI
63
+ also runs as an MCP server (`brand-ui mcp`).
64
+
65
+ ## Full guide
66
+
67
+ Tailwind and Next.js wiring, per-package extras, agent enablement and a
68
+ prompt for migrating an existing project: `docs/CONSUMING.md`.
69
+
70
+ ## License
71
+
72
+ UNLICENSED — private.
73
+
74
+ <!-- brand-ui:gen:readme:end -->
@@ -0,0 +1,293 @@
1
+ import * as react from 'react';
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';
5
+
6
+ /** Snapshot of table slice state — used for saved-view serialise/rehydrate. */
7
+ interface DataTableViewState {
8
+ sorting: SortingState;
9
+ columnVisibility: VisibilityState;
10
+ columnFilters: ColumnFiltersState;
11
+ globalFilter?: string;
12
+ pagination?: PaginationState;
13
+ /**
14
+ * Which columns are frozen to the left/right edge (#333). OPTIONAL on purpose:
15
+ * the other members predate it, and a required key would break every consumer
16
+ * that already constructs a `DataTableViewState` literal.
17
+ */
18
+ columnPinning?: ColumnPinningState;
19
+ }
20
+ /**
21
+ * Argument object fired by `onServerChange` whenever a manual slice changes.
22
+ * The consuming app should re-fetch with these params and update `data`.
23
+ */
24
+ interface DataTableServerArgs {
25
+ pagination: PaginationState;
26
+ sorting: SortingState;
27
+ columnFilters: ColumnFiltersState;
28
+ globalFilter: string;
29
+ }
30
+ /**
31
+ * Fires when a row is activated (#337).
32
+ *
33
+ * Both activation paths deliver a `click`: a pointer click on the row body, and
34
+ * a keyboard Enter/Space on the row's hidden activation `<button>` (which the
35
+ * browser dispatches as a click). So the handler takes ONE event type — there is
36
+ * nothing for the caller to branch on.
37
+ */
38
+ type DataTableRowClickHandler<TData> = (row: Row<TData>, event: React.MouseEvent<HTMLElement>) => void;
39
+ interface DataTableProps<TData, TValue> extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
40
+ columns: ColumnDef<TData, TValue>[];
41
+ data: TData[];
42
+ /** Render a toolbar above the table; receives the table instance. */
43
+ toolbar?: (table: Table<TData>) => ReactNode;
44
+ /** Enable client-side pagination. */
45
+ enablePagination?: boolean;
46
+ pageSize?: number;
47
+ /**
48
+ * Hide the pager once there's genuinely only one page
49
+ * (`table.getPageCount() <= 1`). Default `true`. When `manualPagination` is
50
+ * set without `rowCount`/`pageCount`, the page count isn't knowable (TanStack
51
+ * falls back to the current page's row count) — in that ambiguous case the
52
+ * pager still renders regardless of this flag, so the existing dev warning
53
+ * (#227) stays the diagnostic instead of a silently-hidden pager. Set to
54
+ * `false` to always show the pager (e.g. while a server total is still
55
+ * loading and you'd rather show a disabled pager than none).
56
+ */
57
+ hidePaginationWhenSingle?: boolean;
58
+ /**
59
+ * Controlled global filter value. When provided, the table reflects this
60
+ * value and the component manages no internal filter state. Keep the source
61
+ * of truth in the app and pass it down — never mutate the filter during
62
+ * render (e.g. `table.setGlobalFilter()` in `toolbar`), which loops.
63
+ */
64
+ globalFilter?: string;
65
+ /** Fires when the table requests a global-filter change (e.g. from typeahead). */
66
+ onGlobalFilterChange?: (value: string) => void;
67
+ /** Controlled sorting state. When provided the component is sorted-controlled. */
68
+ sorting?: SortingState;
69
+ onSortingChange?: OnChangeFn<SortingState>;
70
+ /** Controlled column-visibility state. */
71
+ columnVisibility?: VisibilityState;
72
+ onColumnVisibilityChange?: OnChangeFn<VisibilityState>;
73
+ /** Controlled column-filters state. */
74
+ columnFilters?: ColumnFiltersState;
75
+ onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>;
76
+ /** Controlled pagination state. */
77
+ pagination?: PaginationState;
78
+ onPaginationChange?: OnChangeFn<PaginationState>;
79
+ /**
80
+ * Controlled column-pinning state (#333) — the columns frozen against the
81
+ * left and/or right edge while the rest of the table scrolls horizontally.
82
+ * When provided the component is pinning-controlled; otherwise it manages the
83
+ * slice internally and can be seeded once via `initialView.columnPinning`.
84
+ *
85
+ * A pinned column MUST declare an explicit `size` in its `ColumnDef`: the
86
+ * sticky offset is computed from TanStack's `column.getStart("left")` /
87
+ * `getAfter("right")`, which sum the DECLARED sizes, so an auto-width column
88
+ * would render at a width that doesn't match its own offset. A dev-only
89
+ * warning fires for a pinned column with no `size`.
90
+ *
91
+ * Pinning is a LAYOUT concern, not a query concern — it is client-only and
92
+ * never joins `DataTableServerArgs` / `onServerChange`.
93
+ */
94
+ columnPinning?: ColumnPinningState;
95
+ onColumnPinningChange?: OnChangeFn<ColumnPinningState>;
96
+ /**
97
+ * One-shot rehydrate for uncontrolled slices only (ignored for any slice
98
+ * whose corresponding controlled prop is set). Maps to `useReactTable`'s
99
+ * `initialState`.
100
+ */
101
+ initialView?: Partial<DataTableViewState>;
102
+ /**
103
+ * When true, sorting is handled by the server. Pass `sorting` (controlled)
104
+ * and handle `onServerChange` to re-fetch with the new sort params.
105
+ * NOTE: controlled ≠ manual — a controlled `sorting` with `manualSorting:false`
106
+ * still sorts locally.
107
+ */
108
+ manualSorting?: boolean;
109
+ /**
110
+ * When true, filtering is handled by the server.
111
+ * NOTE: a controlled `columnFilters` with `manualFiltering:false` still
112
+ * filters locally.
113
+ */
114
+ manualFiltering?: boolean;
115
+ /** When true, pagination is handled by the server. */
116
+ manualPagination?: boolean;
117
+ /**
118
+ * Total row count — used by the server model so TanStack can derive
119
+ * page count. Required when `manualPagination` is true and `pageCount` is
120
+ * not provided.
121
+ */
122
+ rowCount?: number;
123
+ /**
124
+ * Total page count — alternative to `rowCount` for server pagination. When
125
+ * both are provided, `pageCount` wins.
126
+ */
127
+ pageCount?: number;
128
+ /**
129
+ * Fired after any manual-slice change with the current {pagination, sorting,
130
+ * columnFilters, globalFilter}. The component never fetches; the app must
131
+ * re-fetch and update `data`.
132
+ */
133
+ onServerChange?: (args: DataTableServerArgs) => void;
134
+ /** When true: overlay spinner; on empty+loading show skeleton rows instead of empty message. */
135
+ loading?: boolean;
136
+ /**
137
+ * Opt-in to row virtualization (for very large lists). Mutually exclusive
138
+ * with enablePagination in practice — if both are set, virtualization wins
139
+ * and pagination is silently ignored.
140
+ */
141
+ enableRowVirtualization?: boolean;
142
+ /** Estimated row height in px (used by the virtualizer). Default: 40. */
143
+ estimateRowHeight?: number;
144
+ /** Virtualizer overscan (rows rendered above/below the visible window). Default: 8. */
145
+ overscan?: number;
146
+ /** CSS max-height of the scroll container in virtualized mode. Default: "32rem". */
147
+ maxBodyHeight?: string;
148
+ /**
149
+ * Number of skeleton placeholder rows to render while loading.
150
+ * Defaults to `pageSize` (non-virtualized) or `min(10, pageSize)` (virtualized).
151
+ */
152
+ loadingRows?: number;
153
+ /**
154
+ * Gentle alternating row stripes ("zebra") as the row-separation cue, instead
155
+ * of a hairline divider between every row. Default `true` — the stripe is the
156
+ * single separation gesture, so rows carry no divider (a divider on a striped
157
+ * row would be a redundant boundary). Set `false` for the classic line model
158
+ * (a `border-border-strong` divider between rows, no stripes).
159
+ */
160
+ zebra?: boolean;
161
+ /**
162
+ * Fires when a row is activated (#337). Setting it adds ONE activation
163
+ * target per row: a visually-hidden `<button>` rendered inside the row's
164
+ * first cell. That button is the row's keyboard tab stop and its accessible
165
+ * name; a pointer click anywhere else in the row resolves to the same
166
+ * handler, so mouse and keyboard converge on one control instead of two
167
+ * competing ones (a focusable `<tr>` cannot carry an activation role without
168
+ * destroying `row` table semantics).
169
+ *
170
+ * Guarded: a click that originates on a nested interactive control
171
+ * (button/link/input/checkbox/…) or is the tail end of a text-selection drag
172
+ * does NOT fire it. Optional; omitting it renders rows exactly as before.
173
+ */
174
+ onRowClick?: DataTableRowClickHandler<TData>;
175
+ /**
176
+ * Accessible name for the row's hidden activation button (#337). Only read
177
+ * when `onRowClick` is set. Defaults to the row's first visible cell value
178
+ * when that is a string/number (the row's primary identifier — the same
179
+ * naming a link in that cell would get), else the localized
180
+ * `data.table.rowAction` fallback. Supply it whenever the first cell isn't a
181
+ * good name for the row.
182
+ */
183
+ rowActionLabel?: (row: Row<TData>) => string;
184
+ /**
185
+ * Per-row className, merged alongside the existing zebra/line/hover/selected
186
+ * classes via `cn()` (so it can't accidentally clobber them) (#337).
187
+ */
188
+ rowClassName?: (row: Row<TData>) => string;
189
+ /**
190
+ * Accessible name for the table, rendered as a visually-hidden (`sr-only`)
191
+ * `<caption>` — the first child of `<table>`. Screen readers announce it as
192
+ * the table's name and it makes column-header navigation meaningful.
193
+ * Optional; omit it only when the surrounding page already labels the table
194
+ * unambiguously (e.g. an adjacent heading) (#338).
195
+ */
196
+ caption?: ReactNode;
197
+ /** Message shown when there are no rows and not loading. */
198
+ emptyMessage?: ReactNode;
199
+ className?: string;
200
+ }
201
+ declare const DataTableWithRef: <TData, TValue>(props: DataTableProps<TData, TValue> & {
202
+ ref?: React.Ref<HTMLDivElement>;
203
+ }) => React.ReactElement | null;
204
+
205
+ interface SearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange" | "value"> {
206
+ value: string;
207
+ onValueChange: (value: string) => void;
208
+ /** Visually-hidden accessible label. Defaults to "Search". */
209
+ label?: string;
210
+ containerClassName?: string;
211
+ }
212
+ /**
213
+ * Search field with a leading icon and a clear button. Controlled.
214
+ *
215
+ * `disabled` (available via the extended `InputHTMLAttributes`) is how a
216
+ * consumer signals a pending fetch (D5 — the app owns fetch state, this
217
+ * control just reflects it; see loading-states.md). It is forwarded to the
218
+ * `<Input>` explicitly AND gates the clear button — while disabled the clear
219
+ * affordance is hidden so it can't mutate the filter mid-request (#269/#8).
220
+ */
221
+ declare function SearchInput({ value, onValueChange, label, placeholder, className, containerClassName, disabled, ...props }: SearchInputProps): react.JSX.Element;
222
+
223
+ interface FilterBarProps {
224
+ /** Left cluster: search + facet filters. */
225
+ children: ReactNode;
226
+ /** Right cluster: column picker, export, primary actions. */
227
+ actions?: ReactNode;
228
+ className?: string;
229
+ }
230
+ /** Horizontal toolbar that groups table filters and actions. */
231
+ declare function FilterBar({ children, actions, className }: FilterBarProps): react.JSX.Element;
232
+
233
+ interface FacetOption {
234
+ label: string;
235
+ value: string;
236
+ }
237
+ interface FacetFilterProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "title"> {
238
+ title: string;
239
+ options: FacetOption[];
240
+ /** Currently selected values (controlled). */
241
+ selected: string[];
242
+ onSelectedChange: (values: string[]) => void;
243
+ }
244
+ /**
245
+ * Multi-select faceted filter rendered as a dropdown of toggles.
246
+ *
247
+ * `disabled` (forwarded to the trigger `Button`) is how a consumer signals a
248
+ * pending fetch (D5 — the app owns fetch state, this control just reflects
249
+ * it; see loading-states.md).
250
+ *
251
+ * The trigger takes `Button`'s DEFAULT size (`h-9`), not `sm` (#346): a facet
252
+ * filter lives in a toolbar beside `Select` / `Input` / `DatePicker`, all of
253
+ * which land on `h-9` (Select's own default rung, Input hardcoded, DatePicker
254
+ * via this same Button default). An `sm` trigger was the lone `h-8` outlier in
255
+ * that row, so the top and bottom edges of a filter bar didn't line up.
256
+ */
257
+ declare const FacetFilter: react.ForwardRefExoticComponent<FacetFilterProps & react.RefAttributes<HTMLButtonElement>>;
258
+
259
+ interface ColumnPickerProps<TData> extends ButtonHTMLAttributes<HTMLButtonElement> {
260
+ table: Table<TData>;
261
+ /** Trigger label. Defaults to "Columns". */
262
+ label?: string;
263
+ }
264
+ declare const ColumnPicker: <TData>(props: ColumnPickerProps<TData> & {
265
+ ref?: Ref<HTMLButtonElement>;
266
+ }) => ReactElement | null;
267
+
268
+ type CsvColumn<TData> = {
269
+ key: keyof TData & string;
270
+ header?: string;
271
+ };
272
+ interface ToCsvOptions<TData> {
273
+ /** Subset/reorder of columns. Omitted → all keys from rows[0]. */
274
+ columns?: CsvColumn<TData>[];
275
+ /** Emit header row. Default true. */
276
+ header?: boolean;
277
+ /** Field delimiter. Default ",". */
278
+ delimiter?: string;
279
+ }
280
+ interface DownloadCsvOptions<TData> extends ToCsvOptions<TData> {
281
+ /** File name without extension. Default "download". */
282
+ filename?: string;
283
+ }
284
+ /**
285
+ * Serialize rows to a CSV string (no DOM access — safe for SSR / jsdom).
286
+ */
287
+ declare function toCsv<TData extends Record<string, unknown>>(rows: TData[], opts?: ToCsvOptions<TData>): string;
288
+ /**
289
+ * Trigger a CSV file download in the browser. No-op in SSR environments.
290
+ */
291
+ declare function downloadCsv<TData extends Record<string, unknown>>(rows: TData[], opts?: DownloadCsvOptions<TData>): void;
292
+
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 };