@xswap-link/sdk 0.14.1 → 0.15.1
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/CHANGELOG.md +12 -0
- package/dist/index.global.js +99 -93
- package/dist/index.js +2061 -1846
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1901 -1689
- package/dist/index.mjs.map +1 -1
- package/localTheme.ts +1 -1
- package/package.json +2 -1
- package/postcss.config.js +2 -0
- package/src/assets/icons/ChainlinkMarkIcon.tsx +4 -5
- package/src/assets/icons/CheckCircleThinIcon.tsx +22 -0
- package/src/assets/icons/ChevronDownThinIcon.tsx +39 -0
- package/src/assets/icons/ChevronDownWideThinIcon.tsx +18 -0
- package/src/assets/icons/ChevronRightThinIcon.tsx +23 -0
- package/src/assets/icons/ChevronsUpDownThinIcon.tsx +21 -0
- package/src/assets/icons/CloseIcon.tsx +3 -2
- package/src/assets/icons/ErrorCircleThinIcon.tsx +22 -0
- package/src/assets/icons/InfoThinIcon.tsx +33 -0
- package/src/assets/icons/LinkExternalThinIcon.tsx +19 -0
- package/src/assets/icons/LoadingRingIcon.tsx +22 -0
- package/src/assets/icons/ProgressRingThinIcon.tsx +29 -0
- package/src/assets/icons/RedirectThinIcon.tsx +15 -0
- package/src/assets/icons/SearchThinIcon.tsx +15 -0
- package/src/assets/icons/SignThinIcon.tsx +15 -0
- package/src/assets/icons/WalletCheckThinIcon.tsx +24 -0
- package/src/assets/icons/index.ts +14 -0
- package/src/components/PoweredBy/index.tsx +1 -1
- package/src/components/Swap/Header/index.tsx +5 -5
- package/src/components/Swap/HistoryView/ActivityCard/index.tsx +110 -115
- package/src/components/Swap/HistoryView/index.tsx +10 -7
- package/src/components/Swap/ReorderButton/ReorderButton.tsx +7 -4
- package/src/components/Swap/SettingsView/Delivery/index.tsx +20 -18
- package/src/components/Swap/SettingsView/InfiniteApproval/index.tsx +20 -18
- package/src/components/Swap/SettingsView/Slippage/index.tsx +57 -49
- package/src/components/Swap/SwapView/ConfirmationView/TokenTiles/index.tsx +72 -0
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/Header/index.tsx +13 -25
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/Steps/index.tsx +65 -113
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +19 -23
- package/src/components/Swap/SwapView/ConfirmationView/TxResult/index.tsx +47 -69
- package/src/components/Swap/SwapView/FeesPanel/Fees/index.tsx +1 -1
- package/src/components/Swap/SwapView/FeesPanel/index.tsx +6 -4
- package/src/components/Swap/SwapView/SwapButton/index.tsx +0 -29
- package/src/components/Swap/SwapView/SwapPanel/AmountPanel/index.tsx +11 -2
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/ChainPicker/index.tsx +4 -4
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/NetworkFilter/index.tsx +62 -51
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/TokenItem/index.tsx +1 -1
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +94 -43
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +27 -5
- package/src/components/Swap/SwapView/SwapPanel/UsdValue/index.tsx +2 -2
- package/src/components/Swap/SwapView/SwapPanel/WalletPanel/index.tsx +23 -18
- package/src/components/Swap/SwapView/SwapPanel/index.tsx +17 -5
- package/src/components/Swap/SwapView/WalletPicker/index.tsx +131 -139
- package/src/components/Swap/SwapView/index.tsx +11 -7
- package/src/components/Swap/WalletPickerView/index.tsx +65 -0
- package/src/components/Swap/index.tsx +23 -6
- package/src/components/ToggleButton/index.tsx +6 -6
- package/src/components/TokenLogo/index.tsx +1 -1
- package/src/components/TxModal/index.tsx +13 -7
- package/src/components/global.css +103 -1
- package/src/config/fonts.ts +15 -2
- package/src/constants/index.ts +4 -0
- package/src/context/SwapProvider.tsx +106 -19
- package/src/context/TxUIWrapper.tsx +0 -1
- package/src/utils/validation.ts +9 -4
- package/tailwind.config.js +3 -9
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/ArrowIcon.tsx +0 -13
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/SwapPanel/index.tsx +0 -72
- package/src/components/Swap/SwapView/ConfirmationView/TxResult/TokenItem/index.tsx +0 -25
package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/NetworkFilter/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ChevronDownThinIcon, SearchThinIcon } from "@src/assets/icons";
|
|
2
2
|
import { useSwapContext } from "@src/context";
|
|
3
3
|
import { SwapPanelType } from "@src/components/Swap/SwapView";
|
|
4
4
|
import { Chain } from "@src/models";
|
|
@@ -56,14 +56,15 @@ export const NetworkFilter = ({ type, selectedChain, onSelect }: Props) => {
|
|
|
56
56
|
return () => document.removeEventListener("mousedown", onPointerDown);
|
|
57
57
|
}, []);
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
// 2x2 icon cluster shown when no network is picked
|
|
60
|
+
const clusterChains = chains.slice(0, 4);
|
|
60
61
|
|
|
61
62
|
return (
|
|
62
63
|
<div className="relative" ref={containerRef}>
|
|
63
64
|
<button
|
|
64
65
|
type="button"
|
|
65
66
|
onClick={() => setOpen((state) => !state)}
|
|
66
|
-
className="flex items-center gap-2 h-12 px-3 py-2 rounded-lg bg-t_bg_tertiary bg-opacity-[0.04] hover:bg-opacity-10 transition-colors"
|
|
67
|
+
className="flex items-center gap-2.5 h-12 px-3 py-2 rounded-lg bg-t_bg_tertiary bg-opacity-[0.04] hover:bg-opacity-10 transition-colors"
|
|
67
68
|
>
|
|
68
69
|
{selectedChain ? (
|
|
69
70
|
<img
|
|
@@ -72,13 +73,13 @@ export const NetworkFilter = ({ type, selectedChain, onSelect }: Props) => {
|
|
|
72
73
|
className="w-5 h-5 rounded-full"
|
|
73
74
|
/>
|
|
74
75
|
) : (
|
|
75
|
-
<div className="
|
|
76
|
-
{
|
|
76
|
+
<div className="grid grid-cols-2 gap-0.5 shrink-0">
|
|
77
|
+
{clusterChains.map((chain) => (
|
|
77
78
|
<img
|
|
78
79
|
key={chain.chainId}
|
|
79
80
|
src={chain.image}
|
|
80
81
|
alt={chain.displayName}
|
|
81
|
-
className="w-
|
|
82
|
+
className="w-[11px] h-[11px] rounded-full"
|
|
82
83
|
/>
|
|
83
84
|
))}
|
|
84
85
|
</div>
|
|
@@ -89,67 +90,77 @@ export const NetworkFilter = ({ type, selectedChain, onSelect }: Props) => {
|
|
|
89
90
|
: `${chains.length} Networks`}
|
|
90
91
|
</p>
|
|
91
92
|
<div
|
|
92
|
-
className={`flex items-center justify-center w-6 h-6
|
|
93
|
+
className={`flex items-center justify-center w-6 h-6 text-t_text_primary opacity-50 transition-transform ${
|
|
93
94
|
open ? "rotate-180" : ""
|
|
94
95
|
}`}
|
|
95
96
|
>
|
|
96
|
-
<
|
|
97
|
+
<ChevronDownThinIcon />
|
|
97
98
|
</div>
|
|
98
99
|
</button>
|
|
99
100
|
|
|
100
101
|
{open && (
|
|
101
|
-
<div className="absolute right-
|
|
102
|
-
<div className="flex items-center gap-2 px-
|
|
103
|
-
<div className="
|
|
104
|
-
<
|
|
102
|
+
<div className="absolute -right-2 top-[calc(100%+8px)] z-20 flex flex-col w-[296px] py-5 px-2.5 rounded-xl network-dropdown-surface border border-solid border-t_text_primary border-opacity-5 overflow-hidden">
|
|
103
|
+
<div className="flex items-center gap-2 px-2.5 pb-4">
|
|
104
|
+
<div className="text-t_text_primary opacity-60 [&_svg]:w-4 [&_svg]:h-4 flex items-center">
|
|
105
|
+
<SearchThinIcon />
|
|
105
106
|
</div>
|
|
106
107
|
<input
|
|
107
108
|
value={searchValue}
|
|
108
109
|
onChange={(e) => setSearchValue(e.target.value)}
|
|
109
110
|
placeholder={`Browse ${chains.length} Chains...`}
|
|
110
|
-
className="bg-transparent border-none outline-none w-full text-sm text-t_text_primary placeholder:text-t_text_primary placeholder:text-opacity-40"
|
|
111
|
+
className="bg-transparent border-none outline-none w-full text-sm leading-6 text-t_text_primary placeholder:text-t_text_primary placeholder:text-opacity-40"
|
|
111
112
|
/>
|
|
112
113
|
</div>
|
|
113
|
-
<div className="h-
|
|
114
|
+
<div className="h-px w-full bg-t_bg_tertiary bg-opacity-5 mb-2" />
|
|
115
|
+
{/* The design's list mask is a hard cut 6px into the sixth row —
|
|
116
|
+
that sliver is the hint that the list scrolls. Rows are 40px
|
|
117
|
+
pills around 24px of content, so the cut lands at 214px. */}
|
|
118
|
+
<div className="h-[214px]">
|
|
114
119
|
<Virtuoso
|
|
115
|
-
style={{ height: "
|
|
120
|
+
style={{ height: "214px" }}
|
|
116
121
|
data={[undefined, ...filteredChains] as (Chain | undefined)[]}
|
|
117
|
-
itemContent={(_, chain) =>
|
|
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
|
-
|
|
122
|
+
itemContent={(_, chain) => {
|
|
123
|
+
const isSelected =
|
|
124
|
+
(chain?.chainId || "") === (selectedChain?.chainId || "");
|
|
125
|
+
return (
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
onClick={() => {
|
|
129
|
+
onSelect(chain);
|
|
130
|
+
setOpen(false);
|
|
131
|
+
}}
|
|
132
|
+
className={`network-dropdown-row flex items-center gap-2.5 w-full h-10 px-2.5 rounded-lg text-left ${
|
|
133
|
+
isSelected ? "network-dropdown-row-active" : ""
|
|
134
|
+
}`}
|
|
135
|
+
>
|
|
136
|
+
{chain ? (
|
|
137
|
+
<img
|
|
138
|
+
src={chain.image}
|
|
139
|
+
alt={chain.displayName}
|
|
140
|
+
className="w-6 h-6 rounded-full"
|
|
141
|
+
/>
|
|
142
|
+
) : (
|
|
143
|
+
<div className="grid grid-cols-2 gap-0.5 shrink-0">
|
|
144
|
+
{clusterChains.map((clusterChain) => (
|
|
145
|
+
<img
|
|
146
|
+
key={clusterChain.chainId}
|
|
147
|
+
src={clusterChain.image}
|
|
148
|
+
alt={clusterChain.displayName}
|
|
149
|
+
className="w-[11px] h-[11px] rounded-full"
|
|
150
|
+
/>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
<p
|
|
155
|
+
className={`text-sm leading-[18px] tracking-[0.01em] text-t_text_primary truncate ${
|
|
156
|
+
isSelected ? "" : "text-opacity-80"
|
|
157
|
+
}`}
|
|
158
|
+
>
|
|
159
|
+
{chain ? chain.displayName : "All Networks"}
|
|
160
|
+
</p>
|
|
161
|
+
</button>
|
|
162
|
+
);
|
|
163
|
+
}}
|
|
153
164
|
/>
|
|
154
165
|
</div>
|
|
155
166
|
</div>
|
|
@@ -139,7 +139,7 @@ export const TokenItem = ({
|
|
|
139
139
|
)
|
|
140
140
|
)}
|
|
141
141
|
{resetWarning ? (
|
|
142
|
-
<div className="flex items-center justify-end gap-1.5 text-[#
|
|
142
|
+
<div className="flex items-center justify-end gap-1.5 text-[#F46F28]">
|
|
143
143
|
<WarningIcon className="w-3.5 h-3.5 shrink-0" />
|
|
144
144
|
<span className="text-xs leading-[18px] tracking-[0.01em] whitespace-nowrap">
|
|
145
145
|
<span className="sm:hidden">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
|
2
2
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
3
|
-
import { CloseIcon, CopyIcon,
|
|
3
|
+
import { CloseIcon, CopyIcon, SearchThinIcon } from "@src/assets/icons";
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
Spinner,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
} from "@src/utils";
|
|
21
21
|
import { BigNumber, ethers } from "ethers";
|
|
22
22
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
23
|
-
import {
|
|
23
|
+
import { Virtuoso } from "react-virtuoso";
|
|
24
24
|
import { NetworkFilter } from "./NetworkFilter";
|
|
25
25
|
import { TokenItem } from "./TokenItem";
|
|
26
26
|
import { useAccount } from "wagmi";
|
|
@@ -55,7 +55,11 @@ export const TokenPicker = ({
|
|
|
55
55
|
const [loadingCustomTokenData, setLoadingCustomTokenData] = useState(false);
|
|
56
56
|
const [customTokenData, setCustomTokenData] =
|
|
57
57
|
useState<ImportedTokenData | null>(null);
|
|
58
|
-
|
|
58
|
+
// Always opens on "All Networks" — the cross-chain token list is the
|
|
59
|
+
// default view even when a chain is already set on the swap panel.
|
|
60
|
+
const [networkFilter, setNetworkFilter] = useState<Chain | undefined>(
|
|
61
|
+
undefined,
|
|
62
|
+
);
|
|
59
63
|
const currentRouteRequestNonce = useRef<number>(0);
|
|
60
64
|
const tokenSearchRef = useRef<HTMLInputElement | null>(null);
|
|
61
65
|
|
|
@@ -295,8 +299,7 @@ export const TokenPicker = ({
|
|
|
295
299
|
|
|
296
300
|
useEffect(() => {
|
|
297
301
|
setSearchValue("");
|
|
298
|
-
setNetworkFilter(
|
|
299
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
302
|
+
setNetworkFilter(undefined);
|
|
300
303
|
}, [isOpen]);
|
|
301
304
|
|
|
302
305
|
const isMatch = (value, searchValue) =>
|
|
@@ -422,11 +425,50 @@ export const TokenPicker = ({
|
|
|
422
425
|
: [{ label: "RECOMMENDED.", tokens: recommendedTokens }];
|
|
423
426
|
}, [ownedTokens, recommendedTokens]);
|
|
424
427
|
|
|
425
|
-
|
|
426
|
-
|
|
428
|
+
// The current group's label renders OUTSIDE the scroll container (pinned
|
|
429
|
+
// above it, transparent bg), so rows physically clip on the label's bottom
|
|
430
|
+
// edge — the scroller's top edge. Labels of the following groups stay
|
|
431
|
+
// in-flow and take over the pinned slot once they reach that line.
|
|
432
|
+
const listRows = useMemo(
|
|
433
|
+
() =>
|
|
434
|
+
tokenGroups.flatMap((group, groupIndex) => {
|
|
435
|
+
const rows: (
|
|
436
|
+
| { kind: "label"; label: string; groupLabel: string }
|
|
437
|
+
| { kind: "empty"; groupLabel: string }
|
|
438
|
+
| { kind: "token"; token: TokenOption; groupLabel: string }
|
|
439
|
+
)[] =
|
|
440
|
+
groupIndex === 0
|
|
441
|
+
? []
|
|
442
|
+
: [
|
|
443
|
+
{
|
|
444
|
+
kind: "label",
|
|
445
|
+
label: group.label,
|
|
446
|
+
groupLabel: group.label,
|
|
447
|
+
},
|
|
448
|
+
];
|
|
449
|
+
if (group.tokens.length) {
|
|
450
|
+
rows.push(
|
|
451
|
+
...group.tokens.map((token) => ({
|
|
452
|
+
kind: "token" as const,
|
|
453
|
+
token,
|
|
454
|
+
groupLabel: group.label,
|
|
455
|
+
})),
|
|
456
|
+
);
|
|
457
|
+
} else {
|
|
458
|
+
rows.push({ kind: "empty", groupLabel: group.label });
|
|
459
|
+
}
|
|
460
|
+
return rows;
|
|
461
|
+
}),
|
|
427
462
|
[tokenGroups],
|
|
428
463
|
);
|
|
429
464
|
|
|
465
|
+
// Index of the topmost (partially) visible row, reported by Virtuoso.
|
|
466
|
+
const [topRowIndex, setTopRowIndex] = useState(0);
|
|
467
|
+
const pinnedGroupLabel =
|
|
468
|
+
listRows[Math.min(topRowIndex, listRows.length - 1)]?.groupLabel ??
|
|
469
|
+
tokenGroups[0]?.label ??
|
|
470
|
+
"";
|
|
471
|
+
|
|
430
472
|
useEffect(() => {
|
|
431
473
|
if (isOpen && tokenSearchRef.current) {
|
|
432
474
|
setTimeout(() => {
|
|
@@ -438,7 +480,7 @@ export const TokenPicker = ({
|
|
|
438
480
|
return showImportWarning ? (
|
|
439
481
|
<>
|
|
440
482
|
<div className="flex justify-between items-center mb-6">
|
|
441
|
-
<div className="text-2xl leading-8 tracking-[0.01em]">
|
|
483
|
+
<div className="text-2xl font-medium leading-8 tracking-[0.01em]">
|
|
442
484
|
Import token
|
|
443
485
|
</div>
|
|
444
486
|
<button
|
|
@@ -528,7 +570,7 @@ export const TokenPicker = ({
|
|
|
528
570
|
) : (
|
|
529
571
|
<>
|
|
530
572
|
<div className="flex justify-between items-center mb-6">
|
|
531
|
-
<div className="text-2xl leading-8 tracking-[0.01em] text-t_text_primary">
|
|
573
|
+
<div className="text-2xl font-medium leading-8 tracking-[0.01em] text-t_text_primary">
|
|
532
574
|
Select Token
|
|
533
575
|
</div>
|
|
534
576
|
<button
|
|
@@ -545,8 +587,8 @@ export const TokenPicker = ({
|
|
|
545
587
|
|
|
546
588
|
<div className="flex items-center justify-between gap-2 h-14 pl-4 pr-1 rounded-xl bg-t_bg_tertiary bg-opacity-[0.03] border border-solid border-t_text_primary border-opacity-5 mb-6">
|
|
547
589
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
|
548
|
-
<div className="
|
|
549
|
-
<
|
|
590
|
+
<div className="text-t_text_primary opacity-60 flex items-center shrink-0">
|
|
591
|
+
<SearchThinIcon />
|
|
550
592
|
</div>
|
|
551
593
|
<input
|
|
552
594
|
ref={tokenSearchRef}
|
|
@@ -584,7 +626,7 @@ export const TokenPicker = ({
|
|
|
584
626
|
<div className="text-sm font-medium text-ellipsis">
|
|
585
627
|
{customTokenData.name}
|
|
586
628
|
</div>
|
|
587
|
-
<div className="text-xs text-t_text_primary !text-opacity-50
|
|
629
|
+
<div className="text-xs text-t_text_primary !text-opacity-50">
|
|
588
630
|
{importChain?.displayName}
|
|
589
631
|
</div>
|
|
590
632
|
</div>
|
|
@@ -603,45 +645,54 @@ export const TokenPicker = ({
|
|
|
603
645
|
</div>
|
|
604
646
|
</div>
|
|
605
647
|
) : (
|
|
606
|
-
<div className="flex flex-col h-full">
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
648
|
+
<div className="flex flex-col h-full min-h-0">
|
|
649
|
+
{/* Pinned label of the group at the top of the list; sits
|
|
650
|
+
above the scroller, so rows clip on its bottom edge. */}
|
|
651
|
+
<p className="pb-3 text-[10px] leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30 text-left shrink-0">
|
|
652
|
+
{pinnedGroupLabel}
|
|
653
|
+
</p>
|
|
654
|
+
<div className="relative flex flex-col flex-1 min-h-0">
|
|
655
|
+
<Virtuoso
|
|
656
|
+
style={{ maxHeight: "700px", height: "100%" }}
|
|
657
|
+
totalCount={listRows.length}
|
|
658
|
+
rangeChanged={({ startIndex }) => setTopRowIndex(startIndex)}
|
|
659
|
+
itemContent={(index) => {
|
|
660
|
+
const row = listRows[index];
|
|
661
|
+
if (!row) {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
if (row.kind === "label") {
|
|
665
|
+
return (
|
|
666
|
+
<p className="pt-2 pb-3 text-[10px] leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30 text-left">
|
|
667
|
+
{row.label}
|
|
668
|
+
</p>
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
if (row.kind === "empty") {
|
|
672
|
+
return (
|
|
673
|
+
<div className="text-center py-1 pb-3 text-sm text-t_text_primary text-opacity-50">
|
|
674
|
+
No token found
|
|
675
|
+
</div>
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
return (
|
|
630
679
|
<TokenItem
|
|
631
680
|
panelType={type}
|
|
632
|
-
token={token}
|
|
681
|
+
token={row.token}
|
|
633
682
|
selectedChainId={chain?.chainId}
|
|
634
683
|
selectedTokenAddress={selectedTokenAddress}
|
|
635
|
-
resetWarning={getResetWarning(token)}
|
|
684
|
+
resetWarning={getResetWarning(row.token)}
|
|
636
685
|
onClick={() => {
|
|
637
|
-
onSelect(token);
|
|
686
|
+
onSelect(row.token);
|
|
638
687
|
setModalOpen(false);
|
|
639
688
|
}}
|
|
640
689
|
/>
|
|
641
|
-
)
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
690
|
+
);
|
|
691
|
+
}}
|
|
692
|
+
/>
|
|
693
|
+
{/* Scroll fade — rows dissolve towards the widget bottom */}
|
|
694
|
+
<div className="pointer-events-none absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-t_bg_secondary/65 to-t_bg_secondary/0" />
|
|
695
|
+
</div>
|
|
645
696
|
</div>
|
|
646
697
|
)}
|
|
647
698
|
</>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { TokenLogoWithChain } from "@src/components";
|
|
1
|
+
import { ChevronDownThinIcon } from "@src/assets/icons";
|
|
2
|
+
import { Skeleton, TokenLogoWithChain } from "@src/components";
|
|
3
3
|
import { useSwapContext } from "@src/context";
|
|
4
4
|
import { Chain, Token } from "@src/models";
|
|
5
5
|
import { useMemo } from "react";
|
|
@@ -19,6 +19,7 @@ export const TokenPanel = ({ chain, token, type, className }: Props) => {
|
|
|
19
19
|
srcTokenLocked,
|
|
20
20
|
dstChainLocked,
|
|
21
21
|
setActivePicker,
|
|
22
|
+
isAsyncDataLoaded,
|
|
22
23
|
} = useSwapContext();
|
|
23
24
|
|
|
24
25
|
const integratorLockedDestinationToken = useMemo(
|
|
@@ -43,6 +44,25 @@ export const TokenPanel = ({ chain, token, type, className }: Props) => {
|
|
|
43
44
|
integratorLockedDestinationToken ||
|
|
44
45
|
integratorLockedSourceToken;
|
|
45
46
|
|
|
47
|
+
if (!isAsyncDataLoaded) {
|
|
48
|
+
// Mirror the loaded layout: symbol bar + token circle with the small
|
|
49
|
+
// chain badge, so nothing shifts when the real content arrives.
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
className={`flex items-center shrink-0 min-h-11 ${className || ""}`}
|
|
53
|
+
aria-hidden
|
|
54
|
+
>
|
|
55
|
+
<div className="flex items-center gap-3">
|
|
56
|
+
<Skeleton className="w-12 h-4" />
|
|
57
|
+
<div className="relative flex items-end pr-1 pb-1">
|
|
58
|
+
<Skeleton className="w-10 h-10 rounded-full" />
|
|
59
|
+
<Skeleton className="absolute bottom-0 right-0 w-[18px] h-[18px] rounded-full border border-solid border-t_bg_secondary" />
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
return (
|
|
47
67
|
<button
|
|
48
68
|
type="button"
|
|
@@ -50,7 +70,7 @@ export const TokenPanel = ({ chain, token, type, className }: Props) => {
|
|
|
50
70
|
onClick={() => {
|
|
51
71
|
setActivePicker(type);
|
|
52
72
|
}}
|
|
53
|
-
className={`flex items-center
|
|
73
|
+
className={`flex items-center shrink-0 min-h-11 rounded-lg ${
|
|
54
74
|
locked ? "" : "hover:opacity-80 transition-opacity"
|
|
55
75
|
} ${className || ""}`}
|
|
56
76
|
>
|
|
@@ -86,8 +106,10 @@ export const TokenPanel = ({ chain, token, type, className }: Props) => {
|
|
|
86
106
|
)}
|
|
87
107
|
</div>
|
|
88
108
|
{!locked && (
|
|
89
|
-
|
|
90
|
-
|
|
109
|
+
// The 24px glyph has 8px of built-in whitespace on each side; pull the
|
|
110
|
+
// right side back so the chevron sits flush with the panel edge.
|
|
111
|
+
<div className="flex items-center justify-center w-6 h-6 ml-1 -mr-2 text-t_text_primary opacity-50">
|
|
112
|
+
<ChevronDownThinIcon />
|
|
91
113
|
</div>
|
|
92
114
|
)}
|
|
93
115
|
</button>
|
|
@@ -16,11 +16,11 @@ export const UsdValue = ({ type }: Props) => {
|
|
|
16
16
|
);
|
|
17
17
|
|
|
18
18
|
if (type === "destination" && isFetchingRoute) {
|
|
19
|
-
return <Skeleton className="w-[80px] h-[16px]" />;
|
|
19
|
+
return <Skeleton className="w-[80px] h-[16px] my-1" />;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
|
-
<p className="text-[10px] leading-4 tracking-[0.01em] text-t_text_primary text-opacity-50 whitespace-nowrap">
|
|
23
|
+
<p className="py-1 text-[10px] leading-4 tracking-[0.01em] text-t_text_primary text-opacity-50 whitespace-nowrap">
|
|
24
24
|
{valueUsd ? `${valueUsd} USD` : ""}
|
|
25
25
|
</p>
|
|
26
26
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ChevronDownThinSmallIcon } from "@src/assets/icons";
|
|
2
|
+
import { Skeleton } from "@src/components";
|
|
2
3
|
import { useWallet } from "@src/context/WalletProvider";
|
|
3
4
|
import { useSwapContext } from "@src/context";
|
|
4
5
|
import { SwapPanelType } from "../..";
|
|
@@ -21,25 +22,28 @@ export const WalletPanel = ({ chain, className }: Props) => {
|
|
|
21
22
|
solana,
|
|
22
23
|
sui,
|
|
23
24
|
} = useWallet();
|
|
24
|
-
const {
|
|
25
|
-
|
|
25
|
+
const {
|
|
26
|
+
integrationConfig,
|
|
27
|
+
hasOnConnectWalletCallback,
|
|
28
|
+
onConnectWallet,
|
|
29
|
+
isAsyncDataLoaded,
|
|
30
|
+
} = useSwapContext();
|
|
26
31
|
|
|
27
32
|
const openEvmWalletModal = useCallback(() => {
|
|
28
33
|
// Mirror SwapButton's contract: when the integrator supplied their own
|
|
29
34
|
// connect handler and did not opt into the built-in picker, defer to it
|
|
30
|
-
//
|
|
35
|
+
// in every state — the host decides whether to show its connect modal or
|
|
36
|
+
// its account modal (e.g. RainbowKit) for an already-connected wallet.
|
|
31
37
|
if (!integrationConfig.defaultWalletPicker && hasOnConnectWalletCallback) {
|
|
32
38
|
onConnectWallet();
|
|
33
39
|
return;
|
|
34
40
|
}
|
|
35
41
|
setOpenEVMWalletModal(true);
|
|
36
|
-
evm.connectWallet();
|
|
37
42
|
}, [
|
|
38
43
|
integrationConfig.defaultWalletPicker,
|
|
39
44
|
hasOnConnectWalletCallback,
|
|
40
45
|
onConnectWallet,
|
|
41
46
|
setOpenEVMWalletModal,
|
|
42
|
-
evm,
|
|
43
47
|
]);
|
|
44
48
|
|
|
45
49
|
const openWalletModal = useCallback(() => {
|
|
@@ -50,17 +54,10 @@ export const WalletPanel = ({ chain, className }: Props) => {
|
|
|
50
54
|
openEvmWalletModal();
|
|
51
55
|
} else if (chain.ecosystem === "solana") {
|
|
52
56
|
setOpenSolanaWalletModal(true);
|
|
53
|
-
solana.connectWallet();
|
|
54
57
|
} else if (chain.ecosystem === "sui") {
|
|
55
58
|
setOpenSuiWalletModal(true);
|
|
56
59
|
}
|
|
57
|
-
}, [
|
|
58
|
-
chain,
|
|
59
|
-
openEvmWalletModal,
|
|
60
|
-
solana,
|
|
61
|
-
setOpenSolanaWalletModal,
|
|
62
|
-
setOpenSuiWalletModal,
|
|
63
|
-
]);
|
|
60
|
+
}, [chain, openEvmWalletModal, setOpenSolanaWalletModal, setOpenSuiWalletModal]);
|
|
64
61
|
|
|
65
62
|
const address = useMemo(() => {
|
|
66
63
|
if (!chain) {
|
|
@@ -76,23 +73,31 @@ export const WalletPanel = ({ chain, className }: Props) => {
|
|
|
76
73
|
return "";
|
|
77
74
|
}, [chain, evm, solana, sui]);
|
|
78
75
|
|
|
76
|
+
if (!isAsyncDataLoaded) {
|
|
77
|
+
return <Skeleton className="w-16 h-6" />;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Reserve the button's height before the chain loads so the card
|
|
81
|
+
// doesn't grow when the wallet button appears.
|
|
79
82
|
if (!chain) {
|
|
80
|
-
return
|
|
83
|
+
return <div className="h-6" aria-hidden />;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
return (
|
|
84
87
|
<button
|
|
85
88
|
type="button"
|
|
86
89
|
onClick={openWalletModal}
|
|
87
|
-
className={`flex items-center justify-center
|
|
90
|
+
className={`flex items-center justify-center px-2 py-1 rounded bg-t_bg_tertiary bg-opacity-5 hover:bg-opacity-10 transition-colors ${
|
|
88
91
|
className || ""
|
|
89
92
|
}`}
|
|
90
93
|
>
|
|
91
94
|
<p className="text-[10px] leading-4 tracking-[0.01em] text-t_text_primary whitespace-nowrap">
|
|
92
95
|
{address ? shortAddress(address) : "Connect"}
|
|
93
96
|
</p>
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
{/* The 16px glyph has 6px of built-in whitespace on each side; pull the
|
|
98
|
+
right side back so the button's visual padding stays symmetric. */}
|
|
99
|
+
<div className="flex items-center justify-center w-4 h-4 -mr-1.5 text-t_text_primary opacity-50">
|
|
100
|
+
<ChevronDownThinSmallIcon />
|
|
96
101
|
</div>
|
|
97
102
|
</button>
|
|
98
103
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useSwapContext } from "@src/context";
|
|
2
|
-
import { useMemo } from "react";
|
|
2
|
+
import { MouseEvent, useMemo } from "react";
|
|
3
3
|
import { SwapPanelType } from "../index";
|
|
4
4
|
import { AmountPanel } from "./AmountPanel";
|
|
5
5
|
import { Balance } from "./AmountPanel/Balance";
|
|
@@ -23,15 +23,27 @@ export const SwapPanel = ({ type }: Props) => {
|
|
|
23
23
|
[type, srcChain, dstChain],
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
+
// The whole source card acts as the amount input's hit area; clicks on the
|
|
27
|
+
// nested buttons (wallet, MAX, token picker) must keep their own actions.
|
|
28
|
+
const focusAmountInput = (event: MouseEvent<HTMLDivElement>) => {
|
|
29
|
+
if (type !== "source" || (event.target as HTMLElement).closest("button")) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
event.currentTarget
|
|
33
|
+
.querySelector<HTMLInputElement>("#swap-amount-input")
|
|
34
|
+
?.focus();
|
|
35
|
+
};
|
|
36
|
+
|
|
26
37
|
return (
|
|
27
38
|
<div
|
|
28
|
-
|
|
39
|
+
onClick={focusAmountInput}
|
|
40
|
+
className={`flex flex-col gap-2 px-4 sm:px-6 py-4 rounded-2xl backdrop-blur-[12px] ${
|
|
29
41
|
type === "source"
|
|
30
|
-
? "bg-gradient-to-r from-t_bg_tertiary/5 to-t_bg_tertiary/5
|
|
42
|
+
? "bg-gradient-to-r from-t_bg_tertiary/5 to-t_bg_tertiary/5 swap-panel-gradient-border cursor-text"
|
|
31
43
|
: "bg-gradient-to-r from-t_bg_tertiary/[0.03] to-t_bg_tertiary/[0.03]"
|
|
32
44
|
}`}
|
|
33
45
|
>
|
|
34
|
-
<div className="flex gap-2 items-center w-full">
|
|
46
|
+
<div className="flex gap-2 items-center w-full min-h-6">
|
|
35
47
|
<p className="text-[10px] leading-4 tracking-[0.01em] text-t_text_primary text-opacity-50">
|
|
36
48
|
{type === "source" ? "Source" : "Destination"}
|
|
37
49
|
</p>
|
|
@@ -41,7 +53,7 @@ export const SwapPanel = ({ type }: Props) => {
|
|
|
41
53
|
<AmountPanel type={type} />
|
|
42
54
|
<TokenPanel chain={chain} token={token} type={type} />
|
|
43
55
|
</div>
|
|
44
|
-
<div className="flex items-center justify-between w-full gap-4">
|
|
56
|
+
<div className="flex items-center justify-between w-full gap-4 min-h-6">
|
|
45
57
|
<UsdValue type={type} />
|
|
46
58
|
<Balance type={type} />
|
|
47
59
|
</div>
|