- 
                Notifications
    You must be signed in to change notification settings 
- Fork 613
[MNY-189] SDK: SwapWidget UI improvements #8080
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| The latest updates on your projects. Learn more about Vercel for GitHub. 
 | 
| 🦋 Changeset detectedLatest commit: 93f913c The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
 Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR | 
| WalkthroughUI and API updates to the Swap Widget: responsive two‑pane desktop vs mobile token/chain selection, wallet/account display with disconnect and Max action, token/fiat formatting changes, theme/design token tweaks, new mobile detection hook, minor component API additions (onDisconnect, isMobile, hoverBg, iconSize), and removal of DecimalRenderer. Changes
 Sequence Diagram(s)sequenceDiagram
  autonumber
  participant App as Parent App
  participant SW as SwapWidget
  participant UI as SwapUI
  participant DM as DetailsModal
  participant TS as TokenSelect
  App->>SW: render(props { prefill?, onDisconnect?, ... })
  SW->>UI: render({ prefill, onDisconnect, ... })
  Note over UI: Initial screen (desktop two‑pane or mobile flow)
  User->>UI: Click wallet area
  UI->>DM: open DetailsModal
  DM-->>UI: user confirms "Disconnect"
  UI-->>SW: call onDisconnect?()
  User->>UI: Open token selection
  UI->>TS: open token select (isMobile? controls layout)
  User->>TS: search / select token
  TS-->>UI: onSelectToken(token)
  UI->>UI: update token state, persist if enabled
  User->>UI: Click "Max"
  UI->>UI: set input = wallet.maxBalance
  UI->>UI: fetch quote (async)
  UI-->>User: show Spinner / update CTA / display USD value
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
 ✅ Passed checks (2 passed)
 ✨ Finishing touches
 🧪 Generate unit tests
 Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
 Comment  | 
| How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue: 
 You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. | 
| size-limit report 📦
 | 
| Codecov Report❌ Patch coverage is  
 Additional details and impacted files@@            Coverage Diff             @@
##             main    #8080      +/-   ##
==========================================
+ Coverage   56.33%   56.34%   +0.01%     
==========================================
  Files         906      906              
  Lines       59175    59182       +7     
  Branches     4178     4176       -2     
==========================================
+ Hits        33338    33349      +11     
+ Misses      25731    25727       -4     
  Partials      106      106              
 
 🚀 New features to boost your workflow:
 | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️  Outside diff range comments (1)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (1)
