import type { FC, MouseEvent } from 'react'; import { useCallback, useLayoutEffect, useMemo, useRef, useState, } from 'react'; import { createPortal } from 'react-dom'; import { AllowedPrivateData, PrivateDataPermission, WalletAdapterNetwork, WalletName, WalletReadyState, } from '@miden-sdk/miden-wallet-adapter-base'; import { useWallet, Wallet } from '@miden-sdk/miden-wallet-adapter-react'; import { useWalletModal } from './useWalletModal'; import { WalletListItem } from './WalletListItem'; import { DiscoverMidenMessage } from './DiscoverMidenMessage'; export interface WalletModalProps { className?: string; container?: string; privateDataPermission?: PrivateDataPermission; network?: WalletAdapterNetwork; allowedPrivateData?: AllowedPrivateData; } export const WalletModal: FC = ({ className = '', container = 'body', privateDataPermission, network, allowedPrivateData, }) => { const ref = useRef(null); const { wallets, select, connect, wallet } = useWallet(); const { setVisible } = useWalletModal(); const [fadeIn, setFadeIn] = useState(false); const [portal, setPortal] = useState(null); const [installedWallets, otherWallets] = useMemo(() => { const installed: Wallet[] = []; const notDetected: Wallet[] = []; const loadable: Wallet[] = []; for (const wallet of wallets) { if (wallet.readyState === WalletReadyState.NotDetected) { notDetected.push(wallet); } else if (wallet.readyState === WalletReadyState.Loadable) { loadable.push(wallet); } else if (wallet.readyState === WalletReadyState.Installed) { installed.push(wallet); } } return [installed, [...loadable, ...notDetected]]; }, [wallets]); const getStartedWallet = useMemo(() => { return installedWallets.length ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion installedWallets[0]! : wallets.find( (wallet: { adapter: { name: WalletName } }) => wallet.adapter.name === 'Miden Wallet' ) || // eslint-disable-next-line @typescript-eslint/no-non-null-assertion otherWallets[0]!; }, [installedWallets, wallets, otherWallets]); const otherInstalledWallets = useMemo(() => { return installedWallets.filter( (wallet) => wallet.adapter.name !== getStartedWallet.adapter.name ); }, [installedWallets, getStartedWallet]); const hideModal = useCallback(() => { setFadeIn(false); setTimeout(() => setVisible(false), 150); }, [setVisible]); const handleClose = useCallback( (event: MouseEvent) => { event.preventDefault(); hideModal(); }, [hideModal] ); const handleWalletClick = useCallback( (event: MouseEvent, walletName: WalletName) => { select(walletName); handleClose(event); }, [select, handleClose] ); const handleTabKey = useCallback( (event: KeyboardEvent) => { const node = ref.current; if (!node) return; // here we query all focusable elements const focusableElements = node.querySelectorAll('button'); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const firstElement = focusableElements[0]!; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const lastElement = focusableElements[focusableElements.length - 1]!; if (event.shiftKey) { // if going backward by pressing tab and firstElement is active, shift focus to last focusable element if (document.activeElement === firstElement) { lastElement.focus(); event.preventDefault(); } } else { // if going forward by pressing tab and lastElement is active, shift focus to first focusable element if (document.activeElement === lastElement) { firstElement.focus(); event.preventDefault(); } } }, [ref] ); useLayoutEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { hideModal(); } else if (event.key === 'Tab') { handleTabKey(event); } }; // Get original overflow const { overflow } = window.getComputedStyle(document.body); // Hack to enable fade in animation after mount setTimeout(() => setFadeIn(true), 0); // Prevent scrolling on mount document.body.style.overflow = 'hidden'; // Listen for keydown events window.addEventListener('keydown', handleKeyDown, false); return () => { // Re-enable scrolling when component unmounts document.body.style.overflow = overflow; window.removeEventListener('keydown', handleKeyDown, false); }; }, [hideModal, handleTabKey]); useLayoutEffect( () => setPortal(document.querySelector(container)), [container] ); useLayoutEffect(() => { if (wallet) { connect( privateDataPermission || PrivateDataPermission.UponRequest, network || WalletAdapterNetwork.Testnet, allowedPrivateData ?? AllowedPrivateData.None ).catch((e) => { console.log({ e }); }); } }, [wallet, privateDataPermission, network, allowedPrivateData, connect]); return ( portal && createPortal(
Connect a Wallet
{installedWallets.length ? ( <>
Connect your Miden Wallet and start exploring its powerful features now!
    Recommended handleWalletClick(event, getStartedWallet.adapter.name) } wallet={getStartedWallet} /> {(otherInstalledWallets.length > 0 || otherWallets.length > 0) && ( <>
    Other wallets {otherInstalledWallets.map((wallet) => ( handleWalletClick(event, wallet.adapter.name) } wallet={wallet} /> ))} {otherWallets.map((wallet) => ( handleWalletClick(event, wallet.adapter.name) } wallet={wallet} /> ))} )}
) : ( <> )}
, portal ) ); };