@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.
Files changed (48) hide show
  1. package/dist/cjs/index.js +283 -113
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/esm/components/DataTable.d.ts +5 -2
  4. package/dist/esm/components/DataTable.d.ts.map +1 -1
  5. package/dist/esm/components/DataTable.js +48 -29
  6. package/dist/esm/components/DataTable.js.map +1 -1
  7. package/dist/esm/components/PaginatedCitationsGrid.d.ts.map +1 -1
  8. package/dist/esm/components/PaginatedCitationsGrid.js +8 -4
  9. package/dist/esm/components/PaginatedCitationsGrid.js.map +1 -1
  10. package/dist/esm/components/Pagination.d.ts +5 -4
  11. package/dist/esm/components/Pagination.d.ts.map +1 -1
  12. package/dist/esm/components/Pagination.js +35 -35
  13. package/dist/esm/components/Pagination.js.map +1 -1
  14. package/dist/esm/hooks/index.d.ts +3 -0
  15. package/dist/esm/hooks/index.d.ts.map +1 -0
  16. package/dist/esm/hooks/index.js +3 -0
  17. package/dist/esm/hooks/index.js.map +1 -0
  18. package/dist/esm/hooks/useHashParams.d.ts +18 -0
  19. package/dist/esm/hooks/useHashParams.d.ts.map +1 -0
  20. package/dist/esm/hooks/useHashParams.js +67 -0
  21. package/dist/esm/hooks/useHashParams.js.map +1 -0
  22. package/dist/esm/hooks/usePaginationFromUrl.d.ts +6 -0
  23. package/dist/esm/hooks/usePaginationFromUrl.d.ts.map +1 -0
  24. package/dist/esm/hooks/usePaginationFromUrl.js +23 -0
  25. package/dist/esm/hooks/usePaginationFromUrl.js.map +1 -0
  26. package/dist/esm/index.d.ts +1 -0
  27. package/dist/esm/index.d.ts.map +1 -1
  28. package/dist/esm/index.js +1 -0
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/stories/DataTable.stories.d.ts +2 -0
  31. package/dist/esm/stories/DataTable.stories.d.ts.map +1 -1
  32. package/dist/esm/stories/DataTable.stories.js +133 -106
  33. package/dist/esm/stories/DataTable.stories.js.map +1 -1
  34. package/dist/esm/stories/Pagination.stories.d.ts +1 -0
  35. package/dist/esm/stories/Pagination.stories.d.ts.map +1 -1
  36. package/dist/esm/stories/Pagination.stories.js +25 -3
  37. package/dist/esm/stories/Pagination.stories.js.map +1 -1
  38. package/dist/sparkle.css +4 -0
  39. package/package.json +1 -1
  40. package/src/components/DataTable.tsx +114 -75
  41. package/src/components/PaginatedCitationsGrid.tsx +11 -6
  42. package/src/components/Pagination.tsx +48 -45
  43. package/src/hooks/index.ts +2 -0
  44. package/src/hooks/useHashParams.ts +95 -0
  45. package/src/hooks/usePaginationFromUrl.ts +36 -0
  46. package/src/index.ts +1 -0
  47. package/src/stories/DataTable.stories.tsx +200 -130
  48. 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 [currentPage, setCurrentPage] = useState(1);
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 = (currentPage - 1) * maxItemsPerPage;
38
+ const startIndex = pageIndex * pageSize;
34
39
  // Slice items for current page.
35
- const paginatedItems = items.slice(startIndex, startIndex + maxItemsPerPage);
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
- itemsCount={items.length}
69
- maxItemsPerPage={maxItemsPerPage}
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
- const numPages = Math.ceil(itemsCount / maxItemsPerPage);
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 [currentPage, setCurrentPage] = useState(1);
33
- const firstFileOnPageIndex =
34
- currentPage * maxItemsPerPage - maxItemsPerPage + 1;
35
- const lastFileOnPageIndex =
36
- itemsCount > currentPage * maxItemsPerPage
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
- (pageNb: number) => {
42
- setCurrentPage(pageNb);
43
- onButtonClick(pageNb);
49
+ (pageIndex: number) => {
50
+ setPagination({ pageSize, pageIndex });
44
51
  },
45
- [onButtonClick, setCurrentPage]
52
+ [pageIndex, setPagination]
46
53
  );
47
54
 
48
55
  const pageButtons: React.ReactNode[] = getPageButtons(
49
- currentPage,
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={currentPage === 1 ? true : false}
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={currentPage === numPages ? true : false}
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
- ? `${itemsCount} items`
116
- : `Showing ${firstFileOnPageIndex}-${lastFileOnPageIndex} of ${itemsCount} items`}
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 = 1; i <= totalPages; 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(1, Math.min(currentPage, totalPages));
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 + 2) {
184
- start = 2;
185
- end = remainingSlots;
186
- } else if (currentPage >= totalPages - halfSlots - 1) {
187
- start = totalPages - remainingSlots + 1;
188
- end = totalPages - 1;
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 > 2) {
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 - 1) {
206
+ if (end < totalPages - 2) {
206
207
  pagination.push(renderEllipses(size));
207
208
  }
208
209
 
209
- pagination.push(renderPageNumber(totalPages, currentPage, onPageClick, size)); // Always show the last page
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,2 @@
1
+ export * from "./useHashParams";
2
+ export * from "./usePaginationFromUrl";
@@ -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
+ };
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./components";
2
2
  export { SparkleContext } from "./context";
3
+ export * from "./hooks";
3
4
  export * from "./icons";
4
5
  export * from "./lib";
5
6
  export * from "./logo";