UNPKG

@turnkey/react-wallet-kit

Version:

The easiest and most powerful way to integrate Turnkey's Embedded Wallets into your React applications.

254 lines (250 loc) 9.84 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var reactFontawesome = require('@fortawesome/react-fontawesome'); var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons'); var react = require('react'); var clsx = require('clsx'); var core = require('@turnkey/core'); var Hook$1 = require('../../../providers/modal/Hook.js'); var Hook = require('../../../providers/client/Hook.js'); var WalletConnectProvider = require('../../../providers/WalletConnectProvider.js'); var utils = require('../../../utils/utils.js'); var WalletButton = require('./WalletButton.js'); var WalletConnectAppChainSelector = require('./WalletConnectAppChainSelector.js'); var Inputs = require('../../design/Inputs.js'); var Spinners = require('../../design/Spinners.js'); var constants = require('./constants.js'); var WalletButtonSkeleton = require('./WalletButtonSkeleton.js'); const sameRange = (a, b) => a.start === b.start && a.end === b.end; function ShowAllWalletsScreen(props) { const { onSelect, onSelectQRCode } = props; const { walletProviders, disconnectWalletAccount } = Hook.useTurnkey(); const { walletConnectApps, isLoadingApps } = WalletConnectProvider.useWalletConnect(); const { isMobile, pushPage, popPage } = Hook$1.useModal(); const [searchQuery, setSearchQuery] = react.useState(""); // renderRange (buffered, debounced) governs which real WalletButtons are mounted. // viewportRange (unbuffered, rAF-synchronous) governs the skeleton layer behind them. const [renderRange, setRenderRange] = react.useState({ start: 0, end: constants.INITIAL_VISIBLE_ROWS }); const [viewportRange, setViewportRange] = react.useState({ start: 0, end: constants.INITIAL_VISIBLE_ROWS }); const scrollContainerRef = react.useRef(null); const rafIdRef = react.useRef(null); // Group wallet apps by id (same wallet across different chains) const grouped = react.useMemo(() => walletConnectApps.reduce((acc, app) => { if (!acc[app.id]) acc[app.id] = []; acc[app.id].push(app); return acc; }, {}), [walletConnectApps]); // Filter by search query const walletEntries = react.useMemo(() => { const entries = Object.entries(grouped); if (!searchQuery.trim()) return entries; const query = searchQuery.toLowerCase(); return entries.filter(([_, group]) => { const walletName = group[0]?.name?.toLowerCase() ?? ""; return walletName.includes(query); }); }, [grouped, searchQuery]); // The QR-code row takes the slot at global row 0 when not searching. // renderRange/viewportRange are in *global* row indices, so wallet entry i // lives at global row (qrRowCount + i). const qrRowCount = searchQuery ? 0 : 1; const totalRows = walletEntries.length + qrRowCount; /** * Merges WalletConnectAppEntry (display info) with the actual WalletProvider * and triggers selection. Disconnects any existing session first. */ const selectApp = react.useCallback(async targetApp => { const provider = utils.findWalletConnectProvider(walletProviders, targetApp.chain); if (!provider) { // WalletConnect provider not ready - go back popPage(); return; } // Disconnect existing session to avoid confusion if (provider.connectedAddresses.length > 0) { await disconnectWalletAccount(provider); } // Merge app display info with the actual provider const mergedProvider = { ...provider, uri: targetApp.uri, info: { ...provider.info, name: targetApp.name, icon: targetApp.icon } }; await onSelect(mergedProvider); }, [walletProviders, disconnectWalletAccount, onSelect, popPage]); const handleSelectGroup = react.useCallback(group => { if (group.length === 1) { selectApp(group[0]); } else { pushPage({ key: `Select chain`, content: jsxRuntime.jsx(WalletConnectAppChainSelector.WalletConnectAppChainSelector, { apps: group, onSelect: selectApp }) }); } }, [selectApp, pushPage]); const computeRange = react.useCallback(buffer => { if (!scrollContainerRef.current) return null; const { scrollTop, clientHeight } = scrollContainerRef.current; return { start: Math.max(0, Math.floor(scrollTop / constants.ROW_STRIDE) - buffer), end: Math.min(totalRows, Math.ceil((scrollTop + clientHeight) / constants.ROW_STRIDE) + buffer) }; }, [totalRows]); const handleScroll = utils.useDebouncedCallback(() => { const next = computeRange(constants.BUFFER_SIZE); if (next) setRenderRange(prev => sameRange(prev, next) ? prev : next); }, 50); const onScroll = react.useCallback(() => { if (rafIdRef.current === null) { rafIdRef.current = requestAnimationFrame(() => { rafIdRef.current = null; const next = computeRange(0); if (next) setViewportRange(prev => sameRange(prev, next) ? prev : next); }); } handleScroll(); }, [computeRange, handleScroll]); react.useEffect(() => { return () => { if (rafIdRef.current !== null) { cancelAnimationFrame(rafIdRef.current); } }; }, []); // Reset scroll when search results change (or QR row toggles) react.useEffect(() => { const initial = { start: 0, end: Math.min(constants.INITIAL_VISIBLE_ROWS, totalRows) }; setRenderRange(initial); setViewportRange(initial); if (scrollContainerRef.current) { scrollContainerRef.current.scrollTop = 0; } }, [totalRows]); const debouncedSetSearch = utils.useDebouncedCallback(value => setSearchQuery(value), 300); // renderRange is in global row indices; subtract qrRowCount to slice walletEntries const visibleItems = react.useMemo(() => { const sliceStart = Math.max(0, renderRange.start - qrRowCount); const sliceEnd = Math.max(0, renderRange.end - qrRowCount); return walletEntries.slice(sliceStart, sliceEnd); }, [walletEntries, renderRange, qrRowCount]); const totalHeight = totalRows * constants.ROW_STRIDE; const offsetY = renderRange.start * constants.ROW_STRIDE; if (isLoadingApps) { return jsxRuntime.jsx("div", { className: "flex flex-col items-center py-5", children: jsxRuntime.jsx(Spinners.Spinner, { strokeWidth: 2, className: "w-12 h-12" }) }); } return jsxRuntime.jsxs("div", { className: clsx("flex flex-col mt-10 gap-3", isMobile ? "w-full" : "w-80"), children: [jsxRuntime.jsx(Inputs.SearchInputBox, { value: searchQuery, onChange: debouncedSetSearch, onClear: () => setSearchQuery(""), placeholder: "Search wallets..." }), jsxRuntime.jsxs("div", { ref: scrollContainerRef, onScroll: onScroll, className: clsx("min-h-42 overflow-y-auto tk-scrollbar p-0.5", isMobile ? "max-h-80" : "max-h-72"), children: [jsxRuntime.jsxs("div", { style: { height: totalHeight, position: "relative" }, children: [jsxRuntime.jsx("div", { style: { transform: `translateY(${viewportRange.start * constants.ROW_STRIDE}px)`, position: "absolute", width: "100%" }, className: "flex flex-col gap-2", "aria-hidden": "true", children: Array.from({ length: Math.max(0, viewportRange.end - viewportRange.start) }).map((_, i) => jsxRuntime.jsx(WalletButtonSkeleton.WalletButtonSkeleton, {}, `skeleton-${viewportRange.start + i}`)) }), jsxRuntime.jsxs("div", { style: { transform: `translateY(${offsetY}px)`, position: "absolute", width: "100%" }, className: "flex flex-col gap-2", children: [qrRowCount > 0 && renderRange.start === 0 && jsxRuntime.jsx(WalletButton.WalletButton, { icon: jsxRuntime.jsx("div", { className: "flex items-center justify-center size-6 rounded-full text-icon-text-light dark:text-icon-text-dark bg-icon-background-light dark:bg-icon-background-dark", children: jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faQrcode }) }), name: "Scan QR Code", chains: [{ namespace: core.Chain.Ethereum, isConnected: false }, { namespace: core.Chain.Solana, isConnected: false }], onClick: onSelectQRCode, isMobile: isMobile }, "qr-code-walletconnect"), visibleItems.map(([id, group]) => { const first = group[0]; return jsxRuntime.jsx(WalletButton.WalletButton, { icon: first?.icon ?? "", name: first?.name ?? "", chains: group.map(app => ({ namespace: app.chain, isConnected: false })), onClick: () => handleSelectGroup(group), shouldShowDisconnect: false, isMobile: isMobile }, id); })] })] }), searchQuery && walletEntries.length === 0 && jsxRuntime.jsxs("div", { className: "flex items-center justify-center w-full h-40 text-center text-icon-text-light dark:text-icon-text-dark", children: ["No wallets found matching \"", searchQuery, "\""] })] }), searchQuery && walletEntries.length > 0 && jsxRuntime.jsxs("div", { className: "text-xs text-center text-icon-text-light dark:text-icon-text-dark", children: ["Showing ", walletEntries.length, " wallet", walletEntries.length !== 1 ? "s" : ""] })] }); } exports.ShowAllWalletsScreen = ShowAllWalletsScreen; //# sourceMappingURL=ShowAllWalletsScreen.js.map