Skip to content

Commit 5ff6063

Browse files
committed
[MNY-269] SDK: autofocus token search input when token selector modal opens in BuyWidget, SwapWidget and BridgeWidget (#8266)
<!-- ## 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 enhances user experience by adding autofocus functionality to token selection inputs in various components and refactoring the `BuyWidget` and `SwapWidget` to utilize a common `Variant` component for rendering with different themes. ### Detailed summary - Added `autoFocus` to token search inputs in `BuyWidget`, `SwapWidget`, and `BridgeWidget`. - Refactored `BuyWidget` to use a `Variant` component for theme rendering. - Refactored `SwapWidget` to use a `Variant` component for theme rendering. - Introduced `autoFocusCrossIcon` prop in `Modal` component. - Updated `SearchInput` to accept `autoFocus` prop. > ✨ 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** * Token search input now auto-focuses on desktop when opening token selector modals in Buy, Swap, and Bridge widgets. * Modal gained a configurable option to control initial focus of the close (cross) button. * **Bug Fixes / UX** * Prevents unwanted focus on the modal close icon by default when configured, improving keyboard/voicing navigation. * **Chores** * Storybook examples updated for consistent light/dark variants and layout metadata. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 3db3982 commit 5ff6063

File tree

10 files changed

+103
-50
lines changed

10 files changed

+103
-50
lines changed

.changeset/happy-trees-doubt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
autofocus token search input when token selector modal opens in `BuyWidget`, `SwapWidget` and `BridgeWidget` components

packages/thirdweb/src/react/web/ui/Bridge/FundWallet.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ export function FundWallet(props: FundWalletProps) {
205205
transform: "none",
206206
}}
207207
setOpen={(v) => setIsTokenSelectionOpen(v)}
208+
autoFocusCrossIcon={false}
208209
>
209210
<SelectToken
210211
activeWalletInfo={activeWalletInfo}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function SearchInput(props: {
1111
value: string;
1212
onChange: (value: string) => void;
1313
placeholder: string;
14+
autoFocus?: boolean;
1415
}) {
1516
return (
1617
<div
@@ -41,6 +42,7 @@ export function SearchInput(props: {
4142
paddingLeft: "44px",
4243
}}
4344
onChange={(e) => props.onChange(e.target.value)}
45+
autoFocus={props.autoFocus}
4446
/>
4547
</div>
4648
);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ function TokenSelectionScreen(props: {
496496
value={props.search}
497497
onChange={props.setSearch}
498498
placeholder="Search by token or address"
499+
autoFocus={!props.isMobile}
499500
/>
500501
</Container>
501502

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export function SwapUI(props: SwapUIProps) {
204204
<Container p="md">
205205
<Modal
206206
hide={false}
207+
autoFocusCrossIcon={false}
207208
className="tw-modal__swap-widget"
208209
size={isMobile ? "compact" : "wide"}
209210
title="Select Token"

packages/thirdweb/src/react/web/ui/components/Modal.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const Modal: React.FC<{
4141
title: string;
4242
hide?: boolean;
4343
crossContainerStyles?: React.CSSProperties;
44+
autoFocusCrossIcon?: boolean;
4445
}> = (props) => {
4546
const [open, setOpen] = useState(props.open);
4647
const contentRef = useRef<HTMLDivElement>(null);
@@ -143,7 +144,15 @@ export const Modal: React.FC<{
143144
}}
144145
>
145146
<Dialog.Close asChild>
146-
<IconButton aria-label="Close" autoFocus type="button">
147+
<IconButton
148+
aria-label="Close"
149+
autoFocus={
150+
props.autoFocusCrossIcon === undefined
151+
? true
152+
: props.autoFocusCrossIcon
153+
}
154+
type="button"
155+
>
147156
<Cross2Icon
148157
height={iconSize.md}
149158
style={{
Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
11
import type { Meta } from "@storybook/react";
2-
import { BridgeWidgetScript } from "../../../script-exports/bridge-widget-script.js";
2+
import {
3+
BridgeWidgetScript,
4+
type BridgeWidgetScriptProps,
5+
} from "../../../script-exports/bridge-widget-script.js";
36
import { storyClient } from "../../utils.js";
47

58
const meta: Meta<typeof BridgeWidgetScript> = {
69
title: "Bridge/BridgeWidgetScript",
7-
parameters: {
8-
layout: "centered",
9-
},
10-
decorators: [
11-
(Story) => {
12-
return (
13-
<div>
14-
<Story />
15-
</div>
16-
);
17-
},
18-
],
1910
};
2011
export default meta;
2112

2213
export function BasicUsage() {
2314
return (
24-
<BridgeWidgetScript
15+
<Variant
2516
clientId={storyClient.clientId}
2617
buy={{ chainId: 8453, amount: "0.1" }}
2718
/>
@@ -30,7 +21,7 @@ export function BasicUsage() {
3021

3122
export function LightTheme() {
3223
return (
33-
<BridgeWidgetScript
24+
<Variant
3425
clientId={storyClient.clientId}
3526
theme="light"
3627
buy={{ chainId: 8453, amount: "0.1" }}
@@ -40,7 +31,7 @@ export function LightTheme() {
4031

4132
export function CurrencySet() {
4233
return (
43-
<BridgeWidgetScript
34+
<Variant
4435
clientId={storyClient.clientId}
4536
currency="JPY"
4637
buy={{ chainId: 8453, amount: "0.1" }}
@@ -50,7 +41,7 @@ export function CurrencySet() {
5041

5142
export function NoThirdwebBranding() {
5243
return (
53-
<BridgeWidgetScript
44+
<Variant
5445
clientId={storyClient.clientId}
5546
theme="light"
5647
buy={{ chainId: 8453, amount: "0.1" }}
@@ -61,7 +52,7 @@ export function NoThirdwebBranding() {
6152

6253
export function CustomTheme() {
6354
return (
64-
<BridgeWidgetScript
55+
<Variant
6556
clientId={storyClient.clientId}
6657
buy={{ chainId: 8453, amount: "0.1" }}
6758
theme={{
@@ -77,3 +68,19 @@ export function CustomTheme() {
7768
/>
7869
);
7970
}
71+
72+
function Variant(props: BridgeWidgetScriptProps) {
73+
return (
74+
<div
75+
style={{
76+
display: "flex",
77+
flexDirection: "column",
78+
gap: "40px",
79+
alignItems: "center",
80+
}}
81+
>
82+
<BridgeWidgetScript {...props} theme="dark" />
83+
<BridgeWidgetScript {...props} theme="light" />
84+
</div>
85+
);
86+
}

packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.Prefill.stories.tsx

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import type { Meta } from "@storybook/react-vite";
22
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/addresses.js";
3-
import { SwapWidget } from "../../../react/web/ui/Bridge/swap-widget/SwapWidget.js";
3+
import {
4+
SwapWidget,
5+
type SwapWidgetProps,
6+
} from "../../../react/web/ui/Bridge/swap-widget/SwapWidget.js";
47
import { storyClient } from "../../utils.js";
58

69
const meta = {
7-
parameters: {
8-
layout: "centered",
9-
},
1010
title: "Bridge/Swap/SwapWidget/Prefill",
1111
} satisfies Meta<typeof SwapWidget>;
1212
export default meta;
1313

1414
export function Buy_NativeToken() {
1515
return (
16-
<SwapWidget
16+
<Variant
1717
client={storyClient}
1818
prefill={{
1919
buyToken: {
@@ -26,7 +26,7 @@ export function Buy_NativeToken() {
2626

2727
export function Buy_Base_USDC() {
2828
return (
29-
<SwapWidget
29+
<Variant
3030
client={storyClient}
3131
prefill={{
3232
buyToken: {
@@ -40,7 +40,7 @@ export function Buy_Base_USDC() {
4040

4141
export function Buy_NativeToken_With_Amount() {
4242
return (
43-
<SwapWidget
43+
<Variant
4444
client={storyClient}
4545
prefill={{
4646
buyToken: {
@@ -55,7 +55,7 @@ export function Buy_NativeToken_With_Amount() {
5555

5656
export function Sell_NativeToken() {
5757
return (
58-
<SwapWidget
58+
<Variant
5959
client={storyClient}
6060
prefill={{
6161
sellToken: {
@@ -82,7 +82,7 @@ export function Sell_Base_USDC() {
8282

8383
export function Sell_NativeToken_With_Amount() {
8484
return (
85-
<SwapWidget
85+
<Variant
8686
client={storyClient}
8787
prefill={{
8888
sellToken: {
@@ -97,7 +97,7 @@ export function Sell_NativeToken_With_Amount() {
9797

9898
export function Buy_And_Sell_NativeToken() {
9999
return (
100-
<SwapWidget
100+
<Variant
101101
client={storyClient}
102102
prefill={{
103103
// base native token
@@ -114,3 +114,19 @@ export function Buy_And_Sell_NativeToken() {
114114
/>
115115
);
116116
}
117+
118+
function Variant(props: SwapWidgetProps) {
119+
return (
120+
<div
121+
style={{
122+
display: "flex",
123+
flexDirection: "column",
124+
gap: "40px",
125+
alignItems: "center",
126+
}}
127+
>
128+
<SwapWidget {...props} theme="dark" />
129+
<SwapWidget {...props} theme="light" />
130+
</div>
131+
);
132+
}

packages/thirdweb/src/stories/Bridge/Swap/SwapWidget.stories.tsx

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
import type { Meta } from "@storybook/react";
22
import { lightTheme } from "../../../react/core/design-system/index.js";
3-
import { SwapWidget } from "../../../react/web/ui/Bridge/swap-widget/SwapWidget.js";
3+
import {
4+
SwapWidget,
5+
type SwapWidgetProps,
6+
} from "../../../react/web/ui/Bridge/swap-widget/SwapWidget.js";
47
import { storyClient } from "../../utils.js";
58

69
const meta: Meta<typeof SwapWidget> = {
7-
parameters: {
8-
layout: "centered",
9-
},
1010
title: "Bridge/Swap/SwapWidget",
11-
decorators: [
12-
(Story) => {
13-
return (
14-
<div>
15-
<Story />
16-
</div>
17-
);
18-
},
19-
],
2011
};
2112
export default meta;
2213

@@ -26,7 +17,7 @@ export function BasicUsage() {
2617

2718
export function CurrencySet() {
2819
return (
29-
<SwapWidget
20+
<Variant
3021
client={storyClient}
3122
currency="JPY"
3223
persistTokenSelections={false}
@@ -36,7 +27,7 @@ export function CurrencySet() {
3627

3728
export function LightMode() {
3829
return (
39-
<SwapWidget
30+
<Variant
4031
client={storyClient}
4132
currency="JPY"
4233
theme="light"
@@ -47,7 +38,7 @@ export function LightMode() {
4738

4839
export function NoThirdwebBranding() {
4940
return (
50-
<SwapWidget
41+
<Variant
5142
client={storyClient}
5243
currency="JPY"
5344
showThirdwebBranding={false}
@@ -58,7 +49,7 @@ export function NoThirdwebBranding() {
5849

5950
export function CustomTheme() {
6051
return (
61-
<SwapWidget
52+
<Variant
6253
client={storyClient}
6354
currency="JPY"
6455
persistTokenSelections={false}
@@ -74,3 +65,19 @@ export function CustomTheme() {
7465
/>
7566
);
7667
}
68+
69+
function Variant(props: SwapWidgetProps) {
70+
return (
71+
<div
72+
style={{
73+
display: "flex",
74+
flexDirection: "column",
75+
gap: "40px",
76+
alignItems: "center",
77+
}}
78+
>
79+
<SwapWidget {...props} theme="dark" />
80+
<SwapWidget {...props} theme="light" />
81+
</div>
82+
);
83+
}

packages/thirdweb/src/stories/BuyWidget.stories.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ import {
99
import { storyClient } from "./utils.js";
1010

1111
const meta = {
12-
parameters: {
13-
layout: "centered",
14-
},
1512
title: "Bridge/Buy/BuyWidget",
1613
} satisfies Meta<typeof BuyWidget>;
1714
export default meta;
@@ -143,7 +140,14 @@ export function LargeAmount() {
143140

144141
function Variant(props: BuyWidgetProps) {
145142
return (
146-
<div style={{ display: "flex", flexDirection: "column", gap: "40px" }}>
143+
<div
144+
style={{
145+
display: "flex",
146+
flexDirection: "column",
147+
gap: "40px",
148+
alignItems: "center",
149+
}}
150+
>
147151
<BuyWidget {...props} theme="dark" />
148152
<BuyWidget {...props} theme="light" />
149153
</div>

0 commit comments

Comments
 (0)