196-203: Bug: BigInt compared to Number (throws at runtime)sellTokenBalanceQuery.data.value is a BigInt; toUnits returns BigInt, but it’s converted to Number and compared with BigInt, which throws TypeError in JS (“Cannot mix BigInt and other types”).
- const notEnoughBalance = !!( + const notEnoughBalance = !!( sellTokenBalanceQuery.data && sellTokenWithPrices && props.amountSelection.amount && !!sellTokenAmount && - sellTokenBalanceQuery.data.value < - Number(toUnits(sellTokenAmount, sellTokenWithPrices.decimals)) + sellTokenBalanceQuery.data.value < + toUnits(sellTokenAmount, sellTokenWithPrices.decimals) );
🧹 Nitpick comments (16)
packages/thirdweb/src/react/web/ui/components/buttons.tsx (3)
24-25: Document newhoverBgprop (public surface).Add TSDoc (with example and tag) since this is in
packages/thirdweb.Apply:
type ButtonProps = { variant: @@ gap?: keyof typeof spacing; bg?: keyof Theme["colors"]; - hoverBg?: keyof Theme["colors"]; + /** + * Optional hover background token from the theme. + * When set, certain variants use this color on hover. + * @example + * <Button variant="secondary" hoverBg="secondaryButtonHoverBg">Swap</Button> + * @beta + */ + hoverBg?: keyof Theme["colors"]; };
99-101: Global&:hoverbackground can override variants unintentionally.Passing
hoverBgwill affect all variants (e.g., “ghost”/“outline”), likely not desired.Prefer scoping to variants that support it, or remove the global override:
- "&:hover": { - background: props.hoverBg ? theme.colors[props.hoverBg] : undefined, - }, + // background hover handled per-variant to avoid leaking into other variantsAlso consider smoothing hover with a background transition (see next diff).
92-99: Add background to transition for smoother hover.Minor polish; avoids abrupt hover when variants change background.
- transition: "border 200ms ease", + transition: "border 200ms ease, background 200ms ease",packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
165-166: Add TSDoc foronDisconnect; matches changeset minor bump.Public prop; document behavior and intent.
persistTokenSelections?: boolean; - onDisconnect?: () => void; + /** + * Called when the user disconnects their wallet from the widget UI. + * Useful to clear app state or show a re‑connect prompt. + * @example + * <SwapWidget onDisconnect={() => analytics.track("wallet_disconnected")} /> + * @beta + */ + onDisconnect?: () => void;packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
5-9: Locale/precision considerations for token formatter.Hard‑coding "en-US" and min 2 decimals may hide very small balances; consider env locale or a "<0.01" guard for tiny values.
-export const tokenAmountFormatter = new Intl.NumberFormat("en-US", { +// Uses user's locale by default; override via env if needed. +export const tokenAmountFormatter = new Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 5, minimumFractionDigits: 2, });Confirm downstream code avoids
Number(...)on large token strings to prevent precision loss in displays.packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx (1)
23-76: Add a story showcasing onDisconnectSince SwapWidget now accepts onDisconnect (forwarded to SwapUI), include a minimal story to demonstrate the disconnect flow for reviewers and QA.
Example snippet to add a new story:
export function WithDisconnect() { return ( <SwapWidget client={storyClient} persistTokenSelections={false} onDisconnect={() => console.log("Disconnected")} /> ); }packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (6)
146-155: Avoid mutating props arrays when sorting.sort mutates the input array; here it mutates props.ownedTokens/otherTokens. Clone first to keep inputs immutable.
- const sortedOwnedTokens = useMemo(() => { - return props.ownedTokens.sort((a, b) => { + const sortedOwnedTokens = useMemo(() => { + return [...props.ownedTokens].sort((a, b) => { if (a.icon_uri && !b.icon_uri) return -1; if (!a.icon_uri && b.icon_uri) return 1; return 0; }); }, [props.ownedTokens]); - const sortedOtherTokens = useMemo(() => { - return otherTokens.sort((a, b) => { + const sortedOtherTokens = useMemo(() => { + return [...otherTokens].sort((a, b) => { if (a.iconUri && !b.iconUri) return -1; if (!a.iconUri && b.iconUri) return 1; return 0; }); }, [otherTokens]);Also applies to: 171-180
454-461: Use themed color instead of hardcoded "white" in gradientsHardcoding white can clash in dark themes. Prefer theme-derived start color.
- background: `linear-gradient(45deg, white, ${theme.colors.accentText})`, + background: `linear-gradient(45deg, ${theme.colors.modalBg}, ${theme.colors.accentText})`,Apply to both fallbacks shown in TokenButton and SelectedTokenButton.
Also applies to: 966-974, 981-989
513-519: Render $0.00 instead of hiding when usdValue is zeroThe current condition hides the USD value for zero. Show explicit zero for consistency.
- {usdValue && ( + {usdValue !== undefined && ( <Container flex="row"> <Text size="xs" color="secondaryText" weight={400}> ${usdValue.toFixed(2)} </Text> </Container> )}
484-491: Reuse computed balance instead of recomputingMinor tidy-up and avoids double toTokens conversion.
- <Text size="md" color="primaryText"> - {tokenAmountFormatter.format( - Number( - toTokens(BigInt(props.token.balance), props.token.decimals), - ), - )} - </Text> + <Text size="md" color="primaryText"> + {tokenAmountFormatter.format(Number(tokenBalanceInUnits))} + </Text>
439-447: Add alt text for token imagesImproves a11y and aids screen readers.
- <Img + <Img + alt={props.token.symbol} src={ ("balance" in props.token ? props.token.icon_uri : props.token.iconUri) || "" }
868-873: Prefer spacing tokens over raw pixel literalsReplace "3px"/"2px" with spacing tokens for consistency with the design system.
Example changes:
- gap: "3px", + gap: spacing["3xs"], - padding: "2px", + padding: spacing.xxs,Also applies to: 895-901, 997-1004
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (4)
409-422: Move the error state into the CTA label and keep the layout stableAligns with the PR objective: disable CTA and surface the error on the button instead of a separate message.
- {/* error message */} - {preparedResultQuery.error ? ( - <Text - size="sm" - color="danger" - center - style={{ - paddingBlock: spacing.md, - }} - > - Failed to get a quote - </Text> - ) : ( - <Spacer y="md" /> - )} + {/* keep spacing consistent regardless of error */} + <Spacer y="md" />- {preparedResultQuery.isFetching - ? "Fetching Quote" - : notEnoughBalance - ? "Insufficient Balance" - : "Swap"} + {preparedResultQuery.isFetching + ? "Fetching Quote" + : preparedResultQuery.error + ? "Failed to get quote" + : notEnoughBalance + ? "Insufficient Balance" + : "Swap"}Also applies to: 468-474
463-465: Drop manual opacity on disabled buttonButton already styles [data-disabled='true']; extra opacity can hurt contrast.
style={{ fontSize: fontSize.md, borderRadius: radius.full, - opacity: disableContinue ? 0.5 : 1, }}
1158-1191: Remove duplicate nested WalletProviderTwo WalletProvider wrappers with the same id are nested; keep one to avoid redundant context providers and extra renders.
- return ( - <WalletProvider id={props.activeWalletInfo.activeWallet.id}> - <AccountProvider address={account.address} client={props.client}> - <WalletProvider id={wallet.id}> - <Container flex="row" gap="xxs" center="y"> + return ( + <WalletProvider id={props.activeWalletInfo.activeWallet.id}> + <AccountProvider address={account.address} client={props.client}> + <Container flex="row" gap="xxs" center="y"> ... - </Container> - </WalletProvider> - </AccountProvider> - </WalletProvider> - ); + </Container> + </AccountProvider> + </WalletProvider> + );
313-320: Max button: leave gas buffer for native tokensSelling 100% native balance can fail due to gas. Subtract a small buffer when the sell token is native.
Example patch (uses existing sellTokenWithPrices from closure):
onMaxClick={() => { - if (sellTokenBalanceQuery.data) { - props.setAmountSelection({ - type: "sell", - amount: sellTokenBalanceQuery.data.displayValue, - }); - } + if (sellTokenBalanceQuery.data && sellTokenWithPrices) { + const isNative = + getAddress(sellTokenWithPrices.address) === getAddress(NATIVE_TOKEN_ADDRESS); + if (!isNative) { + return props.setAmountSelection({ + type: "sell", + amount: sellTokenBalanceQuery.data.displayValue, + }); + } + // ~0.001 as gas buffer (tune per chain if needed) + const buffer = toUnits("0.001", sellTokenWithPrices.decimals); + const safe = sellTokenBalanceQuery.data.value > buffer + ? sellTokenBalanceQuery.data.value - buffer + : 0n; + props.setAmountSelection({ + type: "sell", + amount: toTokens(safe, sellTokenWithPrices.decimals), + }); + } }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (12)
- .changeset/nine-otters-pay.md(1 hunks)
- apps/dashboard/src/@/utils/sdk-component-theme.ts(1 hunks)
- packages/thirdweb/src/react/core/design-system/index.ts(1 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx(2 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/common.tsx(0 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx(7 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx(27 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts(1 hunks)
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx(1 hunks)
- packages/thirdweb/src/react/web/ui/components/basic.tsx(1 hunks)
- packages/thirdweb/src/react/web/ui/components/buttons.tsx(4 hunks)
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx(3 hunks)
💤 Files with no reviewable changes (1)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/common.tsx
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
- packages/thirdweb/src/react/core/design-system/index.ts
- apps/dashboard/src/@/utils/sdk-component-theme.ts
- packages/thirdweb/src/react/web/ui/components/basic.tsx
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
- packages/thirdweb/src/react/core/design-system/index.ts
- apps/dashboard/src/@/utils/sdk-component-theme.ts
- packages/thirdweb/src/react/web/ui/components/basic.tsx
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
- packages/thirdweb/src/react/core/design-system/index.ts
- packages/thirdweb/src/react/web/ui/components/basic.tsx
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
.changeset/*.md
📄 CodeRabbit inference engine (AGENTS.md)
.changeset/*.md: Each change inpackages/*must include a changeset for the appropriate package
Version bump rules: patch for non‑API changes; minor for new/modified public API
Files:
- .changeset/nine-otters-pay.md
**/*.stories.tsx
📄 CodeRabbit inference engine (CLAUDE.md)
For new UI components, add Storybook stories (
*.stories.tsx) alongside the code
Files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
🧠 Learnings (12)
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Use design system tokens (e.g., `bg-card`, `border-border`, `text-muted-foreground`)
Applied to files:
- packages/thirdweb/src/react/core/design-system/index.ts
- apps/dashboard/src/@/utils/sdk-component-theme.ts
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Applied to files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Stick to design-tokens: background (`bg-card`), borders (`border-border`), muted text (`text-muted-foreground`) etc.
Applied to files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Never hard-code colors – always go through Tailwind variables.
Applied to files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
📚 Learning: 2025-08-28T19:32:53.229Z
Learnt from: MananTank
PR: thirdweb-dev/js#7939
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx:50-60
Timestamp: 2025-08-28T19:32:53.229Z
Learning: The Button component in packages/ui/src/components/button.tsx has type="button" as the default when no type prop is explicitly provided. This means explicitly adding type="button" to Button components is redundant unless a different type (like "submit") is specifically needed.
Applied to files:
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
📚 Learning: 2025-06-26T19:46:04.024Z
Learnt from: gregfromstl
PR: thirdweb-dev/js#7450
File: packages/thirdweb/src/bridge/Webhook.ts:57-81
Timestamp: 2025-06-26T19:46:04.024Z
Learning: In the onramp webhook schema (`packages/thirdweb/src/bridge/Webhook.ts`), the `currencyAmount` field is intentionally typed as `z.number()` while other amount fields use `z.string()` because `currencyAmount` represents fiat currency amounts in decimals (like $10.50), whereas other amount fields represent token amounts in wei (very large integers that benefit from bigint representation). The different naming convention (`currencyAmount` vs `amount`) reflects this intentional distinction.
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
📚 Learning: 2025-06-06T23:47:55.122Z
Learnt from: MananTank
PR: thirdweb-dev/js#7298
File: apps/dashboard/src/app/nebula-app/move-funds/move-funds.tsx:255-277
Timestamp: 2025-06-06T23:47:55.122Z
Learning: The `transfer` function from `thirdweb/extensions/erc20` accepts human-readable amounts via the `amount` property and automatically handles conversion to base units (wei) by fetching the token decimals internally. Manual conversion using `toWei()` is not required when using the `amount` property.
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
📚 Learning: 2025-09-18T20:09:57.064Z
Learnt from: MananTank
PR: thirdweb-dev/js#8069
File: apps/playground-web/src/app/bridge/swap-widget/components/types.ts:3-12
Timestamp: 2025-09-18T20:09:57.064Z
Learning: The SwapWidgetPlaygroundOptions type should not include persistTokenSelections - the playground intentionally hardcodes persistTokenSelections={false} and doesn't expose it as a configurable option to users.
Applied to files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Interactive UI that relies on hooks (`useState`, `useEffect`, React Query, wallet hooks).
Applied to files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
📚 Learning: 2025-05-30T17:14:25.332Z
Learnt from: MananTank
PR: thirdweb-dev/js#7227
File: apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/OpenEditionMetadata.tsx:26-26
Timestamp: 2025-05-30T17:14:25.332Z
Learning: The ModuleCardUIProps interface already includes a client prop of type ThirdwebClient, so when components use `Omit<ModuleCardUIProps, "children" | "updateButton">`, they inherit the client prop without needing to add it explicitly.
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
📚 Learning: 2025-09-17T11:14:35.659Z
Learnt from: MananTank
PR: thirdweb-dev/js#8044
File: packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx:919-930
Timestamp: 2025-09-17T11:14:35.659Z
Learning: In the thirdweb codebase, useCustomTheme() hook can be used inside styled-components callbacks, contrary to the general React Rules of Hooks. This is a valid pattern in their implementation.
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Import UI primitives from `@/components/ui/_` (e.g., Button, Input, Tabs, Card)
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
🧬 Code graph analysis (4)
packages/thirdweb/src/react/web/ui/components/buttons.tsx (2)
packages/thirdweb/src/react/core/design-system/index.ts (1)
Theme(49-96)packages/thirdweb/src/exports/react.ts (1)
Theme(6-6)
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx (2)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
SwapWidget(239-249)packages/thirdweb/src/stories/utils.tsx (1)
storyClient(15-17)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (11)
packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
Container(80-193)packages/thirdweb/src/react/web/ui/components/Modal.tsx (1)
Modal(32-164)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (1)
SelectToken(55-124)packages/thirdweb/src/react/web/ui/components/Spinner.tsx (1)
Spinner(11-37)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
ActiveWalletInfo(140-144)packages/thirdweb/src/react/web/ui/components/text.tsx (1)
Text(18-34)packages/thirdweb/src/react/web/ui/components/Skeleton.tsx (1)
Skeleton(10-28)packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
Button(27-161)packages/thirdweb/src/pay/convert/type.ts (1)
getFiatSymbol(29-31)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (2)
tokenAmountFormatter(5-9)
cleanedChainName(1-3)packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
Img(12-152)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (4)
packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
Container(80-193)packages/thirdweb/src/react/core/design-system/index.ts (2)
radius(188-196)
iconSize(198-208)packages/thirdweb/src/react/web/ui/components/text.tsx (1)
Text(18-34)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
tokenAmountFormatter(5-9)
🪛 GitHub Check: codecov/patch
packages/thirdweb/src/react/web/ui/components/buttons.tsx
[warning] 127-127: packages/thirdweb/src/react/web/ui/components/buttons.tsx#L127
Added line #L127 was not covered by tests
🔇 Additional comments (8)
packages/thirdweb/src/react/core/design-system/index.ts (1)
203-205: Icon size token addition looks good.
"sm+": "20"fits existing string-valued sizing. No concerns.apps/dashboard/src/@/utils/sdk-component-theme.ts (1)
34-36: Theme tweaks acknowledged; sanity-check contrast in both themes.Visual change only. Please quickly verify skeletonBg/tertiaryBg meet contrast expectations on light/dark.
packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
87-87: Makingchildrenoptional is safe and idiomatic.Matches how the component already renders. No action needed.
packages/thirdweb/src/react/web/ui/components/buttons.tsx (2)
124-131: Variant “ghost-solid”: good fallback; leverage prop without global hover.With the global hover removed, this path is the single source of hover background for this variant. Looks good.
Note: codecov flagged this new branch as uncovered. Please add a minimal visual test/story to exercise
hoverBgon “ghost-solid”.
141-146: Variant “secondary”: scopedhoverBgis correct.Uses
hoverBgor falls back tosecondaryButtonHoverBg. Good.packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx (1)
6-20: Icon viewBox/stroke change: verify cross‑icon consistency.Switching to 16×16 and strokeWidth=1 is fine, but ensure it visually matches adjacent icons (previous set often used 24×24 / stroke 1.5–2).
If inconsistencies appear, consider normalizing strokeWidth or mapping sizes via
iconSize(the newsm+can help).packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
397-398: Prop plumb-through looks correct.onDisconnect is declared on SwapWidget (packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx:165), forwarded to SwapUI (packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx:397), declared in SwapUI props (packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx:88) and invoked in the disconnect UI (packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx:303–304).
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx (1)
24-24: Good: persistTokenSelections explicitly set to false in all storiesMatches the retrieved learning for the playground/stories. No concerns.
Also applies to: 28-34, 38-45, 54-54, 64-64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️  Outside diff range comments (2)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (1)
145-155: Avoid in-place sort of props.ownedTokens.Sorting mutates the input array. Clone before sorting to prevent accidental parent state/order mutations.
- const sortedOwnedTokens = useMemo(() => { - return props.ownedTokens.sort((a, b) => { + const sortedOwnedTokens = useMemo(() => { + return [...props.ownedTokens].sort((a, b) => { if (a.icon_uri && !b.icon_uri) { return -1; } if (!a.icon_uri && b.icon_uri) { return 1; } return 0; }); }, [props.ownedTokens]);packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (1)
196-203: BigInt-safe balance comparison.Comparing a bigint balance to Number(toUnits(...)) risks precision errors on large amounts. Compare bigint-to-bigint.
const notEnoughBalance = !!( sellTokenBalanceQuery.data && sellTokenWithPrices && props.amountSelection.amount && !!sellTokenAmount && - sellTokenBalanceQuery.data.value < - Number(toUnits(sellTokenAmount, sellTokenWithPrices.decimals)) + sellTokenBalanceQuery.data.value < + BigInt(toUnits(sellTokenAmount, sellTokenWithPrices.decimals)) );
🧹 Nitpick comments (10)
packages/thirdweb/src/react/web/ui/components/buttons.tsx (3)
24-25: Document the new hoverBg prop (public API).Add TSDoc with a short example, since this lives under packages/thirdweb and is part of the public surface.
type ButtonProps = { @@ - hoverBg?: keyof Theme["colors"]; + /** + * Background color token to apply on hover. + * @example + * <Button variant="ghost-solid" hoverBg="secondaryButtonBg">Select</Button> + * @beta + */ + hoverBg?: keyof Theme["colors"];
99-101: Global &:hover may conflict with variant-specific hover.This root hover background applies to all variants (incl. "accent" and "link"), potentially overriding variant semantics. Recommend removing the global hover or scoping it to variants that don't declare their own hover.
- "&:hover": { - background: props.hoverBg ? theme.colors[props.hoverBg] : undefined, - },
96-96: Animate background/color for smoother hover.Since hover now changes background, include background/color in the transition.
- transition: "border 200ms ease", + transition: "border 200ms ease, background 200ms ease, color 200ms ease",packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (3)
454-461: Theme-safe fallback gradient (avoid hardcoded white).Use theme tokens for light/dark compatibility.
- style={{ - background: `linear-gradient(45deg, white, ${theme.colors.accentText})`, + style={{ + background: `linear-gradient(45deg, ${theme.colors.modalBg}, ${theme.colors.accentText})`,
485-491: Potential precision loss converting token amount to Number.toTokens can exceed Number precision; formatting via Number may misrepresent large balances. Consider clamping or a bigint/decimal-based formatter for UI. Non-blocking here since it’s display-only.
513-519: Render $0.00 correctly.Guard with !== undefined so zero values show.
- {usdValue && ( + {usdValue !== undefined && ( <Container flex="row"> <Text size="xs" color="secondaryText" weight={400}> ${usdValue.toFixed(2)} </Text> </Container> )}packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (4)
968-989: Theme-safe fallback gradient (avoid hardcoded white).Match the token list fallback approach; use modalBg for dark themes.
- background: `linear-gradient(45deg, white, ${theme.colors.accentText})`, + background: `linear-gradient(45deg, ${theme.colors.modalBg}, ${theme.colors.accentText})`,
1166-1199: Duplicate WalletProvider nesting.Two WalletProviders with the same wallet id are nested; drop one to avoid redundant context providers.
- <WalletProvider id={props.activeWalletInfo.activeWallet.id}> - <AccountProvider address={account.address} client={props.client}> - <WalletProvider id={wallet.id}> + <AccountProvider address={account.address} client={props.client}> + <WalletProvider id={wallet.id}> <Container flex="row" gap="xxs" center="y"> ... </Container> - </WalletProvider> - </AccountProvider> - </WalletProvider> + </WalletProvider> + </AccountProvider>
61-90: Update TSDoc for new onDisconnect prop.SwapUI’s public prop gained onDisconnect; add/refresh TSDoc on the component or its props type.
440-466: Add data-disabled attribute for consistent disabled styling.Button styles use &[data-disabled='true'] in packages/thirdweb/src/react/web/ui/components/buttons.tsx:37 — add data-disabled to this Button so the theme’s disabled rules apply.
- <Button - disabled={disableContinue} + <Button + disabled={disableContinue} + data-disabled={disableContinue ? "true" : undefined} fullWidth
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (12)
- .changeset/nine-otters-pay.md(1 hunks)
- apps/dashboard/src/@/utils/sdk-component-theme.ts(1 hunks)
- packages/thirdweb/src/react/core/design-system/index.ts(1 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx(2 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/common.tsx(0 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx(7 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx(27 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts(1 hunks)
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx(1 hunks)
- packages/thirdweb/src/react/web/ui/components/basic.tsx(1 hunks)
- packages/thirdweb/src/react/web/ui/components/buttons.tsx(4 hunks)
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx(3 hunks)
💤 Files with no reviewable changes (1)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/common.tsx
✅ Files skipped from review due to trivial changes (1)
- .changeset/nine-otters-pay.md
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/thirdweb/src/react/core/design-system/index.ts
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
- apps/dashboard/src/@/utils/sdk-component-theme.ts
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
- packages/thirdweb/src/react/web/ui/components/basic.tsx
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
- packages/thirdweb/src/react/web/ui/components/basic.tsx
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
- packages/thirdweb/src/react/web/ui/components/basic.tsx
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
🧠 Learnings (5)
📚 Learning: 2025-08-28T19:32:53.229Z
Learnt from: MananTank
PR: thirdweb-dev/js#7939
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx:50-60
Timestamp: 2025-08-28T19:32:53.229Z
Learning: The Button component in packages/ui/src/components/button.tsx has type="button" as the default when no type prop is explicitly provided. This means explicitly adding type="button" to Button components is redundant unless a different type (like "submit") is specifically needed.
Applied to files:
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Interactive UI that relies on hooks (`useState`, `useEffect`, React Query, wallet hooks).
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Use design system tokens (e.g., `bg-card`, `border-border`, `text-muted-foreground`)
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
📚 Learning: 2025-09-17T11:14:35.659Z
Learnt from: MananTank
PR: thirdweb-dev/js#8044
File: packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx:919-930
Timestamp: 2025-09-17T11:14:35.659Z
Learning: In the thirdweb codebase, useCustomTheme() hook can be used inside styled-components callbacks, contrary to the general React Rules of Hooks. This is a valid pattern in their implementation.
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Import UI primitives from `@/components/ui/_` (e.g., Button, Input, Tabs, Card)
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
🧬 Code graph analysis (3)
packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
packages/thirdweb/src/react/core/design-system/index.ts (1)
Theme(49-96)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (11)
packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
Container(80-193)packages/thirdweb/src/react/web/ui/components/Modal.tsx (1)
Modal(32-164)packages/thirdweb/src/react/core/design-system/index.ts (4)
spacing(174-186)
fontSize(164-172)
radius(188-196)
iconSize(198-208)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (1)
SelectToken(55-124)packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx (1)
DetailsModal(372-1035)packages/thirdweb/src/react/web/ui/components/Spinner.tsx (1)
Spinner(11-37)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
ActiveWalletInfo(140-144)packages/thirdweb/src/react/web/ui/components/text.tsx (1)
Text(18-34)packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
Button(27-161)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (2)
tokenAmountFormatter(5-9)
cleanedChainName(1-3)packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
Img(12-152)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (4)
packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
Container(80-193)packages/thirdweb/src/react/core/design-system/index.ts (2)
radius(188-196)
iconSize(198-208)packages/thirdweb/src/react/web/ui/components/text.tsx (1)
Text(18-34)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
tokenAmountFormatter(5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Unit Tests
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Size
- GitHub Check: Build Packages
- GitHub Check: Lint Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
packages/thirdweb/src/react/web/ui/components/buttons.tsx (2)
124-131: Variant hover override looks good.ghost-solid correctly prefers hoverBg and falls back to tertiaryBg.
141-147: Secondary hover override looks good.secondary correctly prefers hoverBg and falls back to secondaryButtonHoverBg.
packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
87-91: Making children optional is fine.Safe change; renders as empty when undefined.
<!--
## 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 -->
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️  Outside diff range comments (2)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (1)
148-158: Do not mutate props when sorting
props.ownedTokens.sort mutates the input array. Clone before sorting to avoid side effects.Apply this diff:
- const sortedOwnedTokens = useMemo(() => { - return props.ownedTokens.sort((a, b) => { + const sortedOwnedTokens = useMemo(() => { + return [...props.ownedTokens].sort((a, b) => { if (a.icon_uri && !b.icon_uri) { return -1; } if (!a.icon_uri && b.icon_uri) { return 1; } return 0; }); }, [props.ownedTokens]);packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (1)
198-205: Fix BigInt vs Number comparison in balance checkConverting the required amount to Number loses precision and can flip the notEnoughBalance flag for large values. Compare BigInt to BigInt directly.
- const notEnoughBalance = !!( - sellTokenBalanceQuery.data && - sellTokenWithPrices && - props.amountSelection.amount && - !!sellTokenAmount && - sellTokenBalanceQuery.data.value < - Number(toUnits(sellTokenAmount, sellTokenWithPrices.decimals)) - ); + const notEnoughBalance = !!( + sellTokenBalanceQuery.data && + sellTokenWithPrices && + props.amountSelection.amount && + !!sellTokenAmount && + sellTokenBalanceQuery.data.value < + toUnits(sellTokenAmount, sellTokenWithPrices.decimals) + );
♻️ Duplicate comments (1)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
165-168: Add required TSDoc example and custom tag for public prop
Public API requires an example and a custom tag (@beta/@experimental). Please extend the doc.Apply this diff:
- /** - * Called when the user disconnects the active wallet - */ - onDisconnect?: () => void; + /** + * Called when the user disconnects the active wallet (from the wallet details UI). + * Use this to clear app state or prompt a reconnect flow. + * @example + * ```tsx + * <SwapWidget client={client} onDisconnect={() => console.log("disconnected")} /> + * ``` + * @beta + */ + onDisconnect?: () => void;
🧹 Nitpick comments (14)
packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx (4)
19-34: Add explicit return type to the story functionProject guidelines call for explicit return types in TSX. Annotate the story to return JSX.Element.
-export function WithDataDesktop() { +export function WithDataDesktop(): JSX.Element {
36-53: Add explicit return type to the loading (desktop) storyKeep types explicit for stories as well.
-export function LoadingDesktop() { +export function LoadingDesktop(): JSX.Element {
55-70: Add explicit return type and set a mobile viewport for this story
- Add an explicit return type.
- Optional: if the viewport addon is enabled, set a default mobile viewport so layout matches mobile breakpoints.-export function WithDataMobile() { +export function WithDataMobile(): JSX.Element {
Outside this range, add:
WithDataMobile.parameters = { viewport: { defaultViewport: "mobile2" }, // adjust to your configured viewport id };
72-89: Add explicit return type and mobile viewport for loading storySame as above: keep return type explicit, and optionally set a mobile viewport.
-export function LoadingMobile() { +export function LoadingMobile(): JSX.Element {Outside this range, add:
LoadingMobile.parameters = { viewport: { defaultViewport: "mobile2" }, };packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx (1)
13-20: Good consolidation; minor crispness + a11y nits
- Consolidating stroke props on is clean.
- Nit: the path has odd decimals (e.g., 9.599, 3.476) that can anti‑alias at common sizes. Consider snapping to .5 or one decimal for crisper rendering.
- A11y: since role="presentation", consider aria-hidden="true" to fully hide from AT.
Example path tweak (purely visual; confirm with design before changing):
- <path d="M9.599 2.4v11.2l3.2-3.476M6.4 13.6V2.4L3.2 5.875"></path> + <path d="M9.6 2.4v11.2l3.2-3.6M6.4 13.6V2.4L3.2 5.8" />Outside this hunk (optional a11y improvement):
- <svg + <svg + aria-hidden="true"apps/portal/src/app/bridge/swap/page.mdx (1)
16-16: Add alt text for theme images (a11y)
Both DocImage renders lack explicit alt text. Provide descriptive alts to improve accessibility and SEO.Apply this diff:
-<DocImage src={SwapWidgetImage} /> +<DocImage src={SwapWidgetImage} alt="Swap Widget (dark theme)"/> ... -<DocImage src={SwapWidgetImageLight} /> +<DocImage src={SwapWidgetImageLight} alt="Swap Widget (light theme)"/>If DocImage does not accept alt, please confirm the component sets an accessible name internally.
Also applies to: 59-64
apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx (1)
62-64: Stabilize key derivation to avoid unnecessary remounts
Using JSON.stringify on objects can be sensitive to property order and undefineds. Derive a minimal, stable key for prefill.Apply this diff:
- key={JSON.stringify({ - prefill: props.options.prefill, - })} + key={stablePrefillKey(props.options.prefill)}Add this helper (outside the component):
function stablePrefillKey( prefill: { buyToken?: any; sellToken?: any } | undefined, ): string { if (!prefill) return "prefill:null"; const buy = prefill.buyToken ? { chainId: prefill.buyToken.chainId ?? null, tokenAddress: prefill.buyToken.tokenAddress ?? "NATIVE", amount: prefill.buyToken.amount ?? "", } : null; const sell = prefill.sellToken ? { chainId: prefill.sellToken.chainId ?? null, tokenAddress: prefill.sellToken.tokenAddress ?? "NATIVE", amount: prefill.sellToken.amount ?? "", } : null; return JSON.stringify({ buy, sell }); }packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (5)
172-183: Prefer non-mutating sort for other tokens too
Sort a copy for consistency and future safety.Apply this diff:
- const sortedOtherTokens = useMemo(() => { - return otherTokens.sort((a, b) => { + const sortedOtherTokens = useMemo(() => { + return [...otherTokens].sort((a, b) => { if (a.iconUri && !b.iconUri) { return -1; } if (!a.iconUri && b.iconUri) { return 1; } return 0; }); }, [otherTokens]);
373-381: Guard number conversion for very large/small balances
Number(...) can overflow to Infinity or lose precision. Fallback to the string when not finite.Apply this diff:
- <Text size="md" color="primaryText"> - {tokenAmountFormatter.format( - Number( - toTokens(BigInt(props.token.balance), props.token.decimals), - ), - )} - </Text> + <Text size="md" color="primaryText"> + {(() => { + const s = toTokens( + BigInt(props.token.balance), + props.token.decimals, + ); + const n = Number(s); + return Number.isFinite(n) ? tokenAmountFormatter.format(n) : s; + })()} + </Text>
536-549: Use stable, unique React keys across chains
Keying only by token_address can collide across chains. Include chain_id.Apply this diff:
- <TokenButton - key={token.token_address} + <TokenButton + key={`${token.token_address}-${token.chain_id}`}
576-588: Ditto for “other tokens” list keys
Include chainId to prevent collisions.Apply this diff:
- <TokenButton - key={token.address} + <TokenButton + key={`${token.address}-${token.chainId}`}
493-506: Optional: hide scrollbars consistently
You’re setting scrollbarWidth but not the WebKit rule. Consider reusing noScrollBar for consistent cross-browser behavior.If Container’s style prop can accept it, extend style with noScrollBar; otherwise, move this section into a StyledDiv and spread noScrollBar there (as done in LeftContainer).
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (2)
1166-1201: Remove redundant nested WalletProviderWalletProvider is rendered twice around the same subtree, once with activeWallet.id and again with wallet.id (same value). The inner provider is redundant and may mask context bugs.
- <WalletProvider id={props.activeWalletInfo.activeWallet.id}> - <AccountProvider address={account.address} client={props.client}> - <WalletProvider id={wallet.id}> + <WalletProvider id={props.activeWalletInfo.activeWallet.id}> + <AccountProvider address={account.address} client={props.client}> <Container flex="row" gap="xxs" center="y"> ... </Container> - </WalletProvider> </AccountProvider> </WalletProvider>
441-476: Button disabled UX: consider aria-disabled with tooltip for error contextYou already surface “Fetching Quote” and “Insufficient Balance”. For accessibility, consider adding aria-disabled and a title to communicate reason when disabled.
-<Button - disabled={disableContinue} +<Button + aria-disabled={disableContinue} + title={preparedResultQuery.isFetching ? "Fetching Quote" : notEnoughBalance ? "Insufficient Balance" : undefined} + disabled={disableContinue}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (2)
- apps/portal/src/app/bridge/swap/swap-dark.pngis excluded by- !**/*.png
- apps/portal/src/app/bridge/swap/swap-light.pngis excluded by- !**/*.png
📒 Files selected for processing (20)
- .changeset/nine-otters-pay.md(1 hunks)
- apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx(1 hunks)
- apps/dashboard/src/@/utils/sdk-component-theme.ts(1 hunks)
- apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx(1 hunks)
- apps/portal/src/app/bridge/swap/page.mdx(2 hunks)
- packages/thirdweb/src/react/core/design-system/index.ts(1 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx(1 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx(4 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/common.tsx(0 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx(8 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx(12 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx(25 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts(1 hunks)
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx(1 hunks)
- packages/thirdweb/src/react/web/ui/components/basic.tsx(1 hunks)
- packages/thirdweb/src/react/web/ui/components/buttons.tsx(4 hunks)
- packages/thirdweb/src/react/web/ui/components/formElements.tsx(2 hunks)
- packages/thirdweb/src/react/web/ui/hooks/useisMobile.ts(1 hunks)
- packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx(2 hunks)
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx(3 hunks)
💤 Files with no reviewable changes (1)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/common.tsx
✅ Files skipped from review due to trivial changes (2)
- .changeset/nine-otters-pay.md
- apps/dashboard/src/@/utils/sdk-component-theme.ts
🚧 Files skipped from review as they are similar to previous changes (9)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
- packages/thirdweb/src/react/web/ui/hooks/useisMobile.ts
- packages/thirdweb/src/react/core/design-system/index.ts
- packages/thirdweb/src/react/web/ui/components/formElements.tsx
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
- packages/thirdweb/src/react/web/ui/components/basic.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx
- apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
- packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
- packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
**/*.stories.tsx
📄 CodeRabbit inference engine (CLAUDE.md)
For new UI components, add Storybook stories (
*.stories.tsx) alongside the code
Files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
- packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
- apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx
🧠 Learnings (9)
📚 Learning: 2025-09-18T20:09:57.064Z
Learnt from: MananTank
PR: thirdweb-dev/js#8069
File: apps/playground-web/src/app/bridge/swap-widget/components/types.ts:3-12
Timestamp: 2025-09-18T20:09:57.064Z
Learning: The SwapWidgetPlaygroundOptions type should not include persistTokenSelections - the playground intentionally hardcodes persistTokenSelections={false} and doesn't expose it as a configurable option to users.
Applied to files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Interactive UI that relies on hooks (`useState`, `useEffect`, React Query, wallet hooks).
Applied to files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to packages/thirdweb/src/exports/** : Every public symbol must have comprehensive TSDoc with at least one `example` block that compiles and custom annotation tags (`beta`, `internal`, `experimental`)
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to packages/thirdweb/**/*.{ts,tsx} : Every public symbol must have comprehensive TSDoc with at least one compiling `example` and a custom tag (`beta`, `internal`, `experimental`, etc.)
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Use design system tokens (e.g., `bg-card`, `border-border`, `text-muted-foreground`)
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
📚 Learning: 2025-09-17T11:14:35.659Z
Learnt from: MananTank
PR: thirdweb-dev/js#8044
File: packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx:919-930
Timestamp: 2025-09-17T11:14:35.659Z
Learning: In the thirdweb codebase, useCustomTheme() hook can be used inside styled-components callbacks, contrary to the general React Rules of Hooks. This is a valid pattern in their implementation.
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Spacing utilities (`px-*`, `py-*`, `gap-*`) are preferred over custom margins.
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Client Components must start with `'use client'`; handle interactivity with hooks and browser APIs
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Client Components (browser): Begin files with `'use client';`
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
🧬 Code graph analysis (5)
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx (2)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
SwapWidget(243-253)packages/thirdweb/src/stories/utils.tsx (1)
storyClient(15-17)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (2)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
TokenSelection(149-152)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/storage.ts (1)
getLastUsedTokens(23-41)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (10)
packages/thirdweb/src/react/web/ui/hooks/useisMobile.ts (1)
useIsMobile(5-21)packages/thirdweb/src/react/web/ui/components/basic.tsx (2)
Container(80-193)
noScrollBar(25-31)packages/thirdweb/src/react/core/design-system/index.ts (3)
spacing(174-186)
radius(188-196)
iconSize(198-208)packages/thirdweb/src/react/web/ui/components/text.tsx (1)
Text(18-34)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
tokenAmountFormatter(5-9)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts (1)
TokenBalance(40-58)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
TokenSelection(149-152)packages/thirdweb/src/react/web/ui/components/Spinner.tsx (1)
Spinner(11-37)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SelectChainButton.tsx (1)
SelectChainButton(14-50)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx (1)
SearchInput(10-47)
packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx (4)
packages/thirdweb/src/bridge/types/Chain.ts (1)
BridgeChain(42-42)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
SwapWidgetContainer(258-277)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx (2)
SelectBridgeChain(31-42)
SelectBridgeChainUI(47-133)packages/thirdweb/src/stories/utils.tsx (1)
storyClient(15-17)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (12)
packages/thirdweb/src/react/web/ui/hooks/useisMobile.ts (1)
useIsMobile(5-21)packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
Container(80-193)packages/thirdweb/src/react/web/ui/components/Modal.tsx (1)
Modal(32-164)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (1)
SelectToken(57-126)packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx (1)
DetailsModal(372-1035)packages/thirdweb/src/react/web/ui/components/Spinner.tsx (1)
Spinner(11-37)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
ActiveWalletInfo(140-144)packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
Button(27-161)packages/thirdweb/src/react/web/ui/ConnectWallet/screens/formatTokenBalance.ts (1)
formatCurrencyAmount(36-38)packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
Img(12-152)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
cleanedChainName(1-3)packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx (1)
ArrowUpDownIcon(3-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: Build Packages
- GitHub Check: Unit Tests
- GitHub Check: Lint Packages
- GitHub Check: Size
- GitHub Check: Analyze (javascript)
🔇 Additional comments (6)
packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx (2)
8-8: LGTM on xmlnsKeeping xmlns on inline SVGs is harmless and SSR‑safe.
6-6: Ensure consistent viewBox across ConnectWallet icons (choose 16×16 or 24×24)ArrowUpDownIcon (packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx) uses viewBox="0 0 16 16"; the icons folder has 4 icons with 16×16 and 9 with 24×24 — pick one standard and update the others to match (either revert this file to 24×24 or convert the nine 24×24 icons to 16×16).
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx (1)
24-24: LGTM: disable token selection persistence in stories
Setting persistTokenSelections={false} across stories ensures deterministic demos and aligns with playground guidance.Also applies to: 28-34, 38-45, 54-55, 64-64
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
362-367: LGTM: onDisconnect correctly forwarded to SwapUI
The callback is passed only on the initial screen, matching UX expectations.packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (2)
47-47: Import path casing is correct — fix or confirm the extension (.js → .ts or remove)packages/thirdweb/src/react/web/ui/hooks/useisMobile.ts exists and exports useIsMobile; the import uses '../../hooks/useisMobile.js' — either update the import to match the source (useisMobile.ts or extensionless) or confirm the .js extension is intentional for compiled output.
Likely an incorrect or invalid review comment.
1107-1119: Incorrect — keep useCustomTheme() inside styled(...) callbacks in this repo.Repo defines useCustomTheme (packages/thirdweb/src/react/core/design-system/CustomThemeProvider.tsx) and there are many intentional styled callbacks that call it (e.g. packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx:1107–1119, packages/thirdweb/src/react/web/ui/Bridge/common/TokenBalanceRow.tsx:100–104). Ignore the "avoid hooks in styled()" suggestion for these files; do not apply the refactor.
Likely an incorrect or invalid review comment.
<!--
## 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 -->
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (3)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (2)
331-337: Compute initial tokens once; don’t call getInitialTokens twiceAvoid duplicate work and storage reads; memoize once and use for both states.
-import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; @@ - const [buyToken, setBuyToken] = useState<TokenSelection | undefined>(() => { - return getInitialTokens(props.prefill, isPersistEnabled).buyToken; - }); + const initialTokens = useMemo( + () => getInitialTokens(props.prefill, isPersistEnabled), + [props.prefill, isPersistEnabled], + ); + const [buyToken, setBuyToken] = useState<TokenSelection | undefined>( + () => initialTokens.buyToken, + ); @@ - const [sellToken, setSellToken] = useState<TokenSelection | undefined>(() => { - return getInitialTokens(props.prefill, isPersistEnabled).sellToken; - }); + const [sellToken, setSellToken] = useState<TokenSelection | undefined>( + () => initialTokens.sellToken, + );
165-168: Add full TSDoc (example + tag) for new public propPublic API requires comprehensive TSDoc with a compiling example and a custom tag.
Apply:
- /** - * Called when the user disconnects the active wallet - */ - onDisconnect?: () => void; + /** + * Called when the user disconnects the active wallet (from the Details modal). + * Use this to clear app state or prompt a reconnect. + * @example + * <SwapWidget + * client={client} + * onDisconnect={({ wallet, account }) => console.log("disconnected", wallet.id, account.address)} + * /> + * @beta + */ + onDisconnect?: import("react").ComponentProps< + typeof import("./swap-ui.js").DetailsModal + >["onDisconnect"];packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (1)
89-90: Align onDisconnect typing with DetailsModal and forward payloadExpose the disconnect payload to consumers and pass it through.
-import { useState } from "react"; +import { useState, type ComponentProps } from "react"; @@ - onDisconnect: (() => void) | undefined; + onDisconnect?: ComponentProps<typeof DetailsModal>["onDisconnect"]; @@ - onDisconnect={() => { - props.onDisconnect?.(); - }} + onDisconnect={(info) => { + props.onDisconnect?.(info); + }}Consider mirroring this type in SwapWidgetProps (and its TSDoc) so parent handlers receive the same payload.
Also applies to: 296-311
🧹 Nitpick comments (3)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (3)
536-549: Use a stable unique key for owned tokensAddress-only keys can collide across chains. Include chain_id to avoid React key clashes.
- <TokenButton - key={token.token_address} + <TokenButton + key={`${token.token_address}-${token.chain_id}`} token={token}
343-351: Improve fallback icon contrast in dark modeThe gradient uses literal white which can pop on dark themes. Prefer theme-driven colors.
- <Container - style={{ - background: `linear-gradient(45deg, white, ${theme.colors.accentText})`, + <Container + style={{ + background: `linear-gradient(45deg, ${theme.colors.skeletonBg}, ${theme.colors.accentText})`, borderRadius: radius.full, width: `${iconSize.lg}px`, height: `${iconSize.lg}px`, }} />
375-381: Potential precision loss when formatting large balancesCasting to Number can lose precision for large token amounts. If this list can include large balances, prefer a decimal-aware formatter (e.g., reuse formatTokenAmount(balance, decimals, 5) for owned tokens) or clamp before tokenAmountFormatter.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (2)
- apps/portal/src/app/bridge/swap/swap-dark.pngis excluded by- !**/*.png
- apps/portal/src/app/bridge/swap/swap-light.pngis excluded by- !**/*.png
📒 Files selected for processing (20)
- .changeset/nine-otters-pay.md(1 hunks)
- apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx(1 hunks)
- apps/dashboard/src/@/utils/sdk-component-theme.ts(1 hunks)
- apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx(1 hunks)
- apps/portal/src/app/bridge/swap/page.mdx(2 hunks)
- packages/thirdweb/src/react/core/design-system/index.ts(1 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx(1 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx(4 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/common.tsx(0 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx(8 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx(12 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx(25 hunks)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts(1 hunks)
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx(1 hunks)
- packages/thirdweb/src/react/web/ui/components/basic.tsx(1 hunks)
- packages/thirdweb/src/react/web/ui/components/buttons.tsx(4 hunks)
- packages/thirdweb/src/react/web/ui/components/formElements.tsx(2 hunks)
- packages/thirdweb/src/react/web/ui/hooks/useisMobile.ts(1 hunks)
- packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx(2 hunks)
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx(3 hunks)
💤 Files with no reviewable changes (1)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/common.tsx
🚧 Files skipped from review as they are similar to previous changes (12)
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts
- apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx
- packages/thirdweb/src/react/core/design-system/index.ts
- packages/thirdweb/src/react/web/ui/components/basic.tsx
- packages/thirdweb/src/react/web/ui/components/formElements.tsx
- apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx
- apps/portal/src/app/bridge/swap/page.mdx
- packages/thirdweb/src/react/web/ui/hooks/useisMobile.ts
- .changeset/nine-otters-pay.md
- packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
- packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
- apps/dashboard/src/@/utils/sdk-component-theme.ts
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
- apps/dashboard/src/@/utils/sdk-component-theme.ts
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
**/*.stories.tsx
📄 CodeRabbit inference engine (CLAUDE.md)
For new UI components, add Storybook stories (
*.stories.tsx) alongside the code
Files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
🧠 Learnings (13)
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Applied to files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Use design system tokens (e.g., `bg-card`, `border-border`, `text-muted-foreground`)
Applied to files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Stick to design-tokens: background (`bg-card`), borders (`border-border`), muted text (`text-muted-foreground`) etc.
Applied to files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Never hard-code colors – always go through Tailwind variables.
Applied to files:
- apps/dashboard/src/@/utils/sdk-component-theme.ts
📚 Learning: 2025-09-18T20:09:57.064Z
Learnt from: MananTank
PR: thirdweb-dev/js#8069
File: apps/playground-web/src/app/bridge/swap-widget/components/types.ts:3-12
Timestamp: 2025-09-18T20:09:57.064Z
Learning: The SwapWidgetPlaygroundOptions type should not include persistTokenSelections - the playground intentionally hardcodes persistTokenSelections={false} and doesn't expose it as a configurable option to users.
Applied to files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Interactive UI that relies on hooks (`useState`, `useEffect`, React Query, wallet hooks).
Applied to files:
- packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
📚 Learning: 2025-08-28T19:32:53.229Z
Learnt from: MananTank
PR: thirdweb-dev/js#7939
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx:50-60
Timestamp: 2025-08-28T19:32:53.229Z
Learning: The Button component in packages/ui/src/components/button.tsx has type="button" as the default when no type prop is explicitly provided. This means explicitly adding type="button" to Button components is redundant unless a different type (like "submit") is specifically needed.
Applied to files:
- packages/thirdweb/src/react/web/ui/components/buttons.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Client Components must start with `'use client'`; handle interactivity with hooks and browser APIs
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Client Components (browser): Begin files with `'use client';`
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to packages/thirdweb/src/exports/** : Every public symbol must have comprehensive TSDoc with at least one `example` block that compiles and custom annotation tags (`beta`, `internal`, `experimental`)
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to packages/thirdweb/**/*.{ts,tsx} : Every public symbol must have comprehensive TSDoc with at least one compiling `example` and a custom tag (`beta`, `internal`, `experimental`, etc.)
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
📚 Learning: 2025-09-17T11:14:35.659Z
Learnt from: MananTank
PR: thirdweb-dev/js#8044
File: packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx:919-930
Timestamp: 2025-09-17T11:14:35.659Z
Learning: In the thirdweb codebase, useCustomTheme() hook can be used inside styled-components callbacks, contrary to the general React Rules of Hooks. This is a valid pattern in their implementation.
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Spacing utilities (`px-*`, `py-*`, `gap-*`) are preferred over custom margins.
Applied to files:
- packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
🧬 Code graph analysis (5)
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx (2)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
SwapWidget(243-253)packages/thirdweb/src/stories/utils.tsx (1)
storyClient(15-17)
packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
packages/thirdweb/src/react/core/design-system/index.ts (1)
Theme(49-96)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (10)
packages/thirdweb/src/react/web/ui/hooks/useisMobile.ts (1)
useIsMobile(5-21)packages/thirdweb/src/react/web/ui/components/basic.tsx (1)
Container(80-193)packages/thirdweb/src/react/web/ui/components/Modal.tsx (1)
Modal(32-164)packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx (1)
DetailsModal(372-1035)packages/thirdweb/src/react/web/ui/components/Spinner.tsx (1)
Spinner(11-37)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
ActiveWalletInfo(140-144)packages/thirdweb/src/react/web/ui/ConnectWallet/screens/formatTokenBalance.ts (1)
formatCurrencyAmount(36-38)packages/thirdweb/src/react/web/ui/components/Img.tsx (1)
Img(12-152)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
cleanedChainName(1-3)packages/thirdweb/src/exports/utils.ts (1)
shortenAddress(149-149)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (2)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
TokenSelection(149-152)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/storage.ts (1)
getLastUsedTokens(23-41)
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (5)
packages/thirdweb/src/react/web/ui/hooks/useisMobile.ts (1)
useIsMobile(5-21)packages/thirdweb/src/react/web/ui/components/basic.tsx (2)
Container(80-193)
noScrollBar(25-31)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts (1)
tokenAmountFormatter(5-9)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts (1)
TokenBalance(40-58)packages/thirdweb/src/react/web/ui/Bridge/swap-widget/types.ts (1)
TokenSelection(149-152)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: Unit Tests
- GitHub Check: Lint Packages
- GitHub Check: Build Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
apps/dashboard/src/@/utils/sdk-component-theme.ts (1)
34-36: Token tweaks look goodThe alpha adjustments for skeletonBg and tertiaryBg align with design-token usage.
packages/thirdweb/src/react/web/ui/components/buttons.tsx (1)
24-24: hoverBg prop addition is usefulExtends theming without bloating variants. Nice.
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx (1)
24-25: Explicitly disabling persistence in stories is correctKeeps Storybook deterministic and matches playground behavior.
Also applies to: 28-34, 38-45, 54-55, 64-65
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx (1)
506-565: Nice: gated localStorage reads/writes by persist flagRespects persistTokenSelections intent and avoids unnecessary storage access.
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx (1)
623-633: Left pane styling is cleanGood use of noScrollBar and separatorLine for the desktop two-pane layout.
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx (2)
208-212: Good UX: coherent disabled state and button copydisableContinue gating + spinner/labels are consistent and clear.
Also applies to: 442-442, 465-476
937-971: Nice use of hoverBg and dashed divider in SelectedTokenButtonLeverages the new hoverBg prop effectively and matches the updated design system.
Also applies to: 1057-1070
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx (1)
39-39: Resolved —Inputalready supports thesmboolean prop.
InputProps declaressm?: booleanand the component usesprops.sm(e.g.,padding: props.sm ? spacing.sm : fontSize.sm).
| fullWidth?: boolean; | ||
| gap?: keyof typeof spacing; | ||
| bg?: keyof Theme["colors"]; | ||
| hoverBg?: keyof Theme["colors"]; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent hover styles on disabled buttons
Disabled buttons currently still apply hover background due to the top-level and variant-specific &:hover rules. Gate hover styles to non-disabled states (both [disabled] and [data-disabled="true"]) to avoid misleading interactivity.
Apply this diff:
 type ButtonProps = {
   variant:
@@
-  hoverBg?: keyof Theme["colors"];
+  hoverBg?: keyof Theme["colors"];
@@
-    "&:hover": {
-      background: props.hoverBg ? theme.colors[props.hoverBg] : undefined,
-    },
+    '&:not([data-disabled="true"]):not([disabled]):hover': {
+      background: props.hoverBg ? theme.colors[props.hoverBg] : undefined,
+    },
@@
-      if (props.variant === "ghost-solid") {
+      if (props.variant === "ghost-solid") {
         return {
-          "&:hover": {
-            background: theme.colors[props.hoverBg || "tertiaryBg"],
-          },
+          '&:not([data-disabled="true"]):not([disabled]):hover': {
+            background: theme.colors[props.hoverBg || "tertiaryBg"],
+          },
           border: "1px solid transparent",
         };
       }
@@
-      if (props.variant === "secondary") {
+      if (props.variant === "secondary") {
         return {
-          "&:hover": {
-            background: theme.colors[props.hoverBg || "secondaryButtonHoverBg"],
-          },
+          '&:not([data-disabled="true"]):not([disabled]):hover': {
+            background: theme.colors[props.hoverBg || "secondaryButtonHoverBg"],
+          },
         };
       }Optionally harden disabled styling:
     "&[data-disabled='true']": {
+      pointerEvents: "none",
       "&:hover": {
         borderColor: "transparent",
       },
       background: theme.colors.tertiaryBg,
       borderColor: "transparent",
       boxShadow: "none",
       color: theme.colors.secondaryText,
     },
     "&[disabled]": {
       cursor: "not-allowed",
+      pointerEvents: "none",
     },Also applies to: 99-101, 124-131, 141-147
🤖 Prompt for AI Agents
In packages/thirdweb/src/react/web/ui/components/buttons.tsx around line 24 (and
also apply to ranges 99-101, 124-131, 141-147), hover background rules are
applied even when buttons are disabled; update the CSS-in-JS selectors so hover
rules only apply to non-disabled elements by gating them with
:not([disabled]):not([data-disabled="true"]) (e.g. replace top-level &:hover and
variant-specific &:hover selectors with
&:not([disabled]):not([data-disabled="true"]):hover), and optionally strengthen
disabled styles by explicitly setting background, color, cursor, and
pointer-events for [disabled] and [data-disabled="true"] states to prevent any
hover bleed-through.

PR-Codex overview
This PR focuses on enhancing the
SwapWidgetUI and functionality, introducing mobile support, improving token selection, and refining styling and layouts across various components.Detailed summary
SwapWidget.SearchInputand token selection.Buttonhover effects and styles.SwapWidgetto handlepersistTokenSelections.useIsMobilehook for responsive behavior.TokenSection.SwapWidget.Summary by CodeRabbit
New Features
UI/Style
Refactor
Chores