@blocklet/payment-react
Version:
Reusable react components for payment kit v2
563 lines (562 loc) • 16.7 kB
JavaScript
;
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
})]
})]
});
}