1+ import { useState } from "react"
12import { Connect , useFlowChainId } from "@onflow/react-sdk"
23import { useDarkMode } from "../flow-provider-wrapper"
34import { DemoCard , type PropDefinition } from "../ui/demo-card"
45import { PlusGridIcon } from "../ui/plus-grid"
6+ import { CONTRACT_ADDRESSES } from "../../constants"
57
68const IMPLEMENTATION_CODE = `import { Connect } from "@onflow/react-sdk"
79
8- <Connect />`
10+ // Basic usage - shows FLOW by default
11+ <Connect />
12+
13+ // With multiple tokens - dropdown selector, first token is default
14+ // Note: Provide only ONE identifier per token (the bridge derives the other)
15+ <Connect
16+ balanceTokens={[
17+ {
18+ symbol: "FLOW",
19+ name: "Flow Token",
20+ vaultIdentifier: "A.1654653399040a61.FlowToken.Vault",
21+ },
22+ {
23+ symbol: "USDF",
24+ name: "USDF (PYUSD)",
25+ vaultIdentifier: "A.1e4aa0b87d10b141.EVMVMBridgedToken_2aabea2058b5ac2d339b163c6ab6f2b6d53aabed.Vault",
26+ },
27+ ]}
28+ balanceType="combined"
29+ />`
930
1031const PROPS : PropDefinition [ ] = [
1132 {
@@ -29,11 +50,19 @@ const PROPS: PropDefinition[] = [
2950 } ,
3051 {
3152 name : "balanceType" ,
32- type : '"cadence" | "evm" | "vault "' ,
53+ type : '"cadence" | "evm" | "combined "' ,
3354 required : false ,
34- description : "Type of balance to display (from cross-VM token balance)" ,
55+ description :
56+ "Type of balance to display: cadence (Cadence VM only), evm (EVM only), or combined (sum of both)" ,
3557 defaultValue : '"cadence"' ,
3658 } ,
59+ {
60+ name : "balanceTokens" ,
61+ type : "TokenConfig[]" ,
62+ required : false ,
63+ description :
64+ "Array of tokens with dropdown selector (first token is default). Each token needs symbol, name, and EXACTLY ONE of: vaultIdentifier OR erc20Address (the bridge derives the other automatically)" ,
65+ } ,
3766 {
3867 name : "modalConfig" ,
3968 type : "ConnectModalConfig" ,
@@ -47,6 +76,41 @@ export function ConnectCard() {
4776 const { darkMode} = useDarkMode ( )
4877 const { data : chainId , isLoading} = useFlowChainId ( )
4978 const isEmulator = chainId === "emulator" || chainId === "local"
79+ const [ showMultiToken , setShowMultiToken ] = useState ( false )
80+ const [ balanceType , setBalanceType ] = useState <
81+ "cadence" | "evm" | "combined"
82+ > ( "cadence" )
83+
84+ const getFlowTokenAddress = ( ) => {
85+ if ( chainId === "emulator" || chainId === "local" ) {
86+ return CONTRACT_ADDRESSES . FlowToken . emulator
87+ }
88+ return chainId === "testnet"
89+ ? CONTRACT_ADDRESSES . FlowToken . testnet
90+ : CONTRACT_ADDRESSES . FlowToken . mainnet
91+ }
92+
93+ const multiTokens = [
94+ {
95+ symbol : "FLOW" ,
96+ name : "Flow Token" ,
97+ vaultIdentifier : `A.${ getFlowTokenAddress ( ) . replace ( "0x" , "" ) } .FlowToken.Vault` ,
98+ } ,
99+ // Only show USDF (PYUSD) on testnet and mainnet
100+ // Note: Only vaultIdentifier provided - EVM address is derived by the bridge
101+ ...( ! isEmulator
102+ ? [
103+ {
104+ symbol : "USDF" ,
105+ name : "USDF (PYUSD)" ,
106+ vaultIdentifier :
107+ chainId === "testnet"
108+ ? "A.dfc20aee650fcbdf.EVMVMBridgedToken_f2e5a325f7d678da511e66b1c0ad7d5ba4df93d3.Vault"
109+ : "A.1e4aa0b87d10b141.EVMVMBridgedToken_2aabea2058b5ac2d339b163c6ab6f2b6d53aabed.Vault" ,
110+ } ,
111+ ]
112+ : [ ] ) ,
113+ ]
50114
51115 return (
52116 < DemoCard
@@ -58,7 +122,7 @@ export function ConnectCard() {
58122 docsUrl = "https://developers.flow.com/build/tools/react-sdk/components#connect"
59123 >
60124 < div className = "space-y-6" >
61- < div className = "grid grid-cols-2 gap-4" >
125+ < div className = "grid grid-cols-3 gap-4" >
62126 < div
63127 className = { `relative p-4 rounded-lg border ${
64128 darkMode
@@ -78,7 +142,7 @@ export function ConnectCard() {
78142 </ div >
79143
80144 < div
81- className = { `relative p-4 rounded-lg border ${
145+ className = { `relative col-span-2 p-4 rounded-lg border ${
82146 darkMode
83147 ? "bg-gray-900/50 border-white/10"
84148 : "bg-gray-50 border-black/5"
@@ -88,63 +152,136 @@ export function ConnectCard() {
88152 < h4
89153 className = { `text-xs font-medium mb-1 ${ darkMode ? "text-gray-500" : "text-gray-500" } ` }
90154 >
91- Customizable
155+ Multi-Token Cross-VM
92156 </ h4 >
93- < p className = { `text-sm ${ darkMode ? "text-white" : "text-black" } ` } >
94- Style variants
157+ < p
158+ className = { `text-xs ${ darkMode ? "text-gray-400" : "text-gray-600" } ` }
159+ >
160+ Multi-token support with cross-VM bridge integration. Provide only
161+ a vaultIdentifier or an erc20Address and the bridge derives the
162+ other automatically.
95163 </ p >
96164 </ div >
97165 </ div >
98166
99- < div
100- className = { `relative p-8 rounded-lg border ${
101- darkMode
102- ? "bg-gray-900/50 border-white/10"
103- : "bg-gray-50 border-black/5"
104- } `}
105- >
106- { isLoading ? (
107- < div className = "text-center" >
167+ < div className = "flex gap-4" >
168+ < div
169+ className = { `relative flex-1 p-8 rounded-lg border flex items-center justify-center
170+ min-h-[200px] ${
171+ darkMode
172+ ? "bg-gray-900/50 border-white/10"
173+ : "bg-gray-50 border-black/5"
174+ } `}
175+ >
176+ { isLoading ? (
177+ < div className = "text-center" >
178+ < div
179+ className = { `inline-block animate-spin rounded-full h-8 w-8 border-b-2 ${
180+ darkMode ? "border-white" : "border-black" } `}
181+ > </ div >
182+ </ div >
183+ ) : isEmulator ? (
108184 < div
109- className = { `inline-block animate-spin rounded-full h-8 w-8 border-b-2 ${
110- darkMode ? "border-white" : "border-black" } `}
111- > </ div >
112- </ div >
113- ) : isEmulator ? (
185+ className = { `text-center py-4 px-6 rounded-lg border ${
186+ darkMode
187+ ? "bg-orange-900/20 border-orange-800/50"
188+ : "bg-orange-50 border-orange-200"
189+ } `}
190+ >
191+ < svg
192+ className = "w-8 h-8 mx-auto mb-2 text-orange-500"
193+ fill = "none"
194+ viewBox = "0 0 24 24"
195+ stroke = "currentColor"
196+ >
197+ < path
198+ strokeLinecap = "round"
199+ strokeLinejoin = "round"
200+ strokeWidth = { 2 }
201+ d = "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
202+ />
203+ </ svg >
204+ < p
205+ className = { `text-sm font-medium ${ darkMode ? "text-orange-400" : "text-orange-600" } ` }
206+ >
207+ Emulator Network Detected
208+ </ p >
209+ < p
210+ className = { `text-xs mt-1 ${ darkMode ? "text-orange-400/70" : "text-orange-600/70" } ` }
211+ >
212+ Connect component requires testnet or mainnet
213+ </ p >
214+ </ div >
215+ ) : (
216+ < div >
217+ { showMultiToken ? (
218+ < Connect
219+ balanceType = { balanceType }
220+ balanceTokens = { multiTokens }
221+ />
222+ ) : (
223+ < Connect balanceType = { balanceType } />
224+ ) }
225+ </ div >
226+ ) }
227+ </ div >
228+
229+ { ! isEmulator && (
114230 < div
115- className = { `text-center py -4 px-6 rounded-lg border ${
116- darkMode
117- ? "bg-orange -900/20 border-orange-800/50 "
118- : "bg-orange -50 border-orange-200 "
119- } `}
231+ className = { `w-56 p -4 rounded-lg border space-y-4 ${
232+ darkMode
233+ ? "bg-gray -900/50 border-white/10 "
234+ : "bg-gray -50 border-black/5 "
235+ } `}
120236 >
121- < svg
122- className = "w-8 h-8 mx-auto mb-2 text-orange-500"
123- fill = "none"
124- viewBox = "0 0 24 24"
125- stroke = "currentColor"
126- >
127- < path
128- strokeLinecap = "round"
129- strokeLinejoin = "round"
130- strokeWidth = { 2 }
131- d = "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
132- />
133- </ svg >
134- < p
135- className = { `text-sm font-medium ${ darkMode ? "text-orange-400" : "text-orange-600" } ` }
136- >
137- Emulator Network Detected
138- </ p >
139- < p
140- className = { `text-xs mt-1 ${ darkMode ? "text-orange-400/70" : "text-orange-600/70" } ` }
141- >
142- Connect component requires testnet or mainnet
143- </ p >
144- </ div >
145- ) : (
146- < div className = "flex justify-center" >
147- < Connect />
237+ < div >
238+ < label
239+ className = { `text-xs font-medium mb-2 block ${ darkMode ? "text-gray-500" : "text-gray-500" } ` }
240+ >
241+ Token Mode
242+ </ label >
243+ < button
244+ onClick = { ( ) => setShowMultiToken ( ! showMultiToken ) }
245+ className = { `w-full text-sm px-3 py-2 rounded-lg transition-colors ${
246+ showMultiToken
247+ ? darkMode
248+ ? "bg-blue-600 hover:bg-blue-700 text-white"
249+ : "bg-blue-500 hover:bg-blue-600 text-white"
250+ : darkMode
251+ ? "bg-gray-800 hover:bg-gray-700 text-white"
252+ : "bg-white hover:bg-gray-100 text-black border border-black/10"
253+ } `}
254+ >
255+ { showMultiToken ? "Multi-Token" : "Basic" }
256+ </ button >
257+ </ div >
258+
259+ < div >
260+ < label
261+ className = { `text-xs font-medium mb-2 block ${ darkMode ? "text-gray-500" : "text-gray-500" } ` }
262+ >
263+ Balance Type
264+ </ label >
265+ < div className = "space-y-1.5" >
266+ { ( [ "cadence" , "evm" , "combined" ] as const ) . map ( type => (
267+ < button
268+ key = { type }
269+ onClick = { ( ) => setBalanceType ( type ) }
270+ className = { `w-full text-sm px-3 py-2 rounded-lg transition-colors text-left ${
271+ balanceType === type
272+ ? darkMode
273+ ? "bg-blue-600 hover:bg-blue-700 text-white"
274+ : "bg-blue-500 hover:bg-blue-600 text-white"
275+ : darkMode
276+ ? "bg-gray-800 hover:bg-gray-700 text-white"
277+ : "bg-white hover:bg-gray-100 text-black border border-black/10"
278+ } `}
279+ >
280+ { type . charAt ( 0 ) . toUpperCase ( ) + type . slice ( 1 ) }
281+ </ button >
282+ ) ) }
283+ </ div >
284+ </ div >
148285 </ div >
149286 ) }
150287 </ div >
0 commit comments