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

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.
@@ -0,0 +1,71 @@
1
+ import React from 'react';
2
+ import { IResponsiveTablePlugin, IPluginAPI } from './IResponsiveTablePlugin';
3
+ import IResponsiveTableColumnDefinition from '../Data/IResponsiveTableColumnDefinition';
4
+
5
+ export class FilterPlugin<TData> implements IResponsiveTablePlugin<TData> {
6
+ public id = 'filter';
7
+ private filterText = '';
8
+ private api!: IPluginAPI<TData>;
9
+
10
+ constructor() {
11
+ }
12
+
13
+ public onPluginInit = (api: IPluginAPI<TData>) => {
14
+ this.api = api;
15
+ };
16
+
17
+ public renderHeader = () => {
18
+ if (!this.api.filterProps?.showFilter) {
19
+ return null;
20
+ }
21
+ return (
22
+ <div style={{ float: 'right', marginBottom: '1rem' }}>
23
+ <input
24
+ type="text"
25
+ placeholder={this.api.filterProps.filterPlaceholder || "Search..."}
26
+ onChange={this.handleFilterChange}
27
+ style={{
28
+ padding: '0.5rem',
29
+ border: '1px solid #ccc',
30
+ borderRadius: '4px',
31
+ }}
32
+ />
33
+ </div>
34
+ );
35
+ };
36
+
37
+ public processData = (data: TData[]): TData[] => {
38
+ if (!this.filterText || !this.api.columnDefinitions) {
39
+ return data;
40
+ }
41
+
42
+ const lowercasedFilter = this.filterText.toLowerCase();
43
+
44
+ return data.filter((row) => {
45
+ return this.api.columnDefinitions!.some((colDef) => {
46
+ // If colDef is a function, it won't have getFilterableValue, so skip it.
47
+ if (typeof colDef === 'function') {
48
+ return false;
49
+ }
50
+
51
+ // Now we know colDef is an object (IResponsiveTableColumnDefinition<TData>)
52
+ const typedColDef = colDef as IResponsiveTableColumnDefinition<TData>;
53
+
54
+ // Check if getFilterableValue exists and is a function
55
+ if (typedColDef.getFilterableValue && typeof typedColDef.getFilterableValue === 'function') {
56
+ const value = typedColDef.getFilterableValue(row);
57
+ return value?.toString().toLowerCase().includes(lowercasedFilter);
58
+ }
59
+ return false; // If getFilterableValue is not present or not a function
60
+ });
61
+ });
62
+ };
63
+
64
+ private handleFilterChange = (e: React.ChangeEvent<HTMLInputElement>) => {
65
+ // Debounce the filter change
66
+ setTimeout(() => {
67
+ this.filterText = e.target.value;
68
+ this.api.forceUpdate();
69
+ }, 300);
70
+ };
71
+ }
@@ -0,0 +1,48 @@
1
+ import { ReactNode } from 'react';
2
+ import { ColumnDefinition } from '../UI/ResponsiveTable';
3
+
4
+ export interface IResponsiveTablePlugin<TData> {
5
+ // A unique identifier for the plugin
6
+ id: string;
7
+
8
+ // Optional: Renders a UI component above the table
9
+ renderHeader?: () => ReactNode;
10
+
11
+ // Optional: Renders a UI component below the table
12
+ renderFooter?: () => ReactNode;
13
+
14
+ // Optional: Processes the data before it's rendered
15
+ processData?: (data: TData[]) => TData[];
16
+
17
+ // Optional: A callback that the table can use to provide the plugin with its own API
18
+ onPluginInit?: (api: IPluginAPI<TData>) => void;
19
+ }
20
+
21
+ export interface IPluginAPI<TData> {
22
+ // Function to get the current data from the table
23
+ getData: () => TData[];
24
+
25
+ // Function to force the table to re-render
26
+ forceUpdate: () => void;
27
+
28
+ // Function to get the column definitions from the table
29
+ columnDefinitions: ColumnDefinition<TData>[];
30
+
31
+ // Function to get the scrollable element of the table
32
+ getScrollableElement?: () => HTMLElement | null;
33
+
34
+ // Optional: Infinite scroll props from the ResponsiveTable component
35
+ infiniteScrollProps?: {
36
+ enableInfiniteScroll?: boolean;
37
+ onLoadMore?: (currentData: TData[]) => Promise<TData[] | null>;
38
+ hasMore?: boolean;
39
+ loadingMoreComponent?: ReactNode;
40
+ noMoreDataComponent?: ReactNode;
41
+ };
42
+
43
+ // Optional: Filter props from the ResponsiveTable component
44
+ filterProps?: {
45
+ showFilter?: boolean;
46
+ filterPlaceholder?: string;
47
+ };
48
+ }
@@ -0,0 +1,73 @@
1
+ import React from 'react';
2
+ import { IResponsiveTablePlugin, IPluginAPI } from './IResponsiveTablePlugin';
3
+
4
+ export class InfiniteScrollPlugin<TData> implements IResponsiveTablePlugin<TData> {
5
+ public id = 'infinite-scroll';
6
+ private api!: IPluginAPI<TData>;
7
+ private isLoadingMore = false;
8
+
9
+ constructor() {
10
+ }
11
+
12
+ public onPluginInit = (api: IPluginAPI<TData>) => {
13
+ this.api = api;
14
+ this.attachScrollListener();
15
+ };
16
+
17
+ private attachScrollListener = () => {
18
+ const scrollableElement = this.api.getScrollableElement?.();
19
+ if (scrollableElement) {
20
+ scrollableElement.addEventListener('scroll', this.handleScroll);
21
+ }
22
+ };
23
+
24
+ private handleScroll = async () => {
25
+ const scrollableElement = this.api.getScrollableElement?.();
26
+ if (!scrollableElement || !this.api.infiniteScrollProps?.enableInfiniteScroll) {
27
+ return;
28
+ }
29
+
30
+ const { scrollTop, scrollHeight, clientHeight } = scrollableElement;
31
+
32
+ const scrollThreshold = 200; // Load more data when 200px from the bottom
33
+
34
+ if (
35
+ scrollHeight - scrollTop - clientHeight < scrollThreshold &&
36
+ this.api.infiniteScrollProps.hasMore &&
37
+ !this.isLoadingMore
38
+ ) {
39
+ this.isLoadingMore = true;
40
+ this.api.forceUpdate(); // Trigger re-render to show loading component
41
+
42
+ const newData = await this.api.infiniteScrollProps.onLoadMore?.(this.api.getData());
43
+
44
+ if (newData) {
45
+ // The main component will handle appending data via processData
46
+ } else {
47
+ // No more data, update hasMore in parent if necessary
48
+ }
49
+
50
+ this.isLoadingMore = false;
51
+ this.api.forceUpdate(); // Trigger re-render to hide loading component
52
+ }
53
+ };
54
+
55
+ public processData = (data: TData[]): TData[] => {
56
+ // This plugin doesn't modify the data directly, but rather triggers loading more.
57
+ // The main component's data prop should be updated by the consumer of the table.
58
+ return data;
59
+ };
60
+
61
+ public renderFooter = () => {
62
+ if (!this.api.infiniteScrollProps) {
63
+ return null;
64
+ }
65
+
66
+ if (this.isLoadingMore) {
67
+ return this.api.infiniteScrollProps.loadingMoreComponent || <div>Loading more...</div>;
68
+ } else if (!this.api.infiniteScrollProps.hasMore) {
69
+ return this.api.infiniteScrollProps.noMoreDataComponent || <div>No more data.</div>;
70
+ }
71
+ return null;
72
+ };
73
+ }
@@ -1,5 +1,5 @@
1
1
  /* Using CSS variables for a more maintainable and themeable design */
2
- :root {
2
+ .responsiveTable {
3
3
  --table-border-color: #e0e0e0;
4
4
  --table-header-bg: #f8f9fa;
5
5
  --table-row-hover-bg: #e9ecef;
@@ -226,3 +226,28 @@
226
226
  font-weight: 500; /* Less aggressive than bold */
227
227
  font-size: 1rem;
228
228
  }
229
+
230
+ .row-exit {
231
+ opacity: 0;
232
+ transform: scaleY(0);
233
+ transition: transform 0.3s ease-out, opacity 0.3s ease-out;
234
+ }
235
+
236
+ .row-enter {
237
+ opacity: 0;
238
+ transform: translateY(20px);
239
+ transition: transform 0.5s ease-out, opacity 0.5s ease-out;
240
+ }
241
+
242
+ .row-flash {
243
+ animation: flash 0.5s ease-out;
244
+ }
245
+
246
+ @keyframes flash {
247
+ 0% {
248
+ background-color: var(--table-row-hover-bg);
249
+ }
250
+ 100% {
251
+ background-color: transparent;
252
+ }
253
+ }