jattac.libs.web.responsive-table 0.6.1 → 0.6.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.
package/README.md CHANGED
@@ -1,838 +1,838 @@
1
- # ResponsiveTable: A Modern and Flexible React Table Component
2
-
3
- ResponsiveTable is a powerful, lightweight, and fully responsive React component for creating beautiful and functional tables. It’s designed to look great on any device, adapting from a traditional table layout on desktops to a clean, card-based view on mobile screens.
4
-
5
- ## Table of Contents
6
-
7
- - [Features](#features)
8
- - [Installation](#installation)
9
- - [Getting Started](#getting-started)
10
- - [Comprehensive Examples](#comprehensive-examples)
11
- - [Example 1: Loading State and Animations](#example-1-loading-state-and-animations)
12
- - [Example 2: Adding a Clickable Row Action](#example-2-adding-a-clickable-row-action)
13
- - [Example 3: Row Selection (Controlled Component)](#example-3-row-selection-controlled-component)
14
- - [Example 4: Custom Cell Rendering](#example-4-custom-cell-rendering)
15
- - [Example 5: Dynamic and Conditional Columns](#example-5-dynamic-and-conditional-columns)
16
- - [Example 6: Advanced Footer with Labels and Interactivity](#example-6-advanced-footer-with-labels-and-interactivity)
17
- - [Example 7: Disabling Page-Level Sticky Header](#example-7-disabling-page-level-sticky-header)
18
- - [Plugin System](#plugin-system)
19
- - [Plugin Execution Order](#plugin-execution-order)
20
- - [How to Use Plugins](#how-to-use-plugins)
21
- - [Built-in Plugins](#built-in-plugins)
22
- - [API Reference](#api-reference)
23
- - [ResponsiveTable Props](#responsivetable-props)
24
- - [IResponsiveTableColumnDefinition<TData>](#iresponsivetablecolumndefinitiontdata)
25
- - [IFooterRowDefinition](#ifooterrowdefinition)
26
- - [IFooterColumnDefinition](#ifootercolumndefinition)
27
- - [Breaking Changes](#breaking-changes)
28
- - [Version 0.5.0](#version-050)
29
- - [License](#license)
30
-
31
- ## Features
32
-
33
- - **Mobile-First Design**: Automatically switches to a card layout on smaller screens for optimal readability.
34
- - **Highly Customizable**: Tailor the look and feel of columns, headers, and footers.
35
- - **Dynamic Data Handling**: Define columns and footers based on your data or application state.
36
- - **Delightful Animations**: Includes an optional skeleton loader and staggered row entrance animations.
37
- - **Interactive Elements**: Easily add click handlers for rows, headers, and footer cells.
38
- - **Row Selection**: Built-in support for single or multiple row selection, with full programmatic control.
39
- - **Efficient & Responsive**: Built with efficiency in mind, including debounced resize handling for smooth transitions.
40
- - **Easy to Use**: A simple and intuitive API for quick integration.
41
- - **Extensible Plugin System**: Easily add new functionalities like filtering, sorting, or infinite scrolling.
42
-
43
- ## Installation
44
-
45
- To get started, install the package from npm:
46
-
47
- ```bash
48
- npm install jattac.libs.web.responsive-table
49
- ```
50
-
51
- ## Getting Started
52
-
53
- Here’s a basic example to get you up and running in minutes.
54
-
55
- ```jsx
56
- import React from 'react';
57
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
58
-
59
- const GettingStarted = () => {
60
- const columns = [
61
- { displayLabel: 'Name', dataKey: 'name', cellRenderer: (row) => row.name },
62
- { displayLabel: 'Age', dataKey: 'age', cellRenderer: (row) => row.age },
63
- { displayLabel: 'City', dataKey: 'city', cellRenderer: (row) => row.city },
64
- ];
65
-
66
- const data = [
67
- { name: 'Alice', age: 32, city: 'New York' },
68
- { name: 'Bob', age: 28, city: 'Los Angeles' },
69
- { name: 'Charlie', age: 45, city: 'Chicago' },
70
- ];
71
-
72
- return <ResponsiveTable columnDefinitions={columns} data={data} />;
73
- };
74
-
75
- export default GettingStarted;
76
- ```
77
-
78
- This will render a table that automatically adapts to the screen size. On a desktop, it will look like a standard table, and on mobile, it will switch to a card-based layout.
79
-
80
- ---
81
-
82
- ## Comprehensive Examples
83
-
84
- ### Example 1: Loading State and Animations
85
-
86
- You can provide a seamless user experience by showing a skeleton loader while your data is being fetched, and then animating the rows in when the data is ready.
87
-
88
- ```jsx
89
- import React, { useState, useEffect } from 'react';
90
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
91
-
92
- const AnimatedTable = () => {
93
- const [data, setData] = useState([]);
94
- const [isLoading, setIsLoading] = useState(true);
95
-
96
- const columns = [
97
- { displayLabel: 'User', cellRenderer: (row) => row.name },
98
- { displayLabel: 'Email', cellRenderer: (row) => row.email },
99
- ];
100
-
101
- useEffect(() => {
102
- // Simulate a network request
103
- setTimeout(() => {
104
- setData([
105
- { name: 'Grace', email: 'grace@example.com' },
106
- { name: 'Henry', email: 'henry@example.com' },
107
- ]);
108
- setIsLoading(false);
109
- }, 2000);
110
- }, []);
111
-
112
- return (
113
- <ResponsiveTable columnDefinitions={columns} data={data} animationProps={{ isLoading, animateOnLoad: true }} />
114
- );
115
- };
116
- ```
117
-
118
- ### Example 2: Adding a Clickable Row Action
119
-
120
- You can make rows clickable to perform actions, such as navigating to a details page or opening a modal.
121
-
122
- ```jsx
123
- import React from 'react';
124
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
125
-
126
- const ClickableRows = () => {
127
- const columns = [
128
- { displayLabel: 'Product', cellRenderer: (row) => row.product },
129
- { displayLabel: 'Price', cellRenderer: (row) => `${row.price.toFixed(2)}` },
130
- ];
131
-
132
- const data = [
133
- { id: 1, product: 'Laptop', price: 1200 },
134
- { id: 2, product: 'Keyboard', price: 75 },
135
- ];
136
-
137
- const handleRowClick = (item) => {
138
- alert(`You clicked on product ID: ${item.id}`);
139
- };
140
-
141
- return <ResponsiveTable columnDefinitions={columns} data={data} onRowClick={handleRowClick} />;
142
- };
143
- ```
144
-
145
- ### Example 3: Row Selection (Controlled Component)
146
-
147
- Enable row selection by providing the `selectionProps` object. The table can be a fully "controlled" component, where you manage the state of selected items, or an "uncontrolled" component where the table manages its own state.
148
-
149
- This example demonstrates the **controlled** pattern, which gives you full programmatic control over which rows are selected.
150
-
151
- ```jsx
152
- import React, { useState } from 'react';
153
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
154
-
155
- const SelectableTable = () => {
156
- const columns = [
157
- { displayLabel: 'Task', dataKey: 'task', cellRenderer: (row) => row.task },
158
- { displayLabel: 'Status', dataKey: 'status', cellRenderer: (row) => row.status },
159
- ];
160
-
161
- const initialData = [
162
- { id: 'task-1', task: 'Design new logo', status: 'In Progress' },
163
- { id: 'task-2', task: 'Develop homepage', status: 'Completed' },
164
- { id: 'task-3', task: 'Write documentation', status: 'Pending' },
165
- { id: 'task-4', task: 'Deploy to production', status: 'Pending' },
166
- ];
167
-
168
- // 1. Manage the selection state in your component
169
- const [selected, setSelected] = useState([initialData[1]]); // Initially select the second row
170
-
171
- const handleSelectionChange = (selectedItems) => {
172
- // 2. Update your state when the selection changes
173
- setSelected(selectedItems);
174
- console.log('Selected items:', selectedItems);
175
- };
176
-
177
- return (
178
- <div>
179
- <button onClick={() => setSelected([])} style={{ marginBottom: '1rem' }}>
180
- Clear Selection
181
- </button>
182
- <ResponsiveTable
183
- columnDefinitions={columns}
184
- data={initialData}
185
- selectionProps={{
186
- onSelectionChange: handleSelectionChange,
187
- selectedItems: selected, // 3. Pass the state down to the table
188
- rowIdKey: 'id', // 4. Provide a unique key for each row
189
- mode: 'multiple', // or 'single'
190
- }}
191
- />
192
- <div style={{ marginTop: '1rem' }}>
193
- <strong>Selected Tasks:</strong>
194
- <ul>
195
- {selected.map((item) => (
196
- <li key={item.id}>{item.task}</li>
197
- ))}
198
- </ul>
199
- </div>
200
- </div>
201
- );
202
- };
203
- ```
204
-
205
- ### Example 4: Custom Cell Rendering
206
-
207
- You can render any React component inside a cell, allowing for rich content like buttons, links, or status badges.
208
-
209
- ```jsx
210
- import React from 'react';
211
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
212
-
213
- const CustomCells = () => {
214
- const columns = [
215
- { displayLabel: <strong>User</strong>, cellRenderer: (row) => <strong>{row.user}</strong> },
216
- {
217
- displayLabel: 'Status',
218
- cellRenderer: (row) => (
219
- <span
220
- style={{
221
- color: row.status === 'Active' ? 'green' : 'red',
222
- fontWeight: 'bold',
223
- }}
224
- >
225
- {row.status}
226
- </span>
227
- ),
228
- },
229
- {
230
- displayLabel: 'Action',
231
- cellRenderer: (row) => <button onClick={() => alert(`Editing ${row.user}`)}>Edit</button>,
232
- },
233
- ];
234
-
235
- const data = [
236
- { user: 'Eve', status: 'Active' },
237
- { user: 'Frank', status: 'Inactive' },
238
- ];
239
-
240
- return <ResponsiveTable columnDefinitions={columns} data={data} />;
241
- };
242
- ```
243
-
244
- ### Example 5: Dynamic and Conditional Columns
245
-
246
- Columns can be generated dynamically based on your data or application state. This is useful for creating flexible tables that adapt to different datasets.
247
-
248
- ```jsx
249
- import React from 'react';
250
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
251
-
252
- const DynamicColumns = ({ isAdmin }) => {
253
- // Base columns for all users
254
- const columns = [
255
- { displayLabel: 'File Name', cellRenderer: (row) => row.fileName },
256
- { displayLabel: 'Size', cellRenderer: (row) => `${row.size} KB` },
257
- ];
258
-
259
- // Add an admin-only column conditionally
260
- if (isAdmin) {
261
- columns.push({
262
- displayLabel: 'Admin Actions',
263
- cellRenderer: (row) => <button onClick={() => alert(`Deleting ${row.fileName}`)}>Delete</button>,
264
- });
265
- }
266
-
267
- const data = [
268
- { fileName: 'document.pdf', size: 1024 },
269
- { fileName: 'image.jpg', size: 512 },
270
- ];
271
-
272
- return <ResponsiveTable columnDefinitions={columns} data={data} />;
273
- };
274
- ```
275
-
276
- ### Example 6: Advanced Footer with Labels and Interactivity
277
-
278
- You can add a footer to display summary information, such as totals or averages. The footer is also responsive and will appear correctly in both desktop and mobile views. With the enhanced footer functionality, you can provide explicit labels for mobile view and add click handlers to footer cells.
279
-
280
- ```jsx
281
- import React from 'react';
282
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
283
-
284
- const TableWithFooter = () => {
285
- const columns = [
286
- { displayLabel: 'Item', cellRenderer: (row) => row.item },
287
- { displayLabel: 'Quantity', cellRenderer: (row) => row.quantity },
288
- { displayLabel: 'Price', cellRenderer: (row) => `${row.price.toFixed(2)}` },
289
- ];
290
-
291
- const data = [
292
- { item: 'Apples', quantity: 10, price: 1.5 },
293
- { item: 'Oranges', quantity: 5, price: 2.0 },
294
- { item: 'Bananas', quantity: 15, price: 0.5 },
295
- ];
296
-
297
- const total = data.reduce((sum, row) => sum + row.quantity * row.price, 0);
298
-
299
- const footerRows = [
300
- {
301
- columns: [
302
- {
303
- colSpan: 2,
304
- cellRenderer: () => <strong>Total:</strong>,
305
- },
306
- {
307
- colSpan: 1,
308
- displayLabel: 'Total',
309
- cellRenderer: () => <strong>${total.toFixed(2)}</strong>,
310
- onCellClick: () => alert('Total clicked!'),
311
- },
312
- ],
313
- },
314
- ];
315
-
316
- return <ResponsiveTable columnDefinitions={columns} data={data} footerRows={footerRows} />;
317
- };
318
- ```
319
-
320
- ### Example 7: Disabling Page-Level Sticky Header
321
-
322
- By default, the table header remains fixed to the top of the viewport as the user scrolls down the page. This ensures the column titles are always visible. To disable this behavior and have the header scroll away with the rest of the page, set the `enablePageLevelStickyHeader` prop to `false`.
323
-
324
- ```jsx
325
- import React from 'react';
326
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
327
-
328
- const NonStickyHeaderTable = () => {
329
- // We need enough data to make the page scroll
330
- const data = Array.from({ length: 50 }, (_, i) => ({
331
- id: i + 1,
332
- item: `Item #${i + 1}`,
333
- description: 'This is a sample item.',
334
- }));
335
-
336
- const columns = [
337
- { displayLabel: 'ID', cellRenderer: (row) => row.id },
338
- { displayLabel: 'Item', cellRenderer: (row) => row.item },
339
- { displayLabel: 'Description', cellRenderer: (row) => row.description },
340
- ];
341
-
342
- return (
343
- <div>
344
- <h1 style={{ height: '50vh', display: 'flex', alignItems: 'center' }}>Scroll down to see the table</h1>
345
- <ResponsiveTable
346
- columnDefinitions={columns}
347
- data={data}
348
- enablePageLevelStickyHeader={false} // <-- Here's the magic switch!
349
- />
350
- <div style={{ height: '50vh' }} />
351
- </div>
352
- );
353
- };
354
- ```
355
-
356
- ---
357
-
358
- ## Plugin System
359
-
360
- ResponsiveTable is designed with an extensible plugin system, allowing developers to easily add new functionalities or modify existing behaviors without altering the core component. Plugins can interact with the table's data, render custom UI elements (like headers or footers), and respond to table events.
361
-
362
- ### Plugin Execution Order
363
-
364
- > **Note:** Plugins process data sequentially in the order they are provided in the `plugins` array. This can have important performance and behavioral implications.
365
-
366
- For example, consider the `SortPlugin` and `FilterPlugin`.
367
-
368
- - `plugins={[new SortPlugin(), new FilterPlugin()]}`: This will **sort** the entire dataset first, and then **filter** the sorted data.
369
- - `plugins={[new FilterPlugin(), new SortPlugin()]}`: This will **filter** the dataset first, and then **sort** the much smaller, filtered result.
370
-
371
- For the best performance, it is highly recommended to **filter first, then sort**.
372
-
373
- ### How to Use Plugins
374
-
375
- Plugins are passed to the `ResponsiveTable` component via the `plugins` prop, which accepts an array of `IResponsiveTablePlugin` instances. Some common functionalities, like filtering and infinite scrolling, are provided as built-in plugins that can be enabled via specific props (`filterProps` and `infiniteScrollProps`). When these props are used, the corresponding built-in plugins are automatically initialized if not already provided in the `plugins` array.
376
-
377
- ```jsx
378
- import React from 'react';
379
- // Note: All plugins are exported from the main package entry point.
380
- import ResponsiveTable, { FilterPlugin } from 'jattac.libs.web.responsive-table';
381
-
382
- const MyTableWithPlugins = () => {
383
- const columns = [
384
- { displayLabel: 'Name', dataKey: 'name', cellRenderer: (row) => row.name, getFilterableValue: (row) => row.name },
385
- {
386
- displayLabel: 'Age',
387
- dataKey: 'age',
388
- cellRenderer: (row) => row.age,
389
- getFilterableValue: (row) => row.age.toString(),
390
- },
391
- ];
392
-
393
- const data = [
394
- { name: 'Alice', age: 32 },
395
- { name: 'Bob', age: 28 },
396
- { name: 'Charlie', age: 45 },
397
- ];
398
-
399
- return (
400
- <ResponsiveTable
401
- columnDefinitions={columns}
402
- data={data}
403
- // Enable built-in filter plugin via props
404
- filterProps={{ showFilter: true, filterPlaceholder: 'Search by name or age...' }}
405
- // Or provide a custom instance of the plugin
406
- // plugins={[new FilterPlugin()]}
407
- />
408
- );
409
- };
410
- ```
411
-
412
- ### Built-in Plugins
413
-
414
- #### `SortPlugin`
415
-
416
- The `SortPlugin` provides powerful, type-safe, and highly customizable column sorting. It adds intuitive UI cues, allowing users to click column headers to sort the data in ascending, descending, or original order.
417
-
418
- **Enabling the `SortPlugin`:**
419
-
420
- To use the plugin, you must first import it and provide a generic instance of it to the `plugins` prop. The real power comes from making the plugin instance generic with your data type, which provides type-safety and IDE autocompletion for the sort comparer helpers.
421
-
422
- ```jsx
423
- import React from 'react';
424
- import ResponsiveTable, { IResponsiveTableColumnDefinition, SortPlugin } from 'jattac.libs.web.responsive-table';
425
-
426
- // Define the shape of your data
427
- interface User {
428
- id: number;
429
- name: string;
430
- signupDate: string;
431
- logins: number;
432
- }
433
-
434
- // 1. Create a single, generic instance of the plugin.
435
- const sortPlugin = new SortPlugin<User>({
436
- initialSortColumn: 'user_logins', // Use the columnId
437
- initialSortDirection: 'desc',
438
- });
439
-
440
- // 2. Define the columns, using the helpers directly from the plugin instance.
441
- const columnDefinitions: IResponsiveTableColumnDefinition<User>[] = [
442
- // ... see examples below
443
- ];
444
-
445
- const UserTable = ({ users }) => (
446
- <ResponsiveTable
447
- columnDefinitions={columnDefinitions}
448
- data={users}
449
- // 3. Pass the already-configured plugin to the table.
450
- plugins={[sortPlugin]}
451
- />
452
- );
453
- ```
454
-
455
- **How to Make Columns Sortable (Opt-In):**
456
-
457
- A column is made sortable by adding a **`columnId`** and either a `sortComparer` or a `getSortableValue` property to its definition. The `columnId` must be a unique string that identifies the column.
458
-
459
- - `sortComparer`: A function that defines the exact comparison logic. This is the most powerful option and should be used for complex data types or custom logic.
460
- - `getSortableValue`: A simpler function that just returns the primitive value (string, number, etc.) to be used in a default comparison.
461
-
462
- **Example 1: Using Type-Safe Comparer Helpers**
463
-
464
- The `SortPlugin` instance provides a `comparers` object with pre-built, type-safe helper functions to eliminate boilerplate for common sorting scenarios. This is the recommended approach.
465
-
466
- ```jsx
467
- const columnDefinitions: IResponsiveTableColumnDefinition<User>[] = [
468
- {
469
- columnId: 'user_name',
470
- displayLabel: 'Name',
471
- dataKey: 'name',
472
- cellRenderer: (user) => user.name,
473
- // The plugin instance itself provides the type-safe helpers.
474
- // The string 'name' is fully type-checked against the User interface.
475
- sortComparer: sortPlugin.comparers.caseInsensitiveString('name'),
476
- },
477
- {
478
- columnId: 'user_signup_date',
479
- displayLabel: 'Signup Date',
480
- dataKey: 'signupDate',
481
- cellRenderer: (user) => new Date(user.signupDate).toLocaleDateString(),
482
- // IDE autocompletion for 'signupDate' works perfectly.
483
- sortComparer: sortPlugin.comparers.date('signupDate'),
484
- },
485
- {
486
- columnId: 'user_logins',
487
- displayLabel: 'Logins',
488
- dataKey: 'logins',
489
- cellRenderer: (user) => user.logins,
490
- sortComparer: sortPlugin.comparers.numeric('logins'),
491
- },
492
- {
493
- columnId: 'user_actions',
494
- displayLabel: 'Actions',
495
- // This column is NOT sortable because it has no sort-related properties.
496
- cellRenderer: (user) => <button>View</button>,
497
- },
498
- ];
499
- ```
500
-
501
- **Example 2: Writing a Custom `sortComparer` for a Computed Column**
502
-
503
- > **Important:** When writing a custom `sortComparer`, you **must** include all three parameters (`a`, `b`, and `direction`) in your function signature. Forgetting the `direction` parameter will cause descending sorts to fail. The IDE is configured to provide a warning if you forget, and the developer console will also show a warning at runtime.
504
-
505
- For unique requirements, you can write your own comparison function. Notice how `columnId` is used without a `dataKey`.
506
-
507
- ```jsx
508
- const columnDefinitions: IResponsiveTableColumnDefinition<User>[] = [
509
- {
510
- columnId: 'user_name_custom',
511
- displayLabel: 'Name',
512
- cellRenderer: (user) => user.name,
513
- // Writing custom logic for a case-sensitive sort
514
- sortComparer: (a, b, direction) => {
515
- const nameA = a.name; // No .toLowerCase()
516
- const nameB = b.name;
517
- if (nameA < nameB) return direction === 'asc' ? -1 : 1;
518
- if (nameA > nameB) return direction === 'asc' ? 1 : -1;
519
- return 0;
520
- },
521
- },
522
- ];
523
- ```
524
-
525
- **Example 3: Using `getSortableValue` for Simple Cases**
526
-
527
- If you don't need special logic, `getSortableValue` is a concise way to enable default sorting on a property.
528
-
529
- ```jsx
530
- const columnDefinitions: IResponsiveTableColumnDefinition<User>[] = [
531
- {
532
- columnId: 'user_logins_simple',
533
- displayLabel: 'Logins',
534
- dataKey: 'logins',
535
- cellRenderer: (user) => user.logins,
536
- // This enables a simple, default numerical sort on the 'logins' property.
537
- getSortableValue: (user) => user.logins,
538
- },
539
- ];
540
- ```
541
-
542
- **Plugin Options (via `new SortPlugin(options)`):**
543
-
544
- | Prop | Type | Description |
545
- | ---------------------- | ----------------- | ---------------------------------------------------- |
546
- | `initialSortColumn` | `string` | The `columnId` of the column to sort by initially. |
547
- | `initialSortDirection` | `'asc' \| 'desc'` | The direction for the initial sort. |
548
-
549
- **`SortPlugin.comparers` API:**
550
-
551
- The `comparers` object on your `SortPlugin` instance provides the following helper methods. Each method is a factory that takes a `dataKey` (which is type-checked against your data model) and returns a `sortComparer` function.
552
-
553
- | Method | Description |
554
- | -------------------------------- | ----------------------------------------------------------------------------- |
555
- | `numeric(dataKey)` | Performs a standard numerical sort. |
556
- | `caseInsensitiveString(dataKey)` | Performs a case-insensitive alphabetical sort. |
557
- | `date(dataKey)` | Correctly sorts dates, assuming the data is a valid date string or timestamp. |
558
-
559
- #### `SelectionPlugin`
560
-
561
- The `SelectionPlugin` provides powerful and flexible row selection capabilities. It is enabled automatically by providing the `selectionProps` object to the `ResponsiveTable`. The plugin supports both single and multiple selection modes and can be used as a "controlled" or "uncontrolled" component.
562
-
563
- **Enabling the `SelectionPlugin`:**
564
-
565
- Row selection is enabled by simply passing the `selectionProps` object with an `onSelectionChange` callback.
566
-
567
- ```jsx
568
- import React, { useState } from 'react';
569
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
570
-
571
- const MySelectableTable = ({ data }) => {
572
- const [selection, setSelection] = useState([]);
573
-
574
- const columns = [
575
- // ... your column definitions
576
- ];
577
-
578
- return (
579
- <ResponsiveTable
580
- columnDefinitions={columns}
581
- data={data}
582
- selectionProps={{
583
- onSelectionChange: setSelection,
584
- selectedItems: selection,
585
- rowIdKey: 'id', // A key from your data object to uniquely identify rows
586
- mode: 'multiple',
587
- }}
588
- />
589
- );
590
- };
591
- ```
592
-
593
- **Controlled vs. Uncontrolled Mode:**
594
-
595
- - **Controlled (Recommended):** By providing the `selectedItems` prop, you tell the table to operate as a controlled component. The table will always display the rows passed in this prop as selected. Your `onSelectionChange` callback is responsible for updating the state that you pass to `selectedItems`. This gives you full programmatic control over the selection.
596
- - **Uncontrolled:** If you omit the `selectedItems` prop, the table will manage its own selection state internally. This is simpler for cases where you only need to know what's selected but don't need to modify it from outside the table.
597
-
598
- **Props for `SelectionPlugin` (via `selectionProps` on `ResponsiveTable`):**
599
-
600
- | Prop | Type | Required | Description |
601
- | ---------------------- | ----------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
602
- | `onSelectionChange` | `(selectedItems: TData[]) => void` | Yes | Callback function that receives an array of the currently selected data objects whenever the selection changes. Its presence enables the selection feature. |
603
- | `rowIdKey` | `keyof TData` | Yes | A key from your data object that provides a unique identifier for each row. This is crucial for reliably tracking selections. |
604
- | `mode` | `'single' \| 'multiple'` | No | The selection mode. Defaults to `'multiple'`. |
605
- | `selectedItems` | `TData[]` | No | If provided, the component operates in "controlled" mode. The table's selection will be a direct reflection of this prop. |
606
- | `selectedRowClassName` | `string` | No | An optional CSS class name to apply to selected rows, allowing you to override the default selection styling. |
607
-
608
- #### `FilterPlugin`
609
-
610
- > **Warning: Incompatible with Infinite Scroll**
611
- > The `FilterPlugin` is a client-side utility that only operates on data currently loaded in the browser. It is **not compatible** with the `InfiniteScrollPlugin`, as it cannot search data that has not yet been fetched from the server. For tables using infinite scroll, filtering logic must be implemented server-side by your application and passed into the `onLoadMore` function.
612
-
613
- Provides a search input to filter table data. It can be enabled by setting `filterProps.showFilter` to `true` on the `ResponsiveTable` component. For columns to be filterable, you must provide a `getFilterableValue` function in their `IResponsiveTableColumnDefinition`.
614
-
615
- **Props for `FilterPlugin` (via `filterProps` on `ResponsiveTable`):**
616
-
617
- | Prop | Type | Description |
618
- | ------------------- | --------- | --------------------------------------------------------------- |
619
- | `showFilter` | `boolean` | If `true`, displays a filter input field above the table. |
620
- | `filterPlaceholder` | `string` | Placeholder text for the filter input. Defaults to "Search...". |
621
-
622
- **Example with `FilterPlugin`:**
623
-
624
- ```jsx
625
- import React from 'react';
626
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
627
-
628
- const FilterableTable = () => {
629
- const initialData = [
630
- { id: 1, name: 'Alice', email: 'alice@example.com' },
631
- { id: 2, name: 'Bob', email: 'bob@example.com' },
632
- { id: 3, name: 'Charlie', email: 'charlie@example.com' },
633
- { id: 4, name: 'David', email: 'david@example.com' },
634
- ];
635
-
636
- const columns = [
637
- { displayLabel: 'ID', cellRenderer: (row) => row.id, getFilterableValue: (row) => row.id.toString() },
638
- { displayLabel: 'Name', cellRenderer: (row) => row.name, getFilterableValue: (row) => row.name },
639
- { displayLabel: 'Email', cellRenderer: (row) => row.email, getFilterableValue: (row) => row.email },
640
- ];
641
-
642
- return (
643
- <ResponsiveTable
644
- columnDefinitions={columns}
645
- data={initialData}
646
- filterProps={{ showFilter: true, filterPlaceholder: 'Filter users...' }}
647
- />
648
- );
649
- };
650
- ```
651
-
652
- #### Infinite Scroll
653
-
654
- > **Warning: Incompatible with Client-Side Filtering**
655
- > The infinite scroll feature fetches data in chunks from a server. It is **not compatible** with the client-side `FilterPlugin`. For filtering to work correctly with infinite scroll, you must implement the filtering logic on your server and have the `onLoadMore` function fetch data that is already filtered.
656
-
657
- Enables a simple infinite scroll for loading more data as the user scrolls to the bottom of the table. This is useful for progressively loading data from an API without needing traditional pagination buttons. When enabled, the `ResponsiveTable` renders a specialized internal component optimized for this purpose.
658
-
659
- > **Important:** To enable infinite scroll, you **must** give the table a bounded height. This is done by passing the `maxHeight` prop to the `<ResponsiveTable>`. This prop creates a scrollable container for the table body, which is required for the scroll detection to work.
660
-
661
- > **Performance Note:** This implementation renders all loaded rows into the DOM to guarantee correct column alignment and simplicity. It is **not a virtualized list**. For extremely large datasets (many thousands of rows), performance may degrade as more rows are loaded. It is best suited for scenarios where loading a few hundred up to a couple thousand rows is expected.
662
-
663
- **Configuration (via `infiniteScrollProps` on `ResponsiveTable`):**
664
-
665
- | Prop | Type | Description |
666
- | ---------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
667
-
668
- | `hasMore` | `boolean` | **Optional.** Controls the loading indicator. If omitted, the component infers this state automatically by checking if `onLoadMore` returns an empty array or `null`. If provided, your app is responsible for managing this state. |
669
- | `onLoadMore` | `(currentData: TData[]) => Promise<TData[] | null>` | A callback function that fires when the user scrolls near the end. It should fetch the next page of data and return it in a Promise. The component will stop trying to load more when this function returns `null` or an empty array. |
670
- | `loadingMoreComponent` | `ReactNode` | A custom component to display at the bottom while new data is being loaded. Defaults to a spinner animation. |
671
- | `noMoreDataComponent` | `ReactNode` | A custom component to display at the bottom when `hasMore` is `false`. Defaults to a "No more data" message. |
672
-
673
- **Comprehensive Example:**
674
-
675
- Here is a complete example showing how to use the infinite scroll feature. The parent component only needs to provide a function that fetches the next page of data.
676
-
677
- ```jsx
678
- import React, { useState, useCallback } from 'react';
679
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
680
-
681
- // Define the shape of our data items
682
- interface DataItem {
683
- id: number;
684
- value: string;
685
- }
686
-
687
- // This is a mock API function to simulate fetching data.
688
- // In a real app, this would be an actual network request.
689
- const fetchData = async (page: number): Promise<DataItem[]> => {
690
- console.log(`Fetching page: ${page}`);
691
- return new Promise((resolve) => {
692
- setTimeout(() => {
693
- // Return an empty array for pages > 5 to simulate the end of the data
694
- if (page > 5) {
695
- resolve([]);
696
- return;
697
- }
698
- // Generate 20 new items for the current page
699
- const newData = Array.from({ length: 20 }, (_, i) => ({
700
- id: page * 20 + i,
701
- value: `Item #${page * 20 + i}`,
702
- }));
703
- resolve(newData);
704
- }, 500); // Simulate network latency
705
- });
706
- };
707
-
708
- const InfiniteScrollExample = () => {
709
- // Keep track of the next page to fetch.
710
- const [nextPage, setNextPage] = useState(0);
711
-
712
- // The onLoadMore function is now much simpler.
713
- // It just needs to fetch the data and return it.
714
- // The table will handle appending the data and managing the `hasMore` state internally.
715
- const loadMoreItems = useCallback(async () => {
716
- const newItems = await fetchData(nextPage);
717
- setNextPage((prevPage) => prevPage + 1);
718
- return newItems; // <-- Simply return the new items
719
- }, [nextPage]);
720
-
721
- const columns = [
722
- { displayLabel: 'ID', dataKey: 'id', cellRenderer: (row) => row.id },
723
- { displayLabel: 'Value', dataKey: 'value', cellRenderer: (row) => row.value },
724
- ];
725
-
726
- return (
727
- // The table MUST be inside a container with a defined height.
728
- <div style={{ height: '400px' }}>
729
- <ResponsiveTable
730
- columnDefinitions={columns}
731
- data={[]} // Start with an empty array of initial data
732
- maxHeight="100%"
733
- infiniteScrollProps={{
734
- onLoadMore: loadMoreItems,
735
-
736
- loadingMoreComponent: <h4>Loading more items...</h4>,
737
- noMoreDataComponent: <p>You've reached the end!</p>,
738
- }}
739
- />
740
- </div>
741
- );
742
- };
743
- ```
744
-
745
- ---
746
-
747
- ## API Reference
748
-
749
- ### `ResponsiveTable` Props
750
-
751
- | Prop | Type | Required | Description |
752
- | ----------------------------- | ------------------------------------ | -------- | --------------------------------------------------------------------------------------------------- |
753
- | `columnDefinitions` | `IResponsiveTableColumnDefinition[]` | Yes | An array of objects defining the table columns. Can also accept a function for dynamic column generation. |
754
- | `data` | `TData[]` | Yes | An array of data objects to populate the table rows. |
755
- | `footerRows` | `IFooterRowDefinition[]` | No | An array of objects defining the table footer. |
756
- | `onRowClick` | `(item: TData) => void` | No | A callback function that is triggered when a row is clicked. |
757
- | `noDataComponent` | `ReactNode` | No | A custom component to display when there is no data. |
758
- | `maxHeight` | `string` | No | Sets a maximum height for the table body, making it scrollable. |
759
- | `mobileBreakpoint` | `number` | No | The pixel width at which the table switches to the mobile view. Defaults to `600`. |
760
- | `enablePageLevelStickyHeader` | `boolean` | No | If `false`, disables the header from sticking to the top of the page on scroll. Defaults to `true`. |
761
- | `plugins` | `IResponsiveTablePlugin<TData>[]` | No | An array of plugin instances to extend table functionality. |
762
- | `selectionProps` | `object` | No | Configuration for the built-in row selection feature. See `SelectionPlugin` docs for details. |
763
- | `infiniteScrollProps` | `object` | No | Configuration for the infinite scroll feature. When enabled, a specialized component handles data loading. |
764
- | `filterProps` | `object` | No | Configuration for the built-in filter plugin. |
765
- | `animationProps` | `object` | No | Configuration for animations, including `isLoading` and `animateOnLoad`. |
766
-
767
- ### `IResponsiveTableColumnDefinition<TData>`
768
-
769
- | Property | Type | Required | Description |
770
- | -------------------- | ------------------------------------------------------------ | -------- | ---------------------------------------------------------------------------------------- |
771
- | `displayLabel` | `ReactNode` | Yes | The label displayed in the table header (can be a string or any React component). |
772
- | `cellRenderer` | `(row: TData) => ReactNode` | Yes | A function that returns the content to be rendered in the cell. |
773
- | `columnId` | `string` | No | A unique string to identify the column. **Required** if the column is sortable. |
774
- | `dataKey` | `keyof TData` | No | A key to match the column to a property in the data object. |
775
- | `interactivity` | `object` | No | An object to define header interactivity (`onHeaderClick`, `id`, `className`). |
776
- | `getFilterableValue` | `(row: TData) => string \| number` | No | A function that returns the string or number value to be used for filtering this column. |
777
- | `getSortableValue` | `(row: TData) => any` | No | A function that returns a primitive value from a row to be used for default sorting. |
778
- | `sortComparer` | `(a: TData, b: TData, direction: 'asc' \| 'desc') => number` | No | A function that provides the precise comparison logic for sorting a column. |
779
-
780
- ### `IFooterRowDefinition`
781
-
782
- | Property | Type | Required | Description |
783
- | --------- | --------------------------- | -------- | -------------------------------------------------- |
784
- | `columns` | `IFooterColumnDefinition[]` | Yes | An array of column definitions for the footer row. |
785
-
786
- ### `IFooterColumnDefinition`
787
-
788
- | Property | Type | Required | Description |
789
- | -------------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
790
- | `colSpan` | `number` | Yes | The number of columns the footer cell should span. |
791
- | `cellRenderer` | `() => ReactNode` | Yes | A function that returns the content for the footer cell. |
792
- | `displayLabel` | `ReactNode` | No | An optional, explicit label for the footer cell. In mobile view, if `colSpan` is 1 and this is not provided, the corresponding column header will be used as a fallback. This is required for `colSpan` > 1 if you want a label to be displayed. |
793
- | `onCellClick` | `() => void` | No | An optional click handler for the footer cell. |
794
- | `className` | `string` | No | Optional class name for custom styling of the footer cell. |
795
-
796
- ---
797
-
798
- ## Breaking Changes
799
-
800
- ### Version 0.5.0
801
-
802
- **Change:** The API for `infiniteScrollProps` has been simplified.
803
-
804
- **Details:**
805
- The `enableInfiniteScroll: true` property has been removed. The infinite scroll feature is now automatically enabled whenever the `infiniteScrollProps` object is provided. Additionally, the `onLoadMore` function is now a required property on `infiniteScrollProps`.
806
-
807
- **Reason:**
808
- This change removes unnecessary boilerplate and makes the API more intuitive. If you provide props for infinite scrolling, it's clear you intend to use it.
809
-
810
- **Migration:**
811
- To update your code, remove the `enableInfiniteScroll` property from your `infiniteScrollProps` object.
812
-
813
- **Before:**
814
- ```jsx
815
- <ResponsiveTable
816
- // ...
817
- infiniteScrollProps={{
818
- enableInfiniteScroll: true, // <-- No longer needed
819
- onLoadMore: loadMoreItems,
820
- }}
821
- />
822
- ```
823
-
824
- **After:**
825
- ```jsx
826
- <ResponsiveTable
827
- // ...
828
- infiniteScrollProps={{
829
- onLoadMore: loadMoreItems, // <-- Now required
830
- }}
831
- />
832
- ```
833
-
834
- ---
835
-
836
- ## License
837
-
838
- This project is licensed under the MIT License.
1
+ # ResponsiveTable: A Modern and Flexible React Table Component
2
+
3
+ ResponsiveTable is a powerful, lightweight, and fully responsive React component for creating beautiful and functional tables. It’s designed to look great on any device, adapting from a traditional table layout on desktops to a clean, card-based view on mobile screens.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Features](#features)
8
+ - [Installation](#installation)
9
+ - [Getting Started](#getting-started)
10
+ - [Comprehensive Examples](#comprehensive-examples)
11
+ - [Example 1: Loading State and Animations](#example-1-loading-state-and-animations)
12
+ - [Example 2: Adding a Clickable Row Action](#example-2-adding-a-clickable-row-action)
13
+ - [Example 3: Row Selection (Controlled Component)](#example-3-row-selection-controlled-component)
14
+ - [Example 4: Custom Cell Rendering](#example-4-custom-cell-rendering)
15
+ - [Example 5: Dynamic and Conditional Columns](#example-5-dynamic-and-conditional-columns)
16
+ - [Example 6: Advanced Footer with Labels and Interactivity](#example-6-advanced-footer-with-labels-and-interactivity)
17
+ - [Example 7: Disabling Page-Level Sticky Header](#example-7-disabling-page-level-sticky-header)
18
+ - [Plugin System](#plugin-system)
19
+ - [Plugin Execution Order](#plugin-execution-order)
20
+ - [How to Use Plugins](#how-to-use-plugins)
21
+ - [Built-in Plugins](#built-in-plugins)
22
+ - [API Reference](#api-reference)
23
+ - [ResponsiveTable Props](#responsivetable-props)
24
+ - [IResponsiveTableColumnDefinition<TData>](#iresponsivetablecolumndefinitiontdata)
25
+ - [IFooterRowDefinition](#ifooterrowdefinition)
26
+ - [IFooterColumnDefinition](#ifootercolumndefinition)
27
+ - [Breaking Changes](#breaking-changes)
28
+ - [Version 0.5.0](#version-050)
29
+ - [License](#license)
30
+
31
+ ## Features
32
+
33
+ - **Mobile-First Design**: Automatically switches to a card layout on smaller screens for optimal readability.
34
+ - **Highly Customizable**: Tailor the look and feel of columns, headers, and footers.
35
+ - **Dynamic Data Handling**: Define columns and footers based on your data or application state.
36
+ - **Delightful Animations**: Includes an optional skeleton loader and staggered row entrance animations.
37
+ - **Interactive Elements**: Easily add click handlers for rows, headers, and footer cells.
38
+ - **Row Selection**: Built-in support for single or multiple row selection, with full programmatic control.
39
+ - **Efficient & Responsive**: Built with efficiency in mind, including debounced resize handling for smooth transitions.
40
+ - **Easy to Use**: A simple and intuitive API for quick integration.
41
+ - **Extensible Plugin System**: Easily add new functionalities like filtering, sorting, or infinite scrolling.
42
+
43
+ ## Installation
44
+
45
+ To get started, install the package from npm:
46
+
47
+ ```bash
48
+ npm install jattac.libs.web.responsive-table
49
+ ```
50
+
51
+ ## Getting Started
52
+
53
+ Here’s a basic example to get you up and running in minutes.
54
+
55
+ ```jsx
56
+ import React from 'react';
57
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
58
+
59
+ const GettingStarted = () => {
60
+ const columns = [
61
+ { displayLabel: 'Name', dataKey: 'name', cellRenderer: (row) => row.name },
62
+ { displayLabel: 'Age', dataKey: 'age', cellRenderer: (row) => row.age },
63
+ { displayLabel: 'City', dataKey: 'city', cellRenderer: (row) => row.city },
64
+ ];
65
+
66
+ const data = [
67
+ { name: 'Alice', age: 32, city: 'New York' },
68
+ { name: 'Bob', age: 28, city: 'Los Angeles' },
69
+ { name: 'Charlie', age: 45, city: 'Chicago' },
70
+ ];
71
+
72
+ return <ResponsiveTable columnDefinitions={columns} data={data} />;
73
+ };
74
+
75
+ export default GettingStarted;
76
+ ```
77
+
78
+ This will render a table that automatically adapts to the screen size. On a desktop, it will look like a standard table, and on mobile, it will switch to a card-based layout.
79
+
80
+ ---
81
+
82
+ ## Comprehensive Examples
83
+
84
+ ### Example 1: Loading State and Animations
85
+
86
+ You can provide a seamless user experience by showing a skeleton loader while your data is being fetched, and then animating the rows in when the data is ready.
87
+
88
+ ```jsx
89
+ import React, { useState, useEffect } from 'react';
90
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
91
+
92
+ const AnimatedTable = () => {
93
+ const [data, setData] = useState([]);
94
+ const [isLoading, setIsLoading] = useState(true);
95
+
96
+ const columns = [
97
+ { displayLabel: 'User', cellRenderer: (row) => row.name },
98
+ { displayLabel: 'Email', cellRenderer: (row) => row.email },
99
+ ];
100
+
101
+ useEffect(() => {
102
+ // Simulate a network request
103
+ setTimeout(() => {
104
+ setData([
105
+ { name: 'Grace', email: 'grace@example.com' },
106
+ { name: 'Henry', email: 'henry@example.com' },
107
+ ]);
108
+ setIsLoading(false);
109
+ }, 2000);
110
+ }, []);
111
+
112
+ return (
113
+ <ResponsiveTable columnDefinitions={columns} data={data} animationProps={{ isLoading, animateOnLoad: true }} />
114
+ );
115
+ };
116
+ ```
117
+
118
+ ### Example 2: Adding a Clickable Row Action
119
+
120
+ You can make rows clickable to perform actions, such as navigating to a details page or opening a modal.
121
+
122
+ ```jsx
123
+ import React from 'react';
124
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
125
+
126
+ const ClickableRows = () => {
127
+ const columns = [
128
+ { displayLabel: 'Product', cellRenderer: (row) => row.product },
129
+ { displayLabel: 'Price', cellRenderer: (row) => `${row.price.toFixed(2)}` },
130
+ ];
131
+
132
+ const data = [
133
+ { id: 1, product: 'Laptop', price: 1200 },
134
+ { id: 2, product: 'Keyboard', price: 75 },
135
+ ];
136
+
137
+ const handleRowClick = (item) => {
138
+ alert(`You clicked on product ID: ${item.id}`);
139
+ };
140
+
141
+ return <ResponsiveTable columnDefinitions={columns} data={data} onRowClick={handleRowClick} />;
142
+ };
143
+ ```
144
+
145
+ ### Example 3: Row Selection (Controlled Component)
146
+
147
+ Enable row selection by providing the `selectionProps` object. The table can be a fully "controlled" component, where you manage the state of selected items, or an "uncontrolled" component where the table manages its own state.
148
+
149
+ This example demonstrates the **controlled** pattern, which gives you full programmatic control over which rows are selected.
150
+
151
+ ```jsx
152
+ import React, { useState } from 'react';
153
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
154
+
155
+ const SelectableTable = () => {
156
+ const columns = [
157
+ { displayLabel: 'Task', dataKey: 'task', cellRenderer: (row) => row.task },
158
+ { displayLabel: 'Status', dataKey: 'status', cellRenderer: (row) => row.status },
159
+ ];
160
+
161
+ const initialData = [
162
+ { id: 'task-1', task: 'Design new logo', status: 'In Progress' },
163
+ { id: 'task-2', task: 'Develop homepage', status: 'Completed' },
164
+ { id: 'task-3', task: 'Write documentation', status: 'Pending' },
165
+ { id: 'task-4', task: 'Deploy to production', status: 'Pending' },
166
+ ];
167
+
168
+ // 1. Manage the selection state in your component
169
+ const [selected, setSelected] = useState([initialData[1]]); // Initially select the second row
170
+
171
+ const handleSelectionChange = (selectedItems) => {
172
+ // 2. Update your state when the selection changes
173
+ setSelected(selectedItems);
174
+ console.log('Selected items:', selectedItems);
175
+ };
176
+
177
+ return (
178
+ <div>
179
+ <button onClick={() => setSelected([])} style={{ marginBottom: '1rem' }}>
180
+ Clear Selection
181
+ </button>
182
+ <ResponsiveTable
183
+ columnDefinitions={columns}
184
+ data={initialData}
185
+ selectionProps={{
186
+ onSelectionChange: handleSelectionChange,
187
+ selectedItems: selected, // 3. Pass the state down to the table
188
+ rowIdKey: 'id', // 4. Provide a unique key for each row
189
+ mode: 'multiple', // or 'single'
190
+ }}
191
+ />
192
+ <div style={{ marginTop: '1rem' }}>
193
+ <strong>Selected Tasks:</strong>
194
+ <ul>
195
+ {selected.map((item) => (
196
+ <li key={item.id}>{item.task}</li>
197
+ ))}
198
+ </ul>
199
+ </div>
200
+ </div>
201
+ );
202
+ };
203
+ ```
204
+
205
+ ### Example 4: Custom Cell Rendering
206
+
207
+ You can render any React component inside a cell, allowing for rich content like buttons, links, or status badges.
208
+
209
+ ```jsx
210
+ import React from 'react';
211
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
212
+
213
+ const CustomCells = () => {
214
+ const columns = [
215
+ { displayLabel: <strong>User</strong>, cellRenderer: (row) => <strong>{row.user}</strong> },
216
+ {
217
+ displayLabel: 'Status',
218
+ cellRenderer: (row) => (
219
+ <span
220
+ style={{
221
+ color: row.status === 'Active' ? 'green' : 'red',
222
+ fontWeight: 'bold',
223
+ }}
224
+ >
225
+ {row.status}
226
+ </span>
227
+ ),
228
+ },
229
+ {
230
+ displayLabel: 'Action',
231
+ cellRenderer: (row) => <button onClick={() => alert(`Editing ${row.user}`)}>Edit</button>,
232
+ },
233
+ ];
234
+
235
+ const data = [
236
+ { user: 'Eve', status: 'Active' },
237
+ { user: 'Frank', status: 'Inactive' },
238
+ ];
239
+
240
+ return <ResponsiveTable columnDefinitions={columns} data={data} />;
241
+ };
242
+ ```
243
+
244
+ ### Example 5: Dynamic and Conditional Columns
245
+
246
+ Columns can be generated dynamically based on your data or application state. This is useful for creating flexible tables that adapt to different datasets.
247
+
248
+ ```jsx
249
+ import React from 'react';
250
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
251
+
252
+ const DynamicColumns = ({ isAdmin }) => {
253
+ // Base columns for all users
254
+ const columns = [
255
+ { displayLabel: 'File Name', cellRenderer: (row) => row.fileName },
256
+ { displayLabel: 'Size', cellRenderer: (row) => `${row.size} KB` },
257
+ ];
258
+
259
+ // Add an admin-only column conditionally
260
+ if (isAdmin) {
261
+ columns.push({
262
+ displayLabel: 'Admin Actions',
263
+ cellRenderer: (row) => <button onClick={() => alert(`Deleting ${row.fileName}`)}>Delete</button>,
264
+ });
265
+ }
266
+
267
+ const data = [
268
+ { fileName: 'document.pdf', size: 1024 },
269
+ { fileName: 'image.jpg', size: 512 },
270
+ ];
271
+
272
+ return <ResponsiveTable columnDefinitions={columns} data={data} />;
273
+ };
274
+ ```
275
+
276
+ ### Example 6: Advanced Footer with Labels and Interactivity
277
+
278
+ You can add a footer to display summary information, such as totals or averages. The footer is also responsive and will appear correctly in both desktop and mobile views. With the enhanced footer functionality, you can provide explicit labels for mobile view and add click handlers to footer cells.
279
+
280
+ ```jsx
281
+ import React from 'react';
282
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
283
+
284
+ const TableWithFooter = () => {
285
+ const columns = [
286
+ { displayLabel: 'Item', cellRenderer: (row) => row.item },
287
+ { displayLabel: 'Quantity', cellRenderer: (row) => row.quantity },
288
+ { displayLabel: 'Price', cellRenderer: (row) => `${row.price.toFixed(2)}` },
289
+ ];
290
+
291
+ const data = [
292
+ { item: 'Apples', quantity: 10, price: 1.5 },
293
+ { item: 'Oranges', quantity: 5, price: 2.0 },
294
+ { item: 'Bananas', quantity: 15, price: 0.5 },
295
+ ];
296
+
297
+ const total = data.reduce((sum, row) => sum + row.quantity * row.price, 0);
298
+
299
+ const footerRows = [
300
+ {
301
+ columns: [
302
+ {
303
+ colSpan: 2,
304
+ cellRenderer: () => <strong>Total:</strong>,
305
+ },
306
+ {
307
+ colSpan: 1,
308
+ displayLabel: 'Total',
309
+ cellRenderer: () => <strong>${total.toFixed(2)}</strong>,
310
+ onCellClick: () => alert('Total clicked!'),
311
+ },
312
+ ],
313
+ },
314
+ ];
315
+
316
+ return <ResponsiveTable columnDefinitions={columns} data={data} footerRows={footerRows} />;
317
+ };
318
+ ```
319
+
320
+ ### Example 7: Disabling Page-Level Sticky Header
321
+
322
+ By default, the table header remains fixed to the top of the viewport as the user scrolls down the page. This ensures the column titles are always visible. To disable this behavior and have the header scroll away with the rest of the page, set the `enablePageLevelStickyHeader` prop to `false`.
323
+
324
+ ```jsx
325
+ import React from 'react';
326
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
327
+
328
+ const NonStickyHeaderTable = () => {
329
+ // We need enough data to make the page scroll
330
+ const data = Array.from({ length: 50 }, (_, i) => ({
331
+ id: i + 1,
332
+ item: `Item #${i + 1}`,
333
+ description: 'This is a sample item.',
334
+ }));
335
+
336
+ const columns = [
337
+ { displayLabel: 'ID', cellRenderer: (row) => row.id },
338
+ { displayLabel: 'Item', cellRenderer: (row) => row.item },
339
+ { displayLabel: 'Description', cellRenderer: (row) => row.description },
340
+ ];
341
+
342
+ return (
343
+ <div>
344
+ <h1 style={{ height: '50vh', display: 'flex', alignItems: 'center' }}>Scroll down to see the table</h1>
345
+ <ResponsiveTable
346
+ columnDefinitions={columns}
347
+ data={data}
348
+ enablePageLevelStickyHeader={false} // <-- Here's the magic switch!
349
+ />
350
+ <div style={{ height: '50vh' }} />
351
+ </div>
352
+ );
353
+ };
354
+ ```
355
+
356
+ ---
357
+
358
+ ## Plugin System
359
+
360
+ ResponsiveTable is designed with an extensible plugin system, allowing developers to easily add new functionalities or modify existing behaviors without altering the core component. Plugins can interact with the table's data, render custom UI elements (like headers or footers), and respond to table events.
361
+
362
+ ### Plugin Execution Order
363
+
364
+ > **Note:** Plugins process data sequentially in the order they are provided in the `plugins` array. This can have important performance and behavioral implications.
365
+
366
+ For example, consider the `SortPlugin` and `FilterPlugin`.
367
+
368
+ - `plugins={[new SortPlugin(), new FilterPlugin()]}`: This will **sort** the entire dataset first, and then **filter** the sorted data.
369
+ - `plugins={[new FilterPlugin(), new SortPlugin()]}`: This will **filter** the dataset first, and then **sort** the much smaller, filtered result.
370
+
371
+ For the best performance, it is highly recommended to **filter first, then sort**.
372
+
373
+ ### How to Use Plugins
374
+
375
+ Plugins are passed to the `ResponsiveTable` component via the `plugins` prop, which accepts an array of `IResponsiveTablePlugin` instances. Some common functionalities, like filtering and infinite scrolling, are provided as built-in plugins that can be enabled via specific props (`filterProps` and `infiniteScrollProps`). When these props are used, the corresponding built-in plugins are automatically initialized if not already provided in the `plugins` array.
376
+
377
+ ```jsx
378
+ import React from 'react';
379
+ // Note: All plugins are exported from the main package entry point.
380
+ import ResponsiveTable, { FilterPlugin } from 'jattac.libs.web.responsive-table';
381
+
382
+ const MyTableWithPlugins = () => {
383
+ const columns = [
384
+ { displayLabel: 'Name', dataKey: 'name', cellRenderer: (row) => row.name, getFilterableValue: (row) => row.name },
385
+ {
386
+ displayLabel: 'Age',
387
+ dataKey: 'age',
388
+ cellRenderer: (row) => row.age,
389
+ getFilterableValue: (row) => row.age.toString(),
390
+ },
391
+ ];
392
+
393
+ const data = [
394
+ { name: 'Alice', age: 32 },
395
+ { name: 'Bob', age: 28 },
396
+ { name: 'Charlie', age: 45 },
397
+ ];
398
+
399
+ return (
400
+ <ResponsiveTable
401
+ columnDefinitions={columns}
402
+ data={data}
403
+ // Enable built-in filter plugin via props
404
+ filterProps={{ showFilter: true, filterPlaceholder: 'Search by name or age...' }}
405
+ // Or provide a custom instance of the plugin
406
+ // plugins={[new FilterPlugin()]}
407
+ />
408
+ );
409
+ };
410
+ ```
411
+
412
+ ### Built-in Plugins
413
+
414
+ #### `SortPlugin`
415
+
416
+ The `SortPlugin` provides powerful, type-safe, and highly customizable column sorting. It adds intuitive UI cues, allowing users to click column headers to sort the data in ascending, descending, or original order.
417
+
418
+ **Enabling the `SortPlugin`:**
419
+
420
+ To use the plugin, you must first import it and provide a generic instance of it to the `plugins` prop. The real power comes from making the plugin instance generic with your data type, which provides type-safety and IDE autocompletion for the sort comparer helpers.
421
+
422
+ ```jsx
423
+ import React from 'react';
424
+ import ResponsiveTable, { IResponsiveTableColumnDefinition, SortPlugin } from 'jattac.libs.web.responsive-table';
425
+
426
+ // Define the shape of your data
427
+ interface User {
428
+ id: number;
429
+ name: string;
430
+ signupDate: string;
431
+ logins: number;
432
+ }
433
+
434
+ // 1. Create a single, generic instance of the plugin.
435
+ const sortPlugin = new SortPlugin<User>({
436
+ initialSortColumn: 'user_logins', // Use the columnId
437
+ initialSortDirection: 'desc',
438
+ });
439
+
440
+ // 2. Define the columns, using the helpers directly from the plugin instance.
441
+ const columnDefinitions: IResponsiveTableColumnDefinition<User>[] = [
442
+ // ... see examples below
443
+ ];
444
+
445
+ const UserTable = ({ users }) => (
446
+ <ResponsiveTable
447
+ columnDefinitions={columnDefinitions}
448
+ data={users}
449
+ // 3. Pass the already-configured plugin to the table.
450
+ plugins={[sortPlugin]}
451
+ />
452
+ );
453
+ ```
454
+
455
+ **How to Make Columns Sortable (Opt-In):**
456
+
457
+ A column is made sortable by adding a **`columnId`** and either a `sortComparer` or a `getSortableValue` property to its definition. The `columnId` must be a unique string that identifies the column.
458
+
459
+ - `sortComparer`: A function that defines the exact comparison logic. This is the most powerful option and should be used for complex data types or custom logic.
460
+ - `getSortableValue`: A simpler function that just returns the primitive value (string, number, etc.) to be used in a default comparison.
461
+
462
+ **Example 1: Using Type-Safe Comparer Helpers**
463
+
464
+ The `SortPlugin` instance provides a `comparers` object with pre-built, type-safe helper functions to eliminate boilerplate for common sorting scenarios. This is the recommended approach.
465
+
466
+ ```jsx
467
+ const columnDefinitions: IResponsiveTableColumnDefinition<User>[] = [
468
+ {
469
+ columnId: 'user_name',
470
+ displayLabel: 'Name',
471
+ dataKey: 'name',
472
+ cellRenderer: (user) => user.name,
473
+ // The plugin instance itself provides the type-safe helpers.
474
+ // The string 'name' is fully type-checked against the User interface.
475
+ sortComparer: sortPlugin.comparers.caseInsensitiveString('name'),
476
+ },
477
+ {
478
+ columnId: 'user_signup_date',
479
+ displayLabel: 'Signup Date',
480
+ dataKey: 'signupDate',
481
+ cellRenderer: (user) => new Date(user.signupDate).toLocaleDateString(),
482
+ // IDE autocompletion for 'signupDate' works perfectly.
483
+ sortComparer: sortPlugin.comparers.date('signupDate'),
484
+ },
485
+ {
486
+ columnId: 'user_logins',
487
+ displayLabel: 'Logins',
488
+ dataKey: 'logins',
489
+ cellRenderer: (user) => user.logins,
490
+ sortComparer: sortPlugin.comparers.numeric('logins'),
491
+ },
492
+ {
493
+ columnId: 'user_actions',
494
+ displayLabel: 'Actions',
495
+ // This column is NOT sortable because it has no sort-related properties.
496
+ cellRenderer: (user) => <button>View</button>,
497
+ },
498
+ ];
499
+ ```
500
+
501
+ **Example 2: Writing a Custom `sortComparer` for a Computed Column**
502
+
503
+ > **Important:** When writing a custom `sortComparer`, you **must** include all three parameters (`a`, `b`, and `direction`) in your function signature. Forgetting the `direction` parameter will cause descending sorts to fail. The IDE is configured to provide a warning if you forget, and the developer console will also show a warning at runtime.
504
+
505
+ For unique requirements, you can write your own comparison function. Notice how `columnId` is used without a `dataKey`.
506
+
507
+ ```jsx
508
+ const columnDefinitions: IResponsiveTableColumnDefinition<User>[] = [
509
+ {
510
+ columnId: 'user_name_custom',
511
+ displayLabel: 'Name',
512
+ cellRenderer: (user) => user.name,
513
+ // Writing custom logic for a case-sensitive sort
514
+ sortComparer: (a, b, direction) => {
515
+ const nameA = a.name; // No .toLowerCase()
516
+ const nameB = b.name;
517
+ if (nameA < nameB) return direction === 'asc' ? -1 : 1;
518
+ if (nameA > nameB) return direction === 'asc' ? 1 : -1;
519
+ return 0;
520
+ },
521
+ },
522
+ ];
523
+ ```
524
+
525
+ **Example 3: Using `getSortableValue` for Simple Cases**
526
+
527
+ If you don't need special logic, `getSortableValue` is a concise way to enable default sorting on a property.
528
+
529
+ ```jsx
530
+ const columnDefinitions: IResponsiveTableColumnDefinition<User>[] = [
531
+ {
532
+ columnId: 'user_logins_simple',
533
+ displayLabel: 'Logins',
534
+ dataKey: 'logins',
535
+ cellRenderer: (user) => user.logins,
536
+ // This enables a simple, default numerical sort on the 'logins' property.
537
+ getSortableValue: (user) => user.logins,
538
+ },
539
+ ];
540
+ ```
541
+
542
+ **Plugin Options (via `new SortPlugin(options)`):**
543
+
544
+ | Prop | Type | Description |
545
+ | ---------------------- | ----------------- | ---------------------------------------------------- |
546
+ | `initialSortColumn` | `string` | The `columnId` of the column to sort by initially. |
547
+ | `initialSortDirection` | `'asc' \| 'desc'` | The direction for the initial sort. |
548
+
549
+ **`SortPlugin.comparers` API:**
550
+
551
+ The `comparers` object on your `SortPlugin` instance provides the following helper methods. Each method is a factory that takes a `dataKey` (which is type-checked against your data model) and returns a `sortComparer` function.
552
+
553
+ | Method | Description |
554
+ | -------------------------------- | ----------------------------------------------------------------------------- |
555
+ | `numeric(dataKey)` | Performs a standard numerical sort. |
556
+ | `caseInsensitiveString(dataKey)` | Performs a case-insensitive alphabetical sort. |
557
+ | `date(dataKey)` | Correctly sorts dates, assuming the data is a valid date string or timestamp. |
558
+
559
+ #### `SelectionPlugin`
560
+
561
+ The `SelectionPlugin` provides powerful and flexible row selection capabilities. It is enabled automatically by providing the `selectionProps` object to the `ResponsiveTable`. The plugin supports both single and multiple selection modes and can be used as a "controlled" or "uncontrolled" component.
562
+
563
+ **Enabling the `SelectionPlugin`:**
564
+
565
+ Row selection is enabled by simply passing the `selectionProps` object with an `onSelectionChange` callback.
566
+
567
+ ```jsx
568
+ import React, { useState } from 'react';
569
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
570
+
571
+ const MySelectableTable = ({ data }) => {
572
+ const [selection, setSelection] = useState([]);
573
+
574
+ const columns = [
575
+ // ... your column definitions
576
+ ];
577
+
578
+ return (
579
+ <ResponsiveTable
580
+ columnDefinitions={columns}
581
+ data={data}
582
+ selectionProps={{
583
+ onSelectionChange: setSelection,
584
+ selectedItems: selection,
585
+ rowIdKey: 'id', // A key from your data object to uniquely identify rows
586
+ mode: 'multiple',
587
+ }}
588
+ />
589
+ );
590
+ };
591
+ ```
592
+
593
+ **Controlled vs. Uncontrolled Mode:**
594
+
595
+ - **Controlled (Recommended):** By providing the `selectedItems` prop, you tell the table to operate as a controlled component. The table will always display the rows passed in this prop as selected. Your `onSelectionChange` callback is responsible for updating the state that you pass to `selectedItems`. This gives you full programmatic control over the selection.
596
+ - **Uncontrolled:** If you omit the `selectedItems` prop, the table will manage its own selection state internally. This is simpler for cases where you only need to know what's selected but don't need to modify it from outside the table.
597
+
598
+ **Props for `SelectionPlugin` (via `selectionProps` on `ResponsiveTable`):**
599
+
600
+ | Prop | Type | Required | Description |
601
+ | ---------------------- | ----------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
602
+ | `onSelectionChange` | `(selectedItems: TData[]) => void` | Yes | Callback function that receives an array of the currently selected data objects whenever the selection changes. Its presence enables the selection feature. |
603
+ | `rowIdKey` | `keyof TData` | Yes | A key from your data object that provides a unique identifier for each row. This is crucial for reliably tracking selections. |
604
+ | `mode` | `'single' \| 'multiple'` | No | The selection mode. Defaults to `'multiple'`. |
605
+ | `selectedItems` | `TData[]` | No | If provided, the component operates in "controlled" mode. The table's selection will be a direct reflection of this prop. |
606
+ | `selectedRowClassName` | `string` | No | An optional CSS class name to apply to selected rows, allowing you to override the default selection styling. |
607
+
608
+ #### `FilterPlugin`
609
+
610
+ > **Warning: Incompatible with Infinite Scroll**
611
+ > The `FilterPlugin` is a client-side utility that only operates on data currently loaded in the browser. It is **not compatible** with the `InfiniteScrollPlugin`, as it cannot search data that has not yet been fetched from the server. For tables using infinite scroll, filtering logic must be implemented server-side by your application and passed into the `onLoadMore` function.
612
+
613
+ Provides a search input to filter table data. It can be enabled by setting `filterProps.showFilter` to `true` on the `ResponsiveTable` component. For columns to be filterable, you must provide a `getFilterableValue` function in their `IResponsiveTableColumnDefinition`.
614
+
615
+ **Props for `FilterPlugin` (via `filterProps` on `ResponsiveTable`):**
616
+
617
+ | Prop | Type | Description |
618
+ | ------------------- | --------- | --------------------------------------------------------------- |
619
+ | `showFilter` | `boolean` | If `true`, displays a filter input field above the table. |
620
+ | `filterPlaceholder` | `string` | Placeholder text for the filter input. Defaults to "Search...". |
621
+
622
+ **Example with `FilterPlugin`:**
623
+
624
+ ```jsx
625
+ import React from 'react';
626
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
627
+
628
+ const FilterableTable = () => {
629
+ const initialData = [
630
+ { id: 1, name: 'Alice', email: 'alice@example.com' },
631
+ { id: 2, name: 'Bob', email: 'bob@example.com' },
632
+ { id: 3, name: 'Charlie', email: 'charlie@example.com' },
633
+ { id: 4, name: 'David', email: 'david@example.com' },
634
+ ];
635
+
636
+ const columns = [
637
+ { displayLabel: 'ID', cellRenderer: (row) => row.id, getFilterableValue: (row) => row.id.toString() },
638
+ { displayLabel: 'Name', cellRenderer: (row) => row.name, getFilterableValue: (row) => row.name },
639
+ { displayLabel: 'Email', cellRenderer: (row) => row.email, getFilterableValue: (row) => row.email },
640
+ ];
641
+
642
+ return (
643
+ <ResponsiveTable
644
+ columnDefinitions={columns}
645
+ data={initialData}
646
+ filterProps={{ showFilter: true, filterPlaceholder: 'Filter users...' }}
647
+ />
648
+ );
649
+ };
650
+ ```
651
+
652
+ #### Infinite Scroll
653
+
654
+ > **Warning: Incompatible with Client-Side Filtering**
655
+ > The infinite scroll feature fetches data in chunks from a server. It is **not compatible** with the client-side `FilterPlugin`. For filtering to work correctly with infinite scroll, you must implement the filtering logic on your server and have the `onLoadMore` function fetch data that is already filtered.
656
+
657
+ Enables a simple infinite scroll for loading more data as the user scrolls to the bottom of the table. This is useful for progressively loading data from an API without needing traditional pagination buttons. When enabled, the `ResponsiveTable` renders a specialized internal component optimized for this purpose.
658
+
659
+ > **Important:** To enable infinite scroll, you **must** give the table a bounded height. This is done by passing the `maxHeight` prop to the `<ResponsiveTable>`. This prop creates a scrollable container for the table body, which is required for the scroll detection to work.
660
+
661
+ > **Performance Note:** This implementation renders all loaded rows into the DOM to guarantee correct column alignment and simplicity. It is **not a virtualized list**. For extremely large datasets (many thousands of rows), performance may degrade as more rows are loaded. It is best suited for scenarios where loading a few hundred up to a couple thousand rows is expected.
662
+
663
+ **Configuration (via `infiniteScrollProps` on `ResponsiveTable`):**
664
+
665
+ | Prop | Type | Description |
666
+ | ---------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
667
+
668
+ | `hasMore` | `boolean` | **Optional.** Controls the loading indicator. If omitted, the component infers this state automatically by checking if `onLoadMore` returns an empty array or `null`. If provided, your app is responsible for managing this state. |
669
+ | `onLoadMore` | `(currentData: TData[]) => Promise<TData[] | null>` | A callback function that fires when the user scrolls near the end. It should fetch the next page of data and return it in a Promise. The component will stop trying to load more when this function returns `null` or an empty array. |
670
+ | `loadingMoreComponent` | `ReactNode` | A custom component to display at the bottom while new data is being loaded. Defaults to a spinner animation. |
671
+ | `noMoreDataComponent` | `ReactNode` | A custom component to display at the bottom when `hasMore` is `false`. Defaults to a "No more data" message. |
672
+
673
+ **Comprehensive Example:**
674
+
675
+ Here is a complete example showing how to use the infinite scroll feature. The parent component only needs to provide a function that fetches the next page of data.
676
+
677
+ ```jsx
678
+ import React, { useState, useCallback } from 'react';
679
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
680
+
681
+ // Define the shape of our data items
682
+ interface DataItem {
683
+ id: number;
684
+ value: string;
685
+ }
686
+
687
+ // This is a mock API function to simulate fetching data.
688
+ // In a real app, this would be an actual network request.
689
+ const fetchData = async (page: number): Promise<DataItem[]> => {
690
+ console.log(`Fetching page: ${page}`);
691
+ return new Promise((resolve) => {
692
+ setTimeout(() => {
693
+ // Return an empty array for pages > 5 to simulate the end of the data
694
+ if (page > 5) {
695
+ resolve([]);
696
+ return;
697
+ }
698
+ // Generate 20 new items for the current page
699
+ const newData = Array.from({ length: 20 }, (_, i) => ({
700
+ id: page * 20 + i,
701
+ value: `Item #${page * 20 + i}`,
702
+ }));
703
+ resolve(newData);
704
+ }, 500); // Simulate network latency
705
+ });
706
+ };
707
+
708
+ const InfiniteScrollExample = () => {
709
+ // Keep track of the next page to fetch.
710
+ const [nextPage, setNextPage] = useState(0);
711
+
712
+ // The onLoadMore function is now much simpler.
713
+ // It just needs to fetch the data and return it.
714
+ // The table will handle appending the data and managing the `hasMore` state internally.
715
+ const loadMoreItems = useCallback(async () => {
716
+ const newItems = await fetchData(nextPage);
717
+ setNextPage((prevPage) => prevPage + 1);
718
+ return newItems; // <-- Simply return the new items
719
+ }, [nextPage]);
720
+
721
+ const columns = [
722
+ { displayLabel: 'ID', dataKey: 'id', cellRenderer: (row) => row.id },
723
+ { displayLabel: 'Value', dataKey: 'value', cellRenderer: (row) => row.value },
724
+ ];
725
+
726
+ return (
727
+ // The table MUST be inside a container with a defined height.
728
+ <div style={{ height: '400px' }}>
729
+ <ResponsiveTable
730
+ columnDefinitions={columns}
731
+ data={[]} // Start with an empty array of initial data
732
+ maxHeight="100%"
733
+ infiniteScrollProps={{
734
+ onLoadMore: loadMoreItems,
735
+
736
+ loadingMoreComponent: <h4>Loading more items...</h4>,
737
+ noMoreDataComponent: <p>You've reached the end!</p>,
738
+ }}
739
+ />
740
+ </div>
741
+ );
742
+ };
743
+ ```
744
+
745
+ ---
746
+
747
+ ## API Reference
748
+
749
+ ### `ResponsiveTable` Props
750
+
751
+ | Prop | Type | Required | Description |
752
+ | ----------------------------- | ------------------------------------ | -------- | --------------------------------------------------------------------------------------------------- |
753
+ | `columnDefinitions` | `IResponsiveTableColumnDefinition[]` | Yes | An array of objects defining the table columns. Can also accept a function for dynamic column generation. |
754
+ | `data` | `TData[]` | Yes | An array of data objects to populate the table rows. |
755
+ | `footerRows` | `IFooterRowDefinition[]` | No | An array of objects defining the table footer. |
756
+ | `onRowClick` | `(item: TData) => void` | No | A callback function that is triggered when a row is clicked. |
757
+ | `noDataComponent` | `ReactNode` | No | A custom component to display when there is no data. |
758
+ | `maxHeight` | `string` | No | Sets a maximum height for the table body, making it scrollable. |
759
+ | `mobileBreakpoint` | `number` | No | The pixel width at which the table switches to the mobile view. Defaults to `600`. |
760
+ | `enablePageLevelStickyHeader` | `boolean` | No | If `false`, disables the header from sticking to the top of the page on scroll. Defaults to `true`. |
761
+ | `plugins` | `IResponsiveTablePlugin<TData>[]` | No | An array of plugin instances to extend table functionality. |
762
+ | `selectionProps` | `object` | No | Configuration for the built-in row selection feature. See `SelectionPlugin` docs for details. |
763
+ | `infiniteScrollProps` | `object` | No | Configuration for the infinite scroll feature. When enabled, a specialized component handles data loading. |
764
+ | `filterProps` | `object` | No | Configuration for the built-in filter plugin. |
765
+ | `animationProps` | `object` | No | Configuration for animations, including `isLoading` and `animateOnLoad`. |
766
+
767
+ ### `IResponsiveTableColumnDefinition<TData>`
768
+
769
+ | Property | Type | Required | Description |
770
+ | -------------------- | ------------------------------------------------------------ | -------- | ---------------------------------------------------------------------------------------- |
771
+ | `displayLabel` | `ReactNode` | Yes | The label displayed in the table header (can be a string or any React component). |
772
+ | `cellRenderer` | `(row: TData) => ReactNode` | Yes | A function that returns the content to be rendered in the cell. |
773
+ | `columnId` | `string` | No | A unique string to identify the column. **Required** if the column is sortable. |
774
+ | `dataKey` | `keyof TData` | No | A key to match the column to a property in the data object. |
775
+ | `interactivity` | `object` | No | An object to define header interactivity (`onHeaderClick`, `id`, `className`). |
776
+ | `getFilterableValue` | `(row: TData) => string \| number` | No | A function that returns the string or number value to be used for filtering this column. |
777
+ | `getSortableValue` | `(row: TData) => any` | No | A function that returns a primitive value from a row to be used for default sorting. |
778
+ | `sortComparer` | `(a: TData, b: TData, direction: 'asc' \| 'desc') => number` | No | A function that provides the precise comparison logic for sorting a column. |
779
+
780
+ ### `IFooterRowDefinition`
781
+
782
+ | Property | Type | Required | Description |
783
+ | --------- | --------------------------- | -------- | -------------------------------------------------- |
784
+ | `columns` | `IFooterColumnDefinition[]` | Yes | An array of column definitions for the footer row. |
785
+
786
+ ### `IFooterColumnDefinition`
787
+
788
+ | Property | Type | Required | Description |
789
+ | -------------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
790
+ | `colSpan` | `number` | Yes | The number of columns the footer cell should span. |
791
+ | `cellRenderer` | `() => ReactNode` | Yes | A function that returns the content for the footer cell. |
792
+ | `displayLabel` | `ReactNode` | No | An optional, explicit label for the footer cell. In mobile view, if `colSpan` is 1 and this is not provided, the corresponding column header will be used as a fallback. This is required for `colSpan` > 1 if you want a label to be displayed. |
793
+ | `onCellClick` | `() => void` | No | An optional click handler for the footer cell. |
794
+ | `className` | `string` | No | Optional class name for custom styling of the footer cell. |
795
+
796
+ ---
797
+
798
+ ## Breaking Changes
799
+
800
+ ### Version 0.5.0
801
+
802
+ **Change:** The API for `infiniteScrollProps` has been simplified.
803
+
804
+ **Details:**
805
+ The `enableInfiniteScroll: true` property has been removed. The infinite scroll feature is now automatically enabled whenever the `infiniteScrollProps` object is provided. Additionally, the `onLoadMore` function is now a required property on `infiniteScrollProps`.
806
+
807
+ **Reason:**
808
+ This change removes unnecessary boilerplate and makes the API more intuitive. If you provide props for infinite scrolling, it's clear you intend to use it.
809
+
810
+ **Migration:**
811
+ To update your code, remove the `enableInfiniteScroll` property from your `infiniteScrollProps` object.
812
+
813
+ **Before:**
814
+ ```jsx
815
+ <ResponsiveTable
816
+ // ...
817
+ infiniteScrollProps={{
818
+ enableInfiniteScroll: true, // <-- No longer needed
819
+ onLoadMore: loadMoreItems,
820
+ }}
821
+ />
822
+ ```
823
+
824
+ **After:**
825
+ ```jsx
826
+ <ResponsiveTable
827
+ // ...
828
+ infiniteScrollProps={{
829
+ onLoadMore: loadMoreItems, // <-- Now required
830
+ }}
831
+ />
832
+ ```
833
+
834
+ ---
835
+
836
+ ## License
837
+
838
+ This project is licensed under the MIT License.