@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,200 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { useState, useCallback, useEffect } from "react";
|
|
3
|
-
import { Edit3, InfoIcon } from "lucide-react";
|
|
4
|
-
import { cn } from "@/lib/utils";
|
|
5
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui";
|
|
6
|
-
|
|
7
|
-
const SLIPPAGE_LIMITS = {
|
|
8
|
-
MIN: 0.1,
|
|
9
|
-
MAX: 50,
|
|
10
|
-
} as const;
|
|
11
|
-
|
|
12
|
-
const VALIDATION_REGEX = /^\d*\.?\d*$/;
|
|
13
|
-
|
|
14
|
-
const ICON_SIZE = 10;
|
|
15
|
-
const INPUT_WIDTH = "w-10";
|
|
16
|
-
|
|
17
|
-
interface SlippageSelectorProps {
|
|
18
|
-
/** Current slippage value as percentage */
|
|
19
|
-
value: number;
|
|
20
|
-
/** Callback when slippage value changes */
|
|
21
|
-
onChange: (value: number) => void;
|
|
22
|
-
/** Additional CSS classes */
|
|
23
|
-
className?: string;
|
|
24
|
-
/** Label text for the selector */
|
|
25
|
-
label?: string;
|
|
26
|
-
/** Minimum allowed slippage value */
|
|
27
|
-
minValue?: number;
|
|
28
|
-
/** Maximum allowed slippage value */
|
|
29
|
-
maxValue?: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Validation utilities
|
|
33
|
-
const isValidSlippageInput = (input: string): boolean =>
|
|
34
|
-
VALIDATION_REGEX.test(input);
|
|
35
|
-
|
|
36
|
-
const isValidSlippageValue = (
|
|
37
|
-
value: number,
|
|
38
|
-
min: number,
|
|
39
|
-
max: number,
|
|
40
|
-
): boolean => !isNaN(value) && value >= min && value <= max;
|
|
41
|
-
|
|
42
|
-
const formatSlippageValue = (value: number): string => value.toString();
|
|
43
|
-
|
|
44
|
-
export function SlippageSelector({
|
|
45
|
-
value,
|
|
46
|
-
onChange,
|
|
47
|
-
className,
|
|
48
|
-
label = "Max Slippage",
|
|
49
|
-
minValue = SLIPPAGE_LIMITS.MIN,
|
|
50
|
-
maxValue = SLIPPAGE_LIMITS.MAX,
|
|
51
|
-
}: SlippageSelectorProps) {
|
|
52
|
-
const [isEditing, setIsEditing] = useState(false);
|
|
53
|
-
const [inputValue, setInputValue] = useState(formatSlippageValue(value));
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
if (!isEditing) {
|
|
57
|
-
setInputValue(formatSlippageValue(value));
|
|
58
|
-
}
|
|
59
|
-
}, [value, isEditing]);
|
|
60
|
-
|
|
61
|
-
const startEditing = useCallback(() => {
|
|
62
|
-
setIsEditing(true);
|
|
63
|
-
setInputValue(formatSlippageValue(value * 100));
|
|
64
|
-
}, [value]);
|
|
65
|
-
|
|
66
|
-
const cancelEditing = useCallback(() => {
|
|
67
|
-
setInputValue(formatSlippageValue(value * 100));
|
|
68
|
-
setIsEditing(false);
|
|
69
|
-
}, [value]);
|
|
70
|
-
|
|
71
|
-
const submitValue = useCallback(() => {
|
|
72
|
-
const numValue = parseFloat(inputValue);
|
|
73
|
-
|
|
74
|
-
if (isValidSlippageValue(numValue, minValue, maxValue)) {
|
|
75
|
-
// Convert to percentage
|
|
76
|
-
onChange(numValue / 100);
|
|
77
|
-
setIsEditing(false);
|
|
78
|
-
} else {
|
|
79
|
-
// Reset to current value if invalid
|
|
80
|
-
cancelEditing();
|
|
81
|
-
}
|
|
82
|
-
}, [inputValue, minValue, maxValue, onChange, cancelEditing]);
|
|
83
|
-
|
|
84
|
-
const handleInputChange = useCallback(
|
|
85
|
-
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
86
|
-
const newValue = e.target.value;
|
|
87
|
-
|
|
88
|
-
// First check if the input format is valid (regex validation)
|
|
89
|
-
if (!isValidSlippageInput(newValue)) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// If it's an empty string or just a decimal point, allow it for UX
|
|
94
|
-
if (newValue === "" || newValue === ".") {
|
|
95
|
-
setInputValue(newValue);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Check if the numeric value exceeds the maximum limit
|
|
100
|
-
const numValue = parseFloat(newValue);
|
|
101
|
-
if (!isNaN(numValue) && numValue > maxValue) {
|
|
102
|
-
return; // Block input that exceeds maxValue
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
setInputValue(newValue);
|
|
106
|
-
},
|
|
107
|
-
[maxValue],
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
const handleKeyDown = useCallback(
|
|
111
|
-
(e: React.KeyboardEvent) => {
|
|
112
|
-
switch (e.key) {
|
|
113
|
-
case "Enter":
|
|
114
|
-
e.preventDefault();
|
|
115
|
-
submitValue();
|
|
116
|
-
break;
|
|
117
|
-
case "Escape":
|
|
118
|
-
e.preventDefault();
|
|
119
|
-
cancelEditing();
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
[submitValue, cancelEditing],
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
const handleBlur = useCallback(() => {
|
|
127
|
-
submitValue();
|
|
128
|
-
}, [submitValue]);
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<div className={cn("flex items-center justify-between", className)}>
|
|
132
|
-
<span
|
|
133
|
-
className="text-muted-foreground text-xs font-medium"
|
|
134
|
-
id="slippage-label"
|
|
135
|
-
>
|
|
136
|
-
<div className="flex items-center">
|
|
137
|
-
{label}
|
|
138
|
-
<Tooltip>
|
|
139
|
-
<TooltipTrigger>
|
|
140
|
-
<InfoIcon className="ml-1 size-3" />
|
|
141
|
-
</TooltipTrigger>
|
|
142
|
-
<TooltipContent>
|
|
143
|
-
<p className="text-xs">
|
|
144
|
-
Must be between <span className="font-bold">0.1%</span> and{" "}
|
|
145
|
-
<span className="font-bold">50%</span>
|
|
146
|
-
</p>
|
|
147
|
-
</TooltipContent>
|
|
148
|
-
</Tooltip>
|
|
149
|
-
</div>
|
|
150
|
-
</span>
|
|
151
|
-
|
|
152
|
-
<div className="flex items-center gap-1">
|
|
153
|
-
{isEditing ? (
|
|
154
|
-
<input
|
|
155
|
-
type="text"
|
|
156
|
-
value={inputValue}
|
|
157
|
-
onChange={handleInputChange}
|
|
158
|
-
onKeyDown={handleKeyDown}
|
|
159
|
-
onBlur={handleBlur}
|
|
160
|
-
className={cn(
|
|
161
|
-
INPUT_WIDTH,
|
|
162
|
-
"border-none bg-transparent text-right text-xs outline-none",
|
|
163
|
-
"text-foreground focus:ring-0 focus:outline-none",
|
|
164
|
-
)}
|
|
165
|
-
aria-labelledby="slippage-label"
|
|
166
|
-
aria-describedby="slippage-range"
|
|
167
|
-
autoFocus
|
|
168
|
-
autoComplete="off"
|
|
169
|
-
spellCheck={false}
|
|
170
|
-
/>
|
|
171
|
-
) : (
|
|
172
|
-
<span
|
|
173
|
-
className="text-foreground text-xs font-medium"
|
|
174
|
-
aria-live="polite"
|
|
175
|
-
>
|
|
176
|
-
{value * 100}%
|
|
177
|
-
</span>
|
|
178
|
-
)}
|
|
179
|
-
|
|
180
|
-
<button
|
|
181
|
-
onClick={startEditing}
|
|
182
|
-
className={cn(
|
|
183
|
-
"hover:bg-muted rounded p-1 transition-colors",
|
|
184
|
-
"text-muted-foreground hover:text-foreground",
|
|
185
|
-
"focus:ring-ring focus:ring-2 focus:ring-offset-2 focus:outline-none",
|
|
186
|
-
)}
|
|
187
|
-
aria-label={`Edit slippage value. Current value: ${value * 100}%`}
|
|
188
|
-
type="button"
|
|
189
|
-
>
|
|
190
|
-
<Edit3 size={ICON_SIZE} />
|
|
191
|
-
</button>
|
|
192
|
-
</div>
|
|
193
|
-
|
|
194
|
-
{/* Screen reader helper text */}
|
|
195
|
-
<span id="slippage-range" className="sr-only">
|
|
196
|
-
Enter a value between {minValue}% and {maxValue}%
|
|
197
|
-
</span>
|
|
198
|
-
</div>
|
|
199
|
-
);
|
|
200
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { SlippageSelector } from "@/index";
|
|
3
|
-
|
|
4
|
-
interface SwapDetailsProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
-
details: {
|
|
6
|
-
deposit?: string;
|
|
7
|
-
receive?: string;
|
|
8
|
-
equivalentInUsd?: string;
|
|
9
|
-
defaultOpen?: boolean;
|
|
10
|
-
slippage?: number;
|
|
11
|
-
setSlippage?: (slippage: number) => void;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const SwapDetails = React.forwardRef<HTMLDivElement, SwapDetailsProps>(
|
|
16
|
-
({ className, details, ...props }, ref) => {
|
|
17
|
-
const { deposit, receive, equivalentInUsd, slippage, setSlippage } =
|
|
18
|
-
details;
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div className="text-foreground animate-in fade-in-0 space-y-2 px-2 py-4 transition-all duration-300">
|
|
22
|
-
{/* Amount Out*/}
|
|
23
|
-
<div className="flex items-center justify-between text-sm">
|
|
24
|
-
<span className="text-foreground text-sm font-semibold">
|
|
25
|
-
Amount Out
|
|
26
|
-
</span>
|
|
27
|
-
<span className="font-medium">{receive}</span>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
{/* Slippage Selector */}
|
|
31
|
-
{slippage !== undefined && setSlippage && (
|
|
32
|
-
<SlippageSelector
|
|
33
|
-
className="text-sm"
|
|
34
|
-
value={slippage}
|
|
35
|
-
onChange={setSlippage}
|
|
36
|
-
/>
|
|
37
|
-
)}
|
|
38
|
-
|
|
39
|
-
{/* Amount Out in USD */}
|
|
40
|
-
<div className="flex items-center gap-2">
|
|
41
|
-
<span className="text-sm font-medium">
|
|
42
|
-
{deposit} = {receive}
|
|
43
|
-
</span>
|
|
44
|
-
<span className="text-muted-foreground text-xs">
|
|
45
|
-
(≈ ${equivalentInUsd})
|
|
46
|
-
</span>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
},
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
SwapDetails.displayName = "SwapDetails";
|
|
54
|
-
|
|
55
|
-
export { SwapDetails };
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { cn } from "@/lib/utils";
|
|
3
|
-
import { Card } from "@/components/ui/card";
|
|
4
|
-
import { Input } from "@/components/ui/input";
|
|
5
|
-
import { Chip } from "@/components/ui/chip";
|
|
6
|
-
import { TokenSelector, type Token } from "./token-selector";
|
|
7
|
-
import { BaseSelector } from "./widget/base-selector";
|
|
8
|
-
|
|
9
|
-
interface SwapInputProps extends Omit<
|
|
10
|
-
React.HTMLAttributes<HTMLDivElement>,
|
|
11
|
-
"onChange"
|
|
12
|
-
> {
|
|
13
|
-
value?: string;
|
|
14
|
-
tokens: Token[];
|
|
15
|
-
selectedToken?: string;
|
|
16
|
-
usdValue?: string;
|
|
17
|
-
balance?: string;
|
|
18
|
-
placeholder?: string;
|
|
19
|
-
disabled?: boolean;
|
|
20
|
-
hideTokenSelector?: boolean;
|
|
21
|
-
isWalletConnected?: boolean;
|
|
22
|
-
onChange?: (value: string) => void;
|
|
23
|
-
onMaxClick?: () => void;
|
|
24
|
-
onTokenChange?: (tokenAddress: string) => void;
|
|
25
|
-
// New props for custom token selector
|
|
26
|
-
useCustomTokenSelector?: boolean;
|
|
27
|
-
selectedTokenData?: Token | null;
|
|
28
|
-
onCustomTokenSelectorClick?: () => void;
|
|
29
|
-
showTokenSelectorBalance?: boolean;
|
|
30
|
-
showInputBalance?: boolean;
|
|
31
|
-
triggerTokenSelectorWidth?: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const SwapInput = React.forwardRef<HTMLDivElement, SwapInputProps>(
|
|
35
|
-
(
|
|
36
|
-
{
|
|
37
|
-
className,
|
|
38
|
-
value = "",
|
|
39
|
-
tokens,
|
|
40
|
-
selectedToken,
|
|
41
|
-
usdValue,
|
|
42
|
-
balance,
|
|
43
|
-
placeholder = "0",
|
|
44
|
-
disabled = false,
|
|
45
|
-
hideTokenSelector = false,
|
|
46
|
-
isWalletConnected = true,
|
|
47
|
-
onChange,
|
|
48
|
-
onMaxClick,
|
|
49
|
-
onTokenChange,
|
|
50
|
-
// New props
|
|
51
|
-
useCustomTokenSelector = false,
|
|
52
|
-
selectedTokenData,
|
|
53
|
-
onCustomTokenSelectorClick,
|
|
54
|
-
showTokenSelectorBalance = true,
|
|
55
|
-
showInputBalance = true,
|
|
56
|
-
triggerTokenSelectorWidth,
|
|
57
|
-
...props
|
|
58
|
-
},
|
|
59
|
-
ref,
|
|
60
|
-
) => {
|
|
61
|
-
// Find the selected token for balance display
|
|
62
|
-
const currentToken = useCustomTokenSelector
|
|
63
|
-
? selectedTokenData
|
|
64
|
-
: tokens.find((token) => token.address === selectedToken);
|
|
65
|
-
|
|
66
|
-
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
67
|
-
const newValue = e.target.value;
|
|
68
|
-
// Allow only numbers and decimal point
|
|
69
|
-
if (/^\d*\.?\d*$/.test(newValue)) {
|
|
70
|
-
onChange?.(newValue);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// If wallet is not connected, show connection message
|
|
75
|
-
if (!isWalletConnected) {
|
|
76
|
-
return (
|
|
77
|
-
<Card
|
|
78
|
-
ref={ref}
|
|
79
|
-
className={cn("flex items-center justify-center py-8", className)}
|
|
80
|
-
{...props}
|
|
81
|
-
>
|
|
82
|
-
<div className="text-center">
|
|
83
|
-
<p className="text-muted-foreground text-sm">
|
|
84
|
-
Connect your wallet to continue
|
|
85
|
-
</p>
|
|
86
|
-
</div>
|
|
87
|
-
</Card>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<Card
|
|
93
|
-
variant="shadow"
|
|
94
|
-
ref={ref}
|
|
95
|
-
className={cn("space-y-3", disabled && "opacity-50", className)}
|
|
96
|
-
{...props}
|
|
97
|
-
>
|
|
98
|
-
{/* Main input row */}
|
|
99
|
-
<div className="flex items-center gap-6">
|
|
100
|
-
{/* Amount input */}
|
|
101
|
-
<div className="flex-1">
|
|
102
|
-
<Input
|
|
103
|
-
variant="nofocus"
|
|
104
|
-
value={value}
|
|
105
|
-
onChange={handleInputChange}
|
|
106
|
-
placeholder={placeholder}
|
|
107
|
-
disabled={disabled}
|
|
108
|
-
className="text-foreground/90 h-auto border-none bg-transparent p-0 text-4xl font-[400] shadow-none focus:ring-0"
|
|
109
|
-
/>
|
|
110
|
-
</div>
|
|
111
|
-
|
|
112
|
-
{/* Token selector - conditionally rendered */}
|
|
113
|
-
{!hideTokenSelector &&
|
|
114
|
-
(useCustomTokenSelector ? (
|
|
115
|
-
<BaseSelector
|
|
116
|
-
token={currentToken ?? undefined}
|
|
117
|
-
text={currentToken?.symbol || ""}
|
|
118
|
-
onClick={onCustomTokenSelectorClick}
|
|
119
|
-
placeholder="Select token"
|
|
120
|
-
size="sm"
|
|
121
|
-
variant="bordered"
|
|
122
|
-
className={cn(
|
|
123
|
-
"rounded-full",
|
|
124
|
-
disabled && "cursor-not-allowed opacity-50",
|
|
125
|
-
)}
|
|
126
|
-
showIcon={true}
|
|
127
|
-
/>
|
|
128
|
-
) : (
|
|
129
|
-
<TokenSelector
|
|
130
|
-
tokens={tokens}
|
|
131
|
-
value={selectedToken}
|
|
132
|
-
onValueChange={onTokenChange}
|
|
133
|
-
disabled={disabled}
|
|
134
|
-
placeholder="Select token"
|
|
135
|
-
size="sm"
|
|
136
|
-
variant="default"
|
|
137
|
-
className="rounded-full"
|
|
138
|
-
showBalance={showTokenSelectorBalance}
|
|
139
|
-
triggerWidth={triggerTokenSelectorWidth}
|
|
140
|
-
/>
|
|
141
|
-
))}
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
{/* Bottom row: USD value and balance with MAX */}
|
|
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>
|
|
176
|
-
</div>
|
|
177
|
-
)}
|
|
178
|
-
</Card>
|
|
179
|
-
);
|
|
180
|
-
},
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
SwapInput.displayName = "SwapInput";
|
|
184
|
-
|
|
185
|
-
export { SwapInput };
|
|
186
|
-
export type { Token, SwapInputProps };
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import React, { ReactNode, useMemo, useState } from "react";
|
|
2
|
-
import { NavigationBar, NavigationItem } from "../ui";
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
4
|
-
|
|
5
|
-
interface Tab {
|
|
6
|
-
value: string;
|
|
7
|
-
label: string;
|
|
8
|
-
content: ReactNode;
|
|
9
|
-
disabled?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface TabsProps {
|
|
13
|
-
tabs: Tab[];
|
|
14
|
-
defaultValue?: string;
|
|
15
|
-
value?: string;
|
|
16
|
-
onValueChange?: (value: string) => void;
|
|
17
|
-
layout?: (content: ReactNode) => ReactNode;
|
|
18
|
-
className?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const Tabs = ({
|
|
22
|
-
tabs,
|
|
23
|
-
defaultValue,
|
|
24
|
-
value,
|
|
25
|
-
onValueChange,
|
|
26
|
-
layout,
|
|
27
|
-
className,
|
|
28
|
-
}: TabsProps) => {
|
|
29
|
-
const [internalValue, setInternalValue] = useState(
|
|
30
|
-
defaultValue ||
|
|
31
|
-
value ||
|
|
32
|
-
tabs.find((tab) => !tab.disabled)?.value ||
|
|
33
|
-
tabs[0]?.value,
|
|
34
|
-
);
|
|
35
|
-
const activeTab = value ?? internalValue;
|
|
36
|
-
|
|
37
|
-
const handleTabChange = (newValue: string) => {
|
|
38
|
-
if (value === undefined) {
|
|
39
|
-
setInternalValue(newValue);
|
|
40
|
-
}
|
|
41
|
-
onValueChange?.(newValue);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const activeTabContent = useMemo(
|
|
45
|
-
() => tabs.find((tab) => tab.value === activeTab)?.content,
|
|
46
|
-
[activeTab, tabs],
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const layoutContent = useMemo(() => {
|
|
50
|
-
if (layout) {
|
|
51
|
-
return layout(activeTabContent);
|
|
52
|
-
}
|
|
53
|
-
return (
|
|
54
|
-
<div className="min-h-0 flex-1 overflow-hidden">{activeTabContent}</div>
|
|
55
|
-
);
|
|
56
|
-
}, [activeTabContent, layout]);
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<div className={cn("flex flex-col", className)}>
|
|
60
|
-
<NavigationBar variant="menuBar" activeValue={activeTab} className="mb-2">
|
|
61
|
-
{tabs.map((tab) => (
|
|
62
|
-
<NavigationItem
|
|
63
|
-
key={tab.value}
|
|
64
|
-
value={tab.value}
|
|
65
|
-
active={tab.value === activeTab}
|
|
66
|
-
onClick={() => !tab.disabled && handleTabChange(tab.value)}
|
|
67
|
-
disabled={tab.disabled}
|
|
68
|
-
variant="menuBarDefault"
|
|
69
|
-
>
|
|
70
|
-
{tab.label}
|
|
71
|
-
</NavigationItem>
|
|
72
|
-
))}
|
|
73
|
-
</NavigationBar>
|
|
74
|
-
{layoutContent}
|
|
75
|
-
</div>
|
|
76
|
-
);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export { Tabs, type TabsProps, type Tab };
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
4
|
-
import {
|
|
5
|
-
Select,
|
|
6
|
-
SelectContent,
|
|
7
|
-
SelectItem,
|
|
8
|
-
SelectTrigger,
|
|
9
|
-
} from "@/components/ui/select";
|
|
10
|
-
import { useEffect } from "react";
|
|
11
|
-
|
|
12
|
-
interface Token {
|
|
13
|
-
symbol: string;
|
|
14
|
-
address: string;
|
|
15
|
-
logoUrl?: string;
|
|
16
|
-
// Optional for backwards compatibility
|
|
17
|
-
icon?: React.ReactNode;
|
|
18
|
-
balance?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface TokenSelectorProps {
|
|
22
|
-
tokens: Token[];
|
|
23
|
-
value?: string;
|
|
24
|
-
onValueChange?: (value: string) => void;
|
|
25
|
-
placeholder?: string;
|
|
26
|
-
disabled?: boolean;
|
|
27
|
-
className?: string;
|
|
28
|
-
variant?: "default" | "bordered";
|
|
29
|
-
size?: "xs" | "sm" | "default";
|
|
30
|
-
showBalance?: boolean;
|
|
31
|
-
triggerWidth?: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const TokenSelector = ({
|
|
35
|
-
tokens,
|
|
36
|
-
value,
|
|
37
|
-
onValueChange,
|
|
38
|
-
placeholder = "Select token",
|
|
39
|
-
disabled,
|
|
40
|
-
className,
|
|
41
|
-
variant = "bordered",
|
|
42
|
-
size = "default",
|
|
43
|
-
showBalance = true,
|
|
44
|
-
triggerWidth,
|
|
45
|
-
}: TokenSelectorProps) => {
|
|
46
|
-
// Auto-select first token if no value is provided and tokens exist
|
|
47
|
-
const effectiveValue =
|
|
48
|
-
value || (tokens.length > 0 ? tokens[0].address : undefined);
|
|
49
|
-
|
|
50
|
-
// Find the selected token to display in the trigger
|
|
51
|
-
const selectedToken = tokens.find(
|
|
52
|
-
(token) => token.address === effectiveValue,
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
// Helper to get icon (either provided icon or generated from logoUrl)
|
|
56
|
-
const getTokenIcon = (token: Token) => {
|
|
57
|
-
if (token.icon) return token.icon;
|
|
58
|
-
if (token.logoUrl) {
|
|
59
|
-
return (
|
|
60
|
-
<img
|
|
61
|
-
src={token.logoUrl}
|
|
62
|
-
alt={token.symbol}
|
|
63
|
-
className="size-full rounded-full"
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Handle value change and auto-select first token on mount
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
if (!value && tokens.length > 0 && onValueChange) {
|
|
73
|
-
onValueChange(tokens[0].address);
|
|
74
|
-
}
|
|
75
|
-
}, [value, tokens, onValueChange]);
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<Select
|
|
79
|
-
value={effectiveValue}
|
|
80
|
-
onValueChange={onValueChange}
|
|
81
|
-
disabled={disabled}
|
|
82
|
-
>
|
|
83
|
-
<SelectTrigger
|
|
84
|
-
// variant={variant}
|
|
85
|
-
// size={size}
|
|
86
|
-
style={triggerWidth ? { width: `${triggerWidth}px` } : undefined}
|
|
87
|
-
className={cn(
|
|
88
|
-
"bg-muted hover:bg-muted/80 w-auto min-w-[80px] rounded-md font-medium",
|
|
89
|
-
"focus:ring-primary/20 focus:ring-2 focus:outline-none",
|
|
90
|
-
size === "xs" && "min-w-[80px] gap-2",
|
|
91
|
-
size === "sm" && "min-w-[75px] gap-1.5",
|
|
92
|
-
size === "default" && "min-w-[80px] gap-1.5",
|
|
93
|
-
className,
|
|
94
|
-
)}
|
|
95
|
-
>
|
|
96
|
-
{selectedToken ? (
|
|
97
|
-
<div
|
|
98
|
-
className={cn(
|
|
99
|
-
"flex items-center",
|
|
100
|
-
size === "xs" && "gap-2",
|
|
101
|
-
size === "sm" && "gap-1.5",
|
|
102
|
-
size === "default" && "gap-1.5",
|
|
103
|
-
)}
|
|
104
|
-
>
|
|
105
|
-
<span
|
|
106
|
-
className={cn(
|
|
107
|
-
"flex items-center justify-center",
|
|
108
|
-
size === "xs" && "h-3 w-3.5",
|
|
109
|
-
size === "sm" && "h-3.5 w-4",
|
|
110
|
-
size === "default" && "h-4 w-5",
|
|
111
|
-
)}
|
|
112
|
-
>
|
|
113
|
-
{getTokenIcon(selectedToken)}
|
|
114
|
-
</span>
|
|
115
|
-
<span
|
|
116
|
-
className={cn(
|
|
117
|
-
"font-medium",
|
|
118
|
-
size === "xs" && "text-xs",
|
|
119
|
-
size === "sm" && "text-sm",
|
|
120
|
-
size === "default" && "text-sm",
|
|
121
|
-
)}
|
|
122
|
-
>
|
|
123
|
-
{selectedToken.symbol}
|
|
124
|
-
</span>
|
|
125
|
-
</div>
|
|
126
|
-
) : (
|
|
127
|
-
<span
|
|
128
|
-
className={cn(
|
|
129
|
-
"text-muted-foreground",
|
|
130
|
-
size === "xs" && "text-xs",
|
|
131
|
-
size === "sm" && "text-sm",
|
|
132
|
-
size === "default" && "text-sm",
|
|
133
|
-
)}
|
|
134
|
-
>
|
|
135
|
-
{placeholder}
|
|
136
|
-
</span>
|
|
137
|
-
)}
|
|
138
|
-
</SelectTrigger>
|
|
139
|
-
<SelectContent className="border-muted gap-2 border">
|
|
140
|
-
{tokens.map((token) => (
|
|
141
|
-
<SelectItem
|
|
142
|
-
key={token.address}
|
|
143
|
-
value={token.address}
|
|
144
|
-
className="cursor-pointer py-2"
|
|
145
|
-
>
|
|
146
|
-
<div className="flex flex-col gap-2">
|
|
147
|
-
<div className="flex items-center gap-2">
|
|
148
|
-
<span
|
|
149
|
-
className={cn(
|
|
150
|
-
"flex items-center justify-center",
|
|
151
|
-
size === "xs" && "h-3.5 w-3.5",
|
|
152
|
-
size === "sm" && "h-4 w-4",
|
|
153
|
-
size === "default" && "h-4 w-5",
|
|
154
|
-
)}
|
|
155
|
-
>
|
|
156
|
-
{getTokenIcon(token)}
|
|
157
|
-
</span>
|
|
158
|
-
<span
|
|
159
|
-
className={cn(
|
|
160
|
-
"font-medium",
|
|
161
|
-
size === "xs" && "text-xs",
|
|
162
|
-
size === "sm" && "text-sm",
|
|
163
|
-
size === "default" && "text-sm",
|
|
164
|
-
)}
|
|
165
|
-
>
|
|
166
|
-
{token.symbol}
|
|
167
|
-
</span>
|
|
168
|
-
</div>
|
|
169
|
-
{showBalance && (
|
|
170
|
-
<span className="text-xs text-white/80">≈ {token.balance}</span>
|
|
171
|
-
)}
|
|
172
|
-
</div>
|
|
173
|
-
</SelectItem>
|
|
174
|
-
))}
|
|
175
|
-
</SelectContent>
|
|
176
|
-
</Select>
|
|
177
|
-
);
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
export { TokenSelector, type Token };
|