@xswap-link/sdk 0.15.0 → 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.
Files changed (38) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/index.global.js +86 -80
  3. package/dist/index.js +1117 -976
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +809 -668
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +2 -1
  8. package/postcss.config.js +2 -0
  9. package/src/assets/icons/ChevronRightThinIcon.tsx +23 -0
  10. package/src/assets/icons/InfoThinIcon.tsx +33 -0
  11. package/src/assets/icons/RedirectThinIcon.tsx +15 -0
  12. package/src/assets/icons/index.ts +3 -0
  13. package/src/components/Swap/Header/index.tsx +3 -1
  14. package/src/components/Swap/HistoryView/ActivityCard/index.tsx +2 -2
  15. package/src/components/Swap/ReorderButton/ReorderButton.tsx +4 -1
  16. package/src/components/Swap/SettingsView/Delivery/index.tsx +3 -3
  17. package/src/components/Swap/SettingsView/InfiniteApproval/index.tsx +3 -3
  18. package/src/components/Swap/SettingsView/Slippage/index.tsx +5 -3
  19. package/src/components/Swap/SwapView/ConfirmationView/TokenTiles/index.tsx +5 -2
  20. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/Header/index.tsx +1 -1
  21. package/src/components/Swap/SwapView/ConfirmationView/TxResult/index.tsx +19 -18
  22. package/src/components/Swap/SwapView/FeesPanel/Fees/index.tsx +1 -1
  23. package/src/components/Swap/SwapView/FeesPanel/index.tsx +3 -1
  24. package/src/components/Swap/SwapView/SwapPanel/AmountPanel/index.tsx +9 -0
  25. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/ChainPicker/index.tsx +1 -1
  26. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/NetworkFilter/index.tsx +52 -42
  27. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/TokenItem/index.tsx +1 -1
  28. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +5 -5
  29. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +24 -2
  30. package/src/components/Swap/SwapView/SwapPanel/WalletPanel/index.tsx +14 -3
  31. package/src/components/Swap/SwapView/SwapPanel/index.tsx +14 -2
  32. package/src/components/Swap/SwapView/WalletPicker/index.tsx +1 -1
  33. package/src/components/ToggleButton/index.tsx +5 -3
  34. package/src/components/TxModal/index.tsx +2 -6
  35. package/src/components/global.css +85 -0
  36. package/src/config/fonts.ts +15 -2
  37. package/src/context/TxUIWrapper.tsx +0 -1
  38. package/tailwind.config.js +3 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xswap-link/sdk",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "JavaScript SDK for XSwap platform",
5
5
  "homepage": "https://github.com/xswap-link/xswap-sdk",
