jattac.libs.web.responsive-table 0.2.0 → 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.
- package/README.md +580 -579
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/gemini/index.md +1 -0
- package/gemini/release_git_steps.md +80 -64
- package/package.json +1 -1
- package/src/Data/IResponsiveTableColumnDefinition.tsx +12 -12
- package/src/Plugins/FilterPlugin.tsx +71 -71
- package/src/Plugins/IResponsiveTablePlugin.ts +48 -48
- package/src/Plugins/InfiniteScrollPlugin.tsx +73 -73
- package/src/Styles/ResponsiveTable.module.css +1 -1
- package/src/UI/ResponsiveTable.tsx +25 -26
- package/src/index.tsx +10 -10
package/gemini/index.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Please inspect this directory recursively to understand the project in depth. Analyze its purpose, technology stack, core features, and overall structure. Provide a comprehensive explanation based on your findings. Start by listing all relevant files and then read the contents of key files like `package.json`, `README.md`, and any other configuration or source files that seem important.
|
|
@@ -1,64 +1,80 @@
|
|
|
1
|
-
## Release Git Steps
|
|
2
|
-
|
|
3
|
-
This document outlines the sequence of Git commands to perform at the end of every release cycle to ensure the `master` branch is up-to-date, tagged, and all changes are pushed to the remote repository.
|
|
4
|
-
|
|
5
|
-
**Prerequisites:**
|
|
6
|
-
- Ensure all development work for the release is complete and committed to the `develop` branch.
|
|
7
|
-
- Ensure you are on the `develop` branch (`git checkout develop`).
|
|
8
|
-
|
|
9
|
-
**Steps:**
|
|
10
|
-
|
|
11
|
-
1. **Stage all changes (if any uncommitted changes exist on `develop`):**
|
|
12
|
-
```bash
|
|
13
|
-
git add .
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
2. **Commit changes on `develop` (if not already done):**
|
|
17
|
-
```bash
|
|
18
|
-
git commit -m "feat(release):
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
git push origin
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
## Release Git Steps
|
|
2
|
+
|
|
3
|
+
This document outlines the sequence of Git commands to perform at the end of every release cycle to ensure the `master` branch is up-to-date, tagged, and all changes are pushed to the remote repository.
|
|
4
|
+
|
|
5
|
+
**Prerequisites:**
|
|
6
|
+
- Ensure all development work for the release is complete and committed to the `develop` branch.
|
|
7
|
+
- Ensure you are on the `develop` branch (`git checkout develop`).
|
|
8
|
+
|
|
9
|
+
**Steps:**
|
|
10
|
+
|
|
11
|
+
1. **Stage all changes (if any uncommitted changes exist on `develop`):**
|
|
12
|
+
```bash
|
|
13
|
+
git add .
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
2. **Commit changes on `develop` (if not already done):**
|
|
17
|
+
```bash
|
|
18
|
+
git commit -m "feat(release): Release vX.Y.Z
|
|
19
|
+
|
|
20
|
+
Summarize the key changes, new features, bug fixes, and any breaking changes
|
|
21
|
+
introduced in this release. This message should provide a high-level overview
|
|
22
|
+
of what's new since the last release.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
- Introduced extensible plugin system with Filter and Infinite Scroll plugins.
|
|
26
|
+
- Refactored animation props into a dedicated object.
|
|
27
|
+
- Added virtualized scrolling for large datasets.
|
|
28
|
+
- Updated dependencies."
|
|
29
|
+
# Or, if you have a multi-line message, use a temporary file:
|
|
30
|
+
# echo "Your detailed commit message" > commit_message.txt
|
|
31
|
+
# git commit -F commit_message.txt
|
|
32
|
+
```
|
|
33
|
+
*(Note: We assume the main feature/bugfix commits are already on `develop`.)*
|
|
34
|
+
|
|
35
|
+
3. **Switch to the `master` branch:**
|
|
36
|
+
```bash
|
|
37
|
+
git checkout master
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
4. **Merge `develop` into `master`:**
|
|
41
|
+
```bash
|
|
42
|
+
git merge develop
|
|
43
|
+
```
|
|
44
|
+
*This will bring all the latest changes from `develop` into `master`.*
|
|
45
|
+
|
|
46
|
+
5. **Get the current version from `package.json`:**
|
|
47
|
+
```bash
|
|
48
|
+
# Manually read the "version" field from package.json
|
|
49
|
+
# Example: "version": "1.0.0"
|
|
50
|
+
# Let's assume the version is X.Y.Z
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
6. **Create a new Git tag for the release:**
|
|
54
|
+
```bash
|
|
55
|
+
git tag vX.Y.Z
|
|
56
|
+
```
|
|
57
|
+
*Replace `X.Y.Z` with the actual version number obtained from `package.json`.*
|
|
58
|
+
|
|
59
|
+
7. **Push the `master` branch and all tags to the remote repository:**
|
|
60
|
+
```bash
|
|
61
|
+
git push origin master --tags
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
8. **Switch back to the `develop` branch:**
|
|
65
|
+
```bash
|
|
66
|
+
git checkout develop
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
9. **Push the `develop` branch to the remote repository (to ensure it's up-to-date after the merge):**
|
|
70
|
+
```bash
|
|
71
|
+
git push origin develop
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This sequence ensures a clean release process, updating `master`, creating a version tag, and synchronizing both branches with the remote.
|
|
75
|
+
|
|
76
|
+
10. **Delete temporary files:**
|
|
77
|
+
```bash
|
|
78
|
+
del commit_message.txt
|
|
79
|
+
```
|
|
80
|
+
*Ensure to delete any temporary files created during the release process.*
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
|
|
3
|
-
export default interface IResponsiveTableColumnDefinition<TData> {
|
|
4
|
-
displayLabel: ReactNode;
|
|
5
|
-
cellRenderer: (data: TData) => ReactNode;
|
|
6
|
-
interactivity?: {
|
|
7
|
-
id: string;
|
|
8
|
-
onHeaderClick?: (id: string) => void;
|
|
9
|
-
className?: string;
|
|
10
|
-
};
|
|
11
|
-
getFilterableValue?: (data: TData) => string | number;
|
|
12
|
-
}
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export default interface IResponsiveTableColumnDefinition<TData> {
|
|
4
|
+
displayLabel: ReactNode;
|
|
5
|
+
cellRenderer: (data: TData) => ReactNode;
|
|
6
|
+
interactivity?: {
|
|
7
|
+
id: string;
|
|
8
|
+
onHeaderClick?: (id: string) => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
getFilterableValue?: (data: TData) => string | number;
|
|
12
|
+
}
|
|
@@ -1,71 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,48 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,73 +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
|
+
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
|
+
}
|
|
@@ -10,30 +10,30 @@ import { FixedSizeList as List } from 'react-window';
|
|
|
10
10
|
export type ColumnDefinition<TData> =
|
|
11
11
|
| IResponsiveTableColumnDefinition<TData>
|
|
12
12
|
| ((data: TData, rowIndex?: number) => IResponsiveTableColumnDefinition<TData>);
|
|
13
|
-
interface IProps<TData> {
|
|
14
|
-
columnDefinitions: ColumnDefinition<TData>[];
|
|
15
|
-
data: TData[];
|
|
16
|
-
noDataComponent?: ReactNode;
|
|
17
|
-
maxHeight?: string;
|
|
18
|
-
onRowClick?: (item: TData) => void;
|
|
19
|
-
footerRows?: IFooterRowDefinition[];
|
|
20
|
-
mobileBreakpoint?: number;
|
|
21
|
-
plugins?: IResponsiveTablePlugin<TData>[];
|
|
22
|
-
infiniteScrollProps?: {
|
|
23
|
-
enableInfiniteScroll?: boolean;
|
|
24
|
-
onLoadMore?: (currentData: TData[]) => Promise<TData[] | null>;
|
|
25
|
-
hasMore?: boolean;
|
|
26
|
-
loadingMoreComponent?: ReactNode;
|
|
27
|
-
noMoreDataComponent?: ReactNode;
|
|
28
|
-
};
|
|
29
|
-
filterProps?: {
|
|
30
|
-
showFilter?: boolean;
|
|
31
|
-
filterPlaceholder?: string;
|
|
32
|
-
};
|
|
33
|
-
animationProps?: {
|
|
34
|
-
isLoading?: boolean;
|
|
35
|
-
animateOnLoad?: boolean;
|
|
36
|
-
};
|
|
13
|
+
interface IProps<TData> {
|
|
14
|
+
columnDefinitions: ColumnDefinition<TData>[];
|
|
15
|
+
data: TData[];
|
|
16
|
+
noDataComponent?: ReactNode;
|
|
17
|
+
maxHeight?: string;
|
|
18
|
+
onRowClick?: (item: TData) => void;
|
|
19
|
+
footerRows?: IFooterRowDefinition[];
|
|
20
|
+
mobileBreakpoint?: number;
|
|
21
|
+
plugins?: IResponsiveTablePlugin<TData>[];
|
|
22
|
+
infiniteScrollProps?: {
|
|
23
|
+
enableInfiniteScroll?: boolean;
|
|
24
|
+
onLoadMore?: (currentData: TData[]) => Promise<TData[] | null>;
|
|
25
|
+
hasMore?: boolean;
|
|
26
|
+
loadingMoreComponent?: ReactNode;
|
|
27
|
+
noMoreDataComponent?: ReactNode;
|
|
28
|
+
};
|
|
29
|
+
filterProps?: {
|
|
30
|
+
showFilter?: boolean;
|
|
31
|
+
filterPlaceholder?: string;
|
|
32
|
+
};
|
|
33
|
+
animationProps?: {
|
|
34
|
+
isLoading?: boolean;
|
|
35
|
+
animateOnLoad?: boolean;
|
|
36
|
+
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
interface IState<TData> {
|
|
@@ -426,7 +426,7 @@ class ResponsiveTable<TData> extends Component<IProps<TData>, IState<TData>> {
|
|
|
426
426
|
|
|
427
427
|
return (
|
|
428
428
|
<div style={fixedHeadersStyle} ref={this.tableContainerRef}>
|
|
429
|
-
<table className={styles['responsiveTable']}
|
|
429
|
+
<table className={styles['responsiveTable']}>
|
|
430
430
|
<thead>
|
|
431
431
|
<tr>
|
|
432
432
|
{this.props.columnDefinitions.map((columnDefinition, colIndex) => {
|
|
@@ -439,7 +439,6 @@ class ResponsiveTable<TData> extends Component<IProps<TData>, IState<TData>> {
|
|
|
439
439
|
<th
|
|
440
440
|
key={colIndex}
|
|
441
441
|
className={`${clickableHeaderClassName}`}
|
|
442
|
-
style={{ zIndex: 0 }}
|
|
443
442
|
onClick={
|
|
444
443
|
onHeaderClickCallback
|
|
445
444
|
? () => onHeaderClickCallback(this.getColumnDefinition(columnDefinition, 0).interactivity!.id)
|