@turtleclub/ui 0.7.0-beta.32 → 0.7.0-beta.34
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/index.cjs +10331 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7652 -47844
- package/dist/index.js.map +1 -1
- package/dist/types/components/features/sidebar-layout.d.ts +2 -0
- package/dist/types/components/features/sidebar-layout.d.ts.map +1 -1
- package/dist/types/components/ui/chart.d.ts +1 -1
- package/dist/types/components/ui/chart.d.ts.map +1 -1
- package/package.json +26 -22
- package/.prettierrc.json +0 -4
- package/.turbo/turbo-build.log +0 -182
- package/CHANGELOG.md +0 -795
- package/components.json +0 -21
- package/src/components/charts/QUICK_REFERENCE.md +0 -323
- package/src/components/charts/README.md +0 -658
- package/src/components/charts/RECHARTS_FEATURES.md +0 -458
- package/src/components/charts/area-chart.tsx +0 -248
- package/src/components/charts/bar-chart.tsx +0 -362
- package/src/components/charts/index.ts +0 -4
- package/src/components/charts/pie-chart.tsx +0 -277
- package/src/components/charts/radial-chart.tsx +0 -312
- package/src/components/features/api-status/index.tsx +0 -23
- package/src/components/features/data-table/data-table.tsx +0 -538
- package/src/components/features/data-table/expand-toggle.tsx +0 -17
- package/src/components/features/data-table/fuzzy-filter.tsx +0 -34
- package/src/components/features/data-table/index.ts +0 -3
- package/src/components/features/data-table/item-info.tsx +0 -19
- package/src/components/features/data-table/skeleton.tsx +0 -23
- package/src/components/features/data-table/sort-dropdown.tsx +0 -118
- package/src/components/features/data-table/sortable-header.tsx +0 -37
- package/src/components/features/index.ts +0 -6
- package/src/components/features/page-heading.tsx +0 -27
- package/src/components/features/search-bar.tsx +0 -55
- package/src/components/features/segmented-navigation.tsx +0 -18
- package/src/components/features/sidebar-layout.tsx +0 -193
- package/src/components/features/turtle-tooltip.tsx +0 -67
- package/src/components/icons/arrow.tsx +0 -23
- package/src/components/icons/beta.tsx +0 -95
- package/src/components/icons/dot.tsx +0 -102
- package/src/components/icons/index.ts +0 -7
- package/src/components/icons/issue.tsx +0 -106
- package/src/components/icons/turtle.tsx +0 -156
- package/src/components/icons/update.tsx +0 -113
- package/src/components/icons/warning.tsx +0 -95
- package/src/components/molecules/index.ts +0 -9
- package/src/components/molecules/opportunity/index.ts +0 -10
- package/src/components/molecules/opportunity/opportunity-apr.tsx +0 -129
- package/src/components/molecules/opportunity/opportunity-disclaimer.tsx +0 -46
- package/src/components/molecules/opportunity/opportunity-rate-estimator.tsx +0 -62
- package/src/components/molecules/opportunity/opportunity-section.tsx +0 -113
- package/src/components/molecules/opportunity/opportunity-selector.tsx +0 -30
- package/src/components/molecules/opportunity/opportunity-type.tsx +0 -16
- package/src/components/molecules/route-details.tsx +0 -112
- package/src/components/molecules/slippage-selector.tsx +0 -200
- package/src/components/molecules/swap-details.tsx +0 -55
- package/src/components/molecules/swap-input.tsx +0 -186
- package/src/components/molecules/tabs.tsx +0 -79
- package/src/components/molecules/token-selector.tsx +0 -180
- package/src/components/molecules/tx-status.tsx +0 -312
- package/src/components/molecules/widget/asset-list/asset-filters.tsx +0 -113
- package/src/components/molecules/widget/asset-list/asset-list.tsx +0 -178
- package/src/components/molecules/widget/asset-list/asset-row.tsx +0 -45
- package/src/components/molecules/widget/asset-list/hooks/index.ts +0 -2
- package/src/components/molecules/widget/asset-list/hooks/use-asset-filtering.ts +0 -44
- package/src/components/molecules/widget/asset-list/hooks/use-asset-grouping.ts +0 -87
- package/src/components/molecules/widget/asset-list/index.ts +0 -3
- package/src/components/molecules/widget/base-selector.tsx +0 -121
- package/src/components/molecules/widget/campaign-item.tsx +0 -82
- package/src/components/molecules/widget/deal-item.tsx +0 -92
- package/src/components/molecules/widget/index.ts +0 -36
- package/src/components/molecules/widget/opportunity-item.tsx +0 -105
- package/src/components/molecules/widget/widget-item-stats.tsx +0 -50
- package/src/components/molecules/widget/widget-item.tsx +0 -139
- package/src/components/molecules/widget/widget-list-items.tsx +0 -86
- package/src/components/ui/alert-dialog.tsx +0 -163
- package/src/components/ui/animated-background/animated-background.tsx +0 -182
- package/src/components/ui/animated-background/index.ts +0 -1
- package/src/components/ui/avatar.tsx +0 -73
- package/src/components/ui/badge.tsx +0 -59
- package/src/components/ui/banner.tsx +0 -84
- package/src/components/ui/button.tsx +0 -100
- package/src/components/ui/card.tsx +0 -119
- package/src/components/ui/chart.tsx +0 -346
- package/src/components/ui/checkbox.tsx +0 -32
- package/src/components/ui/chip.tsx +0 -52
- package/src/components/ui/collapsible.tsx +0 -34
- package/src/components/ui/combobox.tsx +0 -730
- package/src/components/ui/command.tsx +0 -184
- package/src/components/ui/dialog.tsx +0 -129
- package/src/components/ui/dropdown.tsx +0 -316
- package/src/components/ui/field.tsx +0 -244
- package/src/components/ui/heading.tsx +0 -74
- package/src/components/ui/hover-card.tsx +0 -139
- package/src/components/ui/icon-animation.tsx +0 -82
- package/src/components/ui/icon-list.tsx +0 -168
- package/src/components/ui/index.ts +0 -48
- package/src/components/ui/info-card.tsx +0 -110
- package/src/components/ui/input-group.tsx +0 -170
- package/src/components/ui/input.tsx +0 -72
- package/src/components/ui/label-with-icon.tsx +0 -122
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/multi-select.tsx +0 -1090
- package/src/components/ui/navigation-bar.tsx +0 -153
- package/src/components/ui/navigation-menu.tsx +0 -188
- package/src/components/ui/opportunity-details-v1.tsx +0 -104
- package/src/components/ui/pagination.tsx +0 -127
- package/src/components/ui/popover.tsx +0 -48
- package/src/components/ui/scroll-area.tsx +0 -64
- package/src/components/ui/segment-control.tsx +0 -146
- package/src/components/ui/select.tsx +0 -199
- package/src/components/ui/separator.tsx +0 -26
- package/src/components/ui/sheet.tsx +0 -139
- package/src/components/ui/sidebar.tsx +0 -728
- package/src/components/ui/skeleton.tsx +0 -14
- package/src/components/ui/slider.tsx +0 -58
- package/src/components/ui/sonner.tsx +0 -24
- package/src/components/ui/switch.tsx +0 -29
- package/src/components/ui/table-shadcn.tsx +0 -110
- package/src/components/ui/table.tsx +0 -117
- package/src/components/ui/textarea.tsx +0 -22
- package/src/components/ui/toggle-group.tsx +0 -71
- package/src/components/ui/toggle.tsx +0 -47
- package/src/components/ui/tooltip.tsx +0 -66
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useIsMobile.ts +0 -77
- package/src/index.ts +0 -16
- package/src/lib/utils.ts +0 -6
- package/src/styles/globals.css +0 -181
- package/src/styles/themes/index.css +0 -9
- package/src/styles/themes/semantic.css +0 -117
- package/src/styles/tokens/colors.css +0 -124
- package/src/styles/tokens/index.css +0 -15
- package/src/styles/tokens/radius.css +0 -18
- package/src/styles/tokens/spacing.css +0 -58
- package/src/styles/tokens/typography.css +0 -87
- package/src/tokens/index.ts +0 -108
- package/tsconfig.json +0 -20
- package/vite.config.js +0 -49
- /package/{src/images/enso.png → dist/enso-22FJ4GNK.png} +0 -0
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cn } from "@/lib/utils";
|
|
3
|
-
import { Card } from "@/components/ui/card";
|
|
4
|
-
import { IconAnimation } from "@/components/ui/icon-animation";
|
|
5
|
-
import { Button } from "@/components/ui/button";
|
|
6
|
-
import { CheckIcon, TurtleIcon, XIcon } from "lucide-react";
|
|
7
|
-
|
|
8
|
-
interface TxStatusProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
|
-
title?: string;
|
|
10
|
-
description?: string;
|
|
11
|
-
txHash?: string;
|
|
12
|
-
explorerUrl?: string;
|
|
13
|
-
estimatedTime?: string;
|
|
14
|
-
amount?: string;
|
|
15
|
-
token?: string;
|
|
16
|
-
protocol?: string;
|
|
17
|
-
steps?: Array<{
|
|
18
|
-
label: string;
|
|
19
|
-
completed: boolean;
|
|
20
|
-
current?: boolean;
|
|
21
|
-
txHash?: string;
|
|
22
|
-
}>;
|
|
23
|
-
variant?: "default" | "compact";
|
|
24
|
-
completed?: boolean;
|
|
25
|
-
cancelled?: boolean;
|
|
26
|
-
onViewDetails?: () => void;
|
|
27
|
-
onClose?: () => void;
|
|
28
|
-
showActions?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const TxStatus = React.forwardRef<HTMLDivElement, TxStatusProps>(
|
|
32
|
-
(
|
|
33
|
-
{
|
|
34
|
-
className,
|
|
35
|
-
title,
|
|
36
|
-
description,
|
|
37
|
-
txHash,
|
|
38
|
-
explorerUrl,
|
|
39
|
-
estimatedTime,
|
|
40
|
-
amount,
|
|
41
|
-
token,
|
|
42
|
-
protocol,
|
|
43
|
-
steps = [],
|
|
44
|
-
variant = "default",
|
|
45
|
-
completed = false,
|
|
46
|
-
cancelled = false,
|
|
47
|
-
onViewDetails,
|
|
48
|
-
onClose,
|
|
49
|
-
showActions = true,
|
|
50
|
-
...props
|
|
51
|
-
},
|
|
52
|
-
ref,
|
|
53
|
-
) => {
|
|
54
|
-
const defaultTitle = cancelled
|
|
55
|
-
? "Transaction Cancelled"
|
|
56
|
-
: completed
|
|
57
|
-
? "Transaction Completed"
|
|
58
|
-
: "Processing Transaction";
|
|
59
|
-
const defaultDescription = cancelled
|
|
60
|
-
? "Transaction was cancelled by user"
|
|
61
|
-
: completed
|
|
62
|
-
? "Your transaction has been successfully processed!"
|
|
63
|
-
: "Please wait while your transaction is being processed...";
|
|
64
|
-
|
|
65
|
-
const ExternalLinkIcon = ({ size = "w-4 h-4" }) => (
|
|
66
|
-
<svg
|
|
67
|
-
className={size}
|
|
68
|
-
fill="none"
|
|
69
|
-
stroke="currentColor"
|
|
70
|
-
viewBox="0 0 24 24"
|
|
71
|
-
>
|
|
72
|
-
<path
|
|
73
|
-
strokeLinecap="round"
|
|
74
|
-
strokeLinejoin="round"
|
|
75
|
-
strokeWidth={2}
|
|
76
|
-
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
77
|
-
/>
|
|
78
|
-
</svg>
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const StepCheckIcon = () => (
|
|
82
|
-
<svg
|
|
83
|
-
className="h-4 w-4"
|
|
84
|
-
fill="none"
|
|
85
|
-
stroke="currentColor"
|
|
86
|
-
viewBox="0 0 24 24"
|
|
87
|
-
>
|
|
88
|
-
<path
|
|
89
|
-
strokeLinecap="round"
|
|
90
|
-
strokeLinejoin="round"
|
|
91
|
-
strokeWidth={2}
|
|
92
|
-
d="M5 13l4 4L19 7"
|
|
93
|
-
/>
|
|
94
|
-
</svg>
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const formatTxHash = (hash: string) => {
|
|
98
|
-
if (hash.length <= 12) return hash;
|
|
99
|
-
return `${hash.slice(0, 6)}...${hash.slice(-6)}`;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<Card
|
|
104
|
-
ref={ref}
|
|
105
|
-
className={cn(
|
|
106
|
-
"space-y-3 p-4 text-center",
|
|
107
|
-
variant === "compact" && "space-y-2 p-3",
|
|
108
|
-
className,
|
|
109
|
-
)}
|
|
110
|
-
{...props}
|
|
111
|
-
>
|
|
112
|
-
{/* Animation */}
|
|
113
|
-
<div className="flex justify-center">
|
|
114
|
-
<IconAnimation
|
|
115
|
-
spinning={!completed && !cancelled}
|
|
116
|
-
size={variant === "compact" ? "sm" : "default"}
|
|
117
|
-
>
|
|
118
|
-
{cancelled ? (
|
|
119
|
-
<XIcon
|
|
120
|
-
className={cn(
|
|
121
|
-
variant === "compact" ? "h-5 w-5" : "h-6 w-6",
|
|
122
|
-
"text-destructive",
|
|
123
|
-
)}
|
|
124
|
-
/>
|
|
125
|
-
) : completed ? (
|
|
126
|
-
<CheckIcon
|
|
127
|
-
className={cn(
|
|
128
|
-
variant === "compact" ? "h-5 w-5" : "h-6 w-6",
|
|
129
|
-
"text-primary",
|
|
130
|
-
)}
|
|
131
|
-
/>
|
|
132
|
-
) : (
|
|
133
|
-
<TurtleIcon
|
|
134
|
-
className={cn(
|
|
135
|
-
variant === "compact" ? "h-5 w-5" : "h-6 w-6",
|
|
136
|
-
"text-primary",
|
|
137
|
-
)}
|
|
138
|
-
/>
|
|
139
|
-
)}
|
|
140
|
-
</IconAnimation>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
{/* Title and description */}
|
|
144
|
-
<div className="space-y-1">
|
|
145
|
-
<h3
|
|
146
|
-
className={cn(
|
|
147
|
-
"text-foreground font-medium",
|
|
148
|
-
variant === "compact" ? "text-xs" : "text-sm",
|
|
149
|
-
)}
|
|
150
|
-
>
|
|
151
|
-
{title || defaultTitle}
|
|
152
|
-
</h3>
|
|
153
|
-
<p
|
|
154
|
-
className={cn(
|
|
155
|
-
"text-muted-foreground",
|
|
156
|
-
variant === "compact" ? "text-xs" : "text-xs",
|
|
157
|
-
)}
|
|
158
|
-
>
|
|
159
|
-
{description || defaultDescription}
|
|
160
|
-
</p>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
{/* Transaction summary (completed state) */}
|
|
164
|
-
{completed && !cancelled && (amount || token || protocol) && (
|
|
165
|
-
<div className="bg-muted/50 space-y-1 rounded-lg p-2">
|
|
166
|
-
{amount && token && (
|
|
167
|
-
<div className="text-xs font-medium">
|
|
168
|
-
{amount} {token}
|
|
169
|
-
</div>
|
|
170
|
-
)}
|
|
171
|
-
{protocol && (
|
|
172
|
-
<div className="text-muted-foreground text-xs">
|
|
173
|
-
via {protocol}
|
|
174
|
-
</div>
|
|
175
|
-
)}
|
|
176
|
-
</div>
|
|
177
|
-
)}
|
|
178
|
-
|
|
179
|
-
{/* Transaction hash link */}
|
|
180
|
-
{txHash && !cancelled && (
|
|
181
|
-
<div className="space-y-1">
|
|
182
|
-
<div className="text-muted-foreground text-xs">
|
|
183
|
-
Transaction Hash
|
|
184
|
-
</div>
|
|
185
|
-
{explorerUrl ? (
|
|
186
|
-
<a
|
|
187
|
-
href={`${explorerUrl}/tx/${txHash}`}
|
|
188
|
-
target="_blank"
|
|
189
|
-
rel="noopener noreferrer"
|
|
190
|
-
className="text-primary hover:text-primary/80 inline-flex items-center gap-2 text-xs transition-colors"
|
|
191
|
-
>
|
|
192
|
-
{formatTxHash(txHash)}
|
|
193
|
-
<ExternalLinkIcon />
|
|
194
|
-
</a>
|
|
195
|
-
) : (
|
|
196
|
-
<div className="text-foreground font-mono text-xs">
|
|
197
|
-
{formatTxHash(txHash)}
|
|
198
|
-
</div>
|
|
199
|
-
)}
|
|
200
|
-
</div>
|
|
201
|
-
)}
|
|
202
|
-
|
|
203
|
-
{/* Estimated time (pending state) */}
|
|
204
|
-
{!completed && !cancelled && estimatedTime && (
|
|
205
|
-
<div className="text-muted-foreground text-xs">
|
|
206
|
-
Estimated time: {estimatedTime}
|
|
207
|
-
</div>
|
|
208
|
-
)}
|
|
209
|
-
|
|
210
|
-
{/* Progress steps (pending state) */}
|
|
211
|
-
{!completed && !cancelled && steps.length > 0 && (
|
|
212
|
-
<div className="border-border space-y-2 border-t pt-2">
|
|
213
|
-
<div className="text-muted-foreground text-left text-xs font-medium">
|
|
214
|
-
Progress
|
|
215
|
-
</div>
|
|
216
|
-
<div className="space-y-2">
|
|
217
|
-
{steps.map((step, index) => (
|
|
218
|
-
<div key={index} className="flex items-center gap-3 text-left">
|
|
219
|
-
<div
|
|
220
|
-
className={cn(
|
|
221
|
-
"flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full border-2",
|
|
222
|
-
step.completed &&
|
|
223
|
-
"bg-primary border-primary text-primary-foreground",
|
|
224
|
-
step.current &&
|
|
225
|
-
!step.completed &&
|
|
226
|
-
"border-primary text-primary animate-pulse",
|
|
227
|
-
!step.completed &&
|
|
228
|
-
!step.current &&
|
|
229
|
-
"border-muted-foreground/30 text-muted-foreground",
|
|
230
|
-
)}
|
|
231
|
-
>
|
|
232
|
-
{step.completed ? (
|
|
233
|
-
<StepCheckIcon />
|
|
234
|
-
) : step.current ? (
|
|
235
|
-
<div className="bg-primary h-2 w-2 animate-pulse rounded-full" />
|
|
236
|
-
) : (
|
|
237
|
-
<div className="bg-muted-foreground/30 h-2 w-2 rounded-full" />
|
|
238
|
-
)}
|
|
239
|
-
</div>
|
|
240
|
-
<div className="flex flex-1 items-center justify-between">
|
|
241
|
-
<span
|
|
242
|
-
className={cn(
|
|
243
|
-
"text-xs",
|
|
244
|
-
step.completed && "text-foreground",
|
|
245
|
-
step.current &&
|
|
246
|
-
!step.completed &&
|
|
247
|
-
"text-foreground font-medium",
|
|
248
|
-
!step.completed &&
|
|
249
|
-
!step.current &&
|
|
250
|
-
"text-muted-foreground",
|
|
251
|
-
)}
|
|
252
|
-
>
|
|
253
|
-
{step.label}
|
|
254
|
-
</span>
|
|
255
|
-
{step.txHash && explorerUrl && (
|
|
256
|
-
<a
|
|
257
|
-
href={`${explorerUrl}/tx/${step.txHash}`}
|
|
258
|
-
target="_blank"
|
|
259
|
-
rel="noopener noreferrer"
|
|
260
|
-
className="text-primary hover:text-primary/80 ml-2 inline-flex items-center gap-1 text-xs transition-colors"
|
|
261
|
-
title={`View transaction: ${step.txHash}`}
|
|
262
|
-
>
|
|
263
|
-
<span className="font-mono">
|
|
264
|
-
{step.txHash.slice(0, 6)}...{step.txHash.slice(-4)}
|
|
265
|
-
</span>
|
|
266
|
-
<ExternalLinkIcon size="w-3 h-3" />
|
|
267
|
-
</a>
|
|
268
|
-
)}
|
|
269
|
-
</div>
|
|
270
|
-
</div>
|
|
271
|
-
))}
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
)}
|
|
275
|
-
|
|
276
|
-
{/* Action buttons (completed or cancelled state) */}
|
|
277
|
-
{(completed || cancelled) &&
|
|
278
|
-
showActions &&
|
|
279
|
-
(onViewDetails || onClose) && (
|
|
280
|
-
<div
|
|
281
|
-
className={cn(
|
|
282
|
-
"flex gap-2 pt-2",
|
|
283
|
-
variant === "compact" ? "flex-col" : "flex-row justify-center",
|
|
284
|
-
)}
|
|
285
|
-
>
|
|
286
|
-
{!cancelled && onViewDetails && (
|
|
287
|
-
<Button
|
|
288
|
-
size={variant === "compact" ? "sm" : "default"}
|
|
289
|
-
onClick={onViewDetails}
|
|
290
|
-
>
|
|
291
|
-
View Details
|
|
292
|
-
</Button>
|
|
293
|
-
)}
|
|
294
|
-
{onClose && (
|
|
295
|
-
<Button
|
|
296
|
-
variant="default"
|
|
297
|
-
size={variant === "compact" ? "sm" : "default"}
|
|
298
|
-
onClick={onClose}
|
|
299
|
-
>
|
|
300
|
-
{cancelled ? "Try Again" : "Done"}
|
|
301
|
-
</Button>
|
|
302
|
-
)}
|
|
303
|
-
</div>
|
|
304
|
-
)}
|
|
305
|
-
</Card>
|
|
306
|
-
);
|
|
307
|
-
},
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
TxStatus.displayName = "TxStatus";
|
|
311
|
-
|
|
312
|
-
export { TxStatus };
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import React, { useCallback } from "react";
|
|
2
|
-
|
|
3
|
-
import { Badge } from "@/components/ui/badge";
|
|
4
|
-
import { NavigationBar, NavigationItem } from "@/components/ui/navigation-bar";
|
|
5
|
-
import { SegmentControl } from "@/components/ui";
|
|
6
|
-
import { SearchBar } from "@/components/features";
|
|
7
|
-
|
|
8
|
-
export interface AssetFilterItem {
|
|
9
|
-
id: string;
|
|
10
|
-
label: string;
|
|
11
|
-
icon?: React.ReactNode;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface AssetFiltersProps {
|
|
15
|
-
// Available filters
|
|
16
|
-
filters: AssetFilterItem[];
|
|
17
|
-
// Currently active filter IDs
|
|
18
|
-
activeFilters: string[];
|
|
19
|
-
// Callback when filters change
|
|
20
|
-
onFiltersChange: (filterIds: string[]) => void;
|
|
21
|
-
// Display variant
|
|
22
|
-
variant?: "badge" | "navigation";
|
|
23
|
-
// Allow multiple filters (only for badge variant)
|
|
24
|
-
multipleFilters?: boolean;
|
|
25
|
-
// Search functionality
|
|
26
|
-
showSearch?: boolean;
|
|
27
|
-
searchValue?: string;
|
|
28
|
-
onSearchChange?: (value: string) => void;
|
|
29
|
-
searchPlaceholder?: string;
|
|
30
|
-
searchDebounceDelay?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const AssetFilters: React.FC<AssetFiltersProps> = ({
|
|
34
|
-
filters,
|
|
35
|
-
activeFilters,
|
|
36
|
-
onFiltersChange,
|
|
37
|
-
variant = "badge",
|
|
38
|
-
multipleFilters = true,
|
|
39
|
-
showSearch = true,
|
|
40
|
-
searchValue = "",
|
|
41
|
-
onSearchChange,
|
|
42
|
-
searchPlaceholder = "Search tokens...",
|
|
43
|
-
searchDebounceDelay = 300,
|
|
44
|
-
}) => {
|
|
45
|
-
// Handle filter toggle
|
|
46
|
-
const handleFilterToggle = useCallback(
|
|
47
|
-
(filterId: string) => {
|
|
48
|
-
if (variant === "badge" && multipleFilters) {
|
|
49
|
-
// Toggle filter in multi-select mode
|
|
50
|
-
const newFilters = activeFilters.includes(filterId)
|
|
51
|
-
? activeFilters.filter((id) => id !== filterId)
|
|
52
|
-
: [...activeFilters, filterId];
|
|
53
|
-
onFiltersChange(newFilters);
|
|
54
|
-
} else {
|
|
55
|
-
// Single select mode (navigation or single badge)
|
|
56
|
-
const newFilters =
|
|
57
|
-
activeFilters.includes(filterId) && activeFilters.length === 1
|
|
58
|
-
? [] // Deselect if it's the only active filter
|
|
59
|
-
: [filterId];
|
|
60
|
-
onFiltersChange(newFilters);
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
[activeFilters, onFiltersChange, variant, multipleFilters],
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div className="space-y-3">
|
|
68
|
-
{/* Search Bar */}
|
|
69
|
-
{showSearch && onSearchChange && (
|
|
70
|
-
<SearchBar
|
|
71
|
-
value={searchValue}
|
|
72
|
-
onChange={onSearchChange}
|
|
73
|
-
placeholder={searchPlaceholder}
|
|
74
|
-
debounce={searchDebounceDelay}
|
|
75
|
-
/>
|
|
76
|
-
)}
|
|
77
|
-
|
|
78
|
-
{/* Filters */}
|
|
79
|
-
{filters.length > 0 && (
|
|
80
|
-
<>
|
|
81
|
-
{variant === "badge" ? (
|
|
82
|
-
<div className="flex flex-wrap gap-2 px-2">
|
|
83
|
-
{filters.map((filter) => (
|
|
84
|
-
<Badge
|
|
85
|
-
key={filter.id}
|
|
86
|
-
variant={
|
|
87
|
-
activeFilters.includes(filter.id) ? "default" : "muted"
|
|
88
|
-
}
|
|
89
|
-
className="bg-muted cursor-pointer rounded-full"
|
|
90
|
-
onClick={() => handleFilterToggle(filter.id)}
|
|
91
|
-
>
|
|
92
|
-
{filter.icon && <span className="mr-1">{filter.icon}</span>}
|
|
93
|
-
{filter.label}
|
|
94
|
-
</Badge>
|
|
95
|
-
))}
|
|
96
|
-
</div>
|
|
97
|
-
) : (
|
|
98
|
-
<SegmentControl
|
|
99
|
-
variant="segment"
|
|
100
|
-
className="w-full"
|
|
101
|
-
value={activeFilters[0]}
|
|
102
|
-
onChange={(e) => handleFilterToggle(e)}
|
|
103
|
-
items={filters.map((filter) => ({
|
|
104
|
-
value: filter.id,
|
|
105
|
-
label: filter.label,
|
|
106
|
-
}))}
|
|
107
|
-
/>
|
|
108
|
-
)}
|
|
109
|
-
</>
|
|
110
|
-
)}
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
};
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React, { useState } from "react";
|
|
3
|
-
import { WidgetListItems } from "../widget-list-items";
|
|
4
|
-
import { AssetRow } from "./asset-row";
|
|
5
|
-
import {
|
|
6
|
-
AssetFilters,
|
|
7
|
-
AssetFilterItem as BaseAssetFilter,
|
|
8
|
-
} from "./asset-filters";
|
|
9
|
-
import { useAssetFiltering, useAssetGrouping } from "./hooks";
|
|
10
|
-
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
11
|
-
import { cn } from "@/lib/utils";
|
|
12
|
-
|
|
13
|
-
export interface Asset {
|
|
14
|
-
id: string;
|
|
15
|
-
symbol: string;
|
|
16
|
-
name: string;
|
|
17
|
-
icon: React.ReactNode;
|
|
18
|
-
balance: string;
|
|
19
|
-
balanceUSD: string;
|
|
20
|
-
chainId: string;
|
|
21
|
-
chainName: string;
|
|
22
|
-
chainIcon?: React.ReactNode;
|
|
23
|
-
address?: string;
|
|
24
|
-
decimals?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface AssetFilterProp extends BaseAssetFilter {
|
|
28
|
-
filterFn: (asset: Asset) => boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface AssetListProps {
|
|
32
|
-
// Data
|
|
33
|
-
assets: Asset[];
|
|
34
|
-
onAssetClick?: (asset: Asset) => void;
|
|
35
|
-
|
|
36
|
-
// Display options
|
|
37
|
-
groupByChain?: boolean;
|
|
38
|
-
showChainIcon?: boolean;
|
|
39
|
-
|
|
40
|
-
// Filtering options
|
|
41
|
-
filters?: AssetFilterProp[];
|
|
42
|
-
filterVariant?: "badge" | "navigation";
|
|
43
|
-
multipleFilters?: boolean;
|
|
44
|
-
|
|
45
|
-
// Search options
|
|
46
|
-
showSearch?: boolean;
|
|
47
|
-
searchPlaceholder?: string;
|
|
48
|
-
searchDebounceDelay?: number;
|
|
49
|
-
|
|
50
|
-
// Styling
|
|
51
|
-
className?: string;
|
|
52
|
-
itemClassName?: string;
|
|
53
|
-
emptyState?: React.ReactNode;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Default filters
|
|
57
|
-
const defaultFilters: AssetFilterProp[] = [
|
|
58
|
-
{
|
|
59
|
-
id: "all",
|
|
60
|
-
label: "All",
|
|
61
|
-
filterFn: (asset) => parseFloat(asset.balance) > 0,
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
id: "stablecoins",
|
|
65
|
-
label: "Stables",
|
|
66
|
-
filterFn: (asset) =>
|
|
67
|
-
["USDC", "USDT", "DAI", "USD"].includes(asset.symbol.toUpperCase()) ||
|
|
68
|
-
["USDC", "USDT", "DAI", "USD"].includes(asset.name.toUpperCase()),
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
id: "eth",
|
|
72
|
-
label: "ETH",
|
|
73
|
-
filterFn: (asset) =>
|
|
74
|
-
["ETH"].includes(asset.symbol.toUpperCase()) ||
|
|
75
|
-
["ETH"].includes(asset.name.toUpperCase()),
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
id: "btc",
|
|
79
|
-
label: "BTC",
|
|
80
|
-
filterFn: (asset) =>
|
|
81
|
-
["BTC"].includes(asset.symbol.toUpperCase()) ||
|
|
82
|
-
["BTC"].includes(asset.name.toUpperCase()),
|
|
83
|
-
},
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
export const AssetList: React.FC<AssetListProps> = ({
|
|
87
|
-
assets,
|
|
88
|
-
onAssetClick,
|
|
89
|
-
groupByChain = true,
|
|
90
|
-
showChainIcon = false,
|
|
91
|
-
filters = defaultFilters,
|
|
92
|
-
filterVariant = "badge",
|
|
93
|
-
multipleFilters = false,
|
|
94
|
-
showSearch = true,
|
|
95
|
-
searchPlaceholder = "Search tokens...",
|
|
96
|
-
searchDebounceDelay = 300,
|
|
97
|
-
className,
|
|
98
|
-
itemClassName,
|
|
99
|
-
emptyState,
|
|
100
|
-
}) => {
|
|
101
|
-
const [searchQuery, setSearchQuery] = useState("");
|
|
102
|
-
const [activeFilterIds, setActiveFilterIds] = useState<string[]>(["all"]);
|
|
103
|
-
|
|
104
|
-
// Use custom hooks for filtering and grouping
|
|
105
|
-
const filteredAssets = useAssetFiltering({
|
|
106
|
-
assets,
|
|
107
|
-
searchQuery,
|
|
108
|
-
activeFilterIds,
|
|
109
|
-
filters,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const groups = useAssetGrouping({
|
|
113
|
-
assets: filteredAssets,
|
|
114
|
-
groupByChain,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const renderAsset = (asset: Asset) => (
|
|
118
|
-
<AssetRow
|
|
119
|
-
icon={
|
|
120
|
-
showChainIcon && asset.chainIcon ? (
|
|
121
|
-
<div className="relative">
|
|
122
|
-
{asset.icon}
|
|
123
|
-
<div className="bg-background border-border absolute -right-1 -bottom-1 flex h-4 w-4 items-center justify-center rounded-full border">
|
|
124
|
-
{asset.chainIcon}
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
) : (
|
|
128
|
-
asset.icon
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
symbol={asset.symbol}
|
|
132
|
-
name={asset.name}
|
|
133
|
-
balance={asset.balance}
|
|
134
|
-
balanceUSD={asset.balanceUSD}
|
|
135
|
-
onClick={() => onAssetClick?.(asset)}
|
|
136
|
-
className={itemClassName}
|
|
137
|
-
/>
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<div className={cn("flex h-full flex-col", className)}>
|
|
142
|
-
<div className="mb-8 flex-shrink-0">
|
|
143
|
-
<AssetFilters
|
|
144
|
-
filters={filters.map(({ filterFn, ...rest }) => rest)}
|
|
145
|
-
activeFilters={activeFilterIds}
|
|
146
|
-
onFiltersChange={setActiveFilterIds}
|
|
147
|
-
variant={filterVariant}
|
|
148
|
-
multipleFilters={multipleFilters}
|
|
149
|
-
showSearch={showSearch}
|
|
150
|
-
searchValue={searchQuery}
|
|
151
|
-
onSearchChange={setSearchQuery}
|
|
152
|
-
searchPlaceholder={searchPlaceholder}
|
|
153
|
-
searchDebounceDelay={searchDebounceDelay}
|
|
154
|
-
/>
|
|
155
|
-
</div>
|
|
156
|
-
|
|
157
|
-
<ScrollArea className="flex-1">
|
|
158
|
-
<div className="pb-4">
|
|
159
|
-
<WidgetListItems
|
|
160
|
-
groups={groups}
|
|
161
|
-
renderItem={renderAsset}
|
|
162
|
-
getItemKey={(asset) => asset.id}
|
|
163
|
-
showGroupHeaders={groupByChain}
|
|
164
|
-
emptyState={
|
|
165
|
-
emptyState || (
|
|
166
|
-
<div className="text-muted-foreground py-8 text-center">
|
|
167
|
-
{searchQuery || activeFilterIds.length > 0
|
|
168
|
-
? "No assets match your filters"
|
|
169
|
-
: "No assets available"}
|
|
170
|
-
</div>
|
|
171
|
-
)
|
|
172
|
-
}
|
|
173
|
-
/>
|
|
174
|
-
</div>
|
|
175
|
-
</ScrollArea>
|
|
176
|
-
</div>
|
|
177
|
-
);
|
|
178
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { cn } from "@/lib/utils";
|
|
3
|
-
|
|
4
|
-
export interface AssetRowProps {
|
|
5
|
-
icon: React.ReactNode;
|
|
6
|
-
symbol: string;
|
|
7
|
-
name: string;
|
|
8
|
-
balance: string;
|
|
9
|
-
balanceUSD: string;
|
|
10
|
-
onClick?: () => void;
|
|
11
|
-
className?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const AssetRow: React.FC<AssetRowProps> = ({
|
|
15
|
-
icon,
|
|
16
|
-
symbol,
|
|
17
|
-
name,
|
|
18
|
-
balance,
|
|
19
|
-
balanceUSD,
|
|
20
|
-
onClick,
|
|
21
|
-
className,
|
|
22
|
-
}) => {
|
|
23
|
-
return (
|
|
24
|
-
<div
|
|
25
|
-
onClick={onClick}
|
|
26
|
-
className={cn(
|
|
27
|
-
"flex cursor-pointer items-center justify-between rounded-lg px-3.5 py-2.5 transition-colors",
|
|
28
|
-
"hover:bg-muted-foreground/60",
|
|
29
|
-
className,
|
|
30
|
-
)}
|
|
31
|
-
>
|
|
32
|
-
<div className="flex items-center gap-3">
|
|
33
|
-
<span className="text-xl">{icon}</span>
|
|
34
|
-
<div>
|
|
35
|
-
<div className="text-sm font-medium">{symbol}</div>
|
|
36
|
-
<div className="text-muted-foreground text-xs">{name}</div>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
<div className="text-right">
|
|
40
|
-
<div className="text-sm font-medium">{balance}</div>
|
|
41
|
-
<div className="text-muted-foreground text-xs">({balanceUSD})</div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
);
|
|
45
|
-
};
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
|
-
import { Asset, AssetFilterProp } from "../asset-list";
|
|
3
|
-
|
|
4
|
-
interface UseAssetFilteringProps {
|
|
5
|
-
assets: Asset[];
|
|
6
|
-
searchQuery: string;
|
|
7
|
-
activeFilterIds: string[];
|
|
8
|
-
filters: AssetFilterProp[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function useAssetFiltering({
|
|
12
|
-
assets,
|
|
13
|
-
searchQuery,
|
|
14
|
-
activeFilterIds,
|
|
15
|
-
filters,
|
|
16
|
-
}: UseAssetFilteringProps): Asset[] {
|
|
17
|
-
return useMemo(() => {
|
|
18
|
-
let result = assets;
|
|
19
|
-
|
|
20
|
-
// Apply search filter
|
|
21
|
-
if (searchQuery) {
|
|
22
|
-
const query = searchQuery.toLowerCase();
|
|
23
|
-
result = result.filter(
|
|
24
|
-
(asset) =>
|
|
25
|
-
asset.symbol.toLowerCase().includes(query) ||
|
|
26
|
-
asset.name.toLowerCase().includes(query) ||
|
|
27
|
-
asset.address?.toLowerCase().includes(query),
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Apply active filters
|
|
32
|
-
if (activeFilterIds.length > 0) {
|
|
33
|
-
const activeFilterFns = filters
|
|
34
|
-
.filter((f) => activeFilterIds.includes(f.id))
|
|
35
|
-
.map((f) => f.filterFn);
|
|
36
|
-
|
|
37
|
-
result = result.filter((asset) =>
|
|
38
|
-
activeFilterFns.every((filterFn) => filterFn(asset)),
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return result;
|
|
43
|
-
}, [assets, searchQuery, activeFilterIds, filters]);
|
|
44
|
-
}
|