@turtleclub/ui 0.7.0-beta.3 → 0.7.0-beta.30
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/.turbo/turbo-build.log +143 -132
- package/CHANGELOG.md +152 -0
- package/dist/index.cjs +76 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +36068 -17674
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/components/charts/area-chart.d.ts +108 -0
- package/dist/types/components/charts/area-chart.d.ts.map +1 -0
- package/dist/types/components/charts/bar-chart.d.ts +110 -0
- package/dist/types/components/charts/bar-chart.d.ts.map +1 -0
- package/dist/types/components/charts/index.d.ts +5 -0
- package/dist/types/components/charts/index.d.ts.map +1 -0
- package/dist/types/components/charts/pie-chart.d.ts +94 -0
- package/dist/types/components/charts/pie-chart.d.ts.map +1 -0
- package/dist/types/components/charts/radial-chart.d.ts +151 -0
- package/dist/types/components/charts/radial-chart.d.ts.map +1 -0
- package/dist/types/components/features/data-table/data-table.d.ts +7 -4
- package/dist/types/components/features/data-table/data-table.d.ts.map +1 -1
- package/dist/types/components/features/data-table/sort-dropdown.d.ts.map +1 -1
- package/dist/types/components/features/search-bar.d.ts +1 -0
- package/dist/types/components/features/search-bar.d.ts.map +1 -1
- package/dist/types/components/molecules/swap-input.d.ts +3 -0
- package/dist/types/components/molecules/swap-input.d.ts.map +1 -1
- package/dist/types/components/molecules/token-selector.d.ts +2 -1
- package/dist/types/components/molecules/token-selector.d.ts.map +1 -1
- package/dist/types/components/ui/avatar.d.ts +2 -2
- package/dist/types/components/ui/avatar.d.ts.map +1 -1
- package/dist/types/components/ui/chart.d.ts +18 -4
- package/dist/types/components/ui/chart.d.ts.map +1 -1
- package/dist/types/components/ui/combobox.d.ts +21 -0
- package/dist/types/components/ui/combobox.d.ts.map +1 -1
- package/dist/types/components/ui/dialog.d.ts.map +1 -1
- package/dist/types/components/ui/dropdown.d.ts +2 -1
- package/dist/types/components/ui/dropdown.d.ts.map +1 -1
- package/dist/types/components/ui/index.d.ts +1 -0
- package/dist/types/components/ui/index.d.ts.map +1 -1
- package/dist/types/components/ui/multi-select.d.ts.map +1 -1
- package/dist/types/components/ui/segment-control.d.ts +1 -0
- package/dist/types/components/ui/segment-control.d.ts.map +1 -1
- package/dist/types/components/ui/slider.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/charts/QUICK_REFERENCE.md +323 -0
- package/src/components/charts/README.md +658 -0
- package/src/components/charts/RECHARTS_FEATURES.md +458 -0
- package/src/components/charts/area-chart.tsx +248 -0
- package/src/components/charts/bar-chart.tsx +362 -0
- package/src/components/charts/index.ts +4 -0
- package/src/components/charts/pie-chart.tsx +277 -0
- package/src/components/charts/radial-chart.tsx +312 -0
- package/src/components/features/data-table/data-table.tsx +136 -125
- package/src/components/features/data-table/sort-dropdown.tsx +8 -11
- package/src/components/features/search-bar.tsx +6 -1
- package/src/components/molecules/swap-input.tsx +44 -30
- package/src/components/molecules/token-selector.tsx +10 -1
- package/src/components/ui/avatar.tsx +8 -15
- package/src/components/ui/chart.tsx +100 -109
- package/src/components/ui/combobox.tsx +150 -137
- package/src/components/ui/dialog.tsx +9 -23
- package/src/components/ui/dropdown.tsx +3 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/multi-select.tsx +325 -307
- package/src/components/ui/segment-control.tsx +7 -2
- package/src/components/ui/slider.tsx +6 -11
- package/src/index.ts +1 -0
- package/src/styles/globals.css +4 -0
- package/src/styles/themes/semantic.css +26 -56
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import React, { Fragment, useMemo } from "react";
|
|
3
|
+
import React, { Fragment, useMemo, useState } from "react";
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
ColumnDef,
|
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
TableHead,
|
|
30
30
|
TableHeader,
|
|
31
31
|
TableRow,
|
|
32
|
+
Input,
|
|
33
|
+
Combobox,
|
|
32
34
|
} from "../../ui";
|
|
33
35
|
import SortableHeader from "./sortable-header";
|
|
34
36
|
import { SearchBar } from "../search-bar";
|
|
@@ -46,6 +48,7 @@ import {
|
|
|
46
48
|
PaginationNext,
|
|
47
49
|
PaginationPrevious,
|
|
48
50
|
} from "../../ui/pagination";
|
|
51
|
+
import { useIsMobile } from "@/hooks";
|
|
49
52
|
|
|
50
53
|
const getScrollAreaHeight = (size: string | "full" | undefined) => {
|
|
51
54
|
if (!size) return "auto";
|
|
@@ -77,6 +80,7 @@ type DataTableProps<TData, TValue> = {
|
|
|
77
80
|
manualFiltering?: boolean;
|
|
78
81
|
manualSorting?: boolean;
|
|
79
82
|
manualPagination?: boolean;
|
|
83
|
+
|
|
80
84
|
pageCount?: number; // Total page count from server (required when manualPagination is true)
|
|
81
85
|
rowCount?: number; // Total row count from server (optional, for display purposes)
|
|
82
86
|
|
|
@@ -92,14 +96,17 @@ type DataTableProps<TData, TValue> = {
|
|
|
92
96
|
grid?: {
|
|
93
97
|
displayAsGrid: boolean;
|
|
94
98
|
className?: string;
|
|
95
|
-
headerSlot: keyof TData;
|
|
96
|
-
rightSlot?: keyof TData;
|
|
97
|
-
excludeColumns?: (keyof TData)[];
|
|
99
|
+
headerSlot: keyof TData | string;
|
|
100
|
+
rightSlot?: keyof TData | string;
|
|
101
|
+
excludeColumns?: (keyof TData | string)[];
|
|
102
|
+
headerTextSize?: string;
|
|
103
|
+
rightSlotTextSize?: string;
|
|
98
104
|
};
|
|
99
105
|
// ScrollArea size configuration
|
|
100
106
|
size?: string | "full";
|
|
101
107
|
emptyState?: React.ReactNode;
|
|
102
108
|
className?: string;
|
|
109
|
+
slotClassName?: string;
|
|
103
110
|
};
|
|
104
111
|
|
|
105
112
|
export function DataTable<TData, TValue>({
|
|
@@ -118,6 +125,7 @@ export function DataTable<TData, TValue>({
|
|
|
118
125
|
grid,
|
|
119
126
|
emptyState,
|
|
120
127
|
className,
|
|
128
|
+
slotClassName,
|
|
121
129
|
onRowClick,
|
|
122
130
|
// Server-side props
|
|
123
131
|
manualFiltering = false,
|
|
@@ -132,22 +140,23 @@ export function DataTable<TData, TValue>({
|
|
|
132
140
|
pagination: controlledPagination,
|
|
133
141
|
onPaginationChange: onControlledPaginationChange,
|
|
134
142
|
}: DataTableProps<TData, TValue>) {
|
|
135
|
-
const [columnVisibility, setColumnVisibility] = React.useState(
|
|
136
|
-
initialColumnVisibility ?? {},
|
|
137
|
-
);
|
|
143
|
+
const [columnVisibility, setColumnVisibility] = React.useState(initialColumnVisibility ?? {});
|
|
138
144
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
|
139
145
|
const [expanded, setExpanded] = React.useState<ExpandedState>({});
|
|
140
146
|
|
|
141
147
|
// Internal state (used when not controlled from outside)
|
|
142
|
-
const [internalGlobalFilter, setInternalGlobalFilter] =
|
|
143
|
-
|
|
144
|
-
const [
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
const [internalGlobalFilter, setInternalGlobalFilter] = React.useState<string>("");
|
|
149
|
+
const [internalSorting, setInternalSorting] = React.useState<SortingState>(initialSorting);
|
|
150
|
+
const [internalPagination, setInternalPagination] = React.useState<PaginationState>({
|
|
151
|
+
pageIndex: 0,
|
|
152
|
+
pageSize: 10,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Store the default page size for reset purposes
|
|
156
|
+
const defaultPageSize = React.useMemo(
|
|
157
|
+
() => controlledPagination?.pageSize ?? internalPagination.pageSize,
|
|
158
|
+
[controlledPagination?.pageSize]
|
|
159
|
+
);
|
|
151
160
|
|
|
152
161
|
// Use controlled state if provided, otherwise use internal state
|
|
153
162
|
const globalFilter = controlledGlobalFilter ?? internalGlobalFilter;
|
|
@@ -162,13 +171,9 @@ export function DataTable<TData, TValue>({
|
|
|
162
171
|
}
|
|
163
172
|
};
|
|
164
173
|
|
|
165
|
-
const setSorting = (
|
|
166
|
-
updaterOrValue: SortingState | ((old: SortingState) => SortingState),
|
|
167
|
-
) => {
|
|
174
|
+
const setSorting = (updaterOrValue: SortingState | ((old: SortingState) => SortingState)) => {
|
|
168
175
|
const newSorting =
|
|
169
|
-
typeof updaterOrValue === "function"
|
|
170
|
-
? updaterOrValue(sorting)
|
|
171
|
-
: updaterOrValue;
|
|
176
|
+
typeof updaterOrValue === "function" ? updaterOrValue(sorting) : updaterOrValue;
|
|
172
177
|
|
|
173
178
|
if (onControlledSortingChange) {
|
|
174
179
|
onControlledSortingChange(newSorting);
|
|
@@ -178,14 +183,10 @@ export function DataTable<TData, TValue>({
|
|
|
178
183
|
};
|
|
179
184
|
|
|
180
185
|
const setPagination = (
|
|
181
|
-
updaterOrValue:
|
|
182
|
-
| PaginationState
|
|
183
|
-
| ((old: PaginationState) => PaginationState),
|
|
186
|
+
updaterOrValue: PaginationState | ((old: PaginationState) => PaginationState)
|
|
184
187
|
) => {
|
|
185
188
|
const newPagination =
|
|
186
|
-
typeof updaterOrValue === "function"
|
|
187
|
-
? updaterOrValue(pagination)
|
|
188
|
-
: updaterOrValue;
|
|
189
|
+
typeof updaterOrValue === "function" ? updaterOrValue(pagination) : updaterOrValue;
|
|
189
190
|
|
|
190
191
|
if (onControlledPaginationChange) {
|
|
191
192
|
onControlledPaginationChange(newPagination);
|
|
@@ -194,6 +195,8 @@ export function DataTable<TData, TValue>({
|
|
|
194
195
|
}
|
|
195
196
|
};
|
|
196
197
|
|
|
198
|
+
const { isMobile } = useIsMobile();
|
|
199
|
+
|
|
197
200
|
const table = useReactTable({
|
|
198
201
|
data,
|
|
199
202
|
columns,
|
|
@@ -219,7 +222,7 @@ export function DataTable<TData, TValue>({
|
|
|
219
222
|
getRowCanExpand: enableExpand ? () => true : undefined,
|
|
220
223
|
|
|
221
224
|
// Server-side control
|
|
222
|
-
manualFiltering,
|
|
225
|
+
manualFiltering: manualFiltering,
|
|
223
226
|
manualSorting,
|
|
224
227
|
manualPagination,
|
|
225
228
|
pageCount: manualPagination ? pageCount : undefined,
|
|
@@ -231,9 +234,7 @@ export function DataTable<TData, TValue>({
|
|
|
231
234
|
getFilteredRowModel: !manualFiltering ? getFilteredRowModel() : undefined,
|
|
232
235
|
getSortedRowModel: !manualSorting ? getSortedRowModel() : undefined,
|
|
233
236
|
getPaginationRowModel:
|
|
234
|
-
enablePagination && !manualPagination
|
|
235
|
-
? getPaginationRowModel()
|
|
236
|
-
: undefined,
|
|
237
|
+
enablePagination && !manualPagination ? getPaginationRowModel() : undefined,
|
|
237
238
|
getExpandedRowModel: getExpandedRowModel(),
|
|
238
239
|
|
|
239
240
|
// debugTable: true,
|
|
@@ -247,13 +248,13 @@ export function DataTable<TData, TValue>({
|
|
|
247
248
|
.getHeaderGroups()
|
|
248
249
|
.map((headerGroup) => headerGroup.headers)
|
|
249
250
|
.flat(),
|
|
250
|
-
[table]
|
|
251
|
+
[table]
|
|
251
252
|
);
|
|
252
253
|
|
|
253
254
|
return (
|
|
254
255
|
<div className="space-y-3">
|
|
255
256
|
{slot || enableSearch || (enableSorting && grid?.displayAsGrid) ? (
|
|
256
|
-
<div className="flex items-
|
|
257
|
+
<div className={cn("flex items-start gap-1", slotClassName)}>
|
|
257
258
|
{slot}
|
|
258
259
|
<span className="grow" />
|
|
259
260
|
{enableSorting && grid?.displayAsGrid && (
|
|
@@ -276,10 +277,7 @@ export function DataTable<TData, TValue>({
|
|
|
276
277
|
)}
|
|
277
278
|
</div>
|
|
278
279
|
) : undefined}
|
|
279
|
-
<ScrollArea
|
|
280
|
-
style={{ height: getScrollAreaHeight(size) }}
|
|
281
|
-
className={className}
|
|
282
|
-
>
|
|
280
|
+
<ScrollArea style={{ height: getScrollAreaHeight(size) }} className={className}>
|
|
283
281
|
{table.getRowModel().rows?.length || isLoading ? (
|
|
284
282
|
grid?.displayAsGrid ? (
|
|
285
283
|
isLoading ? (
|
|
@@ -287,15 +285,15 @@ export function DataTable<TData, TValue>({
|
|
|
287
285
|
className={cn(
|
|
288
286
|
"grid gap-1 pr-1.5",
|
|
289
287
|
"grid-cols-1 md:grid-cols-2 xl:grid-cols-3",
|
|
290
|
-
grid.className
|
|
288
|
+
grid.className
|
|
291
289
|
)}
|
|
292
290
|
/>
|
|
293
291
|
) : (
|
|
294
292
|
<div
|
|
295
293
|
className={cn(
|
|
296
294
|
"grid gap-1 pr-1.5",
|
|
297
|
-
|
|
298
|
-
grid.className
|
|
295
|
+
"grid-cols-1 md:grid-cols-2 xl:grid-cols-3",
|
|
296
|
+
grid.className
|
|
299
297
|
)}
|
|
300
298
|
>
|
|
301
299
|
{table.getRowModel().rows.map((row) => (
|
|
@@ -303,27 +301,20 @@ export function DataTable<TData, TValue>({
|
|
|
303
301
|
<Card
|
|
304
302
|
className={cn(
|
|
305
303
|
"max-w-none",
|
|
306
|
-
onRowClick
|
|
307
|
-
? "hover:bg-neutral-alpha-5 cursor-pointer"
|
|
308
|
-
: "",
|
|
304
|
+
onRowClick ? "hover:bg-neutral-alpha-5 cursor-pointer" : ""
|
|
309
305
|
)}
|
|
310
306
|
data-state={row.getIsSelected() && "selected"}
|
|
311
307
|
variant="border"
|
|
312
308
|
>
|
|
313
309
|
<CardHeader className="flex items-center gap-2">
|
|
314
310
|
{/* Slot for header, pick the colum you display here */}
|
|
315
|
-
<CardTitle className="line-clamp-1 grow text-base">
|
|
311
|
+
<CardTitle className={cn("line-clamp-1 grow", grid.headerTextSize || "text-base")}>
|
|
316
312
|
{row
|
|
317
313
|
.getVisibleCells()
|
|
318
|
-
.filter(
|
|
319
|
-
(cell) => cell.column.id === grid.headerSlot,
|
|
320
|
-
)
|
|
314
|
+
.filter((cell) => cell.column.id === grid.headerSlot)
|
|
321
315
|
?.map((cell) => (
|
|
322
316
|
<Fragment key={cell.id}>
|
|
323
|
-
{flexRender(
|
|
324
|
-
cell.column.columnDef.cell,
|
|
325
|
-
cell.getContext(),
|
|
326
|
-
)}
|
|
317
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
327
318
|
</Fragment>
|
|
328
319
|
))}
|
|
329
320
|
</CardTitle>
|
|
@@ -332,11 +323,8 @@ export function DataTable<TData, TValue>({
|
|
|
332
323
|
.getVisibleCells()
|
|
333
324
|
.filter((cell) => cell.column.id === grid?.rightSlot)
|
|
334
325
|
?.map((cell) => (
|
|
335
|
-
<div key={cell.id} className="text-lg">
|
|
336
|
-
{flexRender(
|
|
337
|
-
cell.column.columnDef.cell,
|
|
338
|
-
cell.getContext(),
|
|
339
|
-
)}
|
|
326
|
+
<div key={cell.id} className={cn(grid.rightSlotTextSize || "text-lg")}>
|
|
327
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
340
328
|
</div>
|
|
341
329
|
))}
|
|
342
330
|
</CardHeader>
|
|
@@ -347,8 +335,7 @@ export function DataTable<TData, TValue>({
|
|
|
347
335
|
(cell) =>
|
|
348
336
|
cell.column.id !== grid.headerSlot &&
|
|
349
337
|
cell.column.id !== grid.rightSlot &&
|
|
350
|
-
|
|
351
|
-
!grid.excludeColumns?.includes(cell.column.id),
|
|
338
|
+
!grid.excludeColumns?.includes(cell.column.id)
|
|
352
339
|
)
|
|
353
340
|
.map((cell) => {
|
|
354
341
|
return (
|
|
@@ -357,15 +344,10 @@ export function DataTable<TData, TValue>({
|
|
|
357
344
|
title={flexRender(
|
|
358
345
|
cell.column.columnDef.header,
|
|
359
346
|
headers
|
|
360
|
-
.find(
|
|
361
|
-
|
|
362
|
-
)
|
|
363
|
-
?.getContext(),
|
|
364
|
-
)}
|
|
365
|
-
value={flexRender(
|
|
366
|
-
cell.column.columnDef.cell,
|
|
367
|
-
cell.getContext(),
|
|
347
|
+
.find((header) => header.id === cell.column.id)
|
|
348
|
+
?.getContext()
|
|
368
349
|
)}
|
|
350
|
+
value={flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
369
351
|
className="justify-between gap-3 *:text-xs"
|
|
370
352
|
/>
|
|
371
353
|
);
|
|
@@ -390,9 +372,7 @@ export function DataTable<TData, TValue>({
|
|
|
390
372
|
minWidth: header.column.columnDef.minSize,
|
|
391
373
|
maxWidth: header.column.columnDef.maxSize,
|
|
392
374
|
}}
|
|
393
|
-
className={
|
|
394
|
-
(header.column.columnDef.meta as any)?.className
|
|
395
|
-
}
|
|
375
|
+
className={(header.column.columnDef.meta as any)?.className}
|
|
396
376
|
>
|
|
397
377
|
<SortableHeader header={header} />
|
|
398
378
|
</TableHead>
|
|
@@ -409,18 +389,11 @@ export function DataTable<TData, TValue>({
|
|
|
409
389
|
<TableRow
|
|
410
390
|
onClick={() => onRowClick?.(row.original)}
|
|
411
391
|
data-state={row.getIsSelected() && "selected"}
|
|
412
|
-
className={
|
|
413
|
-
onRowClick
|
|
414
|
-
? "hover:bg-neutral-alpha-5 cursor-pointer"
|
|
415
|
-
: ""
|
|
416
|
-
}
|
|
392
|
+
className={onRowClick ? "hover:bg-neutral-alpha-5 cursor-pointer" : ""}
|
|
417
393
|
>
|
|
418
394
|
{row.getVisibleCells().map((cell) => (
|
|
419
395
|
<ShadTableCell key={cell.id}>
|
|
420
|
-
{flexRender(
|
|
421
|
-
cell.column.columnDef.cell,
|
|
422
|
-
cell.getContext(),
|
|
423
|
-
)}
|
|
396
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
424
397
|
</ShadTableCell>
|
|
425
398
|
))}
|
|
426
399
|
</TableRow>
|
|
@@ -446,10 +419,26 @@ export function DataTable<TData, TValue>({
|
|
|
446
419
|
))
|
|
447
420
|
)}
|
|
448
421
|
</ScrollArea>
|
|
449
|
-
{enablePagination &&
|
|
450
|
-
(
|
|
451
|
-
|
|
452
|
-
|
|
422
|
+
{enablePagination && (
|
|
423
|
+
<div className={cn(isMobile ? "flex flex-col gap-2" : "flex justify-between gap-1")}>
|
|
424
|
+
<Combobox
|
|
425
|
+
options={[10, 20, 30, 40, 50, 100].map((size) => ({
|
|
426
|
+
label: `Show ${size.toString()} rows per page`,
|
|
427
|
+
value: size,
|
|
428
|
+
}))}
|
|
429
|
+
defaultValue={table.getState().pagination.pageSize}
|
|
430
|
+
onValueChange={(value) => {
|
|
431
|
+
// If value is cleared/null/0, reset to default page size
|
|
432
|
+
const newPageSize = value || defaultPageSize;
|
|
433
|
+
table.setPageSize(newPageSize);
|
|
434
|
+
}}
|
|
435
|
+
autoSize
|
|
436
|
+
className="h-9"
|
|
437
|
+
searchable={false}
|
|
438
|
+
closeOnSelect
|
|
439
|
+
placeholder="Rows shown per page"
|
|
440
|
+
/>
|
|
441
|
+
|
|
453
442
|
<Pagination>
|
|
454
443
|
<PaginationContent>
|
|
455
444
|
<PaginationItem>
|
|
@@ -462,63 +451,85 @@ export function DataTable<TData, TValue>({
|
|
|
462
451
|
}
|
|
463
452
|
/>
|
|
464
453
|
</PaginationItem>
|
|
465
|
-
{Array.from({ length: table.getPageCount() }, (_, i) => i).map(
|
|
466
|
-
(pageIndex
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
(pageIndex >= currentPage - 1 &&
|
|
475
|
-
pageIndex <= currentPage + 1);
|
|
476
|
-
|
|
477
|
-
// Show ellipsis before current range (but not right after first page)
|
|
478
|
-
const showEllipsisBefore =
|
|
479
|
-
pageIndex === currentPage - 2 && currentPage > 2;
|
|
480
|
-
|
|
481
|
-
// Show ellipsis after current range (but not right before last page)
|
|
482
|
-
const showEllipsisAfter =
|
|
483
|
-
pageIndex === currentPage + 2 &&
|
|
484
|
-
currentPage < totalPages - 3;
|
|
485
|
-
|
|
486
|
-
if (showEllipsisBefore || showEllipsisAfter) {
|
|
487
|
-
return (
|
|
488
|
-
<PaginationItem key={pageIndex}>
|
|
489
|
-
<PaginationEllipsis />
|
|
490
|
-
</PaginationItem>
|
|
491
|
-
);
|
|
492
|
-
}
|
|
454
|
+
{Array.from({ length: table.getPageCount() }, (_, i) => i).map((pageIndex) => {
|
|
455
|
+
const currentPage = table.getState().pagination.pageIndex;
|
|
456
|
+
const totalPages = table.getPageCount();
|
|
457
|
+
|
|
458
|
+
// Show first page, last page, current page, and pages around current
|
|
459
|
+
const showPage =
|
|
460
|
+
pageIndex === 0 ||
|
|
461
|
+
pageIndex === totalPages - 1 ||
|
|
462
|
+
(pageIndex >= currentPage - 1 && pageIndex <= currentPage + 1);
|
|
493
463
|
|
|
494
|
-
|
|
464
|
+
// Show ellipsis before current range (but not right after first page)
|
|
465
|
+
const showEllipsisBefore = pageIndex === currentPage - 2 && currentPage > 2;
|
|
495
466
|
|
|
467
|
+
// Show ellipsis after current range (but not right before last page)
|
|
468
|
+
const showEllipsisAfter =
|
|
469
|
+
pageIndex === currentPage + 2 && currentPage < totalPages - 3;
|
|
470
|
+
|
|
471
|
+
if (showEllipsisBefore || showEllipsisAfter) {
|
|
496
472
|
return (
|
|
497
473
|
<PaginationItem key={pageIndex}>
|
|
498
|
-
<
|
|
499
|
-
onClick={() => table.setPageIndex(pageIndex)}
|
|
500
|
-
isActive={currentPage === pageIndex}
|
|
501
|
-
className="cursor-pointer"
|
|
502
|
-
>
|
|
503
|
-
{pageIndex + 1}
|
|
504
|
-
</PaginationLink>
|
|
474
|
+
<PaginationEllipsis />
|
|
505
475
|
</PaginationItem>
|
|
506
476
|
);
|
|
507
|
-
}
|
|
508
|
-
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (!showPage) return null;
|
|
480
|
+
|
|
481
|
+
return (
|
|
482
|
+
<PaginationItem key={pageIndex}>
|
|
483
|
+
<PaginationLink
|
|
484
|
+
onClick={() => table.setPageIndex(pageIndex)}
|
|
485
|
+
isActive={currentPage === pageIndex}
|
|
486
|
+
className="cursor-pointer"
|
|
487
|
+
>
|
|
488
|
+
{pageIndex + 1}
|
|
489
|
+
</PaginationLink>
|
|
490
|
+
</PaginationItem>
|
|
491
|
+
);
|
|
492
|
+
})}
|
|
509
493
|
<PaginationItem>
|
|
510
494
|
<PaginationNext
|
|
511
495
|
onClick={() => table.nextPage()}
|
|
512
496
|
className={
|
|
513
|
-
!table.getCanNextPage()
|
|
514
|
-
? "pointer-events-none opacity-50"
|
|
515
|
-
: "cursor-pointer"
|
|
497
|
+
!table.getCanNextPage() ? "pointer-events-none opacity-50" : "cursor-pointer"
|
|
516
498
|
}
|
|
517
499
|
/>
|
|
518
500
|
</PaginationItem>
|
|
519
501
|
</PaginationContent>
|
|
520
502
|
</Pagination>
|
|
521
|
-
|
|
503
|
+
|
|
504
|
+
<div className="mx-4 flex items-center gap-1">
|
|
505
|
+
<span className="text-muted-foreground truncate text-xs">Go to page:</span>
|
|
506
|
+
<Input
|
|
507
|
+
min={1}
|
|
508
|
+
max={table.getPageCount()}
|
|
509
|
+
pattern="[0-9]*"
|
|
510
|
+
defaultValue={table.getState().pagination.pageIndex + 1}
|
|
511
|
+
onBlurCapture={(e) => {
|
|
512
|
+
const page = e.target.value ? Number(e.target.value) - 1 : 0;
|
|
513
|
+
if (page >= 0 && page < table.getPageCount()) {
|
|
514
|
+
table.setPageIndex(page);
|
|
515
|
+
}
|
|
516
|
+
}}
|
|
517
|
+
onKeyDown={(e) => {
|
|
518
|
+
if (e.key === "Enter") {
|
|
519
|
+
const page = e.currentTarget.value ? Number(e.currentTarget.value) - 1 : 0;
|
|
520
|
+
if (page >= 0 && page < table.getPageCount()) {
|
|
521
|
+
table.setPageIndex(page);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}}
|
|
525
|
+
className="h-9! w-12"
|
|
526
|
+
/>
|
|
527
|
+
<span className="text-muted-foreground truncate text-xs">
|
|
528
|
+
of {table.getPageCount()}
|
|
529
|
+
</span>
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
)}
|
|
522
533
|
</div>
|
|
523
534
|
);
|
|
524
535
|
}
|
|
@@ -27,7 +27,7 @@ export function SortDropdown<TData>({
|
|
|
27
27
|
}: SortDropdownProps<TData>) {
|
|
28
28
|
// Filter only sortable columns
|
|
29
29
|
const sortableHeaders = headers.filter(
|
|
30
|
-
(header) => header.column.getCanSort() && !header.isPlaceholder
|
|
30
|
+
(header) => header.column.getCanSort() && !header.isPlaceholder
|
|
31
31
|
);
|
|
32
32
|
|
|
33
33
|
const getCurrentSortLabel = () => {
|
|
@@ -79,23 +79,20 @@ export function SortDropdown<TData>({
|
|
|
79
79
|
<DropdownMenuLabel>Sort by</DropdownMenuLabel>
|
|
80
80
|
<DropdownMenuSeparator />
|
|
81
81
|
{sortableHeaders.map((header) => {
|
|
82
|
-
const isCurrentAsc =
|
|
83
|
-
|
|
84
|
-
const isCurrentDesc =
|
|
85
|
-
currentSort?.id === header.column.id && currentSort.desc;
|
|
82
|
+
const isCurrentAsc = currentSort?.id === header.column.id && !currentSort.desc;
|
|
83
|
+
const isCurrentDesc = currentSort?.id === header.column.id && currentSort.desc;
|
|
86
84
|
const isActive = currentSort?.id === header.column.id;
|
|
87
85
|
|
|
88
86
|
return (
|
|
89
87
|
<DropdownMenuItem
|
|
90
88
|
key={header.column.id}
|
|
91
89
|
onClick={() => handleColumnClick(header.column.id)}
|
|
92
|
-
className={
|
|
90
|
+
className={
|
|
91
|
+
isActive ? "bg-secondary flex items-center gap-2" : "flex items-center gap-2"
|
|
92
|
+
}
|
|
93
93
|
>
|
|
94
|
-
<span className="
|
|
95
|
-
{flexRender(
|
|
96
|
-
header.column.columnDef.header,
|
|
97
|
-
header.getContext(),
|
|
98
|
-
)}
|
|
94
|
+
<span className="w-fit truncate">
|
|
95
|
+
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
99
96
|
</span>
|
|
100
97
|
{isActive &&
|
|
101
98
|
(isCurrentDesc ? (
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Input, InputProps } from "../ui";
|
|
3
3
|
import { Search } from "lucide-react";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
4
5
|
|
|
5
6
|
type Props = {
|
|
6
7
|
value: string;
|
|
7
8
|
onChange: (value: string) => void;
|
|
8
9
|
debounce?: number;
|
|
10
|
+
size?: "default" | "sm";
|
|
9
11
|
} & Omit<InputProps, "onChange">;
|
|
10
12
|
|
|
11
13
|
export const SearchBar: React.FC<Props> = ({
|
|
12
14
|
value: initialValue,
|
|
13
15
|
onChange,
|
|
14
16
|
debounce = 300,
|
|
17
|
+
size = "default",
|
|
15
18
|
...props
|
|
16
19
|
}) => {
|
|
17
20
|
const [value, setValue] = React.useState<string>(initialValue);
|
|
@@ -31,12 +34,14 @@ export const SearchBar: React.FC<Props> = ({
|
|
|
31
34
|
return () => clearTimeout(timeout);
|
|
32
35
|
}, [value]);
|
|
33
36
|
|
|
37
|
+
const inputClasses = size === "sm" ? "text-sm" : "";
|
|
38
|
+
|
|
34
39
|
return (
|
|
35
40
|
<div className="border-border relative w-full max-w-md rounded-full border">
|
|
36
41
|
<Input
|
|
37
42
|
value={value}
|
|
38
43
|
onChange={handleInputChange}
|
|
39
|
-
className="-my-px h-9"
|
|
44
|
+
className={cn("-my-px h-9", inputClasses)}
|
|
40
45
|
{...props}
|
|
41
46
|
/>
|
|
42
47
|
<button
|
|
@@ -6,8 +6,10 @@ import { Chip } from "@/components/ui/chip";
|
|
|
6
6
|
import { TokenSelector, type Token } from "./token-selector";
|
|
7
7
|
import { BaseSelector } from "./widget/base-selector";
|
|
8
8
|
|
|
9
|
-
interface SwapInputProps
|
|
10
|
-
|
|
9
|
+
interface SwapInputProps extends Omit<
|
|
10
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
11
|
+
"onChange"
|
|
12
|
+
> {
|
|
11
13
|
value?: string;
|
|
12
14
|
tokens: Token[];
|
|
13
15
|
selectedToken?: string;
|
|
@@ -24,6 +26,9 @@ interface SwapInputProps
|
|
|
24
26
|
useCustomTokenSelector?: boolean;
|
|
25
27
|
selectedTokenData?: Token | null;
|
|
26
28
|
onCustomTokenSelectorClick?: () => void;
|
|
29
|
+
showTokenSelectorBalance?: boolean;
|
|
30
|
+
showInputBalance?: boolean;
|
|
31
|
+
triggerTokenSelectorWidth?: number;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
const SwapInput = React.forwardRef<HTMLDivElement, SwapInputProps>(
|
|
@@ -46,6 +51,9 @@ const SwapInput = React.forwardRef<HTMLDivElement, SwapInputProps>(
|
|
|
46
51
|
useCustomTokenSelector = false,
|
|
47
52
|
selectedTokenData,
|
|
48
53
|
onCustomTokenSelectorClick,
|
|
54
|
+
showTokenSelectorBalance = true,
|
|
55
|
+
showInputBalance = true,
|
|
56
|
+
triggerTokenSelectorWidth,
|
|
49
57
|
...props
|
|
50
58
|
},
|
|
51
59
|
ref,
|
|
@@ -127,40 +135,46 @@ const SwapInput = React.forwardRef<HTMLDivElement, SwapInputProps>(
|
|
|
127
135
|
size="sm"
|
|
128
136
|
variant="default"
|
|
129
137
|
className="rounded-full"
|
|
138
|
+
showBalance={showTokenSelectorBalance}
|
|
139
|
+
triggerWidth={triggerTokenSelectorWidth}
|
|
130
140
|
/>
|
|
131
141
|
))}
|
|
132
142
|
</div>
|
|
133
143
|
|
|
134
144
|
{/* Bottom row: USD value and balance with MAX */}
|
|
135
|
-
|
|
136
|
-
<div>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
{showInputBalance && (
|
|
146
|
+
<div className="text-muted-foreground flex items-center justify-between text-sm">
|
|
147
|
+
<div>
|
|
148
|
+
{usdValue ? (
|
|
149
|
+
<span>≈ {usdValue}</span>
|
|
150
|
+
) : value ? (
|
|
151
|
+
<span className="text-destructive/50">
|
|
152
|
+
Insufficient balance
|
|
153
|
+
</span>
|
|
154
|
+
) : null}
|
|
155
|
+
</div>
|
|
156
|
+
<div className="flex items-center gap-2">
|
|
157
|
+
{balance && (
|
|
158
|
+
<span className="text-sm">
|
|
159
|
+
Balance: {balance} {currentToken?.symbol || ""}
|
|
160
|
+
</span>
|
|
161
|
+
)}
|
|
162
|
+
{onMaxClick && (
|
|
163
|
+
<Chip
|
|
164
|
+
variant="default"
|
|
165
|
+
size="xs"
|
|
166
|
+
onClick={onMaxClick}
|
|
167
|
+
className={cn(
|
|
168
|
+
"hover:bg-primary hover:text-primary-foreground h-5 cursor-pointer px-2 py-0.5 text-xs transition-colors",
|
|
169
|
+
disabled && "cursor-not-allowed opacity-50",
|
|
170
|
+
)}
|
|
171
|
+
>
|
|
172
|
+
MAX
|
|
173
|
+
</Chip>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
142
176
|
</div>
|
|
143
|
-
|
|
144
|
-
{balance && (
|
|
145
|
-
<span className="text-sm">
|
|
146
|
-
Balance: {balance} {currentToken?.symbol || ""}
|
|
147
|
-
</span>
|
|
148
|
-
)}
|
|
149
|
-
{onMaxClick && (
|
|
150
|
-
<Chip
|
|
151
|
-
variant="default"
|
|
152
|
-
size="xs"
|
|
153
|
-
onClick={onMaxClick}
|
|
154
|
-
className={cn(
|
|
155
|
-
"hover:bg-primary hover:text-primary-foreground h-5 cursor-pointer px-2 py-0.5 text-xs transition-colors",
|
|
156
|
-
disabled && "cursor-not-allowed opacity-50",
|
|
157
|
-
)}
|
|
158
|
-
>
|
|
159
|
-
MAX
|
|
160
|
-
</Chip>
|
|
161
|
-
)}
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
177
|
+
)}
|
|
164
178
|
</Card>
|
|
165
179
|
);
|
|
166
180
|
},
|