noecosystem-design 0.2.3 → 0.3.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.
Files changed (49) hide show
  1. package/README.md +2 -2
  2. package/apps/storybook/stories/catalog/checkbox.stories.tsx +7 -2
  3. package/apps/storybook/stories/catalog/data-table.stories.tsx +226 -3
  4. package/docs/design-system/components.md +32 -0
  5. package/docs/design-system/inventory.md +1 -0
  6. package/docs/design-system/visual-qa.md +6 -0
  7. package/package.json +1 -1
  8. package/packages/data-ui/package.json +6 -1
  9. package/packages/data-ui/src/data-table.tsx +2 -99
  10. package/packages/data-ui/src/pagination.tsx +2 -2
  11. package/packages/data-ui/src/table/adapters/tanstack.test.ts +116 -0
  12. package/packages/data-ui/src/table/adapters/tanstack.ts +179 -0
  13. package/packages/data-ui/src/table/contracts.ts +133 -0
  14. package/packages/data-ui/src/table/data-table-visual.evidence.browser.test.tsx +157 -0
  15. package/packages/data-ui/src/table/data-table.browser.test.tsx +180 -0
  16. package/packages/data-ui/src/table/data-table.tsx +524 -0
  17. package/packages/design-tokens/src/tokens.css +10 -4
  18. package/packages/design-tokens/src/tokens.json +8 -4
  19. package/packages/design-tokens/src/tokens.mjs +8 -4
  20. package/packages/registry/catalog-index.json +2 -2
  21. package/packages/registry/manifest.json +60 -18
  22. package/packages/registry/r/activity-feed.json +1 -1
  23. package/packages/registry/r/agent-composer.json +7 -1
  24. package/packages/registry/r/agent-message.json +1 -1
  25. package/packages/registry/r/agent-plan.json +1 -1
  26. package/packages/registry/r/agent-workspace.json +1 -1
  27. package/packages/registry/r/approval-request.json +7 -1
  28. package/packages/registry/r/calendar.json +6 -5
  29. package/packages/registry/r/chart-frame.json +1 -1
  30. package/packages/registry/r/chart.json +1 -1
  31. package/packages/registry/r/dashboard-01.json +22 -2
  32. package/packages/registry/r/dashboard-02.json +22 -2
  33. package/packages/registry/r/dashboard-shell.json +1 -1
  34. package/packages/registry/r/data-table.json +22 -2
  35. package/packages/registry/r/date-picker.json +6 -5
  36. package/packages/registry/r/filter-bar.json +1 -1
  37. package/packages/registry/r/inline-cta.json +6 -6
  38. package/packages/registry/r/metric-card.json +1 -1
  39. package/packages/registry/r/pagination.json +7 -5
  40. package/packages/registry/r/run-inspector.json +1 -1
  41. package/packages/registry/r/tool-call.json +1 -1
  42. package/packages/registry/r/workflow-canvas.json +1 -1
  43. package/packages/registry/r/workflow-node.json +1 -1
  44. package/packages/registry/registry.json +12 -3
  45. package/packages/registry/search-index.json +23 -8
  46. package/packages/ui/package.json +5 -1
  47. package/packages/ui/src/checkbox.tsx +1 -1
  48. package/scripts/generate-manifest.mjs +71 -0
  49. package/vitest.config.ts +5 -0
