Skip to content

Conversation

@MananTank
Copy link
Member

@MananTank MananTank commented Sep 19, 2025


PR-Codex overview

This PR focuses on enhancing the SwapWidget UI and functionality, introducing mobile support, improving token selection, and refining styling and layouts across various components.

Detailed summary

  • Added mobile support in SwapWidget.
  • Improved UI for SearchInput and token selection.
  • Updated Button hover effects and styles.
  • Enhanced SwapWidget to handle persistTokenSelections.
  • Refactored token formatting and selection logic.
  • Introduced useIsMobile hook for responsive behavior.
  • Modified token display and layout in TokenSection.
  • Added light and dark mode images for SwapWidget.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features

    • Swap widget: disconnect callback, details modal, max-fill, improved wallet/account display, clearer loading/insufficient-balance states; mobile/desktop select-chain/token flows.
  • UI/Style

    • Refined token/chain visuals (gradient fallbacks, resized icons), adjusted spacing and input sizing, responsive two-pane layout, customizable button hover backgrounds, light/dark doc images.
  • Refactor

    • Unified token amount formatting and simplified numeric displays; removed legacy numeric formatter component.
  • Chores

    • Added changeset entry for UI improvements; stories disable token-selection persistence.

@vercel
Copy link

vercel bot commented Sep 19, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
docs-v2 Ready Ready Preview Comment Sep 19, 2025 9:09pm
nebula Ready Ready Preview Comment Sep 19, 2025 9:09pm
thirdweb_playground Ready Ready Preview Comment Sep 19, 2025 9:09pm
thirdweb-www Ready Ready Preview Comment Sep 19, 2025 9:09pm
wallet-ui Ready Ready Preview Comment Sep 19, 2025 9:09pm

@linear
Copy link

linear bot commented Sep 19, 2025

@changeset-bot
Copy link

changeset-bot bot commented Sep 19, 2025

🦋 Changeset detected

Latest commit: 93f913c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
thirdweb Patch
@thirdweb-dev/nebula Patch
@thirdweb-dev/wagmi-adapter Patch

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 19, 2025

Walkthrough

UI 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

