@dust-tt/sparkle 0.2.224 → 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 +293 -153
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/Breadcrumbs.d.ts +7 -6
- package/dist/esm/components/Breadcrumbs.d.ts.map +1 -1
- package/dist/esm/components/Breadcrumbs.js +3 -2
- package/dist/esm/components/Breadcrumbs.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 +50 -33
- package/dist/esm/components/DataTable.js.map +1 -1
- package/dist/esm/components/DropdownMenu.d.ts +10 -10
- package/dist/esm/components/DropdownMenu.d.ts.map +1 -1
- package/dist/esm/components/DropdownMenu.js +7 -7
- package/dist/esm/components/DropdownMenu.js.map +1 -1
- package/dist/esm/components/Item.d.ts +18 -15
- package/dist/esm/components/Item.d.ts.map +1 -1
- package/dist/esm/components/Item.js +2 -1
- package/dist/esm/components/Item.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 -105
- package/dist/esm/stories/DataTable.stories.js.map +1 -1
- package/dist/esm/stories/DropdownMenu.stories.d.ts.map +1 -1
- package/dist/esm/stories/DropdownMenu.stories.js +32 -31
- package/dist/esm/stories/DropdownMenu.stories.js.map +1 -1
- package/dist/esm/stories/Item.stories.d.ts.map +1 -1
- package/dist/esm/stories/Item.stories.js +8 -6
- package/dist/esm/stories/Item.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/Breadcrumbs.tsx +11 -10
- package/src/components/DataTable.tsx +116 -81
- package/src/components/DropdownMenu.tsx +13 -13
- package/src/components/Item.tsx +20 -16
- 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 -129
- package/src/stories/DropdownMenu.stories.tsx +43 -31
- package/src/stories/Item.stories.tsx +26 -6
- package/src/stories/Pagination.stories.tsx +38 -10
|
@@ -4,9 +4,12 @@ import {
|
|
|
4
4
|
flexRender,
|
|
5
5
|
getCoreRowModel,
|
|
6
6
|
getFilteredRowModel,
|
|
7
|
+
getPaginationRowModel,
|
|
7
8
|
getSortedRowModel,
|
|
9
|
+
PaginationState,
|
|
8
10
|
SortingFn,
|
|
9
11
|
type SortingState,
|
|
12
|
+
Updater,
|
|
10
13
|
useReactTable,
|
|
11
14
|
} from "@tanstack/react-table";
|
|
12
15
|
import React, { ReactNode, useEffect, useState } from "react";
|
|
@@ -17,8 +20,9 @@ import {
|
|
|
17
20
|
DropdownMenu,
|
|
18
21
|
} from "@sparkle/components/DropdownMenu";
|
|
19
22
|
import { IconButton } from "@sparkle/components/IconButton";
|
|
23
|
+
import { Pagination } from "@sparkle/components/Pagination";
|
|
24
|
+
import { Tooltip } from "@sparkle/components/Tooltip";
|
|
20
25
|
import { ArrowDownIcon, ArrowUpIcon, MoreIcon } from "@sparkle/icons";
|
|
21
|
-
import { Tooltip } from "@sparkle/index_with_tw_base";
|
|
22
26
|
import { classNames } from "@sparkle/lib/utils";
|
|
23
27
|
|
|
24
28
|
import { Icon } from "./Icon";
|
|
@@ -35,10 +39,13 @@ interface ColumnBreakpoint {
|
|
|
35
39
|
|
|
36
40
|
interface DataTableProps<TData extends TBaseData> {
|
|
37
41
|
data: TData[];
|
|
42
|
+
totalRowCount?: number;
|
|
38
43
|
columns: ColumnDef<TData, any>[]; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
39
44
|
className?: string;
|
|
40
45
|
filter?: string;
|
|
41
46
|
filterColumn?: string;
|
|
47
|
+
pagination?: PaginationState;
|
|
48
|
+
setPagination?: (pagination: PaginationState) => void;
|
|
42
49
|
initialColumnOrder?: SortingState;
|
|
43
50
|
columnsBreakpoints?: ColumnBreakpoint;
|
|
44
51
|
sortingFn?: SortingFn<TData>;
|
|
@@ -56,12 +63,15 @@ function shouldRenderColumn(
|
|
|
56
63
|
|
|
57
64
|
export function DataTable<TData extends TBaseData>({
|
|
58
65
|
data,
|
|
66
|
+
totalRowCount,
|
|
59
67
|
columns,
|
|
60
68
|
className,
|
|
61
69
|
filter,
|
|
62
70
|
filterColumn,
|
|
63
71
|
initialColumnOrder,
|
|
64
72
|
columnsBreakpoints = {},
|
|
73
|
+
pagination,
|
|
74
|
+
setPagination,
|
|
65
75
|
}: DataTableProps<TData>) {
|
|
66
76
|
const windowSize = useWindowSize();
|
|
67
77
|
const [sorting, setSorting] = useState<SortingState>(
|
|
@@ -69,18 +79,36 @@ export function DataTable<TData extends TBaseData>({
|
|
|
69
79
|
);
|
|
70
80
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
|
71
81
|
|
|
82
|
+
const isServerSidePagination = !!totalRowCount && totalRowCount > data.length;
|
|
83
|
+
const onPaginationChange =
|
|
84
|
+
pagination && setPagination
|
|
85
|
+
? (updater: Updater<PaginationState>) => {
|
|
86
|
+
const newValue =
|
|
87
|
+
typeof updater === "function" ? updater(pagination) : updater;
|
|
88
|
+
setPagination(newValue);
|
|
89
|
+
}
|
|
90
|
+
: undefined;
|
|
91
|
+
|
|
72
92
|
const table = useReactTable({
|
|
73
93
|
data,
|
|
74
94
|
columns,
|
|
95
|
+
rowCount: totalRowCount,
|
|
96
|
+
manualPagination: isServerSidePagination,
|
|
75
97
|
onSortingChange: setSorting,
|
|
76
98
|
getCoreRowModel: getCoreRowModel(),
|
|
77
99
|
getSortedRowModel: getSortedRowModel(),
|
|
78
100
|
getFilteredRowModel: getFilteredRowModel(),
|
|
101
|
+
getPaginationRowModel: pagination ? getPaginationRowModel() : undefined,
|
|
79
102
|
onColumnFiltersChange: setColumnFilters,
|
|
80
103
|
state: {
|
|
81
104
|
columnFilters,
|
|
82
|
-
sorting,
|
|
105
|
+
sorting: isServerSidePagination ? undefined : sorting,
|
|
106
|
+
pagination,
|
|
107
|
+
},
|
|
108
|
+
initialState: {
|
|
109
|
+
pagination,
|
|
83
110
|
},
|
|
111
|
+
onPaginationChange: onPaginationChange,
|
|
84
112
|
});
|
|
85
113
|
|
|
86
114
|
useEffect(() => {
|
|
@@ -90,81 +118,92 @@ export function DataTable<TData extends TBaseData>({
|
|
|
90
118
|
}, [filter, filterColumn]);
|
|
91
119
|
|
|
92
120
|
return (
|
|
93
|
-
<
|
|
94
|
-
<DataTable.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{headerGroup.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
>
|
|
113
|
-
<div className="s-flex s-items-center s-space-x-1 s-whitespace-nowrap">
|
|
114
|
-
{flexRender(
|
|
115
|
-
header.column.columnDef.header,
|
|
116
|
-
header.getContext()
|
|
121
|
+
<div className="s-flex s-flex-col s-gap-2">
|
|
122
|
+
<DataTable.Root className={className}>
|
|
123
|
+
<DataTable.Header>
|
|
124
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
125
|
+
<DataTable.Row key={headerGroup.id}>
|
|
126
|
+
{headerGroup.headers.map((header) => {
|
|
127
|
+
const breakpoint = columnsBreakpoints[header.id];
|
|
128
|
+
if (
|
|
129
|
+
!windowSize.width ||
|
|
130
|
+
!shouldRenderColumn(windowSize.width, breakpoint)
|
|
131
|
+
) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return (
|
|
135
|
+
<DataTable.Head
|
|
136
|
+
key={header.id}
|
|
137
|
+
onClick={header.column.getToggleSortingHandler()}
|
|
138
|
+
className={classNames(
|
|
139
|
+
header.column.getCanSort() ? "s-cursor-pointer" : ""
|
|
117
140
|
)}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
141
|
+
>
|
|
142
|
+
<div className="s-flex s-items-center s-space-x-1 s-whitespace-nowrap">
|
|
143
|
+
{flexRender(
|
|
144
|
+
header.column.columnDef.header,
|
|
145
|
+
header.getContext()
|
|
146
|
+
)}
|
|
147
|
+
{header.column.getCanSort() && (
|
|
148
|
+
<Icon
|
|
149
|
+
visual={
|
|
150
|
+
header.column.getIsSorted() === "asc"
|
|
151
|
+
? ArrowUpIcon
|
|
152
|
+
: header.column.getIsSorted() === "desc"
|
|
153
|
+
? ArrowDownIcon
|
|
154
|
+
: ArrowDownIcon
|
|
155
|
+
}
|
|
156
|
+
size="xs"
|
|
157
|
+
className={classNames(
|
|
158
|
+
"s-ml-1",
|
|
159
|
+
header.column.getIsSorted()
|
|
160
|
+
? "s-opacity-100"
|
|
161
|
+
: "s-opacity-0"
|
|
162
|
+
)}
|
|
163
|
+
/>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
</DataTable.Head>
|
|
167
|
+
);
|
|
168
|
+
})}
|
|
169
|
+
</DataTable.Row>
|
|
170
|
+
))}
|
|
171
|
+
</DataTable.Header>
|
|
172
|
+
<DataTable.Body>
|
|
173
|
+
{table.getRowModel().rows.map((row) => (
|
|
174
|
+
<DataTable.Row
|
|
175
|
+
key={row.id}
|
|
176
|
+
onClick={row.original.onClick}
|
|
177
|
+
moreMenuItems={row.original.moreMenuItems}
|
|
178
|
+
>
|
|
179
|
+
{row.getVisibleCells().map((cell) => {
|
|
180
|
+
const breakpoint = columnsBreakpoints[cell.column.id];
|
|
181
|
+
if (
|
|
182
|
+
!windowSize.width ||
|
|
183
|
+
!shouldRenderColumn(windowSize.width, breakpoint)
|
|
184
|
+
) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
return (
|
|
188
|
+
<DataTable.Cell key={cell.id}>
|
|
189
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
190
|
+
</DataTable.Cell>
|
|
191
|
+
);
|
|
192
|
+
})}
|
|
193
|
+
</DataTable.Row>
|
|
194
|
+
))}
|
|
195
|
+
</DataTable.Body>
|
|
196
|
+
</DataTable.Root>
|
|
197
|
+
{pagination && (
|
|
198
|
+
<div className="s-p-1">
|
|
199
|
+
<Pagination
|
|
200
|
+
pagination={table.getState().pagination}
|
|
201
|
+
setPagination={table.setPagination}
|
|
202
|
+
rowCount={table.getRowCount()}
|
|
203
|
+
/>
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
168
207
|
);
|
|
169
208
|
}
|
|
170
209
|
|
|
@@ -263,11 +302,7 @@ DataTable.Row = function Row({
|
|
|
263
302
|
<td className="s-w-1 s-cursor-pointer s-pl-1 s-text-element-600">
|
|
264
303
|
{moreMenuItems && moreMenuItems.length > 0 && (
|
|
265
304
|
<DropdownMenu className="s-mr-1.5 s-flex">
|
|
266
|
-
<DropdownMenu.Button
|
|
267
|
-
onClick={(e) => {
|
|
268
|
-
e.stopPropagation();
|
|
269
|
-
}}
|
|
270
|
-
>
|
|
305
|
+
<DropdownMenu.Button>
|
|
271
306
|
<IconButton icon={MoreIcon} size="sm" variant="tertiary" />
|
|
272
307
|
</DropdownMenu.Button>
|
|
273
308
|
<DropdownMenu.Items origin="topRight" width={220}>
|
|
@@ -4,7 +4,6 @@ import React, {
|
|
|
4
4
|
Fragment,
|
|
5
5
|
JSXElementConstructor,
|
|
6
6
|
MouseEvent,
|
|
7
|
-
MouseEventHandler,
|
|
8
7
|
MutableRefObject,
|
|
9
8
|
ReactElement,
|
|
10
9
|
useContext,
|
|
@@ -21,7 +20,7 @@ import {
|
|
|
21
20
|
import { classNames } from "@sparkle/lib/utils";
|
|
22
21
|
|
|
23
22
|
import { Icon } from "./Icon";
|
|
24
|
-
import { Item as StandardItem } from "./Item";
|
|
23
|
+
import { Item as StandardItem, LinkProps } from "./Item";
|
|
25
24
|
import { Tooltip, TooltipProps } from "./Tooltip";
|
|
26
25
|
|
|
27
26
|
const ButtonRefContext =
|
|
@@ -103,7 +102,6 @@ export interface DropdownButtonProps {
|
|
|
103
102
|
className?: string;
|
|
104
103
|
disabled?: boolean;
|
|
105
104
|
children?: React.ReactNode;
|
|
106
|
-
onClick?: MouseEventHandler;
|
|
107
105
|
}
|
|
108
106
|
|
|
109
107
|
DropdownMenu.Button = function ({
|
|
@@ -116,7 +114,6 @@ DropdownMenu.Button = function ({
|
|
|
116
114
|
tooltipPosition = "above",
|
|
117
115
|
className = "",
|
|
118
116
|
disabled = false,
|
|
119
|
-
onClick,
|
|
120
117
|
}: DropdownButtonProps) {
|
|
121
118
|
const finalLabelClasses = classNames(
|
|
122
119
|
labelClasses.base,
|
|
@@ -159,7 +156,7 @@ DropdownMenu.Button = function ({
|
|
|
159
156
|
"s-group/dm s-flex s-justify-items-center s-text-sm s-font-medium focus:s-outline-none focus:s-ring-0",
|
|
160
157
|
label ? "s-gap-1.5" : "s-gap-0"
|
|
161
158
|
)}
|
|
162
|
-
onClick={
|
|
159
|
+
onClick={(e) => e.stopPropagation()}
|
|
163
160
|
>
|
|
164
161
|
{tooltip ? (
|
|
165
162
|
<Tooltip position={tooltipPosition} label={tooltip}>
|
|
@@ -192,6 +189,7 @@ DropdownMenu.Button = function ({
|
|
|
192
189
|
"s-group/dm s-flex s-justify-items-center s-text-sm s-font-medium focus:s-outline-none focus:s-ring-0",
|
|
193
190
|
label ? (size === "md" ? "s-gap-2" : "s-gap-1.5") : "s-gap-0.5"
|
|
194
191
|
)}
|
|
192
|
+
onClick={(e) => e.stopPropagation()}
|
|
195
193
|
>
|
|
196
194
|
<Icon visual={icon} size={size} className={finalIconClasses} />
|
|
197
195
|
<Icon
|
|
@@ -212,6 +210,7 @@ DropdownMenu.Button = function ({
|
|
|
212
210
|
label ? (size === "md" ? "s-gap-2" : "s-gap-1.5") : "s-gap-0.5",
|
|
213
211
|
type === "submenu" ? "s-opacity-50" : ""
|
|
214
212
|
)}
|
|
213
|
+
onClick={(e) => e.stopPropagation()}
|
|
215
214
|
>
|
|
216
215
|
<Icon visual={icon} size={size} className={finalIconClasses} />
|
|
217
216
|
<span
|
|
@@ -234,24 +233,24 @@ DropdownMenu.Button = function ({
|
|
|
234
233
|
};
|
|
235
234
|
|
|
236
235
|
export interface DropdownItemProps {
|
|
237
|
-
|
|
238
|
-
label: string;
|
|
236
|
+
children?: React.ReactNode;
|
|
239
237
|
description?: string;
|
|
240
|
-
href?: string;
|
|
241
238
|
disabled?: boolean;
|
|
242
|
-
|
|
239
|
+
hasChildren?: boolean;
|
|
243
240
|
icon?: ComponentType;
|
|
241
|
+
label: string;
|
|
242
|
+
link?: LinkProps;
|
|
244
243
|
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
245
244
|
selected?: boolean;
|
|
246
|
-
|
|
247
|
-
|
|
245
|
+
variant?: "default" | "warning";
|
|
246
|
+
visual?: string | React.ReactNode;
|
|
248
247
|
}
|
|
249
248
|
|
|
250
249
|
DropdownMenu.Item = function ({
|
|
251
250
|
variant = "default",
|
|
252
251
|
label,
|
|
253
252
|
description,
|
|
254
|
-
|
|
253
|
+
link,
|
|
255
254
|
disabled,
|
|
256
255
|
visual,
|
|
257
256
|
icon,
|
|
@@ -278,7 +277,7 @@ DropdownMenu.Item = function ({
|
|
|
278
277
|
className="s-w-full"
|
|
279
278
|
description={description}
|
|
280
279
|
disabled={disabled}
|
|
281
|
-
|
|
280
|
+
link={link}
|
|
282
281
|
icon={icon}
|
|
283
282
|
label={label}
|
|
284
283
|
onClick={onClick}
|
|
@@ -443,6 +442,7 @@ DropdownMenu.Items = function ({
|
|
|
443
442
|
getOriginClass(origin),
|
|
444
443
|
"s-rounded-xl s-border s-border-structure-100 s-bg-structure-0 s-shadow-lg focus:s-outline-none dark:s-border-structure-100-dark dark:s-bg-structure-0-dark"
|
|
445
444
|
)}
|
|
445
|
+
onClick={(e) => e.stopPropagation()}
|
|
446
446
|
style={styleInsert(origin, marginLeft)}
|
|
447
447
|
>
|
|
448
448
|
{topBar}
|
package/src/components/Item.tsx
CHANGED
|
@@ -42,23 +42,27 @@ const iconClasses = {
|
|
|
42
42
|
"s-text-warning-400 group-hover/item:s-text-warning-300 group-active/item:s-text-warning-700 dark:group-hover/item:s-text-warning-600-dark dark:group-active/item:s-text-warning-400-dark",
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
export interface LinkProps {
|
|
46
|
+
href: string;
|
|
47
|
+
target?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface ItemProps {
|
|
51
|
+
action?: ComponentType;
|
|
52
|
+
className?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
disabled?: boolean;
|
|
55
|
+
hasAction?: boolean | "hover";
|
|
56
|
+
icon?: ComponentType;
|
|
57
|
+
label: string;
|
|
58
|
+
link?: LinkProps;
|
|
46
59
|
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
47
|
-
style: "action" | "warning" | "item" | "link";
|
|
48
|
-
spacing?: "sm" | "md" | "lg";
|
|
49
60
|
selectable?: boolean;
|
|
50
61
|
selected?: boolean;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
description?: string;
|
|
62
|
+
spacing?: "sm" | "md" | "lg";
|
|
63
|
+
style: "action" | "warning" | "item" | "link";
|
|
54
64
|
visual?: string | React.ReactNode;
|
|
55
|
-
|
|
56
|
-
action?: ComponentType;
|
|
57
|
-
hasAction?: boolean | "hover";
|
|
58
|
-
className?: string;
|
|
59
|
-
href?: string;
|
|
60
|
-
target?: string;
|
|
61
|
-
};
|
|
65
|
+
}
|
|
62
66
|
|
|
63
67
|
export function Item({
|
|
64
68
|
label,
|
|
@@ -73,11 +77,11 @@ export function Item({
|
|
|
73
77
|
selected = false,
|
|
74
78
|
disabled = false,
|
|
75
79
|
className = "",
|
|
76
|
-
|
|
77
|
-
target,
|
|
80
|
+
link,
|
|
78
81
|
}: ItemProps) {
|
|
79
82
|
const { components } = React.useContext(SparkleContext);
|
|
80
83
|
|
|
84
|
+
const { href, target } = link || {};
|
|
81
85
|
const Link: SparkleContextLinkType = href ? components.link : noHrefLink;
|
|
82
86
|
|
|
83
87
|
let visualElement: React.ReactNode;
|
|
@@ -273,7 +277,7 @@ interface DropdownListItemProps {
|
|
|
273
277
|
visual?: string | React.ReactNode;
|
|
274
278
|
icon?: ComponentType;
|
|
275
279
|
className?: string;
|
|
276
|
-
|
|
280
|
+
link?: LinkProps;
|
|
277
281
|
}
|
|
278
282
|
|
|
279
283
|
Item.Dropdown = function ({ style, ...props }: DropdownListItemProps) {
|
|
@@ -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
|
}
|