diff --git a/apps/dashboard/src/app/bridge/swap-widget/SwapWidgetEmbed.client.tsx b/apps/dashboard/src/app/bridge/swap-widget/SwapWidgetEmbed.client.tsx
index a8836d29550..40c4b6cb475 100644
--- a/apps/dashboard/src/app/bridge/swap-widget/SwapWidgetEmbed.client.tsx
+++ b/apps/dashboard/src/app/bridge/swap-widget/SwapWidgetEmbed.client.tsx
@@ -16,6 +16,7 @@ export function SwapWidgetEmbed({
showThirdwebBranding,
theme,
currency,
+ persistTokenSelections,
}: {
buyChainId?: number;
buyTokenAddress?: Address;
@@ -26,6 +27,7 @@ export function SwapWidgetEmbed({
showThirdwebBranding?: boolean;
theme: "light" | "dark";
currency?: SupportedFiatCurrency;
+ persistTokenSelections?: boolean;
}) {
const client = useMemo(
() =>
@@ -77,6 +79,7 @@ export function SwapWidgetEmbed({
showThirdwebBranding={showThirdwebBranding}
theme={theme}
currency={currency}
+ persistTokenSelections={persistTokenSelections}
onSuccess={() => {
sendMessageToParent({
source: "swap-widget",
diff --git a/apps/dashboard/src/app/bridge/swap-widget/page.tsx b/apps/dashboard/src/app/bridge/swap-widget/page.tsx
index 622f57f8d02..dbd1d80a38f 100644
--- a/apps/dashboard/src/app/bridge/swap-widget/page.tsx
+++ b/apps/dashboard/src/app/bridge/swap-widget/page.tsx
@@ -51,7 +51,14 @@ export default async function Page(props: {
// Optional params
const showThirdwebBranding = parseQueryParams(
searchParams.showThirdwebBranding,
- (v) => v !== "false",
+ // biome-ignore lint/complexity/noUselessTernary: this is easier to understand
+ (v) => (v === "false" ? false : true),
+ );
+
+ const persistTokenSelections = parseQueryParams(
+ searchParams.persistTokenSelections,
+ // biome-ignore lint/complexity/noUselessTernary: this is easier to understand
+ (v) => (v === "false" ? false : true),
);
const theme =
@@ -76,6 +83,7 @@ export default async function Page(props: {
showThirdwebBranding={showThirdwebBranding}
theme={theme}
currency={currency}
+ persistTokenSelections={persistTokenSelections}
/>
diff --git a/apps/playground-web/src/app/bridge/swap-widget/components/buildSwapIframeUrl.ts b/apps/playground-web/src/app/bridge/swap-widget/components/buildSwapIframeUrl.ts
new file mode 100644
index 00000000000..ab16f579b87
--- /dev/null
+++ b/apps/playground-web/src/app/bridge/swap-widget/components/buildSwapIframeUrl.ts
@@ -0,0 +1,67 @@
+import type { SwapWidgetPlaygroundOptions } from "./types";
+
+const SWAP_WIDGET_IFRAME_BASE_URL = "https://thirdweb.com/bridge/swap-widget";
+
+export function buildSwapIframeUrl(
+ options: SwapWidgetPlaygroundOptions,
+ type: "code" | "preview" = "preview",
+) {
+ const url = new URL(SWAP_WIDGET_IFRAME_BASE_URL);
+
+ if (type === "preview") {
+ // always set it to false so playground doesn't show last used tokens
+ url.searchParams.set("persistTokenSelections", "false");
+ }
+
+ // Buy token params
+ if (options.prefill?.buyToken?.chainId) {
+ url.searchParams.set("buyChain", String(options.prefill.buyToken.chainId));
+
+ if (options.prefill.buyToken.tokenAddress) {
+ url.searchParams.set(
+ "buyTokenAddress",
+ options.prefill.buyToken.tokenAddress,
+ );
+ }
+
+ if (options.prefill.buyToken.amount) {
+ url.searchParams.set("buyAmount", options.prefill.buyToken.amount);
+ }
+ }
+
+ // Sell token params
+ if (options.prefill?.sellToken?.chainId) {
+ url.searchParams.set(
+ "sellChain",
+ String(options.prefill.sellToken.chainId),
+ );
+
+ if (options.prefill.sellToken.tokenAddress) {
+ url.searchParams.set(
+ "sellTokenAddress",
+ options.prefill.sellToken.tokenAddress,
+ );
+ }
+
+ if (options.prefill.sellToken.amount) {
+ url.searchParams.set("sellAmount", options.prefill.sellToken.amount);
+ }
+ }
+
+ // Theme (only add if light, dark is default)
+ if (options.theme.type === "light") {
+ url.searchParams.set("theme", "light");
+ }
+
+ // Currency (only add if not USD, USD is default)
+ if (options.currency && options.currency !== "USD") {
+ url.searchParams.set("currency", options.currency);
+ }
+
+ // Branding
+ if (options.showThirdwebBranding === false) {
+ url.searchParams.set("showThirdwebBranding", "false");
+ }
+
+ return url.toString();
+}
diff --git a/apps/playground-web/src/app/bridge/swap-widget/components/code.tsx b/apps/playground-web/src/app/bridge/swap-widget/components/code.tsx
index 9e76d15b36f..9854a9339ac 100644
--- a/apps/playground-web/src/app/bridge/swap-widget/components/code.tsx
+++ b/apps/playground-web/src/app/bridge/swap-widget/components/code.tsx
@@ -1,6 +1,7 @@
import { lazy, Suspense } from "react";
import { LoadingDots } from "@/components/ui/LoadingDots";
import { quotes, stringifyImports, stringifyProps } from "@/lib/code-gen";
+import { buildSwapIframeUrl } from "./buildSwapIframeUrl";
import type { SwapWidgetPlaygroundOptions } from "./types";
const CodeClient = lazy(() =>
@@ -18,16 +19,35 @@ function CodeLoading() {
}
export function CodeGen(props: { options: SwapWidgetPlaygroundOptions }) {
+ const code =
+ props.options.integrationType === "iframe"
+ ? getIframeCode(props.options)
+ : getReactCode(props.options);
+
+ const lang = props.options.integrationType === "iframe" ? "html" : "ts";
+
return (
}>
-
+
);
}
-function getCode(options: SwapWidgetPlaygroundOptions) {
+function getIframeCode(options: SwapWidgetPlaygroundOptions) {
+ // Use "code" type to exclude persistTokenSelections from the generated code
+ const iframeUrl = buildSwapIframeUrl(options, "code");
+
+ return ``;
+}
+
+function getReactCode(options: SwapWidgetPlaygroundOptions) {
const imports = {
thirdweb: ["createThirdwebClient"] as string[],
"thirdweb/react": ["SwapWidget"] as string[],
diff --git a/apps/playground-web/src/app/bridge/swap-widget/components/left-section.tsx b/apps/playground-web/src/app/bridge/swap-widget/components/left-section.tsx
index 28677b19515..2ff75c6554e 100644
--- a/apps/playground-web/src/app/bridge/swap-widget/components/left-section.tsx
+++ b/apps/playground-web/src/app/bridge/swap-widget/components/left-section.tsx
@@ -85,16 +85,18 @@ export function LeftSection(props: {
- {/* Colors */}
- {
- setOptions((v) => ({
- ...v,
- theme: newTheme,
- }));
- }}
- theme={options.theme}
- />
+ {/* Colors - disabled for iframe */}
+ {options.integrationType !== "iframe" && (
+ {
+ setOptions((v) => ({
+ ...v,
+ theme: newTheme,
+ }));
+ }}
+ theme={options.theme}
+ />
+ )}
-
-
- {previewTab === "ui" && (
-
- )}
+ {previewTab === "ui" &&
+ (props.options.integrationType === "iframe" ? (
+
+ ) : (
+
+ ))}
{previewTab === "code" && }
@@ -72,21 +83,6 @@ export function RightSection(props: { options: SwapWidgetPlaygroundOptions }) {
);
}
-function BackgroundPattern() {
- const color = "hsl(var(--foreground)/15%)";
- return (
-
- );
-}
-
function TabButtons(props: {
tabs: Array<{
name: string;
diff --git a/apps/playground-web/src/app/bridge/swap-widget/components/swap-widget-playground.tsx b/apps/playground-web/src/app/bridge/swap-widget/components/swap-widget-playground.tsx
index 920d33fc7c7..fc117d011d5 100644
--- a/apps/playground-web/src/app/bridge/swap-widget/components/swap-widget-playground.tsx
+++ b/apps/playground-web/src/app/bridge/swap-widget/components/swap-widget-playground.tsx
@@ -2,11 +2,13 @@
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
+import { TabButtons } from "@/components/ui/tab-buttons";
import { LeftSection } from "./left-section";
import { RightSection } from "./right-section";
import type { SwapWidgetPlaygroundOptions } from "./types";
const defaultOptions: SwapWidgetPlaygroundOptions = {
+ integrationType: "react",
prefill: undefined,
currency: "USD",
showThirdwebBranding: true,
@@ -17,11 +19,26 @@ const defaultOptions: SwapWidgetPlaygroundOptions = {
},
};
-export function SwapWidgetPlayground() {
+function updatePageUrl(tab: SwapWidgetPlaygroundOptions["integrationType"]) {
+ const url = new URL(window.location.href);
+ if (tab === defaultOptions.integrationType) {
+ url.searchParams.delete("tab");
+ } else {
+ url.searchParams.set("tab", tab || "");
+ }
+
+ window.history.replaceState({}, "", url.toString());
+}
+
+export function SwapWidgetPlayground(props: {
+ defaultTab?: "iframe" | "react";
+}) {
const { theme } = useTheme();
- const [options, setOptions] =
- useState(defaultOptions);
+ const [options, setOptions] = useState(() => ({
+ ...defaultOptions,
+ integrationType: props.defaultTab || defaultOptions.integrationType,
+ }));
// change theme on global theme change
useEffect(() => {
@@ -34,12 +51,36 @@ export function SwapWidgetPlayground() {
}));
}, [theme]);
+ useEffect(() => {
+ updatePageUrl(options.integrationType);
+ }, [options.integrationType]);
+
return (
-
-
-
+
+
setOptions({ ...options, integrationType: "react" }),
+ isActive: options.integrationType === "react",
+ },
+ {
+ name: "Iframe",
+ onClick: () =>
+ setOptions({ ...options, integrationType: "iframe" }),
+ isActive: options.integrationType === "iframe",
+ },
+ ]}
+ />
+
+
+
+
-
);
}
diff --git a/apps/playground-web/src/app/bridge/swap-widget/components/types.ts b/apps/playground-web/src/app/bridge/swap-widget/components/types.ts
index 2dedd030a9b..7984f060590 100644
--- a/apps/playground-web/src/app/bridge/swap-widget/components/types.ts
+++ b/apps/playground-web/src/app/bridge/swap-widget/components/types.ts
@@ -1,6 +1,7 @@
import type { SwapWidgetProps, ThemeOverrides } from "thirdweb/react";
export type SwapWidgetPlaygroundOptions = {
+ integrationType: "iframe" | "react";
theme: {
type: "dark" | "light";
darkColorOverrides: ThemeOverrides["colors"];
diff --git a/apps/playground-web/src/app/bridge/swap-widget/page.tsx b/apps/playground-web/src/app/bridge/swap-widget/page.tsx
index 535b4ed1d22..ae5ae9a74da 100644
--- a/apps/playground-web/src/app/bridge/swap-widget/page.tsx
+++ b/apps/playground-web/src/app/bridge/swap-widget/page.tsx
@@ -19,7 +19,9 @@ export const metadata = createMetadata({
},
});
-export default function Page() {
+export default function Page(props: {
+ searchParams: Promise<{ tab?: string }>;
+}) {
return (
-
+
);
}
+
+async function SwapWidgetPlaygroundAsync(props: {
+ searchParams: Promise<{ tab?: string }>;
+}) {
+ const searchParams = await props.searchParams;
+ const defaultTab =
+ searchParams.tab === "iframe" || searchParams.tab === "react"
+ ? searchParams.tab
+ : undefined;
+
+ return
;
+}
diff --git a/apps/portal/src/app/bridge/bridge-widget/iframe/iframe-code-preview.tsx b/apps/portal/src/app/bridge/bridge-widget/iframe/iframe-code-preview.tsx
index b390253c997..3d26943afa7 100644
--- a/apps/portal/src/app/bridge/bridge-widget/iframe/iframe-code-preview.tsx
+++ b/apps/portal/src/app/bridge/bridge-widget/iframe/iframe-code-preview.tsx
@@ -1,5 +1,12 @@
import { CodeBlock, Tabs, TabsContent, TabsList, TabsTrigger } from "@doc";
+function getPreviewSrc(src: string) {
+ const url = new URL(src);
+ // Disable token persistence for docs previews
+ url.searchParams.set("persistTokenSelections", "false");
+ return url.toString();
+}
+
export function IframeCodePreview(props: { src: string }) {
return (
@@ -22,7 +29,7 @@ export function IframeCodePreview(props: { src: string }) {