@turnkey/react-wallet-kit
Version:
The easiest and most powerful way to integrate Turnkey's Embedded Wallets into your React applications.
367 lines (364 loc) • 18.2 kB
JavaScript
'use client';
import { slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.mjs';
import { jsx, jsxs } from 'react/jsx-runtime';
import { useState, useRef, useEffect, Fragment } from 'react';
import { Transition, Portal, TransitionChild } from '@headlessui/react';
import { useModal } from './Hook.mjs';
import { IconButton } from '../../components/design/Buttons.mjs';
import { faArrowLeft, faClose } from '@fortawesome/free-solid-svg-icons';
import { TurnkeyLogo } from '../../components/design/Svg.mjs';
import { ClientState } from '../../types/base.mjs';
import { useTurnkey } from '../client/Hook.mjs';
import clsx from 'clsx';
import { DeveloperError } from '../../components/design/Failure.mjs';
function ModalRoot(props) {
var _config$ui2, _config$ui$background, _config$ui3, _config$ui4;
var config = props.config; // Note: This is the config passed into the TurnkeyProvider. If we ever need to get config from the dashboard as well, grab `config` from the useTurnkey hook instead.
var _useModal = useModal(),
modalStack = _useModal.modalStack,
popPage = _useModal.popPage,
closeModal = _useModal.closeModal,
screenWidth = _useModal.screenWidth,
isMobile = _useModal.isMobile,
sheet = _useModal.sheet,
closeSheet = _useModal.closeSheet;
var _useTurnkey = useTurnkey(),
clientState = _useTurnkey.clientState;
var root = modalStack.length > 0 ? modalStack[0] : null;
var current = modalStack[modalStack.length - 1];
var _useState = useState(sheet),
_useState2 = _slicedToArray(_useState, 2),
sheetCurrent = _useState2[0],
setSheetCurrent = _useState2[1];
var hasBack = modalStack.length > 1 && !(current !== null && current !== void 0 && current.preventBack);
var _useState3 = useState(0),
_useState4 = _slicedToArray(_useState3, 2),
contentBlur = _useState4[0],
setContentBlur = _useState4[1];
var innerPadding = 16;
var containerRef = useRef(null);
var _useState5 = useState(300),
_useState6 = _slicedToArray(_useState5, 2),
height = _useState6[0],
setHeight = _useState6[1];
var _useState7 = useState(300),
_useState8 = _slicedToArray(_useState7, 2),
width = _useState8[0],
setWidth = _useState8[1];
var _useState9 = useState(true),
_useState0 = _slicedToArray(_useState9, 2),
observeResize = _useState0[0],
setObserveResize = _useState0[1];
useCssLoaded(modalStack, config); // triggers warning overlay if CSS missing
var maxMobileScreenWidth = screenWidth * 0.9; // Only take up 90% of the screen width on mobile
var getBorderRadius = function getBorderRadius() {
var _config$ui$borderRadi, _config$ui;
// Border radius can be passed in as either a number or a string, so we need to handle both cases
var value = (_config$ui$borderRadi = config === null || config === void 0 ? void 0 : (_config$ui = config.ui) === null || _config$ui === void 0 ? void 0 : _config$ui.borderRadius) !== null && _config$ui$borderRadi !== void 0 ? _config$ui$borderRadi : 16;
var stringBorderRadius = typeof value === "number" ? "".concat(value, "px") : value;
// Remove bottom border radius on mobile to avoid rounded corners at the bottom of the screen
return isMobile ? "".concat(stringBorderRadius, " ").concat(stringBorderRadius, " 0 0") : stringBorderRadius;
};
useEffect(function () {
// This useEffect sets up a ResizeObserver to monitor changes in the size of the modal content.
// This only needs to run when the content is changing size without the page being swapped out
var node = containerRef.current;
if (!node || !observeResize) return;
var cancelled = false; // To prevent setting state on unmounted component
var observer = new ResizeObserver(function (_ref) {
var _ref2 = _slicedToArray(_ref, 1),
entry = _ref2[0];
if (!entry || cancelled) return;
setContentBlur(10);
var _entry$contentRect = entry.contentRect,
newWidth = _entry$contentRect.width,
newHeight = _entry$contentRect.height;
setHeight(newHeight);
setWidth(newWidth);
setTimeout(function () {
return !cancelled && setContentBlur(0);
}, 100);
});
observer.observe(node);
return function () {
cancelled = true;
observer.disconnect();
};
}, [containerRef.current, observeResize]);
useEffect(function () {
var cancelled = false; // To prevent setting state on unmounted component
var resize = function resize() {
if (cancelled || !containerRef.current) return;
setContentBlur(10);
var rect = containerRef.current.getBoundingClientRect();
setHeight(rect.height);
setWidth(rect.width);
setTimeout(function () {
return !cancelled && setContentBlur(0);
}, 100);
};
if (current) {
setObserveResize(true);
requestAnimationFrame(resize);
var originalStyle = document.body.style.overflow;
document.body.style.overflow = "hidden";
return function () {
cancelled = true;
document.body.style.overflow = originalStyle;
};
} else {
setObserveResize(false);
setHeight(height / 1.3);
setWidth(isMobile ? width : width / 1.3);
return;
}
}, [current, isMobile]);
useEffect(function () {
if (sheet) {
setSheetCurrent(sheet);
}
}, [sheet]);
return jsx(Transition, {
appear: true,
show: !!current,
as: Fragment,
children: jsx(Portal, {
as: Fragment,
children: jsxs("div", {
className: "relative z-40",
children: [jsx(TransitionChild, {
as: Fragment,
enter: "ease-out duration-200",
enterFrom: "opacity-0",
enterTo: "opacity-100",
leave: "ease-in duration-150",
leaveFrom: "opacity-100",
leaveTo: "opacity-0",
children: jsx("div", {
className: "fixed inset-0 bg-black/40",
style: {
backdropFilter: "blur(".concat(typeof (config === null || config === void 0 ? void 0 : (_config$ui2 = config.ui) === null || _config$ui2 === void 0 ? void 0 : _config$ui2.backgroundBlur) === "number" ? "".concat(config.ui.backgroundBlur, "px") : (_config$ui$background = config === null || config === void 0 ? void 0 : (_config$ui3 = config.ui) === null || _config$ui3 === void 0 ? void 0 : _config$ui3.backgroundBlur) !== null && _config$ui$background !== void 0 ? _config$ui$background : "8px", ")")
}
})
}), jsx("div", {
className: clsx("tk-modal fixed inset-0 flex justify-center transition-colors", {
dark: config === null || config === void 0 ? void 0 : (_config$ui4 = config.ui) === null || _config$ui4 === void 0 ? void 0 : _config$ui4.darkMode
}, {
"items-end": isMobile
}, {
"items-center": !isMobile
}),
onMouseDown: function onMouseDown(e) {
if (e.target === e.currentTarget) {
e.stopPropagation();
closeModal();
}
},
children: jsx(TransitionChild, {
as: Fragment,
enter: "ease-out duration-150",
enterFrom: "opacity-0",
enterTo: "opacity-100",
leave: "ease-in duration-100",
leaveFrom: "opacity-100",
leaveTo: "opacity-0",
children: jsxs("div", {
onClick: function onClick(e) {
return e.stopPropagation();
},
/* White / Black background container */
style: {
height: height,
width: isMobile ? maxMobileScreenWidth : width,
padding: innerPadding,
borderRadius: getBorderRadius()
},
className: "relative bg-modal-background-light dark:bg-modal-background-dark text-modal-text-light dark:text-modal-text-dark flex justify-center shadow-xl transition-all overflow-x-clip",
children: [jsxs("div", {
className: "h-6.5 absolute z-30 flex items-center justify-between transition-all",
style: {
width: isMobile ? maxMobileScreenWidth : width
},
children: [jsx("div", {
className: "w-6.5 h-6.5",
children: hasBack && jsx(IconButton, {
className: "w-6.5 h-6.5",
icon: faArrowLeft,
onClick: popPage
})
}), jsx("h2", {
className: "text-base font-medium",
children: (current === null || current === void 0 ? void 0 : current.showTitle) === undefined ? current === null || current === void 0 ? void 0 : current.key : current.showTitle ? current === null || current === void 0 ? void 0 : current.key : null
}), jsx("div", {
className: "w-6.5 h-6.5",
children: jsx(IconButton, {
className: "w-6.5 h-6.5",
icon: faClose,
onClick: function onClick() {
var _root$onClose;
root === null || root === void 0 ? void 0 : (_root$onClose = root.onClose) === null || _root$onClose === void 0 ? void 0 : _root$onClose.call(root);
closeModal();
}
})
})]
}), jsx(TransitionChild, {
as: Fragment,
enter: "ease-in duration-200",
enterFrom: "opacity-0",
enterTo: "opacity-100",
leave: "ease-in duration-100",
leaveFrom: "opacity-100",
leaveTo: "opacity-0",
children: jsxs("div", {
className: clsx("flex flex-col z-10 transition-all duration-50 self-start",
// This allows the content to take the static `maxMobileScreenWidth` width on mobile
isMobile && "w-full items-center"),
style: {
filter: "blur(".concat(contentBlur, "px)")
},
ref: containerRef,
children: [clientState === ClientState.Error ? jsx(DeveloperError, {
developerTitle: "Turnkey SDK failed to initialize.",
developerMessages: ["Check your config and ensure you're connected to the internet.", "Try attaching an onError callback to the TurnkeyProvider's callbacks to see more details.", "You can also inspect the network tab to see any failed network requests."],
userMessages: ["An underlying error occurred. Try refreshing the page"]
}) : root !== null && root !== void 0 && root.contextProvider ? jsx(root.contextProvider, {
children: current === null || current === void 0 ? void 0 : current.content
}) : current === null || current === void 0 ? void 0 : current.content, (current === null || current === void 0 ? void 0 : current.showTurnkeyBranding) !== false && jsxs("a", {
href: "https://www.turnkey.com/",
target: "_blank",
className: "flex flex-row items-center justify-center gap-0.5 mt-2 mb-1 text-icon-text-light/50 dark:text-icon-text-dark/50 text-xs no-underline",
style: {
opacity: current !== null && current !== void 0 && current.content ? 1 : 0
},
children: [jsx("span", {
children: "Secured by"
}), jsx(TurnkeyLogo, {
className: "w-[60px] h-[11px]"
})]
})]
})
}), jsx("div", {
style: {
borderRadius: getBorderRadius()
},
className: "overflow-clip absolute top-0 left-0 w-full h-full",
children: jsx(Transition, {
appear: true,
show: !!sheet,
as: Fragment,
afterLeave: function afterLeave() {
return setSheetCurrent(null);
},
children: jsxs("div", {
children: [jsx(TransitionChild, {
as: Fragment,
enter: "ease-out duration-150",
enterFrom: "opacity-0",
enterTo: "opacity-100",
leave: "ease-in duration-100",
leaveFrom: "opacity-100",
leaveTo: "opacity-0",
children: jsx("div", {
style: {
borderRadius: getBorderRadius()
},
className: "absolute left-0 top-0 z-40 bg-black/20 w-full h-full transition-all",
onClick: function onClick(e) {
e.stopPropagation();
closeSheet();
}
})
}), jsx(TransitionChild, {
as: Fragment,
enter: "ease-out duration-150",
enterFrom: "translate-y-full",
enterTo: "translate-y-0",
leave: "ease-out duration-150",
leaveFrom: "translate-y-0",
leaveTo: "translate-y-full",
children: jsxs("div", {
style: {
borderRadius: getBorderRadius(),
width: isMobile ? maxMobileScreenWidth : width
},
className: "absolute transition-all duration-100 self-start bottom-0 left-0 right-0 bg-modal-background-light dark:bg-modal-background-dark border-t border-icon-background-light dark:border-icon-background-dark p-4 z-50",
children: [jsxs("div", {
className: "flex justify-between mb-3",
children: [jsx("div", {
className: "w-6.5 h-6.5"
}), jsx("h1", {
className: "text-base font-medium text-center !my-0 !mx-4",
children: sheetCurrent === null || sheetCurrent === void 0 ? void 0 : sheetCurrent.key
}), jsx(IconButton, {
className: "w-6.5 h-6.5",
icon: faClose,
onClick: closeSheet
})]
}), jsx(TransitionChild, {
as: Fragment,
enter: "ease-out duration-200",
enterFrom: "translate-y-full opacity-0",
enterTo: "translate-y-0 opacity-100",
leave: "ease-out duration-150",
leaveFrom: "translate-y-0 opacity-100",
leaveTo: "translate-y-full opacity-0",
children: sheetCurrent === null || sheetCurrent === void 0 ? void 0 : sheetCurrent.content
})]
})
})]
})
})
})]
})
})
})]
})
})
});
}
function useCssLoaded(modalStack, config) {
useEffect(function () {
var _config$ui5;
if (process.env.NODE_ENV !== "development") return; // Only check in development mode
var testElement = document.createElement("div");
testElement.className = "tk-modal"; // Use a class that should definitely be styled
testElement.style.position = "absolute";
testElement.style.visibility = "hidden";
document.body.appendChild(testElement);
// Check if the styles have been applied by inspecting a computed style
var computedStyle = window.getComputedStyle(testElement);
var hasStyles = computedStyle.lineHeight === "normal";
document.body.removeChild(testElement);
if (!hasStyles && modalStack.length === 1 && !((_config$ui5 = config.ui) !== null && _config$ui5 !== void 0 && _config$ui5.supressMissingStylesError)) renderMissingStylesOverlay();
}, [modalStack]);
}
function renderMissingStylesOverlay() {
if (typeof document === "undefined") return;
if (document.getElementById("turnkey-missing-css")) return;
var div = document.createElement("div");
div.id = "turnkey-missing-css";
Object.assign(div.style, {
position: "fixed",
inset: "0",
backgroundColor: "rgba(255,255,255,0.96)",
color: "#b91c1c",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
fontFamily: "monospace",
zIndex: "999999",
padding: "2rem",
textAlign: "center"
});
div.innerHTML = "\n <div style=\"font-size: 1.5rem; font-weight: bold; margin-bottom: 1rem;\">\n \u26A0\uFE0F Turnkey styles are missing\n </div>\n <div style=\"font-size: 1rem; max-width: 600px;\">\n You must import the Turnkey React Wallet Kit styles in your app:\n <pre style=\"background-color: #f3f4f6; color: #111827; padding: 0.5rem 1rem; margin-top: 1rem; border-radius: 6px; font-size: 0.875rem;\">\n import \"@turnkey/react-wallet-kit/styles.css\";\n </pre>\n <p style=\"margin-top: 1rem; font-size: 0.875rem; color: #6b7280;\">\n This warning only shows in development mode.\n </p>\n <button id=\"turnkey-missing-css-close\" style=\"margin-top: 2rem; padding: 0.5rem 1.5rem; font-size: 1rem; background: #b91c1c; color: white; border: none; border-radius: 6px; cursor: pointer;\">\n Close\n </button>\n </div>\n ";
document.body.appendChild(div);
var closeBtn = div.querySelector("#turnkey-missing-css-close");
if (closeBtn) {
closeBtn.addEventListener("click", function () {
div.remove();
});
}
}
export { ModalRoot };
//# sourceMappingURL=Root.mjs.map