Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nice-owls-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": patch
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

🧩 Analysis chain

Changeset bump likely incorrect (API surface changed).

This PR appears to make Modal require a className prop. That’s a public API change and should be a minor bump per our rules, unless we revert to a non‑breaking optional prop. Pick one:

  • Option A (versioning): bump to minor.
  • Option B (code): make className optional and default internally so this remains patch.

Apply this diff for Option A:

-"thirdweb": patch
+"thirdweb": minor

Run to confirm whether className is required and that all usages pass it:


🏁 Script executed:

#!/bin/bash
set -e

echo "Check Modal prop type:"
rg -nC2 -g 'packages/thirdweb/**/Modal.tsx' -P 'export (const|function)\s+Modal\b'

echo "Find <Modal> without className:"
rg -nP --type tsx -g 'packages/thirdweb/**' '<Modal(?![^>]*\bclassName=)'

Length of output: 527


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "==== Modal.tsx (head) ===="
sed -n '1,240p' packages/thirdweb/src/react/web/ui/components/Modal.tsx || true

echo
echo "==== className occurrences inside Modal.tsx ===="
rg -n 'className' packages/thirdweb/src/react/web/ui/components/Modal.tsx || true

echo
echo "==== Scanning all TSX files for <Modal opening tags (reports if className present) ===="
python3 - <<'PY'
import re,glob
open_tag_re = re.compile(r'<Modal\b(.*?>)', re.DOTALL)
for path in glob.glob('packages/thirdweb/**/*.tsx', recursive=True):
    try:
        text = open(path,'r',encoding='utf-8').read()
    except Exception:
        continue
    for m in re.finditer(r'<Modal\b', text):
        start = m.start()
        match = open_tag_re.search(text, pos=start)
        lineno = text.count('\n', 0, start) + 1
        if not match:
            snippet = text[start:start+200].replace('\n','␤')
            print(f"{path}:{lineno}: could not capture opening tag; snippet: {snippet}")
            continue
        tag = '<Modal' + match.group(1)
        has = 'className=' in tag
        tag_one_line = tag.replace('\n','␤')
        print(f"{path}:{lineno}: className_present={has} tag={tag_one_line}")
PY

Length of output: 8874


Bump changeset to minor — Modal's className prop is now required.

Modal's props declare className: string (required). Found usages without className in:

  • packages/thirdweb/src/react/native/ui/connect/ConnectButton.tsx:157
  • packages/thirdweb/src/react/web/ui/ConnectWallet/useConnectModal.tsx:64

Choose one:

  • Option A (versioning): bump to minor — apply:
-"thirdweb": patch
+"thirdweb": minor
  • Option B (code): make className?: string and default internally to keep a patch.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"thirdweb": patch
"thirdweb": minor

---

Add `tw-` class names in connect ui
5 changes: 5 additions & 0 deletions packages/thirdweb/.storybook/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ body {
font-optical-sizing: auto;
font-variation-settings: "slnt" 0;
}