@@ -0,0 +1,524 @@
1
+ import { useId, useMemo, useState } from 'react';
2
+ import type * as React from 'react';
3
+ import { ArrowDown2, ArrowUp2 } from '@noe/icons';
4
+ import { TechnicalValue } from '@noe/ui/bidi';
5
+ import { Button } from '@noe/ui/button';
6
+ import { Checkbox } from '@noe/ui/checkbox';
7
+ import { cn } from '@noe/ui/lib/cn';
8
+ import { Skeleton } from '@noe/ui/skeleton';
9
+ import { functionalUpdate, useTable } from '@tanstack/react-table';
10
+ import type {
11
+ ColumnFiltersState,
12
+ OnChangeFn,
13
+ RowSelectionState,
14
+ SortingState,
15
+ } from '@tanstack/react-table';
16
+
17
+ import { Pagination } from '../pagination';
18
+ import {
19
+ fromTanStackFilters,
20
+ fromTanStackSelection,
21
+ fromTanStackSorting,
22
+ noeTableFeatures,
23
+ toTanStackColumns,
24
+ toTanStackFilters,
25
+ toTanStackSelection,
26
+ toTanStackSorting,
27
+ } from './adapters/tanstack';
28
+ import type { DataColumn, DataSort, FilterSpec, PageSpec, RowId, SelectionMode } from './contracts';
29
+
30
+ export type {
31
+ DataColumn,
32
+ DataColumnSortFn,
33
+ DataQuery,
34
+ DataQueryInput,
35
+ DataSort,
36
+ DataSortDirection,
37
+ FilterOperator,
38
+ FilterSpec,
39
+ PageSpec,
40
+ RowId,
41
+ SelectionMode,
42
+ SelectionState,
43
+ TableStateAdapter,
44
+ } from './contracts';
45
+ export { createMemoryTableStateAdapter, toDataQuery } from './contracts';
46
+
47
+ export interface DataTablePaginationStatusInfo {
48
+ /** One-based index of the first row on the page. */
49
+ startIndex: number;
50
+ /** One-based index of the last row on the page. */
51
+ endIndex: number;
52
+ /** Total row count across pages. */
53
+ total: number;
54
+ }
55
+
56
+ export interface DataTableSelectionInfo {
57
+ selectedIds: RowId[];
58
+ }
59
+
60
+ type DataTableFeatureProps<Row extends { id: string }> = {
61
+ /** Controlled NOE sorting state. */
62
+ sorting?: readonly DataSort[];
63
+ /** Initial sorting state for uncontrolled usage. */
64
+ defaultSorting?: readonly DataSort[];
65
+ onSortingChange?: (sorting: DataSort[]) => void;
66
+ /** Server owns row order; the table only reflects sorting state. */
67
+ manualSorting?: boolean;
68
+ } & (
69
+ | {
70
+ filters?: undefined;
71
+ defaultFilters?: undefined;
72
+ onFiltersChange?: undefined;
73
+ manualFiltering?: undefined;
74
+ }
75
+ | {
76
+ /** Controlled NOE filter state. */
77
+ filters: readonly FilterSpec[];
78
+ defaultFilters?: undefined;
79
+ onFiltersChange: (filters: FilterSpec[]) => void;
80
+ manualFiltering?: boolean;
81
+ }
82
+ | {
83
+ filters?: undefined;
84
+ /** Initial filter state for uncontrolled usage. */
85
+ defaultFilters?: readonly FilterSpec[];
86
+ onFiltersChange?: (filters: FilterSpec[]) => void;
87
+ manualFiltering?: boolean;
88
+ }
89
+ ) &
90
+ (
91
+ | {
92
+ page?: undefined;
93
+ onPageChange?: undefined;
94
+ previousPageLabel?: undefined;
95
+ nextPageLabel?: undefined;
96
+ paginationLabel?: undefined;
97
+ paginationStatus?: undefined;
98
+ totalRowCount?: undefined;
99
+ manualPagination?: undefined;
100
+ }
101
+ | {
102
+ /** Controlled zero-based page state. */
103
+ page: PageSpec;
104
+ onPageChange: (page: PageSpec) => void;
105
+ /** Accessible names for the icon-only pagination controls. */
106
+ previousPageLabel: string;
107
+ nextPageLabel: string;
108
+ paginationLabel?: string;
109
+ paginationStatus?: (info: DataTablePaginationStatusInfo) => React.ReactNode;
110
+ /** Server-side total; required for accurate status in manual mode. */
111
+ totalRowCount?: number;
112
+ /** Server owns row slicing; `rows` holds exactly the current page. */
113
+ manualPagination?: boolean;
114
+ }
115
+ ) &
116
+ (
117
+ | {
118
+ selection?: undefined;
119
+ defaultSelection?: undefined;
120
+ onSelectionChange?: undefined;
121
+ selectionMode?: undefined;
122
+ selectAllLabel?: undefined;
123
+ rowSelectionLabel?: undefined;
124
+ selectedCountLabel?: undefined;
125
+ renderSelectionActions?: undefined;
126
+ }
127
+ | {
128
+ /** Controlled selected row ids. */
129
+ selection?: readonly RowId[];
130
+ /** Initial selection for uncontrolled usage. */
131
+ defaultSelection?: readonly RowId[];
132
+ onSelectionChange?: (ids: RowId[]) => void;
133
+ selectionMode?: SelectionMode;
134
+ /** Accessible name for the select-all header control. */
135
+ selectAllLabel: string;
136
+ /** Accessible name factory for row selection controls. */
137
+ rowSelectionLabel?: (row: Row) => string;
138
+ selectedCountLabel?: (count: number) => React.ReactNode;
139
+ renderSelectionActions?: (info: DataTableSelectionInfo) => React.ReactNode;
140
+ }
141
+ );
142
+
143
+ export type DataTableProps<Row extends { id: string }> = {
144
+ columns: DataColumn<Row>[];
145
+ rows: Row[];
146
+ caption: string;
147
+ empty: React.ReactNode;
148
+ className?: string;
149
+ loading?: boolean;
150
+ /** Skeleton row count while `loading` is true. Default: 5. */
151
+ loadingRows?: number;
152
+ /** Visual rhythm: `regular` (default) or `compact` for dense workspaces. */
153
+ density?: 'compact' | 'regular';
154
+ } & DataTableFeatureProps<Row>;
155
+
156
+ /** Pagination state that keeps every row visible when paging is disabled. */
157
+ const ALL_ROWS_PAGINATION = { pageIndex: 0, pageSize: Number.MAX_SAFE_INTEGER };
158
+
159
+ export function DataTable<Row extends { id: string }>({
160
+ columns,
161
+ rows,
162
+ caption,
163
+ empty,
164
+ className,
165
+ loading = false,
166
+ loadingRows = 5,
167
+ density = 'regular',
168
+ sorting,
169
+ defaultSorting,
170
+ onSortingChange,
171
+ manualSorting,
172
+ filters,
173
+ defaultFilters,
174
+ onFiltersChange,
175
+ manualFiltering,
176
+ page,
177
+ onPageChange,
178
+ previousPageLabel,
179
+ nextPageLabel,
180
+ paginationLabel,
181
+ paginationStatus,
182
+ totalRowCount,
183
+ manualPagination,
184
+ selection,
185
+ defaultSelection,
186
+ onSelectionChange,
187
+ selectionMode = 'multiple',
188
+ selectAllLabel,
189
+ rowSelectionLabel,
190
+ selectedCountLabel,
191
+ renderSelectionActions,
192
+ }: DataTableProps<Row>) {
193
+ const captionId = useId();
194
+ const selectionEnabled = selectAllLabel !== undefined;
195
+ const paginationEnabled = page !== undefined;
196
+
197
+ const [internalSorting, setInternalSorting] = useState<SortingState>(() =>
198
+ toTanStackSorting(defaultSorting ?? []),
199
+ );
200
+ const [internalFilters, setInternalFilters] = useState<ColumnFiltersState>(() =>
201
+ toTanStackFilters(defaultFilters ?? []),
202
+ );
203
+ const [internalSelection, setInternalSelection] = useState<RowSelectionState>(() =>
204
+ toTanStackSelection(defaultSelection ?? []),
205
+ );
206
+
207
+ const controlledSorting = useMemo(
208
+ () => (sorting !== undefined ? toTanStackSorting(sorting) : undefined),
209
+ [sorting],
210
+ );
211
+ const controlledFilters = useMemo(
212
+ () => (filters !== undefined ? toTanStackFilters(filters) : undefined),
213
+ [filters],
214
+ );
215
+ const controlledSelection = useMemo(
216
+ () => (selection !== undefined ? toTanStackSelection(selection) : undefined),
217
+ [selection],
218
+ );
219
+
220
+ const sortingState = controlledSorting ?? internalSorting;
221
+ const filtersState = controlledFilters ?? internalFilters;
222
+ const selectionState = controlledSelection ?? internalSelection;
223
+ const paginationState = useMemo(
224
+ () =>
225
+ page !== undefined ? { pageIndex: page.index, pageSize: page.size } : ALL_ROWS_PAGINATION,
226
+ [page],
227
+ );
228
+
229
+ const tanstackColumns = useMemo(() => toTanStackColumns(columns), [columns]);
230
+
231
+ const handleSortingChange: OnChangeFn<SortingState> = (updater) => {
232
+ const next = functionalUpdate(updater, sortingState);
233
+ setInternalSorting(next);
234
+ onSortingChange?.(fromTanStackSorting(next));
235
+ };
236
+
237
+ const handleFiltersChange: OnChangeFn<ColumnFiltersState> = (updater) => {
238
+ const next = functionalUpdate(updater, filtersState);
239
+ setInternalFilters(next);
240
+ onFiltersChange?.(fromTanStackFilters(next));
241
+ };
242
+
243
+ const handleSelectionChange: OnChangeFn<RowSelectionState> = (updater) => {
244
+ const next = functionalUpdate(updater, selectionState);
245
+ setInternalSelection(next);
246
+ onSelectionChange?.(fromTanStackSelection(next));
247
+ };
248
+
249
+ const table = useTable({
250
+ features: noeTableFeatures,
251
+ columns: tanstackColumns,
252
+ data: rows,
253
+ getRowId: (row: Row) => row.id,
254
+ state: {
255
+ sorting: sortingState,
256
+ columnFilters: filtersState,
257
+ rowSelection: selectionState,
258
+ pagination: paginationState,
259
+ },
260
+ onSortingChange: handleSortingChange,
261
+ onColumnFiltersChange: handleFiltersChange,
262
+ onRowSelectionChange: handleSelectionChange,
263
+ enableMultiSort: false,
264
+ autoResetPageIndex: false,
265
+ ...(manualSorting !== undefined ? { manualSorting } : {}),
266
+ ...(manualFiltering !== undefined ? { manualFiltering } : {}),
267
+ ...(manualPagination !== undefined ? { manualPagination } : {}),
268
+ ...(totalRowCount !== undefined ? { rowCount: totalRowCount } : {}),
269
+ });
270
+
271
+ const modelRows = table.getRowModel().rows;
272
+ const selectedIds =
273
+ selection !== undefined ? [...selection] : fromTanStackSelection(selectionState);
274
+
275
+ const toggleRowSelection = (rowId: RowId) => {
276
+ const isSelected = selectedIds.includes(rowId);
277
+ if (selectionMode === 'single') {
278
+ handleSelectionChange(isSelected ? {} : toTanStackSelection([rowId]));
279
+ return;
280
+ }
281
+ handleSelectionChange(
282
+ toTanStackSelection(
283
+ isSelected ? selectedIds.filter((id) => id !== rowId) : [...selectedIds, rowId],
284
+ ),
285
+ );
286
+ };
287
+
288
+ const allRowsSelected = table.getIsAllRowsSelected();
289
+ const someRowsSelected = table.getIsSomeRowsSelected();
290
+
291
+ const pageSize = page?.size ?? ALL_ROWS_PAGINATION.pageSize;
292
+ const pageIndex = page?.index ?? 0;
293
+ const totalRows =
294
+ manualPagination === true
295
+ ? (totalRowCount ?? pageIndex * pageSize + modelRows.length)
296
+ : table.getSortedRowModel().rows.length;
297
+ const canPreviousPage = paginationEnabled && pageIndex > 0;
298
+ const canNextPage = paginationEnabled
299
+ ? manualPagination === true
300
+ ? totalRowCount !== undefined
301
+ ? (pageIndex + 1) * pageSize < totalRowCount
302
+ : modelRows.length === pageSize
303
+ : pageIndex + 1 < table.getPageCount()
304
+ : false;
305
+ const paginationInfo: DataTablePaginationStatusInfo | undefined = paginationEnabled
306
+ ? {
307
+ startIndex: totalRows === 0 ? 0 : pageIndex * pageSize + 1,
308
+ endIndex: Math.min(totalRows, (pageIndex + 1) * pageSize),
309
+ total: totalRows,
310
+ }
311
+ : undefined;
312
+
313
+ const cellPadding = density === 'compact' ? 'px-3 py-1.5' : 'px-4 py-2.5';
314
+
315
+ if (!loading && modelRows.length === 0) {
316
+ return (
317
+ <div
318
+ className={cn(
319
+ 'flex min-h-32 items-center justify-center rounded-lg border border-dashed border-border bg-surface-sunken/30 p-6 text-center text-sm text-muted-foreground',
320
+ className,
321
+ )}
322
+ data-slot="data-table-empty"
323
+ >
324
+ {empty}
325
+ </div>
326
+ );
327
+ }
328
+
329
+ const showSelectionBar =
330
+ selectionEnabled &&
331
+ selectedIds.length > 0 &&
332
+ (selectedCountLabel !== undefined || renderSelectionActions !== undefined);
333
+
334
+ return (
335
+ <div className={cn('flex flex-col gap-3', className)} data-slot="data-table-root">
336
+ {showSelectionBar ? (
337
+ <div
338
+ className="flex items-center gap-3 rounded-md border border-border bg-surface-sunken/40 px-4 py-2.5 text-sm"
339
+ data-slot="data-table-selection-bar"
340
+ >
341
+ {selectedCountLabel ? (
342
+ <span className="tabular-nums text-muted-foreground">
343
+ {selectedCountLabel(selectedIds.length)}
344
+ </span>
345
+ ) : null}
346
+ {renderSelectionActions ? (
347
+ <div className="ms-auto flex items-center gap-2">
348
+ {renderSelectionActions({ selectedIds })}
349
+ </div>
350
+ ) : null}
351
+ </div>
352
+ ) : null}
353
+ {/* WCAG 2.1: keyboard-accessible scrollable container for overflow tables. */}
354
+ <section
355
+ aria-labelledby={captionId}
356
+ className="overflow-x-auto rounded-lg border border-border"
357
+ data-slot="data-table-scroll"
358
+ // biome-ignore lint/a11y/noNoninteractiveTabindex: scrollable region focus pattern
359
+ tabIndex={0}
360
+ >
361
+ <table
362
+ className="w-full min-w-full border-collapse text-sm"
363
+ data-density={density}
364
+ data-slot="data-table"
365
+ >
366
+ <caption className="sr-only" id={captionId}>
367
+ {caption}
368
+ </caption>
369
+ <thead>
370
+ <tr className="border-b border-border bg-surface-sunken/60">
371
+ {selectionEnabled && selectionMode === 'multiple' ? (
372
+ <th className={cn('w-10', cellPadding)} data-slot="data-table-select-header">
373
+ <Checkbox
374
+ aria-label={selectAllLabel}
375
+ checked={allRowsSelected}
376
+ onChange={() => table.toggleAllRowsSelected(!allRowsSelected)}
377
+ ref={(element) => {
378
+ if (element) element.indeterminate = someRowsSelected && !allRowsSelected;
379
+ }}
380
+ />
381
+ </th>
382
+ ) : null}
383
+ {columns.map((column) => {
384
+ const tanstackColumn = table.getColumn(column.key);
385
+ const canSort = tanstackColumn?.getCanSort() ?? false;
386
+ const sorted = tanstackColumn?.getIsSorted() ?? false;
387
+ return (
388
+ <th
389
+ aria-sort={
390
+ sorted === 'asc'
391
+ ? 'ascending'
392
+ : sorted === 'desc'
393
+ ? 'descending'
394
+ : canSort
395
+ ? 'none'
396
+ : undefined
397
+ }
398
+ className={cn(
399
+ 'whitespace-nowrap text-xs font-medium text-muted-foreground',
400
+ cellPadding,
401
+ column.align === 'end' ? 'text-end' : 'text-start',
402
+ )}
403
+ data-align={column.align ?? 'start'}
404
+ key={column.key}
405
+ scope="col"
406
+ >
407
+ {canSort && tanstackColumn ? (
408
+ <Button
409
+ className="-ms-2 text-muted-foreground hover:text-foreground"
410
+ data-slot="data-table-sort-button"
411
+ onClick={(event) => tanstackColumn.getToggleSortingHandler()?.(event)}
412
+ size="sm"
413
+ variant="ghost"
414
+ >
415
+ {column.header}
416
+ {sorted === 'desc' ? (
417
+ <ArrowDown2 className="size-3.5" />
418
+ ) : (
419
+ <ArrowUp2 className={cn('size-3.5', sorted === false && 'opacity-40')} />
420
+ )}
421
+ </Button>
422
+ ) : (
423
+ column.header
424
+ )}
425
+ </th>
426
+ );
427
+ })}
428
+ </tr>
429
+ </thead>
430
+ <tbody>
431
+ {loading
432
+ ? Array.from(
433
+ { length: loadingRows },
434
+ (_, index) => `data-table-loading-${index}`,
435
+ ).map((rowKey) => (
436
+ <tr
437
+ className="border-b border-border last:border-b-0"
438
+ data-slot="data-table-loading-row"
439
+ key={rowKey}
440
+ >
441
+ {selectionEnabled ? (
442
+ <td className={cellPadding}>
443
+ <Skeleton className="size-4" />
444
+ </td>
445
+ ) : null}
446
+ {columns.map((column) => (
447
+ <td className={cellPadding} key={column.key}>
448
+ <Skeleton className="h-4 w-full max-w-32" />
449
+ </td>
450
+ ))}
451
+ </tr>
452
+ ))
453
+ : modelRows.map((tanstackRow) => {
454
+ const row = tanstackRow.original;
455
+ const isSelected = tanstackRow.getIsSelected();
456
+ return (
457
+ <tr
458
+ className="border-b border-border last:border-b-0 hover:bg-muted/50"
459
+ data-selected={isSelected || undefined}
460
+ key={tanstackRow.id}
461
+ >
462
+ {selectionEnabled ? (
463
+ <td className={cellPadding} data-slot="data-table-select-cell">
464
+ <Checkbox
465
+ aria-label={rowSelectionLabel?.(row)}
466
+ checked={isSelected}
467
+ onChange={() => toggleRowSelection(tanstackRow.id)}
468
+ />
469
+ </td>
470
+ ) : null}
471
+ {columns.map((column) => {
472
+ const content = column.render
473
+ ? column.render(row)
474
+ : String(row[column.key] ?? '');
475
+ const value = column.technical ? (
476
+ <TechnicalValue>{content}</TechnicalValue>
477
+ ) : (
478
+ content
479
+ );
480
+ return (
481
+ <td
482
+ className={cn(
483
+ 'whitespace-nowrap text-foreground',
484
+ cellPadding,
485
+ column.align === 'end' ? 'text-end' : 'text-start',
486
+ )}
487
+ data-align={column.align ?? 'start'}
488
+ key={column.key}
489
+ >
490
+ {value}
491
+ </td>
492
+ );
493
+ })}
494
+ </tr>
495
+ );
496
+ })}
497
+ </tbody>
498
+ </table>
499
+ </section>
500
+ {paginationEnabled && paginationInfo ? (
501
+ <Pagination
502
+ canNext={canNextPage}
503
+ canPrevious={canPreviousPage}
504
+ label={paginationLabel ?? caption}
505
+ nextLabel={nextPageLabel}
506
+ onNext={
507
+ canNextPage ? () => onPageChange({ index: pageIndex + 1, size: pageSize }) : undefined
508
+ }
509
+ onPrevious={
510
+ canPreviousPage
511
+ ? () => onPageChange({ index: pageIndex - 1, size: pageSize })
512
+ : undefined
513
+ }
514
+ previousLabel={previousPageLabel}
515
+ status={
516
+ paginationStatus
517
+ ? paginationStatus(paginationInfo)
518
+ : `${paginationInfo.startIndex}–${paginationInfo.endIndex} / ${paginationInfo.total}`
519
+ }
520
+ />
521
+ ) : null}
522
+ </div>
523
+ );
524
+ }
@@ -13,11 +13,11 @@
13
13
  --noe-primitive-color-ink-500: #565c58;