Cohort / File(s) Summary
Changeset
.changeset/nine-otters-pay.md
Adds a patch changeset for thirdweb titled "SwapWidget UI improvements".
Swap widget entry & initial state
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
Adds onDisconnect?: () => void to SwapWidgetProps, centralizes initial token selection via getInitialTokens, persists last-used tokens (respecting persist flag), and forwards onDisconnect to SwapUI.
Swap UI & interaction flow
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx
Large UI refactor: adds wallet/account display and disconnect flow (DetailsModal), Max action, token/fiat formatting updates, modal state shape changed, CTA states/spinner, and exposes onDisconnect on SwapUIProps.
Token selection UI
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx, packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx, packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
Replaces DiscIcon with theme gradient fallback, two‑pane desktop vs mobile screen flow (new LeftContainer/right content), TokenSelectionScreen introduced, SearchInput uses sm variant, and SelectBridgeChain/related props now support and propagate isMobile.
Formatting utils & removed renderer
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/utils.ts, packages/thirdweb/src/react/web/ui/Bridge/swap-widget/common.tsx
Adds exported tokenAmountFormatter (Intl.NumberFormat) and removes DecimalRenderer and its compact formatter.
Design system & theme tokens
packages/thirdweb/src/react/core/design-system/index.ts, apps/dashboard/src/@/utils/sdk-component-theme.ts
Adds icon size token "sm+" = "20"; updates dashboard theme color tokens (skeletonBghsl(var(--secondary-foreground)/15%), tertiaryBghsl(var(--muted)/30%)).
Shared UI components
packages/thirdweb/src/react/web/ui/components/buttons.tsx, packages/thirdweb/src/react/web/ui/components/basic.tsx, packages/thirdweb/src/react/web/ui/components/formElements.tsx
Adds hoverBg?: keyof Theme["colors"] to ButtonProps and hover behavior; makes Container children optional; adjusts Input font sizes with a mobile breakpoint.
Mobile detection hook
packages/thirdweb/src/react/web/ui/hooks/useisMobile.ts
Adds useIsMobile() hook (MOBILE_BREAKPOINT = 640) returning boolean for viewport <640px.
Icon change
packages/thirdweb/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx
Simplifies SVG: viewBox to 0 0 16 16, moves stroke attrs to a <g>, consolidates paths.
Stories, examples & embed usage
packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx, packages/thirdweb/src/stories/Bridge/Swap/SelectChain.stories.tsx, apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx, apps/portal/src/app/bridge/swap/page.mdx
Disables token persistence in SwapWidget stories, removes ConnectButton from story decorator, adds desktop/mobile SelectChain story variants, changes BuyAndSwapEmbed prefill to prefer a buyToken object (conditionally include sellToken), and renders light/dark SwapWidget images in docs.
Playground mount key change
apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx
Narrows SwapWidget React key to JSON.stringify({ prefill: props.options.prefill }) so remounts occur only when prefill 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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The PR includes several changes that extend beyond the SwapWidget objectives and modify public component surfaces, for example adding hoverBg?: Theme["colors"] to ButtonProps (packages/.../buttons.tsx), a new "sm+" iconSize token in the design system (packages/.../design-system/index.ts), altering ArrowUpDownIcon SVG viewBox/paths, and relaxing Container's children prop; these are broader library-level API or icon changes that should be documented or split out. Either split these unrelated/public-API changes into a separate PR or explicitly document them in this PR (changelog/upgrade notes and visual regression checks) and add tests or a compatibility note to ensure downstream consumers are not impacted.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.38% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive The PR description contains a useful PR-Codex overview and a detailed summary of changes, but it does not populate the repository's required template fields (the commented template remains and required sections like "Notes for the reviewer" and "How to test" are not filled), so explicit testing instructions and reviewer callouts are missing. Please populate the template's "Notes for the reviewer" with important callouts and add a "How to test" section listing manual steps, storybook/playground entries, and any relevant test commands or scenarios so reviewers can validate the UI changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "[MNY-189] SDK: SwapWidget UI improvements" is concise, includes the issue tag and area, and accurately summarizes the primary change (UI improvements to the SwapWidget), so it is clear and appropriate for a teammate scanning PR history.
Linked Issues Check ✅ Passed The changes implement the linked issue [MNY-189]: connected wallet info and disconnect support (Account/Wallet contexts, ActiveWalletDetails, onDisconnect in SwapUI/SwapWidget), a Max button/onMaxClick in the sell flow, error-to-button behavior with disableContinue and adaptive button labels, and a chain list/top-level chain selection in the token selection UI (SelectBridgeChain/select-token-ui), matching the issue tasks.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mny-189

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • TEAM-0000: Entity not found: Issue - Could not find referenced Issue.

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added Dashboard Involves changes to the Dashboard. packages SDK Involves changes to the thirdweb SDK labels Sep 19, 2025
@MananTank MananTank marked this pull request as ready for review September 19, 2025 15:18
@MananTank MananTank requested review from a team as code owners September 19, 2025 15:18
Copy link
Member Author


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

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.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 19, 2025

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
thirdweb (esm) 64.05 KB (0%) 1.3 s (0%) 523 ms (+159.69% 🔺) 1.9 s
thirdweb (cjs) 361.64 KB (0%) 7.3 s (0%) 2.2 s (+4.01% 🔺) 9.4 s
thirdweb (minimal + tree-shaking) 5.73 KB (0%) 115 ms (0%) 154 ms (+1623.06% 🔺) 269 ms
thirdweb/chains (tree-shaking) 526 B (0%) 11 ms (0%) 91 ms (+1405.34% 🔺) 101 ms
thirdweb/react (minimal + tree-shaking) 19.14 KB (0%) 383 ms (0%) 166 ms (+1168.51% 🔺) 549 ms

