jattac.libs.web.responsive-table 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,6 +11,7 @@ ResponsiveTable is a powerful, lightweight, and fully responsive React component
11
11
  - **Interactive Elements**: Easily add click handlers for rows, headers, and footer cells.
12
12
  - **Performant**: Built with performance in mind, including debounced resize handling.
13
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.
14
15
 
15
16
  ## Installation
16
17
 
@@ -229,6 +230,306 @@ const TableWithFooter = () => {
229
230
 
230
231
  ---
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
+
232
533
  ## API Reference
233
534
 
234
535
  ### `ResponsiveTable` Props
@@ -244,6 +545,9 @@ const TableWithFooter = () => {
244
545
  | `noDataComponent` | `ReactNode` | No | A custom component to display when there is no data. |
245
546
  | `maxHeight` | `string` | No | Sets a maximum height for the table body, making it scrollable. |
246
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. |
247
551
 
248
552
  ### `IResponsiveTableColumnDefinition`
249
553
 
@@ -253,6 +557,7 @@ const TableWithFooter = () => {
253
557
  | `cellRenderer` | `(row: TData) => ReactNode` | Yes | A function that returns the content to be rendered in the cell. |
254
558
  | `dataKey` | `string` | No | A key to match the column to a property in the data object (optional). |
255
559
  | `interactivity` | `object` | No | An object to define header interactivity (`onHeaderClick`, `id`, `className`). |
560
+ | `getFilterableValue`| `(row: TData) => string` | No | A function that returns the string value to be used for filtering this column. Required for `FilterPlugin`. |
256
561
 
257
562
  ### `IFooterRowDefinition`
258
563
 
@@ -272,4 +577,4 @@ const TableWithFooter = () => {
272
577
 
273
578
  ## License
274
579
 
275
- This project is licensed under the MIT License.
580
+ This project is licensed under the MIT License.
@@ -7,4 +7,5 @@ export default interface IResponsiveTableColumnDefinition<TData> {
7
7
  onHeaderClick?: (id: string) => void;
8
8
  className?: string;
9
9
  };
10
+ getFilterableValue?: (data: TData) => string | number;
10
11
  }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { IResponsiveTablePlugin, IPluginAPI } from './IResponsiveTablePlugin';
3
+ export declare class FilterPlugin<TData> implements IResponsiveTablePlugin<TData> {
4
+ id: string;
5
+ private filterText;
6
+ private api;
7
+ constructor();
8
+ onPluginInit: (api: IPluginAPI<TData>) => void;
9
+ renderHeader: () => React.JSX.Element | null;
10
+ processData: (data: TData[]) => TData[];
11
+ private handleFilterChange;
12
+ }
@@ -0,0 +1,26 @@
1
+ import { ReactNode } from 'react';
2
+ import { ColumnDefinition } from '../UI/ResponsiveTable';
3
+ export interface IResponsiveTablePlugin<TData> {
4
+ id: string;
5
+ renderHeader?: () => ReactNode;
6
+ renderFooter?: () => ReactNode;
7
+ processData?: (data: TData[]) => TData[];
8
+ onPluginInit?: (api: IPluginAPI<TData>) => void;
9
+ }
10
+ export interface IPluginAPI<TData> {
11
+ getData: () => TData[];
12
+ forceUpdate: () => void;
13
+ columnDefinitions: ColumnDefinition<TData>[];
14
+ getScrollableElement?: () => HTMLElement | null;
15
+ infiniteScrollProps?: {
16
+ enableInfiniteScroll?: boolean;
17
+ onLoadMore?: (currentData: TData[]) => Promise<TData[] | null>;
18
+ hasMore?: boolean;
19
+ loadingMoreComponent?: ReactNode;
20
+ noMoreDataComponent?: ReactNode;
21
+ };
22
+ filterProps?: {
23
+ showFilter?: boolean;
24
+ filterPlaceholder?: string;
25
+ };
26
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { IResponsiveTablePlugin, IPluginAPI } from './IResponsiveTablePlugin';
3
+ export declare class InfiniteScrollPlugin<TData> implements IResponsiveTablePlugin<TData> {
4
+ id: string;
5
+ private api;
6
+ private isLoadingMore;
7
+ constructor();
8
+ onPluginInit: (api: IPluginAPI<TData>) => void;
9
+ private attachScrollListener;
10
+ private handleScroll;
11
+ processData: (data: TData[]) => TData[];
12
+ renderFooter: () => string | number | true | Iterable<React.ReactNode> | React.JSX.Element | null;
13
+ }
@@ -1,6 +1,7 @@
1
1
  import React, { Component, ReactNode } from 'react';
2
2
  import IResponsiveTableColumnDefinition from '../Data/IResponsiveTableColumnDefinition';
3
3
  import IFooterRowDefinition from '../Data/IFooterRowDefinition';
4
+ import { IResponsiveTablePlugin } from '../Plugins/IResponsiveTablePlugin';
4
5
  export type ColumnDefinition<TData> = IResponsiveTableColumnDefinition<TData> | ((data: TData, rowIndex?: number) => IResponsiveTableColumnDefinition<TData>);
5
6
  interface IProps<TData> {
6
7
  columnDefinitions: ColumnDefinition<TData>[];
@@ -10,14 +11,31 @@ interface IProps<TData> {
10
11
  onRowClick?: (item: TData) => void;
11
12
  footerRows?: IFooterRowDefinition[];
12
13
  mobileBreakpoint?: number;
13
- isLoading?: boolean;
14
- animateOnLoad?: boolean;
14
+ plugins?: IResponsiveTablePlugin<TData>[];
15
+ infiniteScrollProps?: {
16
+ enableInfiniteScroll?: boolean;
17
+ onLoadMore?: (currentData: TData[]) => Promise<TData[] | null>;
18
+ hasMore?: boolean;
19
+ loadingMoreComponent?: ReactNode;
20
+ noMoreDataComponent?: ReactNode;
21
+ };
22
+ filterProps?: {
23
+ showFilter?: boolean;
24
+ filterPlaceholder?: string;
25
+ };
26
+ animationProps?: {
27
+ isLoading?: boolean;
28
+ animateOnLoad?: boolean;
29
+ };
15
30
  }
16
- interface IState {
31
+ interface IState<TData> {
17
32
  isMobile: boolean;
33
+ processedData: TData[];
34
+ isLoadingMore: boolean;
18
35
  }
19
- declare class ResponsiveTable<TData> extends Component<IProps<TData>, IState> {
36
+ declare class ResponsiveTable<TData> extends Component<IProps<TData>, IState<TData>> {
20
37
  private debouncedResize;
38
+ private tableContainerRef;
21
39
  constructor(props: IProps<TData>);
22
40
  private get mobileBreakpoint();
23
41
  private debounce;
@@ -27,6 +45,9 @@ declare class ResponsiveTable<TData> extends Component<IProps<TData>, IState> {
27
45
  private get noDataComponent();
28
46
  componentDidMount(): void;
29
47
  componentWillUnmount(): void;
48
+ componentDidUpdate(prevProps: IProps<TData>): void;
49
+ private initializePlugins;
50
+ private processData;
30
51
  handleResize: () => void;
31
52
  private getColumnDefinition;
32
53
  private getRawColumnDefinition;
@@ -39,6 +60,8 @@ declare class ResponsiveTable<TData> extends Component<IProps<TData>, IState> {
39
60
  private get skeletonView();
40
61
  private get mobileView();
41
62
  private get largeScreenView();
42
- render(): React.ReactNode;
63
+ private renderPluginHeaders;
64
+ private renderPluginFooters;
65
+ render(): string | number | boolean | Iterable<React.ReactNode> | React.JSX.Element | null | undefined;
43
66
  }
44
67
  export default ResponsiveTable;
package/dist/index.d.ts CHANGED
@@ -2,5 +2,8 @@ import IFooterColumnDefinition from './Data/IFooterColumnDefinition';
2
2
  import IFooterRowDefinition from './Data/IFooterRowDefinition';
3
3
  import IResponsiveTableColumnDefinition from './Data/IResponsiveTableColumnDefinition';
4
4
  import ResponsiveTable, { ColumnDefinition } from './UI/ResponsiveTable';
5
- export { IResponsiveTableColumnDefinition, ColumnDefinition, IFooterColumnDefinition, IFooterRowDefinition };
5
+ import { FilterPlugin } from './Plugins/FilterPlugin';
6
+ import { InfiniteScrollPlugin } from './Plugins/InfiniteScrollPlugin';
7
+ import { IResponsiveTablePlugin } from './Plugins/IResponsiveTablePlugin';
8
+ export { IResponsiveTableColumnDefinition, ColumnDefinition, IFooterColumnDefinition, IFooterRowDefinition, FilterPlugin, InfiniteScrollPlugin, IResponsiveTablePlugin };
6
9
  export default ResponsiveTable;