14
14
  --noe-primitive-color-ink-600: #302f2e;
15
15
  --noe-primitive-color-ink-700: #252423;
16
- --noe-primitive-color-ink-800: #302f2e;
17
- --noe-primitive-color-ink-850: #302f2e;
16
+ --noe-primitive-color-ink-800: #2a2a2a;
17
+ --noe-primitive-color-ink-850: #202020;
18
18
  --noe-primitive-color-ink-900: #171918;
19
- --noe-primitive-color-ink-950: #252423;
20
- --noe-primitive-color-ink-1000: #1c1b1b;
19
+ --noe-primitive-color-ink-950: #161616;
20
+ --noe-primitive-color-ink-1000: #111111;
21
21
  --noe-primitive-color-mint-100: #98e8cf;
22
22
  --noe-primitive-color-mint-200: #abefd9;
23
23
  --noe-primitive-color-mint-300: #1dbf98;
@@ -140,6 +140,8 @@
140
140
  --noe-color-surface: var(--noe-primitive-color-paper-50);
141
141
  --noe-color-surface-raised: var(--noe-primitive-color-paper-0);
142
142
  --noe-color-surface-sunken: var(--noe-primitive-color-paper-200);
143
+ --noe-color-sidebar: var(--noe-primitive-color-paper-200);
144
+ --noe-color-sidebar-active: var(--noe-primitive-color-paper-0);
143
145
  --noe-color-foreground: var(--noe-primitive-color-ink-900);