6
6
  "repository": {
@@ -81,6 +81,7 @@
81
81
  "@types/react-dom": "^18.2.0",
82
82
  "@typescript-eslint/eslint-plugin": "^7.1.0",
83
83
  "@typescript-eslint/parser": "^7.1.0",
84
+ "autoprefixer": "^10.5.4",
84
85
  "babel-jest": "^29.7.0",
85
86
  "cross-fetch": "^4.1.0",
86
87
  "eslint": "^8.57.0",
package/postcss.config.js CHANGED
@@ -4,6 +4,8 @@ module.exports = {
4
4
  require("postcss-import"),
5
5
  // Resolves TailwindCSS
6
6
  require("tailwindcss")(),
7
+ // Adds vendor prefixes (e.g. -webkit-backdrop-filter for Safari < 18)
8
+ require("autoprefixer"),
7
9
  // Removes comments from CSS and redundant whitespaces
8
10
  require("postcss-minify"),
9
11
  ],
@@ -0,0 +1,23 @@
1
+ // Glyph-tight box (no 16/24px padding frame) so flex gaps around it match
2
+ // the design's spacing exactly. Geometry is Figma's "Vector 8262" chevron
3
+ // rotated to point right.
4
+ export const ChevronRightThinIcon = ({
5
+ className,
6
+ }: {
7
+ className?: string;
8
+ }) => {
9
+ return (
10
+ <svg
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ width="4"
13
+ height="7"
14
+ viewBox="0 0 3.58671 7"
15
+ fill="none"
16
+ stroke="currentColor"
17
+ strokeLinejoin="round"
18
+ className={className}
19
+ >
20
+ <path d="M0 6.5H0.500926L2.79382 4.20711C3.18434 3.81658 3.18434 3.18342 2.79382 2.79289L0.500926 0.5H0" />
21
+ </svg>
22
+ );
23
+ };
@@ -0,0 +1,33 @@
1
+ import { forwardRef } from "react";
2
+
3
+ const InfoThinIcon = forwardRef<SVGSVGElement, { className?: string }>(
4
+ ({ className }, ref) => {
5
+ return (
6
+ <svg
7
+ ref={ref}
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ width="16"
10
+ height="16"
11
+ viewBox="0 0 16 16"
12
+ fill="none"
13
+ className={className || ""}
14
+ >
15
+ <circle cx="8" cy="8" r="8" fill="currentColor" fillOpacity="0.05" />
16
+ <circle
17
+ cx="8"
18
+ cy="8"
19
+ r="7.5"
20
+ stroke="currentColor"
21
+ strokeOpacity="0.05"
22
+ />
23
+ <g opacity="0.6">
24
+ <path d="M8 7.5V10.5" stroke="currentColor" />
25
+ <path d="M8 5.5V6.5" stroke="currentColor" />
26
+ </g>
27
+ </svg>
28
+ );
29
+ },
30
+ );
31
+
32
+ InfoThinIcon.displayName = "InfoThinIcon";
33
+ export { InfoThinIcon };
@@ -0,0 +1,15 @@
1
+ export const RedirectThinIcon = ({ className }: { className?: string }) => {
2
+ return (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ width="12"
6
+ height="12"
7
+ viewBox="0 0 12 12"
8
+ fill="currentColor"
9
+ className={className}
10
+ >
11
+ <path d="M10.9226 10.9226H1.07744V1.07744H5.45455L4.3771 0H0V12H12V7.6229L10.9226 6.54545V10.9226Z" />
12
+ <path d="M7.07071 0L8.14815 1.07744H10.1549L9.15825 2.07407L9.92593 2.84175L10.9226 1.84512V3.83838L12 4.91582V0H7.07071Z" />
13
+ </svg>
14
+ );
15
+ };
@@ -17,6 +17,7 @@ export * from "./CheckIcon";
17
17
  export * from "./ChevronDownIcon";
18
18
  export * from "./ChevronDownThinIcon";
19
19
  export * from "./ChevronDownWideThinIcon";
20
+ export * from "./ChevronRightThinIcon";
20
21
  export * from "./ChevronsUpDownThinIcon";
21
22
  export * from "./ChevronUpIcon";
22
23
  export * from "./CircularProgressIcon";
@@ -34,12 +35,14 @@ export * from "./HistoryGradientBgIcon";
34
35
  export * from "./HistoryIcon";
35
36
  export * from "./HourGlassIcon";
36
37
  export * from "./InfoIcon";
38
+ export * from "./InfoThinIcon";
37
39
  export * from "./LinkExternalIcon";
38
40
  export * from "./LinkExternalThinIcon";
39
41
  export * from "./LoadingRingIcon";
40
42
  export * from "./PercentageIcon";
41
43
  export * from "./ProgressRingThinIcon";
42
44
  export * from "./RankIcon";
45
+ export * from "./RedirectThinIcon";
43
46
  export * from "./SearchIcon";
44
47
  export * from "./SearchThinIcon";
45
48
  export * from "./SettingsGradientBgIcon";
@@ -16,7 +16,9 @@ export const Header = ({ onClose }: Props) => {
16
16
 
17
17
  return (
18
18
  <div className="flex justify-between items-center">
19
- <div className="text-2xl leading-8 tracking-[0.01em]">{title}</div>
19
+ <div className="text-2xl font-medium leading-8 tracking-[0.01em]">
20
+ {title}
21
+ </div>
20
22
  <Controls onClose={onClose} />
21
23
  </div>
22
24
  );
@@ -1,5 +1,5 @@
1
1
  import {
2
- ChevronDownThinSmallIcon,
2
+ ChevronRightThinIcon,
3
3
  LinkExternalThinIcon,
4
4
  LoadingRingIcon,
5
5
  } from "@src/assets/icons";
@@ -170,7 +170,7 @@ export const ActivityCard = ({ transaction }: Props) => {
170
170
  <span>{srcToken?.symbol || "?"}</span>
171
171
  {dstToken && (
172
172
  <>
173
- <ChevronDownThinSmallIcon className="w-4 h-4 shrink-0 -rotate-90 opacity-50" />
173
+ <ChevronRightThinIcon className="shrink-0 opacity-50" />
174
174
  <span className="truncate">{dstToken.symbol}</span>
175
175
  </>
176
176
  )}
@@ -23,10 +23,13 @@ export const ReorderButton: FC = () => {
23
23
 
24
24
  return (
25
25
  <div className="h-0 w-full relative z-[1]">
26
+ {/* Opaque bg on purpose: Chromium ignores backdrop-filter nested
27
+ inside the widget's own backdrop-blur, so a translucent bg would
28
+ let the panel edges show through the button. */}
26
29
  <button
27
30
  onClick={onReorderClick}
28
31
  aria-label="Switch target token with the source token"
29
- className="absolute left-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-[24px] bg-t_bg_secondary bg-opacity-50 bg-gradient-to-r from-t_bg_tertiary/5 to-t_bg_tertiary/5 hover:from-t_bg_tertiary/10 hover:to-t_bg_tertiary/10 transition-colors"
32
+ className="absolute left-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-[24px] bg-t_bg_secondary bg-gradient-to-r from-t_bg_tertiary/5 to-t_bg_tertiary/5 hover:from-t_bg_tertiary/10 hover:to-t_bg_tertiary/10 transition-colors"
30
33
  >
31
34
  <div className="flex text-t_text_primary opacity-50">
32
35
  <ChevronsUpDownThinIcon />
@@ -1,4 +1,4 @@
1
- import { HelpIcon } from "@src/assets/icons";
1
+ import { InfoThinIcon } from "@src/assets/icons";
2
2
  import { ToggleButton, Tooltip } from "@src/components";
3
3
  import { useSwapContext } from "@src/context";
4
4
  import { useRef } from "react";
@@ -26,9 +26,9 @@ export const Delivery = () => {
26
26
  triggerRef={tooltipTriggerRef}
27
27
  id="express-delivery-tooltip"
28
28
  />
29
- <HelpIcon
29
+ <InfoThinIcon
30
30
  ref={tooltipTriggerRef}
31
- className="cursor-help fill-t_text_primary opacity-30 w-4 h-4 shrink-0"
31
+ className="cursor-help text-t_text_primary w-4 h-4 shrink-0"
32
32
  />
33
33
  </div>
34
34
  </div>
@@ -1,4 +1,4 @@
1
- import { HelpIcon } from "@src/assets/icons";
1
+ import { InfoThinIcon } from "@src/assets/icons";
2
2
  import { ToggleButton } from "@src/components/ToggleButton";
3
3
  import { Tooltip } from "@src/components/Tooltip";
4
4
  import { useSwapContext } from "@src/context";
@@ -27,9 +27,9 @@ export const InfiniteApproval = () => {
27
27
  triggerRef={tooltipTriggerRef}
28
28
  id="infinite-approval-tooltip"
29
29
  />
30
- <HelpIcon
30
+ <InfoThinIcon
31
31
  ref={tooltipTriggerRef}
32
- className="cursor-help fill-t_text_primary opacity-30 w-4 h-4 shrink-0"
32
+ className="cursor-help text-t_text_primary w-4 h-4 shrink-0"
33
33
  />
34
34
  </div>
35
35
  </div>
@@ -31,7 +31,7 @@ export const Slippage = () => {
31
31
  <div className="flex flex-col gap-4">
32
32
  <div className="flex items-center gap-2">
33
33
  <h2 className="text-sm leading-6 tracking-[0.01em] text-t_text_primary">
34
- Price Tolerance
34
+ Slippage
35
35
  </h2>
36
36
  <div className="w-0.5 h-0.5 bg-t_text_primary opacity-25 shrink-0" />
37
37
  <p className="text-sm leading-6 tracking-[0.01em] text-t_text_primary text-opacity-50">
@@ -77,11 +77,13 @@ export const Slippage = () => {
77
77
  </div>
78
78
  </div>
79
79
  <div className="flex flex-col gap-3">
80
- <p className="text-[10px] font-medium leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30">
80
+ <p className="text-[10px] leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30">
81
81
  NOTE.
82
82
  </p>
83
83
  <p className="text-sm leading-[18px] tracking-[0.01em] text-t_text_primary text-opacity-40">
84
- {`Price Tolerance defines how much a price can change before your
84
+ {/* The design sets the leading term in Medium, the rest Regular. */}
85
+ <span className="font-medium">Slippage</span>
86
+ {` defines how much a price can change before your
85
87
  transfer falls back to USDC. If the price moves more than your
86
88
  tolerance allows, you'll receive USDC on your destination chain.`}
87
89
  </p>
@@ -57,9 +57,12 @@ export const TokenTiles = () => {
57
57
  return (
58
58
  <div className="flex flex-col gap-1.5">
59
59
  <TokenTile type="source" />
60
- {/* Zero-height anchor between the tiles, like ReorderButton in SwapView */}
60
+ {/* Zero-height anchor between the tiles, like ReorderButton in SwapView.
61
+ Opaque bg on purpose: Chromium ignores backdrop-filter nested inside
62
+ the widget's own backdrop-blur, so a translucent bg would let the
63
+ tile edges show through the marker. */}
61
64
  <div className="h-0 w-full relative z-[1]">
62
- <div className="absolute left-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-[24px] bg-t_bg_secondary bg-opacity-50 bg-gradient-to-b from-t_bg_tertiary/10 to-t_bg_tertiary/[0.08]">
65
+ <div className="absolute left-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-[24px] bg-t_bg_secondary bg-gradient-to-b from-t_bg_tertiary/10 to-t_bg_tertiary/[0.08]">
63
66
  <ChevronDownWideThinIcon className="w-6 h-6 text-t_text_primary opacity-50" />
64
67
  </div>
65
68
  </div>
@@ -7,7 +7,7 @@ type Props = {
7
7
  export const Header = ({ onCloseClick }: Props) => {
8
8
  return (
9
9
  <div className="flex justify-between items-center gap-6">
10
- <div className="text-2xl leading-8 tracking-[0.01em] text-t_text_primary">
10
+ <div className="text-2xl font-medium leading-8 tracking-[0.01em] text-t_text_primary">
11
11
  Confirmation
12
12
  </div>
13
13
  <button
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CheckCircleThinIcon,
3
3
  ErrorCircleThinIcon,
4
- LinkExternalThinIcon,
4
+ RedirectThinIcon,
5
5
  } from "@src/assets/icons";
6
6
  import { useSwapContext, useTxUIWrapper } from "@src/context";
7
7
  import { TokenTiles } from "../TokenTiles";
@@ -18,7 +18,7 @@ export const TxResult = ({ onCloseClick }: Props) => {
18
18
  <div className="flex flex-col gap-8 text-t_text_primary">
19
19
  <div className="flex flex-col gap-2">
20
20
  <div className="flex items-center gap-3">
21
- <div className="text-2xl leading-8 tracking-[0.01em]">
21
+ <div className="text-2xl font-medium leading-8 tracking-[0.01em]">
22
22
  {txError ? "Transaction Error" : "Transaction Success"}
23
23
  </div>
24
24
  {txError ? (
@@ -42,22 +42,7 @@ export const TxResult = ({ onCloseClick }: Props) => {
42
42
  )
43
43
  )}
44
44
  </div>
45
- {!txMsg && (
46
- <div className="flex flex-col gap-4">
47
- <TokenTiles />
48
- {!txError && (
49
- <a
50
- href={txExplorerUrl}
51
- target="_blank"
52
- rel="noreferrer"
53
- className="flex items-center gap-2 self-start text-sm leading-5 tracking-[0.01em] text-t_text_primary text-opacity-50 hover:text-opacity-80 transition-colors"
54
- >
55
- View details on Explorer
56
- <LinkExternalThinIcon className="w-3 h-3" />
57
- </a>
58
- )}
59
- </div>
60
- )}
45
+ {!txMsg && <TokenTiles />}
61
46
  <div className="h-px w-full gradient-sections-separator-container" />
62
47
  <button
63
48
  type="button"
@@ -66,6 +51,22 @@ export const TxResult = ({ onCloseClick }: Props) => {
66
51
  >
67
52
  Continue
68
53
  </button>
54
+ {!txMsg && !txError && (
55
+ <div className="w-full flex gap-3 items-center">
56
+ <a
57
+ href={txExplorerUrl}
58
+ target="_blank"
59
+ rel="noreferrer"
60
+ className="flex items-center gap-1.5 text-sm text-t_text_primary text-opacity-25 hover:text-opacity-50 transition-colors"
61
+ >
62
+ View On Explorer{" "}
63
+ <div className="w-6 h-6 bg-t_bg_tertiary bg-opacity-5 rounded-[4px] flex items-center justify-center">
64
+ <RedirectThinIcon className="w-3 h-3" />
65
+ </div>
66
+ </a>
67
+ <div className="flex-1 h-px bg-t_text_primary bg-opacity-10" />
68
+ </div>
69
+ )}
69
70
  </div>
70
71
  );
71
72
  };
@@ -47,7 +47,7 @@ export const Fees = ({
47
47
  },
48
48
  ]
49
49
  : []),
50
- { name: "Price Tolerance: ", value: `${slippage}%` },
50
+ { name: "Slippage: ", value: `${slippage}%` },
51
51
  {
52
52
  name: "Min. Received: ",
53
53
  value: `${
@@ -200,8 +200,10 @@ export const FeesPanel = () => {
200
200
  </>
201
201
  )}
202
202
  </div>
203
+ {/* The 24px glyph has 8px of built-in whitespace on each side; pull the
204
+ right side back so the chevron sits flush with the panel padding. */}
203
205
  <div
204
- className={`flex items-center justify-center w-6 h-6 text-t_text_primary opacity-50 transition-transform ${
206
+ className={`flex items-center justify-center w-6 h-6 -mr-2 text-t_text_primary opacity-50 transition-transform ${
205
207
  expanded ? "rotate-180" : ""
206
208
  }`}
207
209
  >
@@ -22,6 +22,7 @@ export const AmountPanel = ({ type }: Props) => {
22
22
  setSrcValue,
23
23
  isFetchingRoute,
24
24
  bridgeUI,
25
+ isAsyncDataLoaded,
25
26
  } = useSwapContext();
26
27
 
27
28
  const onMaxClick = () => {
@@ -58,6 +59,14 @@ export const AmountPanel = ({ type }: Props) => {
58
59
  dstChain,
59
60
  ]);
60
61
 
62
+ if (!isAsyncDataLoaded) {
63
+ return (
64
+ <div className="flex items-center flex-1 min-w-0">
65
+ <Skeleton className="w-[100px] h-8" />
66
+ </div>
67
+ );
68
+ }
69
+
61
70
  return (
62
71
  <div className="flex items-center gap-3 flex-1 min-w-0 overflow-hidden text-left">
63
72
  {type === "source" ? (
@@ -56,7 +56,7 @@ export const ChainPicker = ({
56
56
  return (
57
57
  <>
58
58
  <div className="flex justify-between items-center mb-6">
59
- <div className="text-2xl leading-8 tracking-[0.01em] text-t_text_primary">
59
+ <div className="text-2xl font-medium leading-8 tracking-[0.01em] text-t_text_primary">
60
60
  Select Network
61
61
  </div>
62
62
  <button
@@ -99,58 +99,68 @@ export const NetworkFilter = ({ type, selectedChain, onSelect }: Props) => {
99
99
  </button>
100
100
 
101
101
  {open && (
102
- <div className="absolute right-0 top-[calc(100%+8px)] z-20 flex flex-col w-[220px] max-h-[280px] rounded-xl bg-t_bg_primary border border-solid border-t_text_primary border-opacity-10 backdrop-blur-[24px] overflow-hidden shadow-lg">
103
- <div className="flex items-center gap-2 px-4 py-3 border-b border-solid border-t_text_primary border-opacity-10">
104
- <div className="text-t_text_primary opacity-60 [&_svg]:w-3.5 [&_svg]:h-3.5 flex items-center">
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
105
  <SearchThinIcon />
106
106
  </div>
107
107
  <input
108
108
  value={searchValue}
109
109
  onChange={(e) => setSearchValue(e.target.value)}
110
110
  placeholder={`Browse ${chains.length} Chains...`}
111
- 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"
112
112
  />
113
113
  </div>
114
- <div className="h-[200px]">
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]">
115
119
  <Virtuoso
116
- style={{ height: "200px" }}
120
+ style={{ height: "214px" }}
117
121
  data={[undefined, ...filteredChains] as (Chain | undefined)[]}
118
- itemContent={(_, chain) => (
119
- <button
120
- type="button"
121
- onClick={() => {
122
- onSelect(chain);
123
- setOpen(false);
124
- }}
125
- className={`flex items-center gap-2 w-full px-4 py-2 text-left hover:bg-t_bg_tertiary hover:bg-opacity-10 transition-colors ${
126
- (chain?.chainId || "") === (selectedChain?.chainId || "")
127
- ? "bg-t_bg_tertiary bg-opacity-10"
128
- : ""
129
- }`}
130
- >
131
- {chain ? (
132
- <img
133
- src={chain.image}
134
- alt={chain.displayName}
135
- className="w-5 h-5 rounded-full"
136
- />
137
- ) : (
138
- <div className="grid grid-cols-2 gap-0.5 shrink-0">
139
- {clusterChains.map((clusterChain) => (
140
- <img
141
- key={clusterChain.chainId}
142
- src={clusterChain.image}
143
- alt={clusterChain.displayName}
144
- className="w-[11px] h-[11px] rounded-full"
145
- />
146
- ))}
147
- </div>
148
- )}
149
- <p className="text-sm leading-6 text-t_text_primary truncate">
150
- {chain ? chain.displayName : "All Networks"}
151
- </p>
152
- </button>
153
- )}
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
+ }}
154
164
  />
155
165
  </div>
156
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-[#D77F02]">
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">
@@ -480,7 +480,7 @@ export const TokenPicker = ({
480
480
  return showImportWarning ? (
481
481
  <>
482
482
  <div className="flex justify-between items-center mb-6">
483
- <div className="text-2xl leading-8 tracking-[0.01em]">
483
+ <div className="text-2xl font-medium leading-8 tracking-[0.01em]">
484
484
  Import token
485
485
  </div>
486
486
  <button
@@ -570,7 +570,7 @@ export const TokenPicker = ({
570
570
  ) : (
571
571
  <>
572
572
  <div className="flex justify-between items-center mb-6">
573
- <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">
574
574
  Select Token
575
575
  </div>
576
576
  <button
@@ -626,7 +626,7 @@ export const TokenPicker = ({
626
626
  <div className="text-sm font-medium text-ellipsis">
627
627
  {customTokenData.name}
628
628
  </div>
629
- <div className="text-xs text-t_text_primary !text-opacity-50 font-medium">
629
+ <div className="text-xs text-t_text_primary !text-opacity-50">
630
630
  {importChain?.displayName}
631
631
  </div>
632
632
  </div>
@@ -648,7 +648,7 @@ export const TokenPicker = ({
648
648
  <div className="flex flex-col h-full min-h-0">
649
649
  {/* Pinned label of the group at the top of the list; sits
650
650
  above the scroller, so rows clip on its bottom edge. */}
651
- <p className="pb-3 text-[10px] font-medium leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30 text-left shrink-0">
651
+ <p className="pb-3 text-[10px] leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30 text-left shrink-0">
652
652
  {pinnedGroupLabel}
653
653
  </p>
654
654
  <div className="relative flex flex-col flex-1 min-h-0">
@@ -663,7 +663,7 @@ export const TokenPicker = ({
663
663
  }
664
664
  if (row.kind === "label") {
665
665
  return (
666
- <p className="pt-2 pb-3 text-[10px] font-medium leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30 text-left">
666
+ <p className="pt-2 pb-3 text-[10px] leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30 text-left">
667
667
  {row.label}
668
668
  </p>
669
669
  );
@@ -1,5 +1,5 @@
1
1
  import { ChevronDownThinIcon } from "@src/assets/icons";
2
- import { TokenLogoWithChain } from "@src/components";
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"
@@ -86,7 +106,9 @@ export const TokenPanel = ({ chain, token, type, className }: Props) => {
86
106
  )}
87
107
  </div>
88
108
  {!locked && (
89
- <div className="flex items-center justify-center w-6 h-6 text-t_text_primary opacity-50">
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">
90
112
  <ChevronDownThinIcon />
91
113
  </div>
92
114
  )}
@@ -1,4 +1,5 @@
1
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,8 +22,12 @@ export const WalletPanel = ({ chain, className }: Props) => {
21
22
  solana,
22
23
  sui,
23
24
  } = useWallet();
24
- const { integrationConfig, hasOnConnectWalletCallback, onConnectWallet } =
25
- useSwapContext();
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
@@ -68,6 +73,10 @@ export const WalletPanel = ({ chain, className }: Props) => {
68
73
  return "";
69
74
  }, [chain, evm, solana, sui]);
70
75
 
76
+ if (!isAsyncDataLoaded) {
77
+ return <Skeleton className="w-16 h-6" />;
78
+ }
79
+
71
80
  // Reserve the button's height before the chain loads so the card
72
81
  // doesn't grow when the wallet button appears.
73
82
  if (!chain) {
@@ -85,7 +94,9 @@ export const WalletPanel = ({ chain, className }: Props) => {
85
94
  <p className="text-[10px] leading-4 tracking-[0.01em] text-t_text_primary whitespace-nowrap">
86
95
  {address ? shortAddress(address) : "Connect"}
87
96
  </p>
88
- <div className="flex items-center justify-center w-4 h-4 text-t_text_primary opacity-50">
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">
89
100
  <ChevronDownThinSmallIcon />
90
101
  </div>
91
102
  </button>