@codecov
Copy link

codecov bot commented Sep 19, 2025

Codecov Report

❌ Patch coverage is 80.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.34%. Comparing base (0250215) to head (93f913c).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...s/thirdweb/src/react/web/ui/components/buttons.tsx 60.00% 2 Missing ⚠️
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              
Flag Coverage Δ
packages 56.34% <80.00%> (+0.01%) ⬆️
Files with missing lines Coverage Δ
...ges/thirdweb/src/react/core/design-system/index.ts 84.24% <100.00%> (+0.10%) ⬆️
...ges/thirdweb/src/react/web/ui/components/basic.tsx 84.07% <ø> (ø)
...rdweb/src/react/web/ui/components/formElements.tsx 97.08% <100.00%> (+0.08%) ⬆️
...s/thirdweb/src/react/web/ui/components/buttons.tsx 77.41% <60.00%> (-0.22%) ⬇️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 new hoverBg prop (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 &:hover background can override variants unintentionally.

Passing hoverBg will 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 variants

Also 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 for onDisconnect; 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 onDisconnect

Since 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 gradients

Hardcoding 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 zero

The 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 recomputing

Minor 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 images

Improves 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 literals

Replace "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 stable

Aligns 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 button

Button 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 WalletProvider

Two 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 tokens

Selling 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.

📥 Commits

Reviewing files that changed from the base of the PR and between e9225c3 and 1ebdcde.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @example and 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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in 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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in 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 in packages/* 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: Making children optional 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 hoverBg on “ghost-solid”.


141-146: Variant “secondary”: scoped hoverBg is correct.

Uses hoverBg or falls back to secondaryButtonHoverBg. 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 new sm+ 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 stories

Matches the retrieved learning for the playground/stories. No concerns.

Also applies to: 28-34, 38-45, 54-54, 64-64

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1ebdcde and 24fcb70.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @example and 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.

MananTank added a commit that referenced this pull request Sep 19, 2025
<!--

## 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 -->
@github-actions github-actions bot added Playground Changes involving the Playground codebase. Portal Involves changes to the Portal (docs) codebase. labels Sep 19, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 check

Converting 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 function

Project 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) story

Keep 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 story

Same 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 WalletProvider

WalletProvider 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 context

You 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 8b945dd and 93c54f0.

⛔ Files ignored due to path filters (2)
  • apps/portal/src/app/bridge/swap/swap-dark.png is excluded by !**/*.png
  • apps/portal/src/app/bridge/swap/swap-light.png is 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @example and 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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in 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 xmlns

Keeping 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 -->
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 twice

Avoid 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 prop

Public 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 payload

Expose 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 tokens

Address-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 mode

The 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 balances

Casting 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 93c54f0 and 93f913c.

⛔ Files ignored due to path filters (2)
  • apps/portal/src/app/bridge/swap/swap-dark.png is excluded by !**/*.png
  • apps/portal/src/app/bridge/swap/swap-light.png is 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @example and 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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in 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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in 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 good

The 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 useful

Extends theming without bloating variants. Nice.

packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx (1)

24-25: Explicitly disabling persistence in stories is correct

Keeps 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 flag

Respects 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 clean

Good 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 copy

disableContinue gating + spinner/labels are consistent and clear.

Also applies to: 442-442, 465-476


937-971: Nice use of hoverBg and dashed divider in SelectedTokenButton

Leverages 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 — Input already supports the sm boolean prop.
InputProps declares sm?: boolean and the component uses props.sm (e.g., padding: props.sm ? spacing.sm : fontSize.sm).

fullWidth?: boolean;
gap?: keyof typeof spacing;
bg?: keyof Theme["colors"];
hoverBg?: keyof Theme["colors"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Dashboard Involves changes to the Dashboard. packages Playground Changes involving the Playground codebase. Portal Involves changes to the Portal (docs) codebase. SDK Involves changes to the thirdweb SDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants