@dust-tt/sparkle 0.2.225 → 0.2.226
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/cjs/index.js +283 -113
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/DataTable.d.ts +5 -2
- package/dist/esm/components/DataTable.d.ts.map +1 -1
- package/dist/esm/components/DataTable.js +48 -29
- package/dist/esm/components/DataTable.js.map +1 -1
- package/dist/esm/components/PaginatedCitationsGrid.d.ts.map +1 -1
- package/dist/esm/components/PaginatedCitationsGrid.js +8 -4
- package/dist/esm/components/PaginatedCitationsGrid.js.map +1 -1
- package/dist/esm/components/Pagination.d.ts +5 -4
- package/dist/esm/components/Pagination.d.ts.map +1 -1
- package/dist/esm/components/Pagination.js +35 -35
- package/dist/esm/components/Pagination.js.map +1 -1
- package/dist/esm/hooks/index.d.ts +3 -0
- package/dist/esm/hooks/index.d.ts.map +1 -0
- package/dist/esm/hooks/index.js +3 -0
- package/dist/esm/hooks/index.js.map +1 -0
- package/dist/esm/hooks/useHashParams.d.ts +18 -0
- package/dist/esm/hooks/useHashParams.d.ts.map +1 -0
- package/dist/esm/hooks/useHashParams.js +67 -0
- package/dist/esm/hooks/useHashParams.js.map +1 -0
- package/dist/esm/hooks/usePaginationFromUrl.d.ts +6 -0
- package/dist/esm/hooks/usePaginationFromUrl.d.ts.map +1 -0
- package/dist/esm/hooks/usePaginationFromUrl.js +23 -0
- package/dist/esm/hooks/usePaginationFromUrl.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/stories/DataTable.stories.d.ts +2 -0
- package/dist/esm/stories/DataTable.stories.d.ts.map +1 -1
- package/dist/esm/stories/DataTable.stories.js +133 -106
- package/dist/esm/stories/DataTable.stories.js.map +1 -1
- package/dist/esm/stories/Pagination.stories.d.ts +1 -0
- package/dist/esm/stories/Pagination.stories.d.ts.map +1 -1
- package/dist/esm/stories/Pagination.stories.js +25 -3
- package/dist/esm/stories/Pagination.stories.js.map +1 -1
- package/dist/sparkle.css +4 -0
- package/package.json +1 -1
- package/src/components/DataTable.tsx +114 -75
- package/src/components/PaginatedCitationsGrid.tsx +11 -6
- package/src/components/Pagination.tsx +48 -45
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useHashParams.ts +95 -0
- package/src/hooks/usePaginationFromUrl.ts +36 -0
- package/src/index.ts +1 -0
- package/src/stories/DataTable.stories.tsx +200 -130
- package/src/stories/Pagination.stories.tsx +38 -10
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PaginationState } from "@tanstack/react-table";
|
|
1
2
|
import { useState } from "react";
|
|
2
3
|
import React from "react";
|
|
3
4
|
|
|
@@ -21,7 +22,10 @@ export function PaginatedCitationsGrid({
|
|
|
21
22
|
items,
|
|
22
23
|
maxItemsPerPage = 9,
|
|
23
24
|
}: PaginatedCitationsGridProps) {
|
|
24
|
-
const [
|
|
25
|
+
const [pagination, setPagination] = useState<PaginationState>({
|
|
26
|
+
pageIndex: 0,
|
|
27
|
+
pageSize: maxItemsPerPage,
|
|
28
|
+
});
|
|
25
29
|
const cols = 3;
|
|
26
30
|
const rows = Math.ceil(Math.min(maxItemsPerPage, items.length) / cols);
|
|
27
31
|
|
|
@@ -29,10 +33,11 @@ export function PaginatedCitationsGrid({
|
|
|
29
33
|
return null;
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
const { pageIndex, pageSize } = pagination;
|
|
32
37
|
// Calculate start index.
|
|
33
|
-
const startIndex =
|
|
38
|
+
const startIndex = pageIndex * pageSize;
|
|
34
39
|
// Slice items for current page.
|
|
35
|
-
const paginatedItems = items.slice(startIndex, startIndex +
|
|
40
|
+
const paginatedItems = items.slice(startIndex, startIndex + pageSize);
|
|
36
41
|
|
|
37
42
|
return (
|
|
38
43
|
<div className="s-flex s-w-full s-flex-col">
|
|
@@ -65,12 +70,12 @@ export function PaginatedCitationsGrid({
|
|
|
65
70
|
)}
|
|
66
71
|
>
|
|
67
72
|
<Pagination
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
rowCount={items.length}
|
|
74
|
+
pagination={pagination}
|
|
75
|
+
setPagination={setPagination}
|
|
70
76
|
size="xs"
|
|
71
77
|
showDetails={false}
|
|
72
78
|
showPageButtons={false}
|
|
73
|
-
onButtonClick={(pageNb) => setCurrentPage(pageNb)}
|
|
74
79
|
/>
|
|
75
80
|
</div>
|
|
76
81
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { PaginationState } from "@tanstack/react-table";
|
|
1
2
|
import React, { useCallback } from "react";
|
|
2
|
-
import { useState } from "react";
|
|
3
3
|
|
|
4
4
|
import { ChevronLeftIcon, ChevronRightIcon } from "@sparkle/icons/solid";
|
|
5
5
|
import { classNames } from "@sparkle/lib/utils";
|
|
@@ -11,42 +11,49 @@ type Size = "sm" | "xs";
|
|
|
11
11
|
const pagesShownInControls = 7;
|
|
12
12
|
|
|
13
13
|
interface PaginationProps {
|
|
14
|
-
itemsCount: number;
|
|
15
|
-
maxItemsPerPage: number;
|
|
16
|
-
onButtonClick: (pageNumber: number) => void;
|
|
17
14
|
size?: Size;
|
|
18
15
|
showDetails?: boolean;
|
|
19
16
|
showPageButtons?: boolean;
|
|
17
|
+
rowCount: number;
|
|
18
|
+
pagination: PaginationState;
|
|
19
|
+
setPagination: (pagination: PaginationState) => void;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function Pagination({
|
|
23
|
-
itemsCount,
|
|
24
|
-
maxItemsPerPage,
|
|
25
|
-
onButtonClick,
|
|
26
23
|
size = "sm",
|
|
27
24
|
showDetails = true,
|
|
28
25
|
showPageButtons = true,
|
|
26
|
+
rowCount,
|
|
27
|
+
pagination,
|
|
28
|
+
setPagination,
|
|
29
29
|
}: PaginationProps) {
|
|
30
|
-
|
|
30
|
+
// pageIndex is 0-based
|
|
31
|
+
const { pageIndex, pageSize } = pagination;
|
|
32
|
+
|
|
33
|
+
const numPages = Math.ceil(rowCount / pageSize);
|
|
34
|
+
|
|
35
|
+
const canNextPage = pagination.pageIndex < numPages - 1;
|
|
36
|
+
const canPreviousPage = pageIndex > 0;
|
|
37
|
+
const nextPage = () => setPagination({ pageSize, pageIndex: pageIndex + 1 });
|
|
38
|
+
const previousPage = () =>
|
|
39
|
+
setPagination({ pageSize, pageIndex: pageIndex - 1 });
|
|
40
|
+
|
|
31
41
|
const controlsAreHidden = Boolean(numPages <= 1);
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
? currentPage * maxItemsPerPage
|
|
38
|
-
: itemsCount;
|
|
42
|
+
const firstItemOnPageIndex = pageIndex * pageSize + 1;
|
|
43
|
+
const lastItemOnPageIndex =
|
|
44
|
+
rowCount > (pageIndex + 1) * pageSize
|
|
45
|
+
? (pageIndex + 1) * pageSize
|
|
46
|
+
: rowCount;
|
|
39
47
|
|
|
40
48
|
const onPaginationButtonClick = useCallback(
|
|
41
|
-
(
|
|
42
|
-
|
|
43
|
-
onButtonClick(pageNb);
|
|
49
|
+
(pageIndex: number) => {
|
|
50
|
+
setPagination({ pageSize, pageIndex });
|
|
44
51
|
},
|
|
45
|
-
[
|
|
52
|
+
[pageIndex, setPagination]
|
|
46
53
|
);
|
|
47
54
|
|
|
48
55
|
const pageButtons: React.ReactNode[] = getPageButtons(
|
|
49
|
-
|
|
56
|
+
pageIndex,
|
|
50
57
|
numPages,
|
|
51
58
|
pagesShownInControls,
|
|
52
59
|
onPaginationButtonClick,
|
|
@@ -73,11 +80,9 @@ export function Pagination({
|
|
|
73
80
|
label="previous"
|
|
74
81
|
labelVisible={false}
|
|
75
82
|
disabledTooltip={true}
|
|
76
|
-
disabled={
|
|
83
|
+
disabled={!canPreviousPage}
|
|
77
84
|
icon={ChevronLeftIcon}
|
|
78
|
-
onClick={
|
|
79
|
-
onPaginationButtonClick(currentPage - 1);
|
|
80
|
-
}}
|
|
85
|
+
onClick={previousPage}
|
|
81
86
|
/>
|
|
82
87
|
|
|
83
88
|
<div
|
|
@@ -96,11 +101,9 @@ export function Pagination({
|
|
|
96
101
|
label="next"
|
|
97
102
|
labelVisible={false}
|
|
98
103
|
disabledTooltip={true}
|
|
99
|
-
disabled={
|
|
104
|
+
disabled={!canNextPage}
|
|
100
105
|
icon={ChevronRightIcon}
|
|
101
|
-
onClick={
|
|
102
|
-
onPaginationButtonClick(currentPage + 1);
|
|
103
|
-
}}
|
|
106
|
+
onClick={nextPage}
|
|
104
107
|
/>
|
|
105
108
|
</div>
|
|
106
109
|
|
|
@@ -112,8 +115,8 @@ export function Pagination({
|
|
|
112
115
|
)}
|
|
113
116
|
>
|
|
114
117
|
{controlsAreHidden
|
|
115
|
-
? `${
|
|
116
|
-
: `Showing ${
|
|
118
|
+
? `${rowCount} items`
|
|
119
|
+
: `Showing ${firstItemOnPageIndex}-${lastItemOnPageIndex} of ${rowCount} items`}
|
|
117
120
|
</span>
|
|
118
121
|
</div>
|
|
119
122
|
);
|
|
@@ -135,7 +138,7 @@ function renderPageNumber(
|
|
|
135
138
|
)}
|
|
136
139
|
onClick={() => onPageClick(pageNumber)}
|
|
137
140
|
>
|
|
138
|
-
{pageNumber}
|
|
141
|
+
{pageNumber + 1}
|
|
139
142
|
</button>
|
|
140
143
|
);
|
|
141
144
|
}
|
|
@@ -164,7 +167,7 @@ function getPageButtons(
|
|
|
164
167
|
|
|
165
168
|
// If total pages are less than or equal to slots, show all pages
|
|
166
169
|
if (totalPages <= slots) {
|
|
167
|
-
for (let i =
|
|
170
|
+
for (let i = 0; i < totalPages; i++) {
|
|
168
171
|
pagination.push(renderPageNumber(i, currentPage, onPageClick, size));
|
|
169
172
|
}
|
|
170
173
|
return pagination;
|
|
@@ -174,25 +177,23 @@ function getPageButtons(
|
|
|
174
177
|
const halfSlots = Math.floor(remainingSlots / 2);
|
|
175
178
|
|
|
176
179
|
// Ensure current page is within bounds
|
|
177
|
-
currentPage = Math.max(
|
|
178
|
-
|
|
179
|
-
pagination.push(renderPageNumber(1, currentPage, onPageClick, size)); // Always show the first page
|
|
180
|
+
currentPage = Math.max(0, Math.min(currentPage, totalPages - 1));
|
|
180
181
|
|
|
182
|
+
pagination.push(renderPageNumber(0, currentPage, onPageClick, size)); // Always show the first page
|
|
181
183
|
// Determine the range of pages to display
|
|
182
184
|
let start, end;
|
|
183
|
-
if (currentPage <= halfSlots +
|
|
184
|
-
start =
|
|
185
|
-
end = remainingSlots;
|
|
186
|
-
} else if (currentPage >= totalPages - halfSlots -
|
|
187
|
-
start = totalPages - remainingSlots
|
|
188
|
-
end = totalPages -
|
|
185
|
+
if (currentPage <= halfSlots + 1) {
|
|
186
|
+
start = 1;
|
|
187
|
+
end = remainingSlots - 1;
|
|
188
|
+
} else if (currentPage >= totalPages - halfSlots - 2) {
|
|
189
|
+
start = totalPages - remainingSlots;
|
|
190
|
+
end = totalPages - 2;
|
|
189
191
|
} else {
|
|
190
192
|
start = currentPage - halfSlots + 1;
|
|
191
193
|
end = currentPage + halfSlots - 1;
|
|
192
194
|
}
|
|
193
|
-
|
|
194
195
|
// Add ellipsis if there is a gap between the first page and the start of the range
|
|
195
|
-
if (start >
|
|
196
|
+
if (start > 1) {
|
|
196
197
|
pagination.push(renderEllipses(size));
|
|
197
198
|
}
|
|
198
199
|
|
|
@@ -202,11 +203,13 @@ function getPageButtons(
|
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
// Add ellipsis if there is a gap between the end of the range and the last page
|
|
205
|
-
if (end < totalPages -
|
|
206
|
+
if (end < totalPages - 2) {
|
|
206
207
|
pagination.push(renderEllipses(size));
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
pagination.push(
|
|
210
|
+
pagination.push(
|
|
211
|
+
renderPageNumber(totalPages - 1, currentPage, onPageClick, size)
|
|
212
|
+
); // Always show the last page
|
|
210
213
|
|
|
211
214
|
return pagination;
|
|
212
215
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Original code from : https://www.npmjs.com/package/use-hash-param
|
|
3
|
+
* ISC License
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useEffect, useState } from "react";
|
|
7
|
+
|
|
8
|
+
const getHashSearchParams = (location: Location): [string, URLSearchParams] => {
|
|
9
|
+
const hash = location.hash.slice(1);
|
|
10
|
+
const [prefix, query] = hash.split("?");
|
|
11
|
+
|
|
12
|
+
return [prefix, new URLSearchParams(query)];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getHashParam = (key: string, defaultValue?: string) => {
|
|
16
|
+
if (typeof window === "undefined") {
|
|
17
|
+
return defaultValue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const [, searchParams] = getHashSearchParams(window.location);
|
|
21
|
+
|
|
22
|
+
return searchParams.get(key) ?? defaultValue;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const setHashParam = (
|
|
26
|
+
key: string,
|
|
27
|
+
value: string | undefined,
|
|
28
|
+
shouldReplaceState: boolean
|
|
29
|
+
) => {
|
|
30
|
+
if (typeof window !== "undefined") {
|
|
31
|
+
const [prefix, searchParams] = getHashSearchParams(window.location);
|
|
32
|
+
|
|
33
|
+
if (typeof value === "undefined" || value === "") {
|
|
34
|
+
searchParams.delete(key);
|
|
35
|
+
} else {
|
|
36
|
+
searchParams.set(key, value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const search = searchParams.toString();
|
|
40
|
+
const hash = search ? `${prefix}?${search}` : prefix;
|
|
41
|
+
if (shouldReplaceState && "replaceState" in history) {
|
|
42
|
+
history.replaceState(null, "", `#${hash}`);
|
|
43
|
+
} else {
|
|
44
|
+
window.location.hash = hash;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type Updater = (prevValue?: string) => string;
|
|
50
|
+
type SetterOptions = {
|
|
51
|
+
history: "replace" | "push";
|
|
52
|
+
};
|
|
53
|
+
type Setter = (value?: string | Updater, options?: SetterOptions) => void;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param key The parameter-name to use from the hash-string query string.
|
|
57
|
+
* @param defaultValue A default value to use if the parameter is not specified and on the server.
|
|
58
|
+
* @returns A two-tuple, the first element is the selected param value (either extracted from the hash param or the default value).
|
|
59
|
+
* The second element is a setter function to change the param value.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
export const useHashParam = (
|
|
63
|
+
key: string,
|
|
64
|
+
defaultValue?: string
|
|
65
|
+
): [string | undefined, Setter] => {
|
|
66
|
+
const [innerValue, setInnerValue] = useState<string | undefined>(() =>
|
|
67
|
+
getHashParam(key, defaultValue)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const handleHashChange = () =>
|
|
72
|
+
setInnerValue(getHashParam(key, defaultValue));
|
|
73
|
+
handleHashChange();
|
|
74
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
75
|
+
return () => window.removeEventListener("hashchange", handleHashChange);
|
|
76
|
+
}, [key]);
|
|
77
|
+
|
|
78
|
+
const setValue = useCallback<Setter>(
|
|
79
|
+
(
|
|
80
|
+
newValue?: string | Updater,
|
|
81
|
+
options: SetterOptions = { history: "replace" }
|
|
82
|
+
) => {
|
|
83
|
+
const newInnerValue =
|
|
84
|
+
typeof newValue === "function"
|
|
85
|
+
? newValue(getHashParam(key, defaultValue))
|
|
86
|
+
: newValue;
|
|
87
|
+
|
|
88
|
+
setInnerValue(newInnerValue);
|
|
89
|
+
setHashParam(key, newInnerValue, options.history === "replace");
|
|
90
|
+
},
|
|
91
|
+
[key]
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return [innerValue || defaultValue, setValue];
|
|
95
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PaginationState } from "@tanstack/react-table";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
|
|
4
|
+
import { useHashParam } from "@sparkle/hooks/useHashParams";
|
|
5
|
+
|
|
6
|
+
const defaultPageSize = 25;
|
|
7
|
+
|
|
8
|
+
export const usePaginationFromUrl = (
|
|
9
|
+
urlPrefix?: string,
|
|
10
|
+
initialPageSize = defaultPageSize
|
|
11
|
+
) => {
|
|
12
|
+
const [pageIndexParam, setPageIndexParam] = useHashParam(
|
|
13
|
+
urlPrefix ? `${urlPrefix}PageIndex` : "pageIndex"
|
|
14
|
+
);
|
|
15
|
+
const [pageSizeParam, setPageSizeParam] = useHashParam(
|
|
16
|
+
urlPrefix ? `${urlPrefix}PageSize` : "pageSize"
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const pageIndex = pageIndexParam ? parseInt(pageIndexParam) : 0;
|
|
20
|
+
const pageSize = pageSizeParam ? parseInt(pageSizeParam) : initialPageSize;
|
|
21
|
+
|
|
22
|
+
const res = useMemo(() => {
|
|
23
|
+
const pagination: PaginationState = { pageIndex, pageSize };
|
|
24
|
+
|
|
25
|
+
const setPagination = (newValue: PaginationState) => {
|
|
26
|
+
if (newValue.pageIndex !== pageIndex || newValue.pageSize !== pageSize) {
|
|
27
|
+
setPageIndexParam(newValue.pageIndex.toString());
|
|
28
|
+
setPageSizeParam(newValue.pageSize.toString());
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return { pagination, setPagination };
|
|
33
|
+
}, [pageIndex, pageSize, urlPrefix]);
|
|
34
|
+
|
|
35
|
+
return res;
|
|
36
|
+
};
|