144
146
  --noe-color-muted: var(--noe-primitive-color-paper-200);
145
147
  --noe-color-muted-foreground: var(--noe-primitive-color-ink-500);
@@ -170,6 +172,8 @@
170
172
  --noe-color-surface: var(--noe-primitive-color-ink-850);
171
173
  --noe-color-surface-raised: var(--noe-primitive-color-ink-800);
172
174
  --noe-color-surface-sunken: var(--noe-primitive-color-ink-1000);
175
+ --noe-color-sidebar: var(--noe-primitive-color-ink-850);
176
+ --noe-color-sidebar-active: var(--noe-primitive-color-ink-800);
173
177
  --noe-color-foreground: var(--noe-primitive-color-paper-250);
174
178
  --noe-color-muted: var(--noe-primitive-color-ink-800);
175
179
  --noe-color-muted-foreground: var(--noe-primitive-color-ink-400);
@@ -201,6 +205,8 @@
201
205
  --noe-color-surface: var(--noe-primitive-color-ink-850);
202
206
  --noe-color-surface-raised: var(--noe-primitive-color-ink-800);
203
207
  --noe-color-surface-sunken: var(--noe-primitive-color-ink-1000);
208
+ --noe-color-sidebar: var(--noe-primitive-color-ink-850);
209
+ --noe-color-sidebar-active: var(--noe-primitive-color-ink-800);
204
210
  --noe-color-foreground: var(--noe-primitive-color-paper-250);
