@synerise/ds-table-new 1.0.1 → 1.0.3

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 (61) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +57 -1
  3. package/dist/Table.d.ts +1 -1
  4. package/dist/Table.js +29 -3
  5. package/dist/Table.styles.d.ts +0 -1
  6. package/dist/Table.styles.js +0 -5
  7. package/dist/Table.types.d.ts +164 -10
  8. package/dist/VirtualTable.d.ts +1 -1
  9. package/dist/VirtualTable.js +33 -3
  10. package/dist/components/BackToTopButton/BackToTopButton.d.ts +11 -0
  11. package/dist/components/BackToTopButton/BackToTopButton.js +34 -0
  12. package/dist/components/BackToTopButton/BackToTopButton.styles.d.ts +3 -0
  13. package/dist/components/BackToTopButton/BackToTopButton.styles.js +8 -0
  14. package/dist/components/BaseTable/BaseTable.d.ts +1 -1
  15. package/dist/components/BaseTable/BaseTable.js +10 -5
  16. package/dist/components/BaseTable/BaseTable.styles.js +1 -1
  17. package/dist/components/Cell/LabelsWithShowMore/Modal/Modal.js +9 -14
  18. package/dist/components/ItemsMenu/ItemsMenu.d.ts +7 -0
  19. package/dist/components/ItemsMenu/ItemsMenu.js +9 -0
  20. package/dist/components/ItemsMenu/ItemsMenu.styles.d.ts +3 -0
  21. package/dist/components/ItemsMenu/ItemsMenu.styles.js +10 -0
  22. package/dist/components/TableBody/TableBody.d.ts +1 -1
  23. package/dist/components/TableBody/TableBody.js +26 -5
  24. package/dist/components/TableBody/TableBody.styles.d.ts +4 -1
  25. package/dist/components/TableBody/TableBody.styles.js +2 -2
  26. package/dist/components/TableBody/TableCell/TableCell.d.ts +2 -1
  27. package/dist/components/TableBody/TableCell/TableCell.js +2 -1
  28. package/dist/components/TableBody/TableCell/TableCell.styles.d.ts +4 -1
  29. package/dist/components/TableBody/TableCell/TableCell.styles.js +7 -2
  30. package/dist/components/TableBody/TableRow/TableRow.js +2 -2
  31. package/dist/components/TableBody/TableRow/TableRowVirtual.js +3 -2
  32. package/dist/components/TableBody/TableRowSelection/TableRowSelection.js +2 -1
  33. package/dist/components/TableColumns/TableColumns.js +2 -1
  34. package/dist/components/TableColumns/TableColumns.styles.d.ts +4 -1
  35. package/dist/components/TableColumns/TableColumns.styles.js +7 -2
  36. package/dist/components/TableHeader/TableHeader.d.ts +1 -1
  37. package/dist/components/TableHeader/TableHeader.js +14 -6
  38. package/dist/components/TableHeader/TableHeaderSelection/TableHeaderSelection.js +37 -16
  39. package/dist/components/TableHeader/TableLimit/TableLimit.js +4 -3
  40. package/dist/components/TableHeader/TableLimit/TableLimit.styles.d.ts +0 -1
  41. package/dist/components/TableHeader/TableLimit/TableLimit.styles.js +0 -5
  42. package/dist/components/TableHorizontalScroll/TableHorizontalScroll.js +7 -11
  43. package/dist/components/TreeTable/TreeTable.d.ts +3 -0
  44. package/dist/components/TreeTable/TreeTable.js +106 -0
  45. package/dist/components/TreeTable/TreeTable.styles.d.ts +15 -0
  46. package/dist/components/TreeTable/TreeTable.styles.js +46 -0
  47. package/dist/components/TreeTable/TreeTable.types.d.ts +26 -0
  48. package/dist/components/TreeTable/TreeTable.types.js +1 -0
  49. package/dist/hooks/useDefaultTexts.js +19 -15
  50. package/dist/hooks/useTable.d.ts +7 -2
  51. package/dist/hooks/useTable.js +23 -2
  52. package/dist/hooks/useTableHighlight.d.ts +5 -0
  53. package/dist/hooks/useTableHighlight.js +33 -0
  54. package/dist/hooks/useTableSearch.d.ts +13 -0
  55. package/dist/hooks/useTableSearch.js +36 -0
  56. package/dist/index.d.ts +2 -0
  57. package/dist/index.js +2 -0
  58. package/dist/types/table.d.ts +1 -0
  59. package/dist/utils/legacyColumnConfigAdapter.d.ts +1 -1
  60. package/dist/utils/legacyColumnConfigAdapter.js +3 -3
  61. package/package.json +21 -22
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.0.3](https://github.com/Synerise/synerise-design/compare/@synerise/ds-table-new@1.0.2...@synerise/ds-table-new@1.0.3) (2026-04-21)
7
+
8
+ **Note:** Version bump only for package @synerise/ds-table-new
9
+
10
+ ## [1.0.2](https://github.com/Synerise/synerise-design/compare/@synerise/ds-table-new@1.0.1...@synerise/ds-table-new@1.0.2) (2026-04-20)
11
+
12
+ **Note:** Version bump only for package @synerise/ds-table-new
13
+
6
14
  ## [1.0.1](https://github.com/Synerise/synerise-design/compare/@synerise/ds-table-new@0.1.0...@synerise/ds-table-new@1.0.1) (2026-04-14)
7
15
 
8
16
  ### Bug Fixes
package/README.md CHANGED
@@ -86,7 +86,11 @@ import { VirtualTable } from '@synerise/ds-table-new';
86
86
  | `className` | CSS class for styled-components composition | `string` | - |
87
87
  | `showHeader` | Show or hide the table header bar | `boolean` | - |
88
88
  | `itemsMenu` | Rendered next to title when rows are selected (batch actions) | `ReactNode` | - |
89
- | `searchComponent` | Search input rendered on the right side of the header | `ReactNode` | - |
89
+ | `matchesSearchQuery` | Pure predicate for built-in search table manages query state and renders SearchInput | `(query: string, row: TData) => boolean` | - |
90
+ | `searchProps` | Override SearchInput defaults when using built-in search (placeholder, clearTooltip, etc.) | `Partial<SearchInputProps>` | - |
91
+ | `onSearchQueryChange` | Callback fired when internal search query changes (built-in search only) | `(query: string) => void` | - |
92
+ | `filterData` | Closure-based filter for custom search — table filters internally, consumer manages query state | `(row: TData) => boolean` | - |
93
+ | `searchComponent` | Custom search UI rendered in the header — replaces built-in SearchInput when provided | `ReactNode` | - |
90
94
  | `filterComponent` | Filter controls rendered on the right side of the header | `ReactNode` | - |
91
95
  | `headerButton` | Button rendered in the header | `ReactNode` | - |
92
96
  | `cardStyles` | Render table with rounded corners and shadow | `boolean` | - |
@@ -136,6 +140,58 @@ import { VirtualTable } from '@synerise/ds-table-new';
136
140
  | `checkRowSelectionStatus` | Per-row disabled/unavailable check | `(record: TData) => { disabled?; unavailable? }` | - |
137
141
  | `independentSelectionExpandedRows` | Treat expanded child rows as independent selections | `boolean` | - |
138
142
 
143
+ ### Search
144
+
145
+ TableNew supports three search modes:
146
+
147
+ **Built-in search** — table manages query state, renders SearchInput, and filters data internally:
148
+
149
+ ```tsx
150
+ import { Table } from '@synerise/ds-table-new';
151
+
152
+ <Table
153
+ data={data}
154
+ columns={columns}
155
+ matchesSearchQuery={(query, row) =>
156
+ row.name.toLowerCase().includes(query.toLowerCase())
157
+ }
158
+ searchProps={{
159
+ placeholder: 'Search by name...',
160
+ clearTooltip: 'Clear search',
161
+ }}
162
+ onSearchQueryChange={(query) => console.log('query:', query)}
163
+ />
164
+ ```
165
+
166
+ **Custom search with internal filtering** — consumer manages query state, table filters via `filterData`:
167
+
168
+ ```tsx
169
+ import { Table } from '@synerise/ds-table-new';
170
+ import { SearchInput } from '@synerise/ds-search';
171
+
172
+ const [query, setQuery] = useState('');
173
+
174
+ <Table
175
+ data={data}
176
+ columns={columns}
177
+ filterData={(row) =>
178
+ !query || row.name.toLowerCase().includes(query.toLowerCase())
179
+ }
180
+ searchComponent={
181
+ <SearchInput
182
+ value={query}
183
+ onChange={setQuery}
184
+ onClear={() => setQuery('')}
185
+ placeholder="Search..."
186
+ clearTooltip="Clear"
187
+ closeOnClickOutside
188
+ />
189
+ }
190
+ />
191
+ ```
192
+
193
+ When using `matchesSearchQuery` or `filterData` with selection, the table preserves selected items that are filtered out of the display. Selection always operates on the full `data` array.
194
+
139
195
  ### InfiniteScrollProps
140
196
 
141
197
  | Property | Description | Type | Default |
package/dist/Table.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { default as React } from 'react';
2
2
  import { TableProps } from './Table.types';
3
- export declare const Table: <TData extends object, TValue>({ data, columns, texts: defaultTexts, selectionConfig, isLoading, selectedRowKeys, pagination, ...props }: TableProps<TData, TValue>) => React.JSX.Element;
3
+ export declare const Table: <TData extends object, TValue>({ data, columns, texts: defaultTexts, selectionConfig, isLoading, selectedRowKeys, pagination, matchesSearchQuery, filterData, onSearchQueryChange, searchProps, expandable, rowKey, onSort, tableRef, ...props }: TableProps<TData, TValue>) => React.JSX.Element;
package/dist/Table.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { useRef, useMemo } from "react";
2
+ import { useRef, useMemo, useImperativeHandle } from "react";
3
3
  import { BaseTable } from "./components/BaseTable/BaseTable.js";
4
4
  import { SelectionContext } from "./contexts/SelectionContext.js";
5
5
  import { TableContext } from "./contexts/TableContext.js";
6
6
  import { useDefaultTexts } from "./hooks/useDefaultTexts.js";
7
7
  import { useTable } from "./hooks/useTable.js";
8
+ import { useTableHighlight } from "./hooks/useTableHighlight.js";
8
9
  import { getDefaultSkeletonColumns } from "./utils/getDefaultSkeletonColumns.js";
9
10
  import { processColumns } from "./utils/processColumns.js";
10
11
  const Table = ({
@@ -15,6 +16,14 @@ const Table = ({
15
16
  isLoading,
16
17
  selectedRowKeys,
17
18
  pagination,
19
+ matchesSearchQuery,
20
+ filterData,
21
+ onSearchQueryChange,
22
+ searchProps,
23
+ expandable,
24
+ rowKey,
25
+ onSort,
26
+ tableRef,
18
27
  ...props
19
28
  }) => {
20
29
  const texts = useDefaultTexts(defaultTexts);
@@ -30,21 +39,38 @@ const Table = ({
30
39
  paginationProps,
31
40
  hasPagination,
32
41
  columnSizing,
33
- isColumnSizingReady
42
+ isColumnSizingReady,
43
+ searchQuery,
44
+ setSearchQuery,
45
+ handleSearchClear,
46
+ hasBuiltInSearch,
47
+ totalDataCount
34
48
  } = useTable({
35
49
  data,
36
50
  columns: finalColumns,
37
51
  pagination,
38
52
  selectionConfig,
39
53
  selectedRowKeys,
54
+ matchesSearchQuery,
55
+ filterData,
56
+ onSearchQueryChange,
57
+ expandable,
58
+ rowKey,
59
+ onSort,
40
60
  wrapperRef,
41
61
  requireColumnSizing: false
42
62
  });
63
+ const {
64
+ highlightRow
65
+ } = useTableHighlight(wrapperRef);
66
+ useImperativeHandle(tableRef, () => ({
67
+ highlightRow
68
+ }));
43
69
  const tableContextValue = useMemo(() => ({
44
70
  table,
45
71
  getScrollContainer: () => null
46
72
  }), [table]);
47
- return /* @__PURE__ */ jsx(TableContext.Provider, { value: tableContextValue, children: /* @__PURE__ */ jsx(SelectionContext.Provider, { value: selectionConfig, children: /* @__PURE__ */ jsx(BaseTable, { texts, isLoading, hasPagination, paginationProps, tableOuterRef: wrapperRef, selectedRowKeys, columnSizing, isColumnSizingReady, ...props }) }) });
73
+ return /* @__PURE__ */ jsx(TableContext.Provider, { value: tableContextValue, children: /* @__PURE__ */ jsx(SelectionContext.Provider, { value: selectionConfig, children: /* @__PURE__ */ jsx(BaseTable, { texts, isLoading, hasPagination, paginationProps, tableOuterRef: wrapperRef, selectedRowKeys, columnSizing, isColumnSizingReady, searchQuery, setSearchQuery, handleSearchClear, hasBuiltInSearch, searchProps, dataSourceTotalCount: totalDataCount, ...props }) }) });
48
74
  };
49
75
  export {
50
76
  Table
@@ -12,4 +12,3 @@ export declare const commonCellStyles: import('styled-components').FlattenInterp
12
12
  export declare const TableSkeleton: import('styled-components').StyledComponent<({ size, numberOfSkeletons, width, height, className, }: import('@synerise/ds-skeleton').SkeletonProps) => React.JSX.Element, any, {
13
13
  skeletonWidth?: string;
14
14
  }, never>;
15
- export declare const StyledTable: import('styled-components').StyledComponent<"table", any, {}, never>;
@@ -15,12 +15,7 @@ const TableSkeleton = /* @__PURE__ */ styled(DSSkeleton).withConfig({
15
15
  displayName: "Tablestyles__TableSkeleton",
16
16
  componentId: "sc-zrmzmn-1"
17
17
  })(["padding:0;", ""], (props) => props.skeletonWidth && `width: ${props.skeletonWidth};`);
18
- const StyledTable = /* @__PURE__ */ styled.table.withConfig({
19
- displayName: "Tablestyles__StyledTable",
20
- componentId: "sc-zrmzmn-2"
21
- })(["width:100%;border-spacing:0;border-collapse:separate;"]);
22
18
  export {
23
- StyledTable,
24
19
  TableContainer,
25
20
  TableSkeleton,
26
21
  commonCellStyles,
@@ -1,19 +1,67 @@
1
1
  import { PaginationProps } from 'antd/lib/pagination';
2
2
  import { MouseEvent, MutableRefObject, ReactElement, ReactNode, RefObject } from 'react';
3
+ import { SearchInputProps } from '@synerise/ds-search';
3
4
  import { TooltipProps } from '@synerise/ds-tooltip';
4
5
  import { Column, ColumnDef, ColumnMeta, Row, RowData } from '@tanstack/react-table';
5
6
  import { VirtualItem, Virtualizer } from '@tanstack/react-virtual';
7
+ export type HighlightOptions = {
8
+ /** Duration in milliseconds. Default: 600 */
9
+ duration?: number;
10
+ };
11
+ export type TableRef = {
12
+ /** Temporarily highlight a row by key. Highlight fades out after duration. */
13
+ highlightRow: (rowKey: string, options?: HighlightOptions) => void;
14
+ };
6
15
  /**
7
16
  * Standard paginated table component props
8
17
  */
9
18
  export type TableProps<TData, TValue> = SharedTableProps<TData, TValue> & PaginatedProps & {
10
19
  texts?: Partial<TableTexts>;
20
+ tableRef?: RefObject<TableRef>;
11
21
  };
12
22
  /**
13
23
  * Virtual non-paginated table component props
14
24
  */
15
25
  export type VirtualTableProps<TData, TValue> = SharedTableProps<TData, TValue> & VirtualProps & {};
16
26
  export type DSColumnMeta<TData extends RowData, TValue> = ColumnMeta<TData, TValue>;
27
+ /**
28
+ * Legacy column type matching @synerise/ds-table's VirtualColumnType.
29
+ * Used by legacyColumnConfigAdapter to convert old column configs to TanStack ColumnDef.
30
+ * For new code, prefer ColumnDef from @tanstack/react-table directly.
31
+ */
32
+ export type LegacyColumnType<T> = {
33
+ dataIndex?: string | string[];
34
+ key?: string | number;
35
+ title?: ReactNode | ((props: Record<string, unknown>) => ReactNode);
36
+ render?: (value: unknown, record: T, index: number) => ReactNode;
37
+ childRender?: (value: unknown, row: T, index: number) => ReactNode;
38
+ sorter?: boolean | ((a: T, b: T) => number) | {
39
+ compare?: (a: T, b: T) => number;
40
+ multiple?: number | false;
41
+ };
42
+ sortOrder?: 'ascend' | 'descend' | null;
43
+ sortRender?: 'default' | 'string' | ((sortStateApi: SortStateAPI, column: LegacyColumnType<T>) => ReactNode);
44
+ width?: number | string;
45
+ minWidth?: number | string;
46
+ maxWidth?: number | string;
47
+ fixed?: 'left' | 'right';
48
+ align?: 'left' | 'center' | 'right';
49
+ id?: string | number;
50
+ fixedFirst?: boolean;
51
+ left?: number;
52
+ right?: number;
53
+ ellipsis?: boolean;
54
+ className?: string;
55
+ getCellTooltipProps?: (row: T) => TooltipProps | false;
56
+ };
57
+ /**
58
+ * @deprecated Compatibility alias. Use LegacyColumnType with legacyColumnConfigAdapter, or ColumnDef for new code.
59
+ */
60
+ export type DSColumnType<TData> = LegacyColumnType<TData>;
61
+ /**
62
+ * @deprecated Compatibility alias. Use TableProps or VirtualTableProps for new code.
63
+ */
64
+ export type DSTableProps<TData, TValue = unknown> = SharedTableProps<TData, TValue>;
17
65
  export type Expandable<TData> = {
18
66
  childrenColumnName?: keyof TData;
19
67
  expandedRowKeys?: string[];
@@ -56,15 +104,102 @@ export type SharedTableProps<TData, TValue> = {
56
104
  */
57
105
  disableColumnNamesLineBreak?: boolean;
58
106
  /**
59
- * renders on the right side of table header
60
- * can be used for filtering data based on query
61
- * data filtering is to be set up in the implementation / server-side
107
+ * Enables built-in search. Table manages query state internally, renders SearchInput,
108
+ * and filters `data` for rendering while keeping full data for selection.
109
+ * Takes precedence over `filterData` if both provided.
110
+ *
111
+ * Should be a stable reference (defined outside component or wrapped in useCallback)
112
+ * since it receives the query as a parameter and doesn't need to close over state.
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * const matchesSearchQuery = (query: string, row: User) =>
117
+ * row.name.toLowerCase().includes(query.toLowerCase());
118
+ *
119
+ * <Table data={users} matchesSearchQuery={matchesSearchQuery} />
120
+ * ```
121
+ */
122
+ matchesSearchQuery?: (query: string, row: TData) => boolean;
123
+ /**
124
+ * Override SearchInput props when using built-in search (matchesSearchQuery).
125
+ * Ignored when searchComponent is provided.
126
+ *
127
+ * @example
128
+ * ```tsx
129
+ * <Table
130
+ * data={data}
131
+ * columns={columns}
132
+ * matchesSearchQuery={matchFn}
133
+ * searchProps={{
134
+ * placeholder: 'Search by name...',
135
+ * clearTooltip: 'Clear search',
136
+ * }}
137
+ * />
138
+ * ```
139
+ */
140
+ searchProps?: Partial<SearchInputProps>;
141
+ /**
142
+ * Called when the internal search query changes. Useful for URL sync or analytics.
143
+ * Only fires when using built-in search (matchesSearchQuery).
144
+ *
145
+ * @example
146
+ * ```tsx
147
+ * <Table
148
+ * data={data}
149
+ * columns={columns}
150
+ * matchesSearchQuery={matchFn}
151
+ * onSearchQueryChange={(query) => {
152
+ * updateUrlParam('search', query);
153
+ * }}
154
+ * />
155
+ * ```
156
+ */
157
+ onSearchQueryChange?: (query: string) => void;
158
+ /**
159
+ * Filters `data` internally for rendering while keeping full data for selection.
160
+ * For use with `searchComponent` where the consumer manages query state.
161
+ * Ignored when `matchesSearchQuery` is provided.
162
+ *
163
+ * @example
164
+ * ```tsx
165
+ * const [query, setQuery] = useState('');
166
+ *
167
+ * <Table
168
+ * data={users}
169
+ * filterData={query ? (row) => row.name.includes(query) : undefined}
170
+ * searchComponent={<CustomSearch value={query} onChange={setQuery} />}
171
+ * />
172
+ * ```
173
+ */
174
+ filterData?: (row: TData) => boolean;
175
+ /**
176
+ * Custom search component rendered in the table header.
177
+ * When provided, takes precedence over built-in SearchInput rendering.
178
+ *
179
+ * Can be used with `filterData` for internal filtering, or alone for
180
+ * external/legacy filtering where the consumer pre-filters `data`.
181
+ *
182
+ * @example
183
+ * ```tsx
184
+ * <Table
185
+ * data={data}
186
+ * columns={columns}
187
+ * searchComponent={
188
+ * <SearchInput
189
+ * value={query}
190
+ * onChange={setQuery}
191
+ * onClear={() => setQuery('')}
192
+ * placeholder="Search..."
193
+ * clearTooltip="Clear"
194
+ * closeOnClickOutside
195
+ * />
196
+ * }
197
+ * />
198
+ * ```
62
199
  */
63
200
  searchComponent?: ReactNode;
64
201
  /**
65
- * renders on the right side of table header
66
- * can be used for rendering custom filter buttons
67
- * data filtering is to be set up in the implementation / server-side
202
+ * Custom filter component rendered in the table header.
68
203
  */
69
204
  filterComponent?: ReactNode;
70
205
  rowKey?: RowKey<TData>;
@@ -123,6 +258,10 @@ export type BaseTableProps<TData, TValue> = Omit<SharedTableProps<TData, TValue>
123
258
  texts: TableTexts;
124
259
  columnSizing: Record<string, number>;
125
260
  isColumnSizingReady: boolean;
261
+ searchQuery?: string;
262
+ setSearchQuery?: (query: string) => void;
263
+ handleSearchClear?: () => void;
264
+ hasBuiltInSearch?: boolean;
126
265
  } & VirtualProps & PaginatedProps;
127
266
  export type ScrollDirection = 'forward' | 'backward' | null;
128
267
  export type TableInternalProps = {
@@ -144,6 +283,8 @@ export type VirtualTableRef = {
144
283
  scrollToTop: () => void;
145
284
  scrollTo: (top: number) => void;
146
285
  scrollToIndex: (index: number, options?: Parameters<Virtualizer<HTMLDivElement, Element>['scrollToIndex']>[1]) => void;
286
+ /** Temporarily highlight a row by key. Highlight fades out after duration. */
287
+ highlightRow: (rowKey: string, options?: HighlightOptions) => void;
147
288
  };
148
289
  type VirtualProps = {
149
290
  /** max-height (px) for the built-in scroll container. Ignored when scrollElementRef is provided. Defaults to 800. */
@@ -158,6 +299,16 @@ type VirtualProps = {
158
299
  onItemsRendered?: (props: OnItemsRenderedProps) => void;
159
300
  scrollElementRef?: MutableRefObject<HTMLDivElement | null>;
160
301
  onScrollToRecordIndex?: (recordIndex: number, callback?: () => void) => void;
302
+ /**
303
+ * render a sticky back-to-top button once the top of the table is scrolled out of view.
304
+ * Available with or without `infiniteScroll`.
305
+ */
306
+ showBackToTopButton?: boolean;
307
+ /**
308
+ * Called when the back-to-top button is clicked.
309
+ * Defaults to scrolling the virtual table to the top when omitted.
310
+ */
311
+ onBackToTop?: () => void;
161
312
  };
162
313
  export type OnItemsRenderedProps = {
163
314
  visibleStartIndex: number;
@@ -181,14 +332,12 @@ export type InfiniteScrollState = {
181
332
  };
182
333
  export type InfiniteScrollProps = InfiniteScrollState & {
183
334
  maxScroll?: number;
184
- showBackToTopButton?: boolean;
185
335
  nextPage?: InfiniteScrollState;
186
336
  prevPage?: InfiniteScrollState;
187
337
  render?: (state: InfiniteScrollState) => ReactElement;
188
338
  onRetryButtonClick?: (position: LoaderItemPosition | undefined) => void;
189
339
  onScrollEndReach?: () => void;
190
340
  onScrollTopReach?: () => void;
191
- onBackToTop?: () => void;
192
341
  };
193
342
  export type LoaderItemProps = {
194
343
  infiniteScroll: InfiniteScrollProps & InfiniteScrollState;
@@ -241,7 +390,9 @@ export type RowSelection = {
241
390
  selectedRowKeys: string[];
242
391
  };
243
392
  export type RowKey<TData> = ((row: TData) => string) | keyof TData;
244
- export type TableTexts = TableHeaderTexts & TableBodyTexts & TableColumnSorterTexts & TableRowSelectionTexts & {};
393
+ export type TableTexts = TableHeaderTexts & TableBodyTexts & TableColumnSorterTexts & TableRowSelectionTexts & {
394
+ infiniteScrollBackToTop: ReactNode;
395
+ };
245
396
  export type TableColumnSorterTexts = {
246
397
  columnSortAscend: ReactNode;
247
398
  columnSortDescend: ReactNode;
@@ -284,6 +435,9 @@ export type CustomCounterArgs = {
284
435
  export type CustomCounterFn = (props: CustomCounterArgs) => ReactNode;
285
436
  export type TableBodyProps<TData, TValue> = Pick<BaseTableProps<TData, TValue>, 'infiniteScroll' | 'cellHeight' | 'emptyDataComponent' | 'onRowClick' | 'getRowTooltipProps'> & {
286
437
  texts: TableBodyTexts;
438
+ withBodyScroll?: boolean;
439
+ tableBodyScrollRef?: React.MutableRefObject<HTMLElement | null>;
440
+ maxHeight?: number;
287
441
  };
288
442
  export type TableEmptyBodyProps<TData, TValue> = Pick<TableBodyProps<TData, TValue>, 'emptyDataComponent'> & {
289
443
  texts: TableEmptyBodyTexts;
@@ -293,7 +447,7 @@ export type TableCounterProps<TData, TValue> = Pick<BaseTableProps<TData, TValue
293
447
  selectedItemsCount?: number;
294
448
  tableTotal: number;
295
449
  };
296
- export type TableHeaderProps<TData, TValue> = Pick<BaseTableProps<TData, TValue>, 'itemsMenu' | 'title' | 'isCounterLoading' | 'searchComponent' | 'filterComponent' | 'rowKey' | 'headerButton' | 'renderCustomCounter' | 'renderSelectionTitle' | 'dataSourceTotalCount' | 'hideTitlePart' | 'isLoading'> & {
450
+ export type TableHeaderProps<TData, TValue> = Pick<BaseTableProps<TData, TValue>, 'itemsMenu' | 'title' | 'isCounterLoading' | 'searchComponent' | 'filterComponent' | 'rowKey' | 'headerButton' | 'renderCustomCounter' | 'renderSelectionTitle' | 'dataSourceTotalCount' | 'hideTitlePart' | 'isLoading' | 'searchQuery' | 'setSearchQuery' | 'handleSearchClear' | 'hasBuiltInSearch' | 'searchProps'> & {
297
451
  selectedRows?: number;
298
452
  selectionConfig?: SelectionConfig<TData>;
299
453
  texts: TableHeaderTexts;
@@ -1,3 +1,3 @@
1
1
  import { default as React } from 'react';
2
2
  import { VirtualTableProps } from './Table.types';
3
- export declare const VirtualTable: <TData extends object, TValue>({ data, infiniteScroll, scrollElementRef, columns, tableRef, selectionConfig, selectedRowKeys, rowKey, onSort, stickyHeader, texts: defaultTexts, cellHeight, expandable, onItemsRendered, isLoading, onScrollToRecordIndex, maxHeight, ...props }: VirtualTableProps<TData, TValue>) => React.JSX.Element;
3
+ export declare const VirtualTable: <TData extends object, TValue>({ data, infiniteScroll, scrollElementRef, columns, tableRef, selectionConfig, selectedRowKeys, rowKey, onSort, stickyHeader, texts: defaultTexts, cellHeight, expandable, onItemsRendered, isLoading, onScrollToRecordIndex, maxHeight, matchesSearchQuery, filterData, onSearchQueryChange, searchProps, showBackToTopButton, onBackToTop, ...props }: VirtualTableProps<TData, TValue>) => React.JSX.Element;
@@ -1,7 +1,8 @@
1
- import { jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useState, useRef, useMemo, useCallback, useEffect, useImperativeHandle } from "react";
3
3
  import { useVirtualizer } from "@tanstack/react-virtual";
4
4
  import { DEFAULT_STICKY_VALUE, DEFAULT_CELL_HEIGHT, INFINITE_LOADER_ITEM_HEIGHT, INFINITE_SCROLL_PADDING_START } from "./Table.const.js";
5
+ import { BackToTopButton } from "./components/BackToTopButton/BackToTopButton.js";
5
6
  import { BaseTable } from "./components/BaseTable/BaseTable.js";
6
7
  import { SelectionContext } from "./contexts/SelectionContext.js";
7
8
  import { StickyContext } from "./contexts/StickyContext.js";
@@ -9,6 +10,7 @@ import { TableContext } from "./contexts/TableContext.js";
9
10
  import { useDefaultTexts } from "./hooks/useDefaultTexts.js";
10
11
  import { useInfiniteScroll } from "./hooks/useInfiniteScroll.js";
11
12
  import { useTable } from "./hooks/useTable.js";
13
+ import { useTableHighlight } from "./hooks/useTableHighlight.js";
12
14
  import { getDefaultSkeletonColumns } from "./utils/getDefaultSkeletonColumns.js";
13
15
  import { getInfiniteScrollPadding } from "./utils/getInfiniteScrollPadding.js";
14
16
  import { getIsRevealed } from "./utils/getIsRevealed.js";
@@ -31,6 +33,12 @@ const VirtualTable = ({
31
33
  isLoading,
32
34
  onScrollToRecordIndex,
33
35
  maxHeight,
36
+ matchesSearchQuery,
37
+ filterData,
38
+ onSearchQueryChange,
39
+ searchProps,
40
+ showBackToTopButton,
41
+ onBackToTop,
34
42
  ...props
35
43
  }) => {
36
44
  const [stickyData, setStickyData] = useState(DEFAULT_STICKY_VALUE);
@@ -50,7 +58,12 @@ const VirtualTable = ({
50
58
  const {
51
59
  table,
52
60
  columnSizing,
53
- isColumnSizingReady
61
+ isColumnSizingReady,
62
+ searchQuery,
63
+ setSearchQuery,
64
+ handleSearchClear,
65
+ hasBuiltInSearch,
66
+ totalDataCount
54
67
  } = useTable({
55
68
  data,
56
69
  expandable,
@@ -60,6 +73,9 @@ const VirtualTable = ({
60
73
  rowKey,
61
74
  selectionConfig,
62
75
  selectedRowKeys,
76
+ matchesSearchQuery,
77
+ filterData,
78
+ onSearchQueryChange,
63
79
  onSort,
64
80
  pagination: false
65
81
  });
@@ -159,8 +175,12 @@ const VirtualTable = ({
159
175
  rowVirtualizer.scrollToOffset(INFINITE_LOADER_ITEM_HEIGHT);
160
176
  }
161
177
  }, [rowVirtualizer, infiniteScroll?.prevPage?.hasMore]);
178
+ const {
179
+ highlightRow
180
+ } = useTableHighlight(tableOuterRef);
162
181
  useImperativeHandle(tableRef, () => ({
163
182
  getDimensions,
183
+ highlightRow,
164
184
  scrollTo: rowVirtualizer.scrollToOffset,
165
185
  scrollToIndex: (topLevelIndex, options) => {
166
186
  const {
@@ -215,7 +235,17 @@ const VirtualTable = ({
215
235
  rowVirtualizer,
216
236
  getScrollContainer
217
237
  }), [table, rowVirtualizer, getScrollContainer]);
218
- return /* @__PURE__ */ jsx(TableContext.Provider, { value: tableContextValue, children: /* @__PURE__ */ jsx(StickyContext.Provider, { value: stickyValue, children: /* @__PURE__ */ jsx(SelectionContext.Provider, { value: selectionConfig, children: /* @__PURE__ */ jsx(BaseTable, { columnSizing, isColumnSizingReady, texts, tableOuterRef, tableBodyScrollRef, withScroll: withContainerScroll, withBodyScroll, maxHeight, hasPagination: false, infiniteScroll, isLoading, ...props }) }) }) });
238
+ const handleBackToTop = useCallback(() => {
239
+ if (onBackToTop) {
240
+ onBackToTop();
241
+ return;
242
+ }
243
+ rowVirtualizer.scrollToOffset(0);
244
+ }, [onBackToTop, rowVirtualizer]);
245
+ return /* @__PURE__ */ jsx(TableContext.Provider, { value: tableContextValue, children: /* @__PURE__ */ jsx(StickyContext.Provider, { value: stickyValue, children: /* @__PURE__ */ jsxs(SelectionContext.Provider, { value: selectionConfig, children: [
246
+ /* @__PURE__ */ jsx(BaseTable, { columnSizing, isColumnSizingReady, texts, tableOuterRef, tableBodyScrollRef, withScroll: withContainerScroll, withBodyScroll, maxHeight, hasPagination: false, infiniteScroll, isLoading, searchQuery, setSearchQuery, handleSearchClear, hasBuiltInSearch, searchProps, dataSourceTotalCount: totalDataCount, ...props }),
247
+ showBackToTopButton && /* @__PURE__ */ jsx(BackToTopButton, { label: texts.infiniteScrollBackToTop, onClick: handleBackToTop, scrollContainerRef: scrollableContainerRef, hasData: !tableIsEmpty && columns.length > 0, threshold: (stickyData.titleBarHeight || 0) + (Number.isFinite(stickyData.containerPaddingTop) ? stickyData.containerPaddingTop : 0) })
248
+ ] }) }) });
219
249
  };
220
250
  export {
221
251
  VirtualTable
@@ -0,0 +1,11 @@
1
+ import { default as React, MutableRefObject, ReactNode } from 'react';
2
+ type BackToTopButtonProps = {
3
+ label?: ReactNode;
4
+ onClick: () => void;
5
+ scrollContainerRef?: MutableRefObject<HTMLDivElement | null>;
6
+ hasData: boolean;
7
+ /** Scroll threshold in px above which the button becomes visible. Should match the sticky-header offset. */
8
+ threshold: number;
9
+ };
10
+ export declare const BackToTopButton: ({ label, onClick, scrollContainerRef, hasData, threshold, }: BackToTopButtonProps) => React.JSX.Element;
11
+ export {};
@@ -0,0 +1,34 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import Button from "@synerise/ds-button";
4
+ import Icon, { ArrowUpCircleM } from "@synerise/ds-icon";
5
+ import { BackToTopWrapper } from "./BackToTopButton.styles.js";
6
+ const BackToTopButton = ({
7
+ label,
8
+ onClick,
9
+ scrollContainerRef,
10
+ hasData,
11
+ threshold
12
+ }) => {
13
+ const [isPastThreshold, setIsPastThreshold] = useState(false);
14
+ useEffect(() => {
15
+ const el = scrollContainerRef?.current;
16
+ if (!el) {
17
+ return;
18
+ }
19
+ const update = () => setIsPastThreshold(el.scrollTop > threshold);
20
+ update();
21
+ el.addEventListener("scroll", update, {
22
+ passive: true
23
+ });
24
+ return () => el.removeEventListener("scroll", update);
25
+ }, [scrollContainerRef, threshold]);
26
+ const isVisible = hasData && isPastThreshold;
27
+ return /* @__PURE__ */ jsx(BackToTopWrapper, { $visible: isVisible, children: /* @__PURE__ */ jsxs(Button, { type: "custom-color", mode: "icon-label", color: "grey", "data-testid": "ds-table-back-to-top-button", onClick, children: [
28
+ /* @__PURE__ */ jsx(Icon, { component: /* @__PURE__ */ jsx(ArrowUpCircleM, {}) }),
29
+ label
30
+ ] }) });
31
+ };
32
+ export {
33
+ BackToTopButton
34
+ };
@@ -0,0 +1,3 @@
1
+ export declare const BackToTopWrapper: import('styled-components').StyledComponent<"div", any, {
2
+ $visible?: boolean;
3
+ }, never>;
@@ -0,0 +1,8 @@
1
+ import styled from "styled-components";
2
+ const BackToTopWrapper = /* @__PURE__ */ styled.div.withConfig({
3
+ displayName: "BackToTopButtonstyles__BackToTopWrapper",
4
+ componentId: "sc-5jc0of-0"
5
+ })(["display:", ";position:fixed;bottom:24px;right:24px;z-index:2;&&&& > .ds-table-back-to-top-button{transform:none;}"], (props) => props.$visible ? "flex" : "none");
6
+ export {
7
+ BackToTopWrapper
8
+ };
@@ -1,3 +1,3 @@
1
1
  import { default as React } from 'react';
2
2
  import { BaseTableProps, TableInternalProps } from '../../Table.types';
3
- export declare const BaseTable: <TData extends object, TValue>({ infiniteScroll, cellHeight, className, itemsMenu, texts, isCounterLoading, isLoading, rowKey, title, hideTitleBar, hideTitlePart, renderCustomCounter, renderSelectionTitle, searchComponent, filterComponent, headerWithBorderTop, emptyDataComponent, headerButton, hideColumnNames, expandable, columnSizing, isColumnSizingReady, paginationProps, hasPagination, disableColumnNamesLineBreak, dataSourceTotalCount, cardStyles, tableOuterRef, withScroll, withBodyScroll, maxHeight, tableBodyScrollRef, onRowClick, getRowTooltipProps, }: BaseTableProps<TData, TValue> & TableInternalProps) => React.JSX.Element;
3
+ export declare const BaseTable: <TData extends object, TValue>({ infiniteScroll, cellHeight, className, itemsMenu, texts, isCounterLoading, isLoading, rowKey, title, hideTitleBar, hideTitlePart, renderCustomCounter, renderSelectionTitle, searchComponent, filterComponent, headerWithBorderTop, emptyDataComponent, headerButton, hideColumnNames, expandable, columnSizing, isColumnSizingReady, paginationProps, hasPagination, disableColumnNamesLineBreak, dataSourceTotalCount, cardStyles, tableOuterRef, withScroll, withBodyScroll, maxHeight, tableBodyScrollRef, onRowClick, getRowTooltipProps, searchQuery, setSearchQuery, handleSearchClear, hasBuiltInSearch, searchProps, }: BaseTableProps<TData, TValue> & TableInternalProps) => React.JSX.Element;
@@ -46,7 +46,12 @@ const BaseTable = ({
46
46
  maxHeight,
47
47
  tableBodyScrollRef,
48
48
  onRowClick,
49
- getRowTooltipProps
49
+ getRowTooltipProps,
50
+ searchQuery,
51
+ setSearchQuery,
52
+ handleSearchClear,
53
+ hasBuiltInSearch,
54
+ searchProps
50
55
  }) => {
51
56
  const horizontalScrollRefs = useRef([]);
52
57
  const tableBodyWrapperRef = useRef(null);
@@ -58,7 +63,7 @@ const BaseTable = ({
58
63
  } = useTableContext();
59
64
  const stickyContext = useStickyContext();
60
65
  const stickyContextRef = useRef(stickyContext);
61
- const useUnifiedScroll = !stickyContext && !withBodyScroll;
66
+ const useUnifiedScroll = !stickyContext;
62
67
  useEffect(() => {
63
68
  stickyContextRef.current = stickyContext;
64
69
  }, [stickyContext]);
@@ -95,18 +100,18 @@ const BaseTable = ({
95
100
  addNode(element);
96
101
  }, children: /* @__PURE__ */ jsxs(StyledTable, { role: "table", children: [
97
102
  !hideColumnNames && (!isEmpty || isLoading) && /* @__PURE__ */ jsx(TableColumns, { texts, disableColumnNamesLineBreak }),
98
- isLoading ? /* @__PURE__ */ jsx(TableBodySkeleton, { wrapperRef: tableBodyWrapperRef, cellHeight }, "table-body-skeleton") : /* @__PURE__ */ jsx(TableBody, { cellHeight, infiniteScroll, emptyDataComponent, texts, onRowClick, getRowTooltipProps }, "table-body")
103
+ isLoading ? /* @__PURE__ */ jsx(TableBodySkeleton, { wrapperRef: tableBodyWrapperRef, cellHeight }, "table-body-skeleton") : /* @__PURE__ */ jsx(TableBody, { withBodyScroll, tableBodyScrollRef, maxHeight, cellHeight, infiniteScroll, emptyDataComponent, texts, onRowClick, getRowTooltipProps }, "table-body")
99
104
  ] }) });
100
105
  return /* @__PURE__ */ jsxs(BaseTableWrapper, { isEmpty, columnSizing, $isColumnSizingReady: isColumnSizingReady, $size: size, children: [
101
106
  /* @__PURE__ */ jsxs(TableContainer, { ref: tableOuterRef, className, withBorderTop: headerWithBorderTop, cardStyles, withScroll, $maxHeight: maxHeight, "data-testid": "ds-table-container", children: [
102
- !hideTitleBar && /* @__PURE__ */ jsx(TableHeader, { itemsMenu, texts, childrenColumnName: expandable?.childrenColumnName, isCounterLoading, isLoading, rowKey, title, hideTitlePart, renderCustomCounter, renderSelectionTitle, searchComponent, filterComponent, headerButton, dataSourceTotalCount }),
107
+ !hideTitleBar && /* @__PURE__ */ jsx(TableHeader, { itemsMenu, texts, childrenColumnName: expandable?.childrenColumnName, isCounterLoading, isLoading, rowKey, title, hideTitlePart, renderCustomCounter, renderSelectionTitle, searchComponent, filterComponent, headerButton, dataSourceTotalCount, searchQuery, setSearchQuery, handleSearchClear, hasBuiltInSearch, searchProps }),
103
108
  useUnifiedScroll ? unifiedTableContent : /* @__PURE__ */ jsxs(Fragment, { children: [
104
109
  !hideColumnNames && (!isEmpty || isLoading) && /* @__PURE__ */ jsx(TableColumnsHorizontalScroll, { stickyRight: table.getRightTotalSize(), stickyLeft: table.getLeftTotalSize(), stickyData: stickyContext?.stickyData, isScrolled: rowVirtualizer?.scrollOffset, ref: handleRef, children: /* @__PURE__ */ jsx(StyledTable, { role: "table", children: /* @__PURE__ */ jsx(TableColumns, { texts, disableColumnNamesLineBreak }) }) }),
105
110
  withBodyScroll ? /* @__PURE__ */ jsx(TableBodyScrollWrapper, { ref: tableBodyScrollRef, $maxHeight: maxHeight, children: tableBodyContent }) : tableBodyContent
106
111
  ] }),
107
112
  hasPagination && /* @__PURE__ */ jsx(TablePagination, { ...paginationProps })
108
113
  ] }),
109
- !isEmpty && /* @__PURE__ */ jsx(TableHorizontalScrollBar, { contentRef: (element) => addNode(element) })
114
+ !isEmpty && !!stickyContext && /* @__PURE__ */ jsx(TableHorizontalScrollBar, { contentRef: (element) => addNode(element) })
110
115
  ] });
111
116
  };
112
117
  export {
@@ -20,7 +20,7 @@ const TableSkeleton = /* @__PURE__ */ styled(DSSkeleton).withConfig({
20
20
  const StyledTable = /* @__PURE__ */ styled.table.withConfig({
21
21
  displayName: "BaseTablestyles__StyledTable",
22
22
  componentId: "sc-1oa5lc2-4"
23
- })(["width:var(--table-size);border-spacing:0;border-collapse:separate;"]);
23
+ })(["width:var(--table-size);border-spacing:0;border-collapse:separate;@keyframes ds-table-row-highlight{0%{background-color:transparent;}5%{background-color:var( --ds-highlight-color,", " );}30%{background-color:var( --ds-highlight-color,", " );}100%{background-color:transparent;}}tr.ds-table-row-highlight td{transition:background 0.3s ease-in-out;animation:ds-table-row-highlight var(--ds-highlight-duration,600ms) ease-in-out forwards;}"], (props) => props.theme.palette["blue-050"], (props) => props.theme.palette["blue-050"]);
24
24
  const TableColumnsHorizontalScroll = /* @__PURE__ */ styled(TableHorizontalScroll).withConfig({
25
25
  displayName: "BaseTablestyles__TableColumnsHorizontalScroll",
26
26
  componentId: "sc-1oa5lc2-5"