/* Use this to debug the elements having tw- classes */
/* [class*="tw-"] {
outline: 1px dotted yellow !important;
} */
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export function SwapUI(props: SwapUIProps) {
return (
<Container p="md">
<Modal
className="tw-modal__swap-widget"
size="compact"
title="Select Token"
open={!!modalState}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,19 @@ const TW_CONNECT_WALLET = "tw-connect-wallet";
*
* [View all available themes properties](https://portal.thirdweb.com/references/typescript/v5/Theme)
*
* ### Overriding styles using class names
*
* Some elements in this component have classes with a `tw-` prefix.
* You can target these classes in your own CSS stylesheet to override their styles.
*
* In some cases, you may need to use the `!important` flag for the override to take effect. Do not use on auto-generated class names, as they may change between builds.
*
* ```css
* .tw-back-button {
* background-color: red !important;
* }
* ```
*
* ### Changing the display language
*
* ```tsx
Expand Down Expand Up @@ -543,6 +556,7 @@ function ConnectButtonInner(
)}
</Button>
<Modal
className="tw-modal__sign-in"
title="Sign in"
open={showSignatureModal}
setOpen={setShowSignatureModal}
Expand Down
12 changes: 11 additions & 1 deletion packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -572,13 +572,15 @@ export function DetailsModal(props: {
<Container px="lg">
{/* Send, Receive, Swap */}
<Container
className="tw-highlight-buttons"
style={{
display: "flex",
gap: spacing.xs,
}}
>
{!hideSendFunds && (
<Button
className="tw-highlight-button__send"
onClick={() => {
setScreen("send");
}}
Expand Down Expand Up @@ -608,6 +610,7 @@ export function DetailsModal(props: {

{!hideReceiveFunds && (
<Button
className="tw-highlight-button__receive"
onClick={() => {
setScreen("receive");
}}
Expand All @@ -632,6 +635,7 @@ export function DetailsModal(props: {
chainMetadataQuery.data &&
!chainMetadataQuery.data.testnet && (
<Button
className="tw-highlight-button__buy"
onClick={() => {
trackPayEvent({
client: client,
Expand Down Expand Up @@ -682,6 +686,7 @@ export function DetailsModal(props: {

{/* Transactions */}
<MenuButton
className="tw-view-transactions-button"
onClick={() => {
setScreen("transactions");
}}
Expand All @@ -699,6 +704,7 @@ export function DetailsModal(props: {
{/* Hide the View Funds button if the assetTabs props is set to an empty array */}
{(props.assetTabs === undefined || props.assetTabs.length > 0) && (
<MenuButton
className="tw-view-assets-button"
onClick={() => {
setScreen("view-assets");
}}
Expand All @@ -713,6 +719,7 @@ export function DetailsModal(props: {

{/* Manage Wallet */}
<MenuButton
className="tw-manage-wallet-button"
onClick={() => {
setScreen("manage-wallet");
}}
Expand All @@ -729,6 +736,7 @@ export function DetailsModal(props: {
(chainFaucetsQuery.faucets.length > 0 ||
walletChain?.id === LocalhostChainId) && (
<MenuLink
className="tw-request-testnet-funds-button"
as="a"
href={
chainFaucetsQuery.faucets ? chainFaucetsQuery.faucets[0] : "#"
Expand Down Expand Up @@ -759,6 +767,7 @@ export function DetailsModal(props: {
<Spacer y="sm" />
<Container px="md">
<MenuButton
className="tw-disconnect-wallet-button"
data-variant="danger"
onClick={() => {
if (activeWallet && activeAccount) {
Expand Down Expand Up @@ -1000,6 +1009,7 @@ export function DetailsModal(props: {
<WalletUIStatesProvider isOpen={false} theme={props.theme}>
<ScreenSetupContext.Provider value={screenSetup}>
<Modal
className="tw-modal__wallet-details"
title="Manage Wallet"
open={isOpen}
setOpen={(_open) => {
Expand Down Expand Up @@ -1043,7 +1053,7 @@ export function NetworkSwitcherButton(props: {
}
return (
<MenuButton
className="tw-internal-network-switcher-button"
className="tw-internal-network-switcher-button tw-switch-network-button"
data-variant="primary"
disabled={disableSwitchChain}
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ function AllWalletsUI(props: {
}, [searchResults, itemsToShow]);

return (
<Container animate="fadein" flex="column" fullHeight>
<Container
animate="fadein"
flex="column"
fullHeight
className="tw-all-wallets-screen"
>
<Container p="lg">
<ModalHeader onBack={props.onBack} title="Select Wallet" />
</Container>
Expand Down Expand Up @@ -140,6 +145,7 @@ function AllWalletsUI(props: {
}}
>
<WalletEntryButton
className="tw-select-wallet-button"
badge={undefined}
client={props.client}
connectLocale={props.connectLocale}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useIsAutoConnecting } from "../../../../core/hooks/wallets/useIsAutoCon
import { useConnectionManager } from "../../../../core/providers/connection-manager.js";
import { WalletUIStatesProvider } from "../../../providers/wallet-ui-states-provider.js";
import { canFitWideModal } from "../../../utils/canFitWideModal.js";
import { cls } from "../../../utils/cls.js";
import { usePreloadWalletProviders } from "../../../utils/usePreloadWalletProviders.js";
import { LoadingScreen } from "../../../wallets/shared/LoadingScreen.js";
import { AutoConnect } from "../../AutoConnect/AutoConnect.js";
Expand Down Expand Up @@ -161,6 +162,19 @@ import { useSetupScreen } from "./screen.js";
*
* [View all available themes properties](https://portal.thirdweb.com/references/typescript/v5/Theme)
*
* ### Overriding styles using class names
*
* Some elements in this component have classes with a `tw-` prefix.
* You can target these classes in your own CSS stylesheet to override their styles.
*
* In some cases, you may need to use the `!important` flag for the override to take effect. Do not use on auto-generated class names, as they may change between builds.
*
* ```css
* .tw-back-button {
* background-color: red !important;
* }
* ```
*
* ### Changing the display language
*
* ```tsx
Expand Down Expand Up @@ -266,7 +280,11 @@ export function ConnectEmbed(props: ConnectEmbedProps) {
<>
{autoConnectComp}
<CustomThemeProvider theme={props.theme}>
<EmbedContainer modalSize={modalSize}>
<EmbedContainer
modalSize={modalSize}
className={cls("tw-widget-loading", props.className)}
style={props.style}
>
<LoadingScreen />
</EmbedContainer>
</CustomThemeProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ const ConnectModal = (props: ConnectModalOptions) => {

return (
<Modal
className="tw-modal__connect-wallet"
title="Connect Wallet"
hide={hideModal}
open={isWalletModalOpen}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ type NetworkSelectorContentProps = {
showTabs?: boolean;
connectLocale: ConnectLocale;
client: ThirdwebClient;
className?: string;
};

/**
Expand Down Expand Up @@ -364,7 +365,7 @@ export function NetworkSelectorContent(props: NetworkSelectorContentProps) {
);

return (
<Container>
<Container className={props.className}>
<Container p="lg">
{props.onBack ? (
<ModalHeader onBack={props.onBack} title={locale.title} />
Expand Down Expand Up @@ -954,6 +955,7 @@ export function useNetworkSwitcherModal() {
setRootEl(
<CustomThemeProvider theme={props.theme}>
<Modal
className="tw-modal__switch-network"
title="Switch Network"
open={true}
setOpen={(value) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { ConnectLocale } from "./locale/types.js";
* @internal
*/
export function WalletEntryButton(props: {
className?: string;
wallet: Wallet;
selectWallet: () => void;
connectLocale: ConnectLocale;
Expand Down Expand Up @@ -56,6 +57,7 @@ export function WalletEntryButton(props: {

return (
<WalletButtonEl
className={props.className}
data-active={props.isActive}
onClick={selectWallet}
type="button"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";
import { ChevronLeftIcon } from "@radix-ui/react-icons";
import { lazy, Suspense, useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
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

Reintroduce lazy loading for InAppWalletSelectionUI (perf regression)

Switching from lazy to eager import pulls the in‑app flow into the initial bundle and penalizes TTI for users who never open it. Restore code-splitting with Suspense.

-import { useEffect, useRef, useState } from "react";
+import { lazy, Suspense, useEffect, useRef, useState } from "react";
-import InAppWalletSelectionUI from "../../wallets/in-app/InAppWalletSelectionUI.js";
+const InAppWalletSelectionUI = lazy(
+  () => import("../../wallets/in-app/InAppWalletSelectionUI.js"),
+);
+import { LoadingScreen } from "../../wallets/shared/LoadingScreen.js";
-            {wallet.id === "inApp" && props.size === "compact" ? (
-              <InAppWalletSelectionUI
+            {wallet.id === "inApp" && props.size === "compact" ? (
+              <Suspense fallback={<LoadingScreen />}>
+                <InAppWalletSelectionUI
                   chain={props.chain}
                   client={props.client}
                   connectLocale={props.connectLocale}
                   disabled={props.disabled}
-                done={() => props.done(wallet)}
+                  done={() => props.done(wallet)}
                   goBack={props.goBack}
                   recommendedWallets={props.recommendedWallets}
-                select={() => props.selectWallet(wallet)}
+                  select={() => props.selectWallet(wallet)}
                   size={props.size}
                   wallet={wallet as Wallet<"inApp">}
-              />
+                />
+              </Suspense>
             ) : (

Also applies to: 21-21, 622-633

🤖 Prompt for AI Agents
In packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx around
lines 3 (and similarly at 21 and 622-633), the eager import of
InAppWalletSelectionUI caused a perf regression by pulling the in-app flow into
the initial bundle; restore code-splitting by replacing the direct import with
React.lazy(() => import(...)) and wrap its usage in a <Suspense fallback={...}>
(provide a lightweight spinner/placeholder) so the component is loaded only when
needed; ensure TypeScript types are preserved (use default export or adjust
import) and update any tests/SSR guards if necessary to avoid rendering lazy on
the server.

import type { Chain } from "../../../../chains/types.js";
import type { ThirdwebClient } from "../../../../client/client.js";
import type { InjectedSupportedWalletIds } from "../../../../wallets/__generated__/wallet-ids.js";
Expand All @@ -18,7 +18,7 @@ import {
} from "../../../core/design-system/index.js";
import { useSetSelectionData } from "../../providers/wallet-ui-states-provider.js";
import { sortWallets } from "../../utils/sortWallets.js";
import { LoadingScreen } from "../../wallets/shared/LoadingScreen.js";
import InAppWalletSelectionUI from "../../wallets/in-app/InAppWalletSelectionUI.js";
import {
Container,
Line,
Expand All @@ -43,10 +43,6 @@ import { PoweredByThirdweb } from "./PoweredByTW.js";
import { WalletButtonEl, WalletEntryButton } from "./WalletEntryButton.js";
import { WalletTypeRowButton } from "./WalletTypeRowButton.js";

const InAppWalletSelectionUI = /* @__PURE__ */ lazy(
() => import("../../wallets/in-app/InAppWalletSelectionUI.js"),
);

// const localWalletId = "local";
const inAppWalletId: WalletId = "inApp";

Expand Down Expand Up @@ -202,7 +198,7 @@ const WalletSelectorInner: React.FC<WalletSelectorProps> = (props) => {
title={props.modalHeader.title}
/>
) : (
<Container center="y" flex="row" gap="xxs">
<Container center="y" flex="row" gap="xs" className="tw-header">
{!props.meta.titleIconUrl ? null : (
<Img
client={props.client}
Expand All @@ -222,6 +218,7 @@ const WalletSelectorInner: React.FC<WalletSelectorProps> = (props) => {

const connectAWallet = (
<WalletTypeRowButton
className="tw-select-connect-a-wallet-button"
client={props.client}
disabled={props.meta.requireApproval && !approvedTOS}
icon={OutlineWalletIcon}
Expand All @@ -234,6 +231,7 @@ const WalletSelectorInner: React.FC<WalletSelectorProps> = (props) => {

const newToWallets = (
<Container
className="tw-get-started-container"
flex="row"
style={{
justifyContent: "space-between",
Expand Down Expand Up @@ -489,6 +487,7 @@ const WalletSelectorInner: React.FC<WalletSelectorProps> = (props) => {
return (
<Container
animate="fadein"
className="tw-wallet-selection"
flex="column"
fullHeight
scrollY
Expand Down Expand Up @@ -620,22 +619,21 @@ const WalletSelection: React.FC<{
// data-full-width={!!walletConfig.selectUI}
>
{wallet.id === "inApp" && props.size === "compact" ? (
<Suspense fallback={<LoadingScreen height="195px" />}>
<InAppWalletSelectionUI
chain={props.chain}
client={props.client}
connectLocale={props.connectLocale}
disabled={props.disabled}
done={() => props.done(wallet)}
goBack={props.goBack}
recommendedWallets={props.recommendedWallets}
select={() => props.selectWallet(wallet)}
size={props.size}
wallet={wallet as Wallet<"inApp">}
/>
</Suspense>
<InAppWalletSelectionUI
chain={props.chain}
client={props.client}
connectLocale={props.connectLocale}
disabled={props.disabled}
done={() => props.done(wallet)}
goBack={props.goBack}
recommendedWallets={props.recommendedWallets}
select={() => props.selectWallet(wallet)}
size={props.size}
wallet={wallet as Wallet<"inApp">}
/>
) : (
<WalletEntryButton
className="tw-wallet-select-wallet-button"
badge={undefined}
client={props.client}
connectLocale={props.connectLocale}
Expand All @@ -655,7 +653,7 @@ const WalletSelection: React.FC<{
})}

{props.onShowAll && props.showAllWallets !== false && (
<ButtonContainer>
<ButtonContainer className="tw-show-all-wallets-button">
<WalletButtonEl onClick={props.onShowAll}>
<ShowAllWalletsIcon>
<div data-dot />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ type WalletTypeRowProps = {
title: string;
icon: IconFC;
disabled?: boolean;
className?: string;
};

export function WalletTypeRowButton(props: WalletTypeRowProps) {
return (
<Button
disabled={props.disabled}
className={props.className}
fullWidth
onClick={() => {
props.onClick();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function ReceiveFunds(props: {
const locale = connectLocale.receiveFundsScreen;

return (
<Container p="lg">
<Container p="lg" className="tw-receive-funds-screen">
<ModalHeader onBack={props.onBack} title={locale.title} />

<Spacer y="xl" />
Expand All @@ -57,7 +57,10 @@ export function ReceiveFunds(props: {
</Container>
<Spacer y="xl" />

<WalletAddressContainer onClick={onCopy}>
<WalletAddressContainer
onClick={onCopy}
className="tw-copy-address-button"
>
<Text color="primaryText" size="md">
{shortenString(address || "")}
</Text>
Expand Down
Loading
Loading