jattac.libs.web.responsive-table 0.2.1 → 0.2.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,581 +1,626 @@
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
- ## Features
6
-
7
- - **Mobile-First Design**: Automatically switches to a card layout on smaller screens for optimal readability.
8
- - **Highly Customizable**: Tailor the look and feel of columns, headers, and footers.
9
- - **Dynamic Data Handling**: Define columns and footers based on your data or application state.
10
- - **Delightful Animations**: Includes an optional skeleton loader and staggered row entrance animations.
11
- - **Interactive Elements**: Easily add click handlers for rows, headers, and footer cells.
12
- - **Performant**: Built with performance in mind, including debounced resize handling.
13
- - **Easy to Use**: A simple and intuitive API for quick integration.
14
- - **Extensible Plugin System**: Easily add new functionalities like filtering, infinite scrolling, or custom behaviors.
15
-
16
- ## Installation
17
-
18
- To get started, install the package from npm:
19
-
20
- ```bash
21
- npm install jattac.libs.web.responsive-table
22
- ```
23
-
24
- ## Getting Started
25
-
26
- Here’s a basic example to get you up and running in minutes.
27
-
28
- ```jsx
29
- import React from 'react';
30
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
31
-
32
- const GettingStarted = () => {
33
- const columns = [
34
- { displayLabel: 'Name', dataKey: 'name', cellRenderer: (row) => row.name },
35
- { displayLabel: 'Age', dataKey: 'age', cellRenderer: (row) => row.age },
36
- { displayLabel: 'City', dataKey: 'city', cellRenderer: (row) => row.city },
37
- ];
38
-
39
- const data = [
40
- { name: 'Alice', age: 32, city: 'New York' },
41
- { name: 'Bob', age: 28, city: 'Los Angeles' },
42
- { name: 'Charlie', age: 45, city: 'Chicago' },
43
- ];
44
-
45
- return <ResponsiveTable columnDefinitions={columns} data={data} />;
46
- };
47
-
48
- export default GettingStarted;
49
- ```
50
-
51
- 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.
52
-
53
- ---
54
-
55
- ## Comprehensive Examples
56
-
57
- ### Example 1: Loading State and Animations
58
-
59
- 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.
60
-
61
- ```jsx
62
- import React, { useState, useEffect } from 'react';
63
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
64
-
65
- const AnimatedTable = () => {
66
- const [data, setData] = useState([]);
67
- const [isLoading, setIsLoading] = useState(true);
68
-
69
- const columns = [
70
- { displayLabel: 'User', cellRenderer: (row) => row.name },
71
- { displayLabel: 'Email', cellRenderer: (row) => row.email },
72
- ];
73
-
74
- useEffect(() => {
75
- // Simulate a network request
76
- setTimeout(() => {
77
- setData([
78
- { name: 'Grace', email: 'grace@example.com' },
79
- { name: 'Henry', email: 'henry@example.com' },
80
- ]);
81
- setIsLoading(false);
82
- }, 2000);
83
- }, []);
84
-
85
- return <ResponsiveTable columnDefinitions={columns} data={data} animationProps={{ isLoading, animateOnLoad: true }} />;
86
- };
87
- ```
88
-
89
- ### Example 2: Adding a Clickable Row Action
90
-
91
- You can make rows clickable to perform actions, such as navigating to a details page or opening a modal.
92
-
93
- ```jsx
94
- import React from 'react';
95
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
96
-
97
- const ClickableRows = () => {
98
- const columns = [
99
- { displayLabel: 'Product', cellRenderer: (row) => row.product },
100
- { displayLabel: 'Price', cellRenderer: (row) => `${row.price.toFixed(2)}` },
101
- ];
102
-
103
- const data = [
104
- { id: 1, product: 'Laptop', price: 1200 },
105
- { id: 2, product: 'Keyboard', price: 75 },
106
- ];
107
-
108
- const handleRowClick = (item) => {
109
- alert(`You clicked on product ID: ${item.id}`);
110
- };
111
-
112
- return <ResponsiveTable columnDefinitions={columns} data={data} onRowClick={handleRowClick} />;
113
- };
114
- ```
115
-
116
- ### Example 3: Custom Cell Rendering
117
-
118
- You can render any React component inside a cell, allowing for rich content like buttons, links, or status badges.
119
-
120
- ```jsx
121
- import React from 'react';
122
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
123
-
124
- const CustomCells = () => {
125
- const columns = [
126
- { displayLabel: 'User', cellRenderer: (row) => <strong>{row.user}</strong> },
127
- {
128
- displayLabel: 'Status',
129
- cellRenderer: (row) => (
130
- <span
131
- style={{
132
- color: row.status === 'Active' ? 'green' : 'red',
133
- fontWeight: 'bold',
134
- }}
135
- >
136
- {row.status}
137
- </span>
138
- ),
139
- },
140
- {
141
- displayLabel: 'Action',
142
- cellRenderer: (row) => <button onClick={() => alert(`Editing ${row.user}`)}>Edit</button>,
143
- },
144
- ];
145
-
146
- const data = [
147
- { user: 'Eve', status: 'Active' },
148
- { user: 'Frank', status: 'Inactive' },
149
- ];
150
-
151
- return <ResponsiveTable columnDefinitions={columns} data={data} />;
152
- };
153
- ```
154
-
155
- ### Example 4: Dynamic and Conditional Columns
156
-
157
- Columns can be generated dynamically based on your data or application state. This is useful for creating flexible tables that adapt to different datasets.
158
-
159
- ```jsx
160
- import React from 'react';
161
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
162
-
163
- const DynamicColumns = ({ isAdmin }) => {
164
- // Base columns for all users
165
- const columns = [
166
- { displayLabel: 'File Name', cellRenderer: (row) => row.fileName },
167
- { displayLabel: 'Size', cellRenderer: (row) => `${row.size} KB` },
168
- ];
169
-
170
- // Add an admin-only column conditionally
171
- if (isAdmin) {
172
- columns.push({
173
- displayLabel: 'Admin Actions',
174
- cellRenderer: (row) => <button onClick={() => alert(`Deleting ${row.fileName}`)}>Delete</button>,
175
- });
176
- }
177
-
178
- const data = [
179
- { fileName: 'document.pdf', size: 1024 },
180
- { fileName: 'image.jpg', size: 512 },
181
- ];
182
-
183
- return <ResponsiveTable columnDefinitions={columns} data={data} />;
184
- };
185
- ```
186
-
187
- ### Example 5: Advanced Footer with Labels and Interactivity
188
-
189
- 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.
190
-
191
- ```jsx
192
- import React from 'react';
193
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
194
-
195
- const TableWithFooter = () => {
196
- const columns = [
197
- { displayLabel: 'Item', cellRenderer: (row) => row.item },
198
- { displayLabel: 'Quantity', cellRenderer: (row) => row.quantity },
199
- { displayLabel: 'Price', cellRenderer: (row) => `${row.price.toFixed(2)}` },
200
- ];
201
-
202
- const data = [
203
- { item: 'Apples', quantity: 10, price: 1.5 },
204
- { item: 'Oranges', quantity: 5, price: 2.0 },
205
- { item: 'Bananas', quantity: 15, price: 0.5 },
206
- ];
207
-
208
- const total = data.reduce((sum, row) => sum + row.quantity * row.price, 0);
209
-
210
- const footerRows = [
211
- {
212
- columns: [
213
- {
214
- colSpan: 2,
215
- cellRenderer: () => <strong>Total:</strong>,
216
- },
217
- {
218
- colSpan: 1,
219
- displayLabel: 'Total',
220
- cellRenderer: () => <strong>${total.toFixed(2)}</strong>,
221
- onCellClick: () => alert('Total clicked!'),
222
- },
223
- ],
224
- },
225
- ];
226
-
227
- return <ResponsiveTable columnDefinitions={columns} data={data} footerRows={footerRows} />;
228
- };
229
- ```
230
-
231
- ---
232
-
233
- ## Plugin System
234
-
235
- 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.
236
-
237
- ### How to Use Plugins
238
-
239
- 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.
240
-
241
- ```jsx
242
- import React from 'react';
243
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
244
- import { FilterPlugin } from 'jattac.libs.web.responsive-table/dist/Plugins/FilterPlugin'; // Adjust path as needed
245
-
246
- const MyTableWithPlugins = () => {
247
- const columns = [
248
- { displayLabel: 'Name', dataKey: 'name', cellRenderer: (row) => row.name, getFilterableValue: (row) => row.name },
249
- { displayLabel: 'Age', dataKey: 'age', cellRenderer: (row) => row.age, getFilterableValue: (row) => row.age.toString() },
250
- ];
251
-
252
- const data = [
253
- { name: 'Alice', age: 32 },
254
- { name: 'Bob', age: 28 },
255
- { name: 'Charlie', age: 45 },
256
- ];
257
-
258
- return (
259
- <ResponsiveTable
260
- columnDefinitions={columns}
261
- data={data}
262
- // Enable built-in filter plugin via props
263
- filterProps={{ showFilter: true, filterPlaceholder: "Search by name or age..." }}
264
- // Or provide a custom instance of the plugin
265
- // plugins={[new FilterPlugin()]}
266
- />
267
- );
268
- };
269
- ```
270
-
271
- ### Built-in Plugins
272
-
273
- #### `FilterPlugin`
274
-
275
- 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`.
276
-
277
- **Props for `FilterPlugin` (via `filterProps` on `ResponsiveTable`):**
278
-
279
- | Prop | Type | Description |
280
- | ----------------- | -------- | --------------------------------------------------------------------------- |
281
- | `showFilter` | `boolean`| If `true`, displays a filter input field above the table. |
282
- | `filterPlaceholder`| `string` | Placeholder text for the filter input. Defaults to "Search...". |
283
-
284
- **Example with `FilterPlugin`:**
285
-
286
- ```jsx
287
- import React, { useState } from 'react';
288
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
289
-
290
- const FilterableTable = () => {
291
- const initialData = [
292
- { id: 1, name: 'Alice', email: 'alice@example.com' },
293
- { id: 2, name: 'Bob', email: 'bob@example.com' },
294
- { id: 3, name: 'Charlie', email: 'charlie@example.com' },
295
- { id: 4, name: 'David', email: 'david@example.com' },
296
- ];
297
-
298
- const columns = [
299
- { displayLabel: 'ID', cellRenderer: (row) => row.id, getFilterableValue: (row) => row.id.toString() },
300
- { displayLabel: 'Name', cellRenderer: (row) => row.name, getFilterableValue: (row) => row.name },
301
- { displayLabel: 'Email', cellRenderer: (row) => row.email, getFilterableValue: (row) => row.email },
302
- ];
303
-
304
- return (
305
- <ResponsiveTable
306
- columnDefinitions={columns}
307
- data={initialData}
308
- filterProps={{ showFilter: true, filterPlaceholder: "Filter users..." }}
309
- />
310
- );
311
- };
312
- ```
313
-
314
- #### `InfiniteScrollPlugin`
315
-
316
- Enables infinite scrolling for the table, loading more data as the user scrolls to the bottom. This plugin requires the `maxHeight` prop to be set on the `ResponsiveTable` to define a scrollable area.
317
-
318
- **Props for `InfiniteScrollPlugin` (via `infiniteScrollProps` on `ResponsiveTable`):**
319
-
320
- | Prop | Type | Description |
321
- | --------------------- | ------------------------------------ | --------------------------------------------------------------------------- |
322
- | `enableInfiniteScroll`| `boolean` | If `true`, enables infinite scrolling. |
323
- | `onLoadMore` | `(currentData: TData[]) => Promise<TData[] | null>` | Callback function to load more data. Should return a Promise resolving to new data or `null`. |
324
- | `hasMore` | `boolean` | Indicates if there is more data to load. |
325
- | `loadingMoreComponent`| `ReactNode` | Custom component to display while loading more data. Defaults to "Loading more...". |
326
- | `noMoreDataComponent` | `ReactNode` | Custom component to display when no more data is available. Defaults to "No more data.". |
327
-
328
- **Example with `InfiniteScrollPlugin`:**
329
-
330
- ```jsx
331
- import React, { useState, useEffect } from 'react';
332
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
333
-
334
- const InfiniteScrollTable = () => {
335
- const [data, setData] = useState([]);
336
- const [hasMore, setHasMore] = useState(true);
337
- const [page, setPage] = useState(0);
338
-
339
- const fetchData = async (currentPage) => {
340
- // Simulate API call
341
- return new Promise((resolve) => {
342
- setTimeout(() => {
343
- const newData = [];
344
- for (let i = 0; i < 10; i++) {
345
- newData.push({ id: currentPage * 10 + i, value: `Item ${currentPage * 10 + i}` });
346
- }
347
- resolve(newData);
348
- }, 500);
349
- });
350
- };
351
-
352
- useEffect(() => {
353
- fetchData(0).then(initialData => {
354
- setData(initialData);
355
- setPage(1);
356
- });
357
- }, []);
358
-
359
- const onLoadMore = async (currentData) => {
360
- if (!hasMore) return null;
361
-
362
- const newItems = await fetchData(page);
363
- if (newItems.length === 0) {
364
- setHasMore(false);
365
- return null;
366
- }
367
- setData((prevData) => [...prevData, ...newItems]);
368
- setPage((prevPage) => prevPage + 1);
369
- return newItems; // Return new items for the plugin to know data was loaded
370
- };
371
-
372
- const columns = [
373
- { displayLabel: 'ID', cellRenderer: (row) => row.id },
374
- { displayLabel: 'Value', cellRenderer: (row) => row.value },
375
- ];
376
-
377
- return (
378
- <div style={{ height: '300px' }}> {/* Container for scrollable table */}
379
- <ResponsiveTable
380
- columnDefinitions={columns}
381
- data={data}
382
- maxHeight="100%"
383
- infiniteScrollProps={{
384
- enableInfiniteScroll: true,
385
- onLoadMore: onLoadMore,
386
- hasMore: hasMore,
387
- loadingMoreComponent: <div>Loading more items...</div>,
388
- noMoreDataComponent: <div>All items loaded.</div>,
389
- }}
390
- />
391
- </div>
392
- );
393
- };
394
- ```
395
-
396
- ### Extending Functionality with Custom Plugins
397
-
398
- Developers can create their own custom plugins to add unique features to the `ResponsiveTable`. This is achieved by implementing the `IResponsiveTablePlugin` interface.
399
-
400
- **`IResponsiveTablePlugin<TData>` Interface:**
401
-
402
- | Property | Type | Description |
403
- | -------------- | ------------------------------------ | --------------------------------------------------------------------------- |
404
- | `id` | `string` | A unique identifier for the plugin. |
405
- | `renderHeader?`| `() => ReactNode` | Optional. A function that returns a React component to be rendered above the table. |
406
- | `renderFooter?`| `() => ReactNode` | Optional. A function that returns a React component to be rendered below the table. |
407
- | `processData?` | `(data: TData[]) => TData[]` | Optional. A function that processes the table data before it is rendered. Useful for sorting, filtering, or transforming data. |
408
- | `onPluginInit?`| `(api: IPluginAPI<TData>) => void` | Optional. A callback function that provides the plugin with an API to interact with the `ResponsiveTable` component. |
409
-
410
- **`IPluginAPI<TData>` Interface:**
411
-
412
- This interface provides methods and properties for plugins to interact with the `ResponsiveTable` component.
413
-
414
- | Property | Type | Description |
415
- | -------------------- | ------------------------------------ | --------------------------------------------------------------------------- |
416
- | `getData` | `() => TData[]` | Returns the current raw data array being used by the table. |
417
- | `forceUpdate` | `() => void` | Forces the `ResponsiveTable` component to re-render. Useful after a plugin modifies internal state that affects rendering. |
418
- | `columnDefinitions` | `ColumnDefinition<TData>[]` | Provides access to the table's column definitions. |
419
- | `getScrollableElement?`| `() => HTMLElement | null` | Optional. Returns the HTML element that is scrollable, if `maxHeight` is set. Useful for implementing scroll-based features. |
420
- | `infiniteScrollProps?`| `object` | Optional. Provides access to the `infiniteScrollProps` passed to the `ResponsiveTable`. |
421
- | `filterProps?` | `object` | Optional. Provides access to the `filterProps` passed to the `ResponsiveTable`. |
422
-
423
- **Example: Custom Sorting Plugin**
424
-
425
- This example demonstrates a simple sorting plugin that allows sorting by a specified column.
426
-
427
- ```typescript
428
- // src/Plugins/SortPlugin.ts
429
- import React from 'react';
430
- import { IResponsiveTablePlugin, IPluginAPI } from './IResponsiveTablePlugin';
431
- import IResponsiveTableColumnDefinition from '../Data/IResponsiveTableColumnDefinition';
432
-
433
- export class SortPlugin<TData> implements IResponsiveTablePlugin<TData> {
434
- public id = 'sort';
435
- private api!: IPluginAPI<TData>;
436
- private sortColumn: string | null = null;
437
- private sortDirection: 'asc' | 'desc' = 'asc';
438
-
439
- public onPluginInit = (api: IPluginAPI<TData>) => {
440
- this.api = api;
441
- };
442
-
443
- public renderHeader = () => {
444
- return (
445
- <div style={{ marginBottom: '1rem' }}>
446
- Sort by:
447
- <select onChange={this.handleColumnChange} style={{ marginLeft: '0.5rem' }}>
448
- <option value="">None</option>
449
- {this.api.columnDefinitions.map((colDef) => {
450
- const rawColDef = colDef as IResponsiveTableColumnDefinition<TData>;
451
- if (rawColDef.dataKey) {
452
- return <option key={rawColDef.dataKey} value={rawColDef.dataKey}>{rawColDef.displayLabel}</option>;
453
- }
454
- return null;
455
- })}
456
- </select>
457
- {this.sortColumn && (
458
- <button onClick={this.toggleSortDirection} style={{ marginLeft: '0.5rem' }}>
459
- {this.sortDirection === 'asc' ? 'Ascending' : 'Descending'}
460
- </button>
461
- )}
462
- </div>
463
- );
464
- };
465
-
466
- public processData = (data: TData[]): TData[] => {
467
- if (!this.sortColumn) {
468
- return data;
469
- }
470
-
471
- const sortedData = [...data].sort((a, b) => {
472
- const aValue = a[this.sortColumn as keyof TData];
473
- const bValue = b[this.sortColumn as keyof TData];
474
-
475
- if (aValue < bValue) return this.sortDirection === 'asc' ? -1 : 1;
476
- if (aValue > bValue) return this.sortDirection === 'asc' ? 1 : -1;
477
- return 0;
478
- });
479
-
480
- return sortedData;
481
- };
482
-
483
- private handleColumnChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
484
- this.sortColumn = e.target.value || null;
485
- this.api.forceUpdate();
486
- };
487
-
488
- private toggleSortDirection = () => {
489
- this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
490
- this.api.forceUpdate();
491
- };
492
- }
493
-
494
- // Usage in your component:
495
- ```jsx
496
- import React from 'react';
497
- import ResponsiveTable from 'jattac.libs.web.responsive-table';
498
- import { SortPlugin } from './SortPlugin'; // Assuming SortPlugin.ts is in the same directory
499
-
500
- const SortableTable = () => {
501
- const data = [
502
- { id: 1, name: 'Alice', age: 30 },
503
- { id: 2, name: 'Bob', age: 25 },
504
- { id: 3, name: 'Charlie', age: 35 },
505
- ];
506
-
507
- const columns = [
508
- { displayLabel: 'ID', dataKey: 'id', cellRenderer: (row) => row.id },
509
- { displayLabel: 'Name', dataKey: 'name', cellRenderer: (row) => row.name },
510
- { displayLabel: 'Age', dataKey: 'age', cellRenderer: (row) => row.age },
511
- ];
512
-
513
- return (
514
- <ResponsiveTable
515
- columnDefinitions={columns}
516
- data={data}
517
- plugins={[new SortPlugin()]}
518
- />
519
- );
520
- };
521
- ```
522
-
523
- **Other Plugin Ideas (beyond Filtering and Infinite Scroll):**
524
-
525
- - **Column Resizing Plugin:** Allows users to drag column headers to resize columns.
526
- - **Row Selection Plugin:** Adds checkboxes to rows for multi-row selection.
527
- - **Export Data Plugin:** Provides buttons to export table data to CSV, Excel, or PDF.
528
- - **Drag-and-Drop Reordering Plugin:** Enables reordering of rows or columns via drag and drop.
529
- - **Column Visibility Toggle Plugin:** Allows users to show/hide specific columns.
530
-
531
- ---
532
-
533
- ---
534
-
535
- ## API Reference
536
-
537
- ### `ResponsiveTable` Props
538
-
539
- | Prop | Type | Required | Description |
540
- | ------------------- | ------------------------------------ | -------- | ----------------------------------------------------------------------------------- |
541
- | `columnDefinitions` | `IResponsiveTableColumnDefinition[]` | Yes | An array of objects defining the table columns. |
542
- | `data` | `TData[]` | Yes | An array of data objects to populate the table rows. |
543
- | `footerRows` | `IFooterRowDefinition[]` | No | An array of objects defining the table footer. |
544
- | `onRowClick` | `(item: TData) => void` | No | A callback function that is triggered when a row is clicked. |
545
- | `noDataComponent` | `ReactNode` | No | A custom component to display when there is no data. |
546
- | `maxHeight` | `string` | No | Sets a maximum height for the table body, making it scrollable. |
547
- | `mobileBreakpoint` | `number` | No | The pixel width at which the table switches to the mobile view. Defaults to `600`. |
548
- | `plugins` | `IResponsiveTablePlugin<TData>[]` | No | An array of plugin instances to extend table functionality. |
549
- | `infiniteScrollProps`| `object` | No | Configuration for the built-in infinite scroll plugin. |
550
- | `filterProps` | `object` | No | Configuration for the built-in filter plugin. |
551
- | `animationProps` | `object` | No | Configuration for animations, including `isLoading` and `animateOnLoad`. |
552
-
553
- ### `IResponsiveTableColumnDefinition`
554
-
555
- | Property | Type | Required | Description |
556
- | --------------- | --------------------------- | -------- | ------------------------------------------------------------------------------ |
557
- | `displayLabel` | `string` | Yes | The label displayed in the table header. |
558
- | `cellRenderer` | `(row: TData) => ReactNode` | Yes | A function that returns the content to be rendered in the cell. |
559
- | `dataKey` | `string` | No | A key to match the column to a property in the data object (optional). |
560
- | `interactivity` | `object` | No | An object to define header interactivity (`onHeaderClick`, `id`, `className`). |
561
- | `getFilterableValue`| `(row: TData) => string` | No | A function that returns the string value to be used for filtering this column. Required for `FilterPlugin`. |
562
-
563
- ### `IFooterRowDefinition`
564
-
565
- | Property | Type | Required | Description |
566
- | --------- | --------------------------- | -------- | -------------------------------------------------- |
567
- | `columns` | `IFooterColumnDefinition[]` | Yes | An array of column definitions for the footer row. |
568
-
569
- ### `IFooterColumnDefinition`
570
-
571
- | Property | Type | Required | Description |
572
- | -------------- | ----------------- | -------- | ------------------------------------------------------------------------------ |
573
- | `colSpan` | `number` | Yes | The number of columns the footer cell should span. |
574
- | `cellRenderer` | `() => ReactNode` | Yes | A function that returns the content for the footer cell. |
575
- | `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. |
576
- | `onCellClick` | `() => void` | No | An optional click handler for the footer cell. |
577
- | `className` | `string` | No | Optional class name for custom styling of the footer cell. |
578
-
579
- ## License
580
-
581
- 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
+ ## Features
6
+
7
+ - **Mobile-First Design**: Automatically switches to a card layout on smaller screens for optimal readability.
8
+ - **Highly Customizable**: Tailor the look and feel of columns, headers, and footers.
9
+ - **Dynamic Data Handling**: Define columns and footers based on your data or application state.
10
+ - **Delightful Animations**: Includes an optional skeleton loader and staggered row entrance animations.
11
+ - **Interactive Elements**: Easily add click handlers for rows, headers, and footer cells.
12
+ - **Performant**: Built with performance in mind, including debounced resize handling.
13
+ - **Easy to Use**: A simple and intuitive API for quick integration.
14
+ - **Extensible Plugin System**: Easily add new functionalities like filtering, infinite scrolling, or custom behaviors.
15
+
16
+ ## Installation
17
+
18
+ To get started, install the package from npm:
19
+
20
+ ```bash
21
+ npm install jattac.libs.web.responsive-table
22
+ ```
23
+
24
+ ## Getting Started
25
+
26
+ Here’s a basic example to get you up and running in minutes.
27
+
28
+ ```jsx
29
+ import React from 'react';
30
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
31
+
32
+ const GettingStarted = () => {
33
+ const columns = [
34
+ { displayLabel: 'Name', dataKey: 'name', cellRenderer: (row) => row.name },
35
+ { displayLabel: 'Age', dataKey: 'age', cellRenderer: (row) => row.age },
36
+ { displayLabel: 'City', dataKey: 'city', cellRenderer: (row) => row.city },
37
+ ];
38
+
39
+ const data = [
40
+ { name: 'Alice', age: 32, city: 'New York' },
41
+ { name: 'Bob', age: 28, city: 'Los Angeles' },
42
+ { name: 'Charlie', age: 45, city: 'Chicago' },
43
+ ];
44
+
45
+ return <ResponsiveTable columnDefinitions={columns} data={data} />;
46
+ };
47
+
48
+ export default GettingStarted;
49
+ ```
50
+
51
+ 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.
52
+
53
+ ---
54
+
55
+ ## Comprehensive Examples
56
+
57
+ ### Example 1: Loading State and Animations
58
+
59
+ 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.
60
+
61
+ ```jsx
62
+ import React, { useState, useEffect } from 'react';
63
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
64
+
65
+ const AnimatedTable = () => {
66
+ const [data, setData] = useState([]);
67
+ const [isLoading, setIsLoading] = useState(true);
68
+
69
+ const columns = [
70
+ { displayLabel: 'User', cellRenderer: (row) => row.name },
71
+ { displayLabel: 'Email', cellRenderer: (row) => row.email },
72
+ ];
73
+
74
+ useEffect(() => {
75
+ // Simulate a network request
76
+ setTimeout(() => {
77
+ setData([
78
+ { name: 'Grace', email: 'grace@example.com' },
79
+ { name: 'Henry', email: 'henry@example.com' },
80
+ ]);
81
+ setIsLoading(false);
82
+ }, 2000);
83
+ }, []);
84
+
85
+ return <ResponsiveTable columnDefinitions={columns} data={data} animationProps={{ isLoading, animateOnLoad: true }} />;
86
+ };
87
+ ```
88
+
89
+ ### Example 2: Adding a Clickable Row Action
90
+
91
+ You can make rows clickable to perform actions, such as navigating to a details page or opening a modal.
92
+
93
+ ```jsx
94
+ import React from 'react';
95
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
96
+
97
+ const ClickableRows = () => {
98
+ const columns = [
99
+ { displayLabel: 'Product', cellRenderer: (row) => row.product },
100
+ { displayLabel: 'Price', cellRenderer: (row) => `${row.price.toFixed(2)}` },
101
+ ];
102
+
103
+ const data = [
104
+ { id: 1, product: 'Laptop', price: 1200 },
105
+ { id: 2, product: 'Keyboard', price: 75 },
106
+ ];
107
+
108
+ const handleRowClick = (item) => {
109
+ alert(`You clicked on product ID: ${item.id}`);
110
+ };
111
+
112
+ return <ResponsiveTable columnDefinitions={columns} data={data} onRowClick={handleRowClick} />;
113
+ };
114
+ ```
115
+
116
+ ### Example 3: Custom Cell Rendering
117
+
118
+ You can render any React component inside a cell, allowing for rich content like buttons, links, or status badges.
119
+
120
+ ```jsx
121
+ import React from 'react';
122
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
123
+
124
+ const CustomCells = () => {
125
+ const columns = [
126
+ { displayLabel: 'User', cellRenderer: (row) => <strong>{row.user}</strong> },
127
+ {
128
+ displayLabel: 'Status',
129
+ cellRenderer: (row) => (
130
+ <span
131
+ style={{
132
+ color: row.status === 'Active' ? 'green' : 'red',
133
+ fontWeight: 'bold',
134
+ }}
135
+ >
136
+ {row.status}
137
+ </span>
138
+ ),
139
+ },
140
+ {
141
+ displayLabel: 'Action',
142
+ cellRenderer: (row) => <button onClick={() => alert(`Editing ${row.user}`)}>Edit</button>,
143
+ },
144
+ ];
145
+
146
+ const data = [
147
+ { user: 'Eve', status: 'Active' },
148
+ { user: 'Frank', status: 'Inactive' },
149
+ ];
150
+
151
+ return <ResponsiveTable columnDefinitions={columns} data={data} />;
152
+ };
153
+ ```
154
+
155
+ ### Example 4: Dynamic and Conditional Columns
156
+
157
+ Columns can be generated dynamically based on your data or application state. This is useful for creating flexible tables that adapt to different datasets.
158
+
159
+ ```jsx
160
+ import React from 'react';
161
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
162
+
163
+ const DynamicColumns = ({ isAdmin }) => {
164
+ // Base columns for all users
165
+ const columns = [
166
+ { displayLabel: 'File Name', cellRenderer: (row) => row.fileName },
167
+ { displayLabel: 'Size', cellRenderer: (row) => `${row.size} KB` },
168
+ ];
169
+
170
+ // Add an admin-only column conditionally
171
+ if (isAdmin) {
172
+ columns.push({
173
+ displayLabel: 'Admin Actions',
174
+ cellRenderer: (row) => <button onClick={() => alert(`Deleting ${row.fileName}`)}>Delete</button>,
175
+ });
176
+ }
177
+
178
+ const data = [
179
+ { fileName: 'document.pdf', size: 1024 },
180
+ { fileName: 'image.jpg', size: 512 },
181
+ ];
182
+
183
+ return <ResponsiveTable columnDefinitions={columns} data={data} />;
184
+ };
185
+ ```
186
+
187
+ ### Example 5: Advanced Footer with Labels and Interactivity
188
+
189
+ 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.
190
+
191
+ ```jsx
192
+ import React from 'react';
193
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
194
+
195
+ const TableWithFooter = () => {
196
+ const columns = [
197
+ { displayLabel: 'Item', cellRenderer: (row) => row.item },
198
+ { displayLabel: 'Quantity', cellRenderer: (row) => row.quantity },
199
+ { displayLabel: 'Price', cellRenderer: (row) => `${row.price.toFixed(2)}` },
200
+ ];
201
+
202
+ const data = [
203
+ { item: 'Apples', quantity: 10, price: 1.5 },
204
+ { item: 'Oranges', quantity: 5, price: 2.0 },
205
+ { item: 'Bananas', quantity: 15, price: 0.5 },
206
+ ];
207
+
208
+ const total = data.reduce((sum, row) => sum + row.quantity * row.price, 0);
209
+
210
+ const footerRows = [
211
+ {
212
+ columns: [
213
+ {
214
+ colSpan: 2,
215
+ cellRenderer: () => <strong>Total:</strong>,
216
+ },
217
+ {
218
+ colSpan: 1,
219
+ displayLabel: 'Total',
220
+ cellRenderer: () => <strong>${total.toFixed(2)}</strong>,
221
+ onCellClick: () => alert('Total clicked!'),
222
+ },
223
+ ],
224
+ },
225
+ ];
226
+
227
+ return <ResponsiveTable columnDefinitions={columns} data={data} footerRows={footerRows} />;
228
+ };
229
+ ```
230
+
231
+ ### Example 6: Disabling the Page-Level Sticky Header (ELI5)
232
+
233
+ **Explain Like I'm 5:** Imagine you have a super long grocery list on a piece of paper (the webpage). The titles of the columns are "Item", "Quantity", and "Price" (the table header).
234
+
235
+ Normally, as you slide the paper up to see items at the bottom, the titles disappear off the top.
236
+
237
+ This table has a special power: by default, the header "sticks" to the top of your view so you never forget which column is which.
238
+
239
+ But what if you don't want it to stick? The `enablePageLevelStickyHeader={false}` prop is like a magic switch. Flipping it to `false` tells the header to scroll away normally with the rest of the page.
240
+
241
+ ```jsx
242
+ import React from 'react';
243
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
244
+
245
+ const NonStickyHeaderTable = () => {
246
+ // We need enough data to make the page scroll
247
+ const data = Array.from({ length: 50 }, (_, i) => ({
248
+ id: i + 1,
249
+ item: `Item #${i + 1}`,
250
+ description: 'This is a sample item.',
251
+ }));
252
+
253
+ const columns = [
254
+ { displayLabel: 'ID', cellRenderer: (row) => row.id },
255
+ { displayLabel: 'Item', cellRenderer: (row) => row.item },
256
+ { displayLabel: 'Description', cellRenderer: (row) => row.description },
257
+ ];
258
+
259
+ return (
260
+ <div>
261
+ <h1 style={{ height: '50vh', display: 'flex', alignItems: 'center' }}>
262
+ Scroll down to see the table
263
+ </h1>
264
+ <ResponsiveTable
265
+ columnDefinitions={columns}
266
+ data={data}
267
+ enablePageLevelStickyHeader={false} // <-- Here's the magic switch!
268
+ />
269
+ <div style={{ height: '50vh' }} />
270
+ </div>
271
+ );
272
+ };
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Plugin System
278
+
279
+ 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.
280
+
281
+ ### How to Use Plugins
282
+
283
+ 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.
284
+
285
+ ```jsx
286
+ import React from 'react';
287
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
288
+ import { FilterPlugin } from 'jattac.libs.web.responsive-table/dist/Plugins/FilterPlugin'; // Adjust path as needed
289
+
290
+ const MyTableWithPlugins = () => {
291
+ const columns = [
292
+ { displayLabel: 'Name', dataKey: 'name', cellRenderer: (row) => row.name, getFilterableValue: (row) => row.name },
293
+ { displayLabel: 'Age', dataKey: 'age', cellRenderer: (row) => row.age, getFilterableValue: (row) => row.age.toString() },
294
+ ];
295
+
296
+ const data = [
297
+ { name: 'Alice', age: 32 },
298
+ { name: 'Bob', age: 28 },
299
+ { name: 'Charlie', age: 45 },
300
+ ];
301
+
302
+ return (
303
+ <ResponsiveTable
304
+ columnDefinitions={columns}
305
+ data={data}
306
+ // Enable built-in filter plugin via props
307
+ filterProps={{ showFilter: true, filterPlaceholder: "Search by name or age..." }}
308
+ // Or provide a custom instance of the plugin
309
+ // plugins={[new FilterPlugin()]}
310
+ />
311
+ );
312
+ };
313
+ ```
314
+
315
+ ### Built-in Plugins
316
+
317
+ #### `FilterPlugin`
318
+
319
+ 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`.
320
+
321
+ **Props for `FilterPlugin` (via `filterProps` on `ResponsiveTable`):**
322
+
323
+ | Prop | Type | Description |
324
+ | ----------------- | -------- | --------------------------------------------------------------------------- |
325
+ | `showFilter` | `boolean`| If `true`, displays a filter input field above the table. |
326
+ | `filterPlaceholder`| `string` | Placeholder text for the filter input. Defaults to "Search...". |
327
+
328
+ **Example with `FilterPlugin`:**
329
+
330
+ ```jsx
331
+ import React, { useState } from 'react';
332
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
333
+
334
+ const FilterableTable = () => {
335
+ const initialData = [
336
+ { id: 1, name: 'Alice', email: 'alice@example.com' },
337
+ { id: 2, name: 'Bob', email: 'bob@example.com' },
338
+ { id: 3, name: 'Charlie', email: 'charlie@example.com' },
339
+ { id: 4, name: 'David', email: 'david@example.com' },
340
+ ];
341
+
342
+ const columns = [
343
+ { displayLabel: 'ID', cellRenderer: (row) => row.id, getFilterableValue: (row) => row.id.toString() },
344
+ { displayLabel: 'Name', cellRenderer: (row) => row.name, getFilterableValue: (row) => row.name },
345
+ { displayLabel: 'Email', cellRenderer: (row) => row.email, getFilterableValue: (row) => row.email },
346
+ ];
347
+
348
+ return (
349
+ <ResponsiveTable
350
+ columnDefinitions={columns}
351
+ data={initialData}
352
+ filterProps={{ showFilter: true, filterPlaceholder: "Filter users..." }}
353
+ />
354
+ );
355
+ };
356
+ ```
357
+
358
+ #### `InfiniteScrollPlugin`
359
+
360
+ Enables infinite scrolling for the table, loading more data as the user scrolls to the bottom. This plugin requires the `maxHeight` prop to be set on the `ResponsiveTable` to define a scrollable area.
361
+
362
+ **Props for `InfiniteScrollPlugin` (via `infiniteScrollProps` on `ResponsiveTable`):**
363
+
364
+ | Prop | Type | Description |
365
+ | --------------------- | ------------------------------------ | --------------------------------------------------------------------------- |
366
+ | `enableInfiniteScroll`| `boolean` | If `true`, enables infinite scrolling. |
367
+ | `onLoadMore` | `(currentData: TData[]) => Promise<TData[] | null>` | Callback function to load more data. Should return a Promise resolving to new data or `null`. |
368
+ | `hasMore` | `boolean` | Indicates if there is more data to load. |
369
+ | `loadingMoreComponent`| `ReactNode` | Custom component to display while loading more data. Defaults to "Loading more...". |
370
+ | `noMoreDataComponent` | `ReactNode` | Custom component to display when no more data is available. Defaults to "No more data.". |
371
+
372
+ **Example with `InfiniteScrollPlugin`:**
373
+
374
+ ```jsx
375
+ import React, { useState, useEffect } from 'react';
376
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
377
+
378
+ const InfiniteScrollTable = () => {
379
+ const [data, setData] = useState([]);
380
+ const [hasMore, setHasMore] = useState(true);
381
+ const [page, setPage] = useState(0);
382
+
383
+ const fetchData = async (currentPage) => {
384
+ // Simulate API call
385
+ return new Promise((resolve) => {
386
+ setTimeout(() => {
387
+ const newData = [];
388
+ for (let i = 0; i < 10; i++) {
389
+ newData.push({ id: currentPage * 10 + i, value: `Item ${currentPage * 10 + i}` });
390
+ }
391
+ resolve(newData);
392
+ }, 500);
393
+ });
394
+ };
395
+
396
+ useEffect(() => {
397
+ fetchData(0).then(initialData => {
398
+ setData(initialData);
399
+ setPage(1);
400
+ });
401
+ }, []);
402
+
403
+ const onLoadMore = async (currentData) => {
404
+ if (!hasMore) return null;
405
+
406
+ const newItems = await fetchData(page);
407
+ if (newItems.length === 0) {
408
+ setHasMore(false);
409
+ return null;
410
+ }
411
+ setData((prevData) => [...prevData, ...newItems]);
412
+ setPage((prevPage) => prevPage + 1);
413
+ return newItems; // Return new items for the plugin to know data was loaded
414
+ };
415
+
416
+ const columns = [
417
+ { displayLabel: 'ID', cellRenderer: (row) => row.id },
418
+ { displayLabel: 'Value', cellRenderer: (row) => row.value },
419
+ ];
420
+
421
+ return (
422
+ <div style={{ height: '300px' }}> {/* Container for scrollable table */}
423
+ <ResponsiveTable
424
+ columnDefinitions={columns}
425
+ data={data}
426
+ maxHeight="100%"
427
+ infiniteScrollProps={{
428
+ enableInfiniteScroll: true,
429
+ onLoadMore: onLoadMore,
430
+ hasMore: hasMore,
431
+ loadingMoreComponent: <div>Loading more items...</div>,
432
+ noMoreDataComponent: <div>All items loaded.</div>,
433
+ }}
434
+ />
435
+ </div>
436
+ );
437
+ };
438
+ ```
439
+
440
+ ### Extending Functionality with Custom Plugins
441
+
442
+ Developers can create their own custom plugins to add unique features to the `ResponsiveTable`. This is achieved by implementing the `IResponsiveTablePlugin` interface.
443
+
444
+ **`IResponsiveTablePlugin<TData>` Interface:**
445
+
446
+ | Property | Type | Description |
447
+ | -------------- | ------------------------------------ | --------------------------------------------------------------------------- |
448
+ | `id` | `string` | A unique identifier for the plugin. |
449
+ | `renderHeader?`| `() => ReactNode` | Optional. A function that returns a React component to be rendered above the table. |
450
+ | `renderFooter?`| `() => ReactNode` | Optional. A function that returns a React component to be rendered below the table. |
451
+ | `processData?` | `(data: TData[]) => TData[]` | Optional. A function that processes the table data before it is rendered. Useful for sorting, filtering, or transforming data. |
452
+ | `onPluginInit?`| `(api: IPluginAPI<TData>) => void` | Optional. A callback function that provides the plugin with an API to interact with the `ResponsiveTable` component. |
453
+
454
+ **`IPluginAPI<TData>` Interface:**
455
+
456
+ This interface provides methods and properties for plugins to interact with the `ResponsiveTable` component.
457
+
458
+ | Property | Type | Description |
459
+ | -------------------- | ------------------------------------ | --------------------------------------------------------------------------- |
460
+ | `getData` | `() => TData[]` | Returns the current raw data array being used by the table. |
461
+ | `forceUpdate` | `() => void` | Forces the `ResponsiveTable` component to re-render. Useful after a plugin modifies internal state that affects rendering. |
462
+ | `columnDefinitions` | `ColumnDefinition<TData>[]` | Provides access to the table's column definitions. |
463
+ | `getScrollableElement?`| `() => HTMLElement | null` | Optional. Returns the HTML element that is scrollable, if `maxHeight` is set. Useful for implementing scroll-based features. |
464
+ | `infiniteScrollProps?`| `object` | Optional. Provides access to the `infiniteScrollProps` passed to the `ResponsiveTable`. |
465
+ | `filterProps?` | `object` | Optional. Provides access to the `filterProps` passed to the `ResponsiveTable`. |
466
+
467
+ **Example: Custom Sorting Plugin**
468
+
469
+ This example demonstrates a simple sorting plugin that allows sorting by a specified column.
470
+
471
+ ```typescript
472
+ // src/Plugins/SortPlugin.ts
473
+ import React from 'react';
474
+ import { IResponsiveTablePlugin, IPluginAPI } from './IResponsiveTablePlugin';
475
+ import IResponsiveTableColumnDefinition from '../Data/IResponsiveTableColumnDefinition';
476
+
477
+ export class SortPlugin<TData> implements IResponsiveTablePlugin<TData> {
478
+ public id = 'sort';
479
+ private api!: IPluginAPI<TData>;
480
+ private sortColumn: string | null = null;
481
+ private sortDirection: 'asc' | 'desc' = 'asc';
482
+
483
+ public onPluginInit = (api: IPluginAPI<TData>) => {
484
+ this.api = api;
485
+ };
486
+
487
+ public renderHeader = () => {
488
+ return (
489
+ <div style={{ marginBottom: '1rem' }}>
490
+ Sort by:
491
+ <select onChange={this.handleColumnChange} style={{ marginLeft: '0.5rem' }}>
492
+ <option value="">None</option>
493
+ {this.api.columnDefinitions.map((colDef) => {
494
+ const rawColDef = colDef as IResponsiveTableColumnDefinition<TData>;
495
+ if (rawColDef.dataKey) {
496
+ return <option key={rawColDef.dataKey} value={rawColDef.dataKey}>{rawColDef.displayLabel}</option>;
497
+ }
498
+ return null;
499
+ })}
500
+ </select>
501
+ {this.sortColumn && (
502
+ <button onClick={this.toggleSortDirection} style={{ marginLeft: '0.5rem' }}>
503
+ {this.sortDirection === 'asc' ? 'Ascending' : 'Descending'}
504
+ </button>
505
+ )}
506
+ </div>
507
+ );
508
+ };
509
+
510
+ public processData = (data: TData[]): TData[] => {
511
+ if (!this.sortColumn) {
512
+ return data;
513
+ }
514
+
515
+ const sortedData = [...data].sort((a, b) => {
516
+ const aValue = a[this.sortColumn as keyof TData];
517
+ const bValue = b[this.sortColumn as keyof TData];
518
+
519
+ if (aValue < bValue) return this.sortDirection === 'asc' ? -1 : 1;
520
+ if (aValue > bValue) return this.sortDirection === 'asc' ? 1 : -1;
521
+ return 0;
522
+ });
523
+
524
+ return sortedData;
525
+ };
526
+
527
+ private handleColumnChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
528
+ this.sortColumn = e.target.value || null;
529
+ this.api.forceUpdate();
530
+ };
531
+
532
+ private toggleSortDirection = () => {
533
+ this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
534
+ this.api.forceUpdate();
535
+ };
536
+ }
537
+
538
+ // Usage in your component:
539
+ ```jsx
540
+ import React from 'react';
541
+ import ResponsiveTable from 'jattac.libs.web.responsive-table';
542
+ import { SortPlugin } from './SortPlugin'; // Assuming SortPlugin.ts is in the same directory
543
+
544
+ const SortableTable = () => {
545
+ const data = [
546
+ { id: 1, name: 'Alice', age: 30 },
547
+ { id: 2, name: 'Bob', age: 25 },
548
+ { id: 3, name: 'Charlie', age: 35 },
549
+ ];
550
+
551
+ const columns = [
552
+ { displayLabel: 'ID', dataKey: 'id', cellRenderer: (row) => row.id },
553
+ { displayLabel: 'Name', dataKey: 'name', cellRenderer: (row) => row.name },
554
+ { displayLabel: 'Age', dataKey: 'age', cellRenderer: (row) => row.age },
555
+ ];
556
+
557
+ return (
558
+ <ResponsiveTable
559
+ columnDefinitions={columns}
560
+ data={data}
561
+ plugins={[new SortPlugin()]}
562
+ />
563
+ );
564
+ };
565
+ ```
566
+
567
+ **Other Plugin Ideas (beyond Filtering and Infinite Scroll):**
568
+
569
+ - **Column Resizing Plugin:** Allows users to drag column headers to resize columns.
570
+ - **Row Selection Plugin:** Adds checkboxes to rows for multi-row selection.
571
+ - **Export Data Plugin:** Provides buttons to export table data to CSV, Excel, or PDF.
572
+ - **Drag-and-Drop Reordering Plugin:** Enables reordering of rows or columns via drag and drop.
573
+ - **Column Visibility Toggle Plugin:** Allows users to show/hide specific columns.
574
+
575
+ ---
576
+
577
+ ---
578
+
579
+ ## API Reference
580
+
581
+ ### `ResponsiveTable` Props
582
+
583
+ | Prop | Type | Required | Description |
584
+ | ----------------------------- | ------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------- |
585
+ | `columnDefinitions` | `IResponsiveTableColumnDefinition[]` | Yes | An array of objects defining the table columns. |
586
+ | `data` | `TData[]` | Yes | An array of data objects to populate the table rows. |
587
+ | `footerRows` | `IFooterRowDefinition[]` | No | An array of objects defining the table footer. |
588
+ | `onRowClick` | `(item: TData) => void` | No | A callback function that is triggered when a row is clicked. |
589
+ | `noDataComponent` | `ReactNode` | No | A custom component to display when there is no data. |
590
+ | `maxHeight` | `string` | No | Sets a maximum height for the table body, making it scrollable. |
591
+ | `mobileBreakpoint` | `number` | No | The pixel width at which the table switches to the mobile view. Defaults to `600`. |
592
+ | `enablePageLevelStickyHeader` | `boolean` | No | If `false`, disables the header from sticking to the top of the page on scroll. Defaults to `true`. |
593
+ | `plugins` | `IResponsiveTablePlugin<TData>[]` | No | An array of plugin instances to extend table functionality. |
594
+ | `infiniteScrollProps` | `object` | No | Configuration for the built-in infinite scroll plugin. |
595
+ | `filterProps` | `object` | No | Configuration for the built-in filter plugin. |
596
+ | `animationProps` | `object` | No | Configuration for animations, including `isLoading` and `animateOnLoad`. |
597
+
598
+ ### `IResponsiveTableColumnDefinition`
599
+
600
+ | Property | Type | Required | Description |
601
+ | --------------- | --------------------------- | -------- | ------------------------------------------------------------------------------ |
602
+ | `displayLabel` | `string` | Yes | The label displayed in the table header. |
603
+ | `cellRenderer` | `(row: TData) => ReactNode` | Yes | A function that returns the content to be rendered in the cell. |
604
+ | `dataKey` | `string` | No | A key to match the column to a property in the data object (optional). |
605
+ | `interactivity` | `object` | No | An object to define header interactivity (`onHeaderClick`, `id`, `className`). |
606
+ | `getFilterableValue`| `(row: TData) => string` | No | A function that returns the string value to be used for filtering this column. Required for `FilterPlugin`. |
607
+
608
+ ### `IFooterRowDefinition`
609
+
610
+ | Property | Type | Required | Description |
611
+ | --------- | --------------------------- | -------- | -------------------------------------------------- |
612
+ | `columns` | `IFooterColumnDefinition[]` | Yes | An array of column definitions for the footer row. |
613
+
614
+ ### `IFooterColumnDefinition`
615
+
616
+ | Property | Type | Required | Description |
617
+ | -------------- | ----------------- | -------- | ------------------------------------------------------------------------------ |
618
+ | `colSpan` | `number` | Yes | The number of columns the footer cell should span. |
619
+ | `cellRenderer` | `() => ReactNode` | Yes | A function that returns the content for the footer cell. |
620
+ | `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. |
621
+ | `onCellClick` | `() => void` | No | An optional click handler for the footer cell. |
622
+ | `className` | `string` | No | Optional class name for custom styling of the footer cell. |
623
+
624
+ ## License
625
+
626
+ This project is licensed under the MIT License.