UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

563 lines (562 loc) 16.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); module.exports = CountrySelect; var _jsxRuntime = require("react/jsx-runtime"); var _react = require("react"); var _material = require("@mui/material"); var _iconsMaterial = require("@mui/icons-material"); var _reactHookForm = require("react-hook-form"); var _reactInternationalPhone = require("react-international-phone"); var _mobile = require("../hooks/mobile"); function useFormContextSafe() { try { return (0, _reactHookForm.useFormContext)(); } catch { return null; } } function Transition({ ref, ...props }) { return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Slide, { direction: "up", ref, timeout: 200, ...props }); } function CountrySelect({ ref = void 0, value, onChange, name = "", sx = {}, showDialCode = false, label = "", bordered = false, allowClear = false, disabled = false }) { const formContext = useFormContextSafe(); const [open, setOpen] = (0, _react.useState)(false); const [searchText, setSearchText] = (0, _react.useState)(""); const inputRef = (0, _react.useRef)(null); const menuRef = (0, _react.useRef)(null); const listRef = (0, _react.useRef)(null); const [focusedIndex, setFocusedIndex] = (0, _react.useState)(-1); const itemHeightRef = (0, _react.useRef)(40); const { isMobile } = (0, _mobile.useMobile)(); const measuredRef = (0, _react.useRef)(false); (0, _react.useEffect)(() => { if (!open) return () => {}; const handleResize = () => { measuredRef.current = false; }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, [open]); const scrollToTop = () => { if (listRef.current) { listRef.current.scrollTop = 0; } }; const measureItemHeight = () => { if (!listRef.current || !open) return; const items = listRef.current.querySelectorAll(".MuiMenuItem-root"); if (items.length > 0) { const firstItem = items[0]; if (firstItem.offsetHeight > 0) { itemHeightRef.current = firstItem.offsetHeight; } } }; const controlScrollPosition = index => { if (!listRef.current) return; if (open && !measuredRef.current) { measureItemHeight(); measuredRef.current = true; } const listHeight = listRef.current.clientHeight; const targetPosition = index * itemHeightRef.current; if (index < 2) { listRef.current.scrollTop = 0; } else if (index > filteredCountries.length - 3) { listRef.current.scrollTop = listRef.current.scrollHeight - listHeight; } else { const scrollPosition = targetPosition - listHeight / 2 + itemHeightRef.current / 2; listRef.current.scrollTop = Math.max(0, scrollPosition); } }; (0, _react.useEffect)(() => { let timeout = null; if (open) { timeout = setTimeout(() => { scrollToTop(); if (!isMobile && inputRef.current) { inputRef.current.focus(); } }, 100); } else { setSearchText(""); setFocusedIndex(-1); } return () => { if (timeout) { clearTimeout(timeout); } }; }, [open, isMobile]); const filteredCountries = (0, _react.useMemo)(() => { if (!searchText) return _reactInternationalPhone.defaultCountries; return _reactInternationalPhone.defaultCountries.filter(c => { const parsed = (0, _reactInternationalPhone.parseCountry)(c); return parsed.name.toLowerCase().includes(searchText.toLowerCase()) || parsed.iso2.toLowerCase().includes(searchText.toLowerCase()) || `+${parsed.dialCode}`.includes(searchText); }); }, [searchText]); (0, _react.useEffect)(() => { scrollToTop(); setFocusedIndex(-1); }, [searchText]); (0, _react.useEffect)(() => { let timeout = null; if (focusedIndex >= 0) { timeout = setTimeout(() => { controlScrollPosition(focusedIndex); }, 10); } return () => { if (timeout) { clearTimeout(timeout); } }; }, [focusedIndex, filteredCountries.length]); const countryDetail = (0, _react.useMemo)(() => { const item = _reactInternationalPhone.defaultCountries.find(v => v[1] === value); return value && item ? (0, _reactInternationalPhone.parseCountry)(item) : { name: "" }; }, [value]); const handleCountryClick = code => { onChange(code); if (formContext && name) { formContext.setValue(name, code); } setOpen(false); }; const handleClear = e => { e.preventDefault(); e.stopPropagation(); onChange(""); if (formContext && name) { formContext.setValue(name, ""); } }; const handleSearchChange = e => { e.stopPropagation(); setSearchText(e.target.value); }; const handleKeyDown = e => { e.stopPropagation(); if (e.key === "Escape") { setOpen(false); return; } const handleNavigation = direction => { e.preventDefault(); setFocusedIndex(prev => { if (direction === "next") { if (prev === -1) return 0; return prev >= filteredCountries.length - 1 ? 0 : prev + 1; } if (prev === -1) return filteredCountries.length - 1; return prev <= 0 ? filteredCountries.length - 1 : prev - 1; }); }; if (e.key === "Tab") { handleNavigation(e.shiftKey ? "prev" : "next"); return; } if (e.key === "ArrowDown") { handleNavigation("next"); return; } if (e.key === "ArrowUp") { handleNavigation("prev"); return; } if (e.key === "Enter" && focusedIndex >= 0 && focusedIndex < filteredCountries.length) { e.preventDefault(); const country = (0, _reactInternationalPhone.parseCountry)(filteredCountries[focusedIndex]); handleCountryClick(country.iso2); } }; const countryListContent = /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { position: "sticky", top: 0, zIndex: 1, bgcolor: "background.paper", p: 1 }, onClick: e => { e.stopPropagation(); }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.TextField, { inputRef, autoFocus: !isMobile, fullWidth: true, placeholder: label || "Search country...", value: searchText, onChange: handleSearchChange, onKeyDown: handleKeyDown, onClick: e => e.stopPropagation(), size: "small", variant: "outlined", disabled }) }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { ref: listRef, sx: { flex: 1, overflowY: "auto", overflowX: "hidden", maxHeight: isMobile ? "calc(60vh - 80px)" : "calc(300px - 65px)", scrollBehavior: "smooth" }, children: filteredCountries.length > 0 ? filteredCountries.map((c, index) => { const parsed = (0, _reactInternationalPhone.parseCountry)(c); const isFocused = index === focusedIndex; return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.MenuItem, { value: parsed.iso2, selected: parsed.iso2 === value, onClick: () => handleCountryClick(parsed.iso2), sx: { display: "flex", alignItems: "center", "&.Mui-selected": { backgroundColor: "primary.lighter" }, "&:hover": { backgroundColor: "grey.100" }, ...(isFocused ? { backgroundColor: "grey.100", outline: "none" } : {}) }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_reactInternationalPhone.FlagEmoji, { iso2: parsed.iso2, style: { marginRight: "8px" } }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { children: parsed.name }), showDialCode && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { color: "text.secondary", ml: 1 }, children: `+${parsed.dialCode}` })] }, parsed.iso2); }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.MenuItem, { disabled: true, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { color: "text.secondary" }, children: "No countries found" }) }) })] }); if (!isMobile) { return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Select, { ref, open, onOpen: () => setOpen(true), onClose: () => setOpen(false), MenuProps: { style: { maxHeight: "300px", top: "10px" }, anchorOrigin: { vertical: "bottom", horizontal: "left" }, transformOrigin: { vertical: "top", horizontal: "left" }, PaperProps: { ref: menuRef, sx: { display: "flex", "& .MuiList-root": { pt: 0, display: "flex", flexDirection: "column", overflowY: "hidden", width: "100%" } } } }, sx: { width: "100%", minWidth: "60px", fieldset: { display: "none" }, '&.Mui-focused:has(div[aria-expanded="false"])': { fieldset: { display: "block" } }, ".MuiSelect-select": { padding: "8px", paddingRight: allowClear && value ? "48px !important" : "24px !important" }, svg: { right: 0 }, ".MuiMenuItem-root": { justifyContent: "flex-start" }, ...sx }, value, onChange: e => { onChange(e.target.value); if (formContext && name) { formContext.setValue(name, e.target.value); } }, disabled, renderValue: code => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { display: "flex", alignItems: "center", flexWrap: "nowrap", gap: 0.5, cursor: "pointer", width: "100%" }, children: code ? /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", sx: { alignItems: "center", gap: 0.5, justifyContent: "space-between", width: "100%" }, children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", sx: { alignItems: "center", gap: 0.5 }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_reactInternationalPhone.FlagEmoji, { iso2: code, style: { display: "flex" } }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { flex: 1 }, children: countryDetail?.name })] }), allowClear && value && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { onClick: handleClear, onMouseDown: event => { event.stopPropagation(); event.preventDefault(); }, onTouchStart: event => { event.stopPropagation(); }, sx: { display: "flex", alignItems: "center", cursor: "pointer", p: 0, borderRadius: "50%", border: "none", background: "transparent", zIndex: 2, marginRight: "-28px", "&:hover": { backgroundColor: "action.hover" } }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.Clear, { fontSize: "small" }) })] }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { color: "text.secondary", flex: 1 }, children: label || "Select country..." }) }), children: countryListContent }); } return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { ref, sx: { ...sx }, children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { onClick: () => { if (disabled) return; setOpen(true); }, sx: { display: "flex", alignItems: "center", flexWrap: "nowrap", gap: 0.5, cursor: "pointer", padding: "8px", paddingRight: "24px", position: "relative", ...(!bordered ? { border: "none" } : {}), "-webkit-tap-highlight-color": "transparent", userSelect: "none", background: "none", "&:hover, &:focus, &:active": { backgroundColor: "transparent", outline: "none" }, ...(disabled ? { backgroundColor: "grey.100", cursor: "not-allowed", color: "text.disabled", "&:hover, &:focus, &:active": { backgroundColor: "grey.100", color: "text.disabled" } } : {}), touchAction: "manipulation" }, children: [value ? /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_reactInternationalPhone.FlagEmoji, { iso2: value, style: { display: "flex" } }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { flex: 1 }, children: countryDetail?.name }), allowClear && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { component: "button", type: "button", onClick: handleClear, onMouseDown: event => { event.stopPropagation(); event.preventDefault(); }, onTouchStart: event => { event.stopPropagation(); }, sx: { display: "flex", alignItems: "center", cursor: "pointer", p: 0.5, mr: 1, borderRadius: "50%", border: "none", background: "transparent", "&:hover": { backgroundColor: "action.hover" } }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.Clear, { fontSize: "small" }) })] }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { color: "text.secondary", flex: 1 }, children: label || "Select country..." }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { position: "absolute", right: "8px", width: 0, height: 0, borderLeft: "5px solid transparent", borderRight: "5px solid transparent", borderTop: "5px solid currentColor" } })] }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Dialog, { open, onClose: () => setOpen(false), fullWidth: true, maxWidth: "xs", slotProps: { paper: { sx: { position: "absolute", bottom: 0, m: 0, p: 0, borderBottomLeftRadius: 0, borderBottomRightRadius: 0, width: "100%" } } }, slots: { transition: Transition }, children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { sx: { p: 2, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { variant: "h6", children: "Select Country" }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, { edge: "end", onClick: () => setOpen(false), sx: { "-webkit-tap-highlight-color": "transparent" }, children: "\u2715" })] }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.DialogContent, { sx: { "&.MuiDialogContent-root": { p: 0 } }, children: countryListContent })] })] }); }