Skip to content

Commit 93c54f0

Browse files
committed
[MNY-189] SDK: SwapWidget UI improvements (#8080)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on UI improvements for the `SwapWidget`, enhancing responsiveness, styling, and functionality, particularly for mobile users. It also refines token selection and formatting for better user experience. ### Detailed summary - Removed a changeset file related to `SwapWidget` UI improvements. - Adjusted breakpoints and font sizes in `design-system/index.ts`. - Enhanced `SearchInput` styling in `SearchInput.tsx`. - Added `tokenAmountFormatter` utility in `utils.ts`. - Made `children` prop optional in `basic.tsx`. - Updated color values in `sdk-component-theme.ts`. - Enhanced `Input` component styles in `formElements.tsx`. - Improved `BuyAndSwapEmbed.tsx` with better token handling. - Introduced `useIsMobile` hook for mobile detection. - Updated `ArrowUpDownIcon.tsx` SVG for better rendering. - Added hover background support in `buttons.tsx`. - Renamed `WithData` to `WithDataDesktop` in `SelectChain.stories.tsx`. - Added mobile versions of `WithData` and `Loading` functions in `SelectChain.stories.tsx`. - Updated `SwapWidget` stories to manage token selections and themes. - Refined `select-chain.tsx` for better mobile handling. - Improved `swap-ui.tsx` with mobile responsiveness and token selection logic. - Enhanced `select-token-ui.tsx` with mobile UI improvements and token display logic. - Added `ActiveWalletDetails` for displaying wallet information. - Updated various button and token display styles for consistency and better UX. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Swap widget: onDisconnect callback, details modal, max-fill, improved wallet/account display, clearer loading/insufficient-balance states; stories now disable token-selection persistence. * Mobile detection hook enabling separate mobile/desktop flows. * **UI/Style** * Enhanced token/chain visuals (gradients, smaller icon size), adjusted spacing and input sizing, customizable button hover backgrounds. * **Refactor** * Unified token amount formatting and simplified numeric displays. * **Chores** * Added changeset entry for UI improvements. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent e9225c3 commit 93c54f0

File tree

22 files changed

+1010
-558
lines changed

22 files changed

+1010
-558
lines changed

.changeset/nine-otters-pay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
SwapWidget UI improvements

apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,13 @@ export function BuyAndSwapEmbed(props: {
107107
theme={themeObj}
108108
className="!rounded-2xl !border-none !w-full"
109109
prefill={{
110-
sellToken: {
110+
// buy this token by default
111+
buyToken: {
111112
chainId: props.chain.id,
112113
tokenAddress: props.tokenAddress,
113114
},
114-
// only set `buyToken` as "Native token" if `sellToken` is not a "native token" already
115-
buyToken: props.tokenAddress
115+
// sell the native token by default (but if buytoken is a native token, don't set)
116+
sellToken: props.tokenAddress
116117
? {
117118
chainId: props.chain.id,
118119
}

apps/dashboard/src/@/utils/sdk-component-theme.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ export function getSDKTheme(theme: "light" | "dark"): Theme {
3131
selectedTextBg: "hsl(var(--inverted))",
3232
selectedTextColor: "hsl(var(--inverted-foreground))",
3333
separatorLine: "hsl(var(--border))",
34-
skeletonBg: "hsl(var(--muted))",
34+
skeletonBg: "hsl(var(--secondary-foreground)/15%)",
3535
success: "hsl(var(--success-text))",
36-
tertiaryBg: "hsl(var(--muted)/50%)",
36+
tertiaryBg: "hsl(var(--muted)/30%)",
3737
tooltipBg: "hsl(var(--popover))",
3838
tooltipText: "hsl(var(--popover-foreground))",
3939
},

apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ export function RightSection(props: { options: SwapWidgetPlaygroundOptions }) {
5959
prefill={props.options.prefill}
6060
currency={props.options.currency}
6161
showThirdwebBranding={props.options.showThirdwebBranding}
62-
key={JSON.stringify(props.options)}
62+
key={JSON.stringify({
63+
prefill: props.options.prefill,
64+
})}
6365
persistTokenSelections={false}
6466
/>
6567
)}

apps/portal/src/app/bridge/swap/page.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
TypeScriptIcon,
1414
} from "@/icons";
1515
import SwapWidgetImage from "./swap-dark.png";
16+
import SwapWidgetImageLight from "./swap-light.png";
1617

1718
export const metadata = createMetadata({
1819
image: {
@@ -55,7 +56,12 @@ function Example() {
5556
}
5657
```
5758

59+
<div className='dark-only'>
5860
<DocImage src={SwapWidgetImage} />
61+
</div>
62+
<div className='light-only'>
63+
<DocImage src={SwapWidgetImageLight} />
64+
</div>
5965

6066
## Live Playground
6167

17.6 KB
Loading
142 KB
Loading

packages/thirdweb/src/react/core/design-system/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ export const iconSize = {
200200
"4xl": "128",
201201
lg: "32",
202202
md: "24",
203+
"sm+": "20",
203204
sm: "16",
204205
xl: "48",
205206
xs: "12",

packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export function SearchInput(props: {
3636
variant="outline"
3737
placeholder={props.placeholder}
3838
value={props.value}
39+
sm
3940
style={{
4041
paddingLeft: "44px",
4142
}}

packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx

Lines changed: 59 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ export type SwapWidgetProps = {
162162
* @default true
163163
*/
164164
persistTokenSelections?: boolean;
165+
/**
166+
* Called when the user disconnects the active wallet
167+
*/
168+
onDisconnect?: () => void;
165169
};
166170

167171
/**
@@ -325,46 +329,11 @@ function SwapWidgetContent(props: SwapWidgetProps) {
325329
});
326330

327331
const [buyToken, setBuyToken] = useState<TokenSelection | undefined>(() => {
328-
if (props.prefill?.buyToken) {
329-
return {
330-
tokenAddress:
331-
props.prefill.buyToken.tokenAddress ||
332-
getAddress(NATIVE_TOKEN_ADDRESS),
333-
chainId: props.prefill.buyToken.chainId,
334-
};
335-
}
336-
337-
if (!isPersistEnabled) {
338-
return undefined;
339-
}
340-
341-
const lastUsedBuyToken = getLastUsedTokens()?.buyToken;
342-
343-
// the token that will be set as initial value of sell token
344-
const sellToken = getInitialSellToken(
345-
props.prefill,
346-
getLastUsedTokens()?.sellToken,
347-
);
348-
349-
// if both tokens are same, ignore "buyToken", keep "sellToken"
350-
if (
351-
lastUsedBuyToken &&
352-
sellToken &&
353-
lastUsedBuyToken.tokenAddress.toLowerCase() ===
354-
sellToken.tokenAddress.toLowerCase() &&
355-
lastUsedBuyToken.chainId === sellToken.chainId
356-
) {
357-
return undefined;
358-
}
359-
360-
return lastUsedBuyToken;
332+
return getInitialTokens(props.prefill).buyToken;
361333
});
362334

363335
const [sellToken, setSellToken] = useState<TokenSelection | undefined>(() => {
364-
return getInitialSellToken(
365-
props.prefill,
366-
isPersistEnabled ? getLastUsedTokens()?.sellToken : undefined,
367-
);
336+
return getInitialTokens(props.prefill).sellToken;
368337
});
369338

370339
// persist selections to localStorage whenever they change
@@ -394,6 +363,7 @@ function SwapWidgetContent(props: SwapWidgetProps) {
394363
if (screen.id === "1:swap-ui" || !activeWalletInfo) {
395364
return (
396365
<SwapUI
366+
onDisconnect={props.onDisconnect}
397367
showThirdwebBranding={
398368
props.showThirdwebBranding === undefined
399369
? true
@@ -533,17 +503,60 @@ function SwapWidgetContent(props: SwapWidgetProps) {
533503
return null;
534504
}
535505

536-
function getInitialSellToken(
537-
prefill: SwapWidgetProps["prefill"],
538-
lastUsedSellToken: TokenSelection | undefined,
539-
) {
540-
if (prefill?.sellToken) {
506+
function getInitialTokens(prefill: SwapWidgetProps["prefill"]): {
507+
buyToken: TokenSelection | undefined;
508+
sellToken: TokenSelection | undefined;
509+
} {
510+
const lastUsedTokens = getLastUsedTokens();
511+
const buyToken = prefill?.buyToken
512+
? {
513+
tokenAddress:
514+
prefill.buyToken.tokenAddress || getAddress(NATIVE_TOKEN_ADDRESS),
515+
chainId: prefill.buyToken.chainId,
516+
}
517+
: lastUsedTokens?.buyToken;
518+
519+
const sellToken = prefill?.sellToken
520+
? {
521+
tokenAddress:
522+
prefill.sellToken.tokenAddress || getAddress(NATIVE_TOKEN_ADDRESS),
523+
chainId: prefill.sellToken.chainId,
524+
}
525+
: lastUsedTokens?.sellToken;
526+
527+
// if both tokens are same
528+
if (
529+
buyToken &&
530+
sellToken &&
531+
buyToken.tokenAddress?.toLowerCase() ===
532+
sellToken.tokenAddress?.toLowerCase() &&
533+
buyToken.chainId === sellToken.chainId
534+
) {
535+
// if sell token prefill is specified, ignore buy token
536+
if (prefill?.sellToken) {
537+
return {
538+
buyToken: undefined,
539+
sellToken: sellToken,
540+
};
541+
}
542+
543+
// if buy token prefill is specified, ignore sell token
544+
if (prefill?.buyToken) {
545+
return {
546+
buyToken: buyToken,
547+
sellToken: undefined,
548+
};
549+
}
550+
551+
// if none of the two are specified via prefill, keep buy token
541552
return {
542-
tokenAddress:
543-
prefill.sellToken.tokenAddress || getAddress(NATIVE_TOKEN_ADDRESS),
544-
chainId: prefill.sellToken.chainId,
553+
buyToken: buyToken,
554+
sellToken: undefined,
545555
};
546556
}
547557

548-
return lastUsedSellToken;
558+
return {
559+
buyToken: buyToken,
560+
sellToken: sellToken,
561+
};
549562
}

0 commit comments

Comments
 (0)