205
211
  --noe-color-muted: var(--noe-primitive-color-ink-800);
206
212
  --noe-color-muted-foreground: var(--noe-primitive-color-ink-400);
@@ -15,11 +15,11 @@
15
15
  "500": "#565c58",
16
16
  "600": "#302f2e",
17
17
  "700": "#252423",
18
- "800": "#302f2e",
19
- "850": "#302f2e",
18
+ "800": "#2a2a2a",
19
+ "850": "#202020",
20
20
  "900": "#171918",
21
- "950": "#252423",
22
- "1000": "#1c1b1b"
21
+ "950": "#161616",
22
+ "1000": "#111111"
23
23
  },
24
24
  "mint": {
25
25
  "100": "#98e8cf",
@@ -106,6 +106,8 @@
106
106
  "surface": "{primitive.color.paper.50}",
107
107
  "surfaceRaised": "{primitive.color.paper.0}",
108
108
  "surfaceSunken": "{primitive.color.paper.200}",
109
+ "sidebar": "{primitive.color.paper.200}",
110
+ "sidebarActive": "{primitive.color.paper.0}",
109
111
  "foreground": "{primitive.color.ink.900}",
110
112
  "muted": "{primitive.color.paper.200}",
111
113
  "mutedForeground": "{primitive.color.ink.500}",
@@ -131,6 +133,8 @@
131
133
  "surface": "{primitive.color.ink.850}",
132
134
  "surfaceRaised": "{primitive.color.ink.800}",
133
135
  "surfaceSunken": "{primitive.color.ink.1000}",
136
+ "sidebar": "{primitive.color.ink.850}",
137
+ "sidebarActive": "{primitive.color.ink.800}",
134
138
  "foreground": "{primitive.color.paper.250}",
135
139
  "muted": "{primitive.color.ink.800}",
136
140
  "mutedForeground": "{primitive.color.ink.400}",
@@ -16,11 +16,11 @@ export const tokens = {
16
16
  "500": "#565c58",
17
17
  "600": "#302f2e",
18
18
  "700": "#252423",
19
- "800": "#302f2e",
20
- "850": "#302f2e",
19
+ "800": "#2a2a2a",
20
+ "850": "#202020",
21
21
  "900": "#171918",
22
- "950": "#252423",
23
- "1000": "#1c1b1b"
22
+ "950": "#161616",
23
+ "1000": "#111111"
24
24
  },
25
25
  "mint": {
26
26
  "100": "#98e8cf",
@@ -107,6 +107,8 @@ export const tokens = {
107
107
  "surface": "{primitive.color.paper.50}",
108
108
  "surfaceRaised": "{primitive.color.paper.0}",
109
109
  "surfaceSunken": "{primitive.color.paper.200}",
110
+ "sidebar": "{primitive.color.paper.200}",
111
+ "sidebarActive": "{primitive.color.paper.0}",
110
112
  "foreground": "{primitive.color.ink.900}",
111
113
  "muted": "{primitive.color.paper.200}",
112
114
  "mutedForeground": "{primitive.color.ink.500}",
@@ -132,6 +134,8 @@ export const tokens = {
132
134
  "surface": "{primitive.color.ink.850}",
133
135
  "surfaceRaised": "{primitive.color.ink.800}",
134
136
  "surfaceSunken": "{primitive.color.ink.1000}",
137
+ "sidebar": "{primitive.color.ink.850}",
138
+ "sidebarActive": "{primitive.color.ink.800}",
135
139
  "foreground": "{primitive.color.paper.250}",
136
140
  "muted": "{primitive.color.ink.800}",
137
141
  "mutedForeground": "{primitive.color.ink.400}",
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-08-29T09:30:43.936Z",
2
+ "generatedAt": "2026-08-30T12:09:40.539Z",
3
3
  "items": [
4
4
  {
5
5
  "id": "bidi-text",
@@ -208,7 +208,7 @@
208
208
  "package": "@noe/data-ui",
209
209
  "status": "available",
210
210
  "maturity": "beta",
211
- "description": "Data Table Data component. data-table provides data behavior with RTL, i18n and accessibility built-in.",
211
+ "description": "Semantic data table with TanStack Table v9 as a headless engine behind the internal NOE adapter: sorting, NOE filter operators, controlled pagination, stable-id selection, loading skeletons and server mode.",
212
212
  "registryItem": "data-table"
213
213
  },
214
214
  {