@7span/react-list 0.0.6 → 1.0.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/dist/react-list.js +199 -198
- package/dist/react-list.umd.cjs +1 -1
- package/package.json +19 -10
- package/src/components/attributes.jsx +53 -0
- package/src/components/empty.jsx +22 -0
- package/src/components/error.jsx +27 -0
- package/src/components/go-to.jsx +58 -0
- package/src/components/initial-loader.jsx +25 -0
- package/src/components/items.jsx +50 -0
- package/src/components/list.jsx +361 -0
- package/src/components/load-more.jsx +39 -0
- package/src/components/loader.jsx +29 -0
- package/src/components/pagination.jsx +158 -0
- package/src/components/per-page.jsx +65 -0
- package/src/components/refresh.jsx +34 -0
- package/src/components/search.jsx +59 -0
- package/src/components/summary.jsx +51 -0
- package/src/components/utils.js +42 -0
- package/src/context/list-provider.jsx +55 -0
- package/src/index.js +15 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { memo, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useListContext } from "../context/list-provider";
|
|
3
|
+
|
|
4
|
+
export const ReactListSearch = memo(({ children, debounceTime = 500 }) => {
|
|
5
|
+
const { listState } = useListContext();
|
|
6
|
+
const { search, setSearch } = listState;
|
|
7
|
+
const [localSearch, setLocalSearch] = useState(search ?? "");
|
|
8
|
+
const debounceTimerRef = useRef(null);
|
|
9
|
+
|
|
10
|
+
// Sync local state with context when search prop changes
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (search !== localSearch) {
|
|
13
|
+
setLocalSearch(search ?? "");
|
|
14
|
+
}
|
|
15
|
+
}, [search]);
|
|
16
|
+
|
|
17
|
+
const handleChange = (value) => {
|
|
18
|
+
setLocalSearch(value);
|
|
19
|
+
|
|
20
|
+
// Clear any existing timer
|
|
21
|
+
if (debounceTimerRef.current) {
|
|
22
|
+
clearTimeout(debounceTimerRef.current);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Set a new timer
|
|
26
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
27
|
+
setSearch(value);
|
|
28
|
+
}, debounceTime);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Clean up timer on unmount
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
return () => {
|
|
34
|
+
if (debounceTimerRef.current) {
|
|
35
|
+
clearTimeout(debounceTimerRef.current);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
const scope = {
|
|
41
|
+
search: localSearch,
|
|
42
|
+
setSearch: handleChange,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="react-list-search">
|
|
47
|
+
{children ? (
|
|
48
|
+
children(scope)
|
|
49
|
+
) : (
|
|
50
|
+
<input
|
|
51
|
+
type="text"
|
|
52
|
+
value={localSearch}
|
|
53
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
54
|
+
placeholder="Search..."
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { memo, useMemo } from "react";
|
|
2
|
+
import { useListContext } from "../context/list-provider";
|
|
3
|
+
|
|
4
|
+
export const ReactListSummary = memo(({ children }) => {
|
|
5
|
+
const { listState } = useListContext();
|
|
6
|
+
const { data, count, pagination, loader, error } = listState;
|
|
7
|
+
const { page, perPage } = pagination;
|
|
8
|
+
const { initialLoading, isLoading } = loader;
|
|
9
|
+
|
|
10
|
+
const summaryData = useMemo(() => {
|
|
11
|
+
const from = page * perPage - perPage + 1;
|
|
12
|
+
const to = Math.min(page * perPage, count);
|
|
13
|
+
const visibleCount = data?.length || 0;
|
|
14
|
+
|
|
15
|
+
return { from, to, visibleCount };
|
|
16
|
+
}, [page, perPage, count, data]);
|
|
17
|
+
|
|
18
|
+
const scope = useMemo(
|
|
19
|
+
() => ({
|
|
20
|
+
...summaryData,
|
|
21
|
+
count,
|
|
22
|
+
}),
|
|
23
|
+
[summaryData, count]
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (initialLoading) return null;
|
|
27
|
+
|
|
28
|
+
if (!data || data.length === 0) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (error) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="react-list-summary">
|
|
38
|
+
{children ? (
|
|
39
|
+
children(scope)
|
|
40
|
+
) : (
|
|
41
|
+
<span>
|
|
42
|
+
Showing <span>{summaryData.visibleCount}</span> items (
|
|
43
|
+
<span>
|
|
44
|
+
{summaryData.from} - {summaryData.to}
|
|
45
|
+
</span>
|
|
46
|
+
) out of <span>{count}</span>
|
|
47
|
+
</span>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const deepEqual = (obj1, obj2) => {
|
|
2
|
+
if (obj1 === obj2) return true;
|
|
3
|
+
|
|
4
|
+
if (obj1 == null || obj2 == null) return obj1 === obj2;
|
|
5
|
+
|
|
6
|
+
if (typeof obj1 !== "object" || typeof obj2 !== "object")
|
|
7
|
+
return obj1 === obj2;
|
|
8
|
+
|
|
9
|
+
if (Array.isArray(obj1) && Array.isArray(obj2)) {
|
|
10
|
+
if (obj1.length !== obj2.length) return false;
|
|
11
|
+
for (let i = 0; i < obj1.length; i++) {
|
|
12
|
+
if (!deepEqual(obj1[i], obj2[i])) return false;
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (Array.isArray(obj1) || Array.isArray(obj2)) return false;
|
|
18
|
+
|
|
19
|
+
const keys1 = Object.keys(obj1).filter((key) => obj1[key] !== undefined);
|
|
20
|
+
const keys2 = Object.keys(obj2).filter((key) => obj2[key] !== undefined);
|
|
21
|
+
|
|
22
|
+
if (keys1.length !== keys2.length) return false;
|
|
23
|
+
|
|
24
|
+
for (let key of keys1) {
|
|
25
|
+
if (!keys2.includes(key)) return false;
|
|
26
|
+
if (!deepEqual(obj1[key], obj2[key])) return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const hasActiveFilters = (currentFilters, initialFilters) => {
|
|
33
|
+
if (!initialFilters || Object.keys(initialFilters).length === 0) {
|
|
34
|
+
return currentFilters && Object.keys(currentFilters).length > 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!currentFilters || Object.keys(currentFilters).length === 0) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return !deepEqual(currentFilters, initialFilters);
|
|
42
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const ListContext = createContext(null);
|
|
4
|
+
|
|
5
|
+
export const ReactListProvider = ({ children, config }) => {
|
|
6
|
+
const { requestHandler, stateManager = {} } = config;
|
|
7
|
+
const [listState, setListState] = useState({
|
|
8
|
+
data: [],
|
|
9
|
+
response: null,
|
|
10
|
+
error: null,
|
|
11
|
+
count: 0,
|
|
12
|
+
selection: [],
|
|
13
|
+
pagination: {
|
|
14
|
+
page: 1,
|
|
15
|
+
perPage: 25,
|
|
16
|
+
},
|
|
17
|
+
loader: {
|
|
18
|
+
isLoading: false,
|
|
19
|
+
initialLoading: true,
|
|
20
|
+
},
|
|
21
|
+
sort: {
|
|
22
|
+
sortBy: null,
|
|
23
|
+
sortOrder: "desc",
|
|
24
|
+
},
|
|
25
|
+
search: "",
|
|
26
|
+
filters: {},
|
|
27
|
+
attrs: [],
|
|
28
|
+
isEmpty: true,
|
|
29
|
+
isInitializing: true,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!requestHandler) {
|
|
33
|
+
throw new Error("ListProvider: requestHandler is required.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const value = useMemo(
|
|
37
|
+
() => ({
|
|
38
|
+
requestHandler,
|
|
39
|
+
stateManager,
|
|
40
|
+
listState,
|
|
41
|
+
setListState,
|
|
42
|
+
}),
|
|
43
|
+
[requestHandler, stateManager, listState]
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return <ListContext.Provider value={value}>{children}</ListContext.Provider>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const useListContext = () => {
|
|
50
|
+
const context = useContext(ListContext);
|
|
51
|
+
if (!context) {
|
|
52
|
+
throw new Error("useListContext must be used within a ListProvider");
|
|
53
|
+
}
|
|
54
|
+
return context;
|
|
55
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { ReactListAttributes } from "./components/attributes";
|
|
2
|
+
export { ReactListEmpty } from "./components/empty";
|
|
3
|
+
export { ReactListError } from "./components/error";
|
|
4
|
+
export { ReactListGoTo } from "./components/go-to";
|
|
5
|
+
export { ReactListInitialLoader } from "./components/initial-loader";
|
|
6
|
+
export { ReactListItems } from "./components/items";
|
|
7
|
+
export { default } from "./components/list";
|
|
8
|
+
export { ReactListLoadMore } from "./components/load-more";
|
|
9
|
+
export { ReactListLoader } from "./components/loader";
|
|
10
|
+
export { ReactListPagination } from "./components/pagination";
|
|
11
|
+
export { ReactListPerPage } from "./components/per-page";
|
|
12
|
+
export { ReactListRefresh } from "./components/refresh";
|
|
13
|
+
export { ReactListSearch } from "./components/search";
|
|
14
|
+
export { ReactListSummary } from "./components/summary";
|
|
15
|
+
export { ReactListProvider } from "./context/list-provider";
|