@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
JavaScript
'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