255 lines
7.2 KiB
TypeScript
255 lines
7.2 KiB
TypeScript
import {
|
|
Box,
|
|
InputAdornment,
|
|
ListItem,
|
|
ListItemIcon,
|
|
ListItemText,
|
|
Menu,
|
|
MenuItem,
|
|
TextField,
|
|
Typography,
|
|
} from '@mui/material';
|
|
import { useMemo, useRef, useState, type RefObject } from 'react';
|
|
import { ArrowDown2 } from 'iconsax-react';
|
|
import ReactCountryFlag from 'react-country-flag';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { countries, type Country } from '@/data/countries';
|
|
|
|
interface CountryCodeSelectorProps {
|
|
show: boolean;
|
|
value: string;
|
|
onChange: (newValue: string) => void;
|
|
menuAnchor: HTMLElement | null;
|
|
onCloseFocusRef: RefObject<HTMLInputElement | null>;
|
|
}
|
|
|
|
/**
|
|
* An animated country code adornment that fades and slides into view.
|
|
* Its visibility is controlled by the `show` prop.
|
|
*/
|
|
export function CountryCodeSelector({
|
|
show,
|
|
value,
|
|
onChange,
|
|
menuAnchor,
|
|
onCloseFocusRef,
|
|
}: CountryCodeSelectorProps) {
|
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const open = Boolean(anchorEl);
|
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
const menuWidth = menuAnchor ? menuAnchor.clientWidth : 'auto';
|
|
const { t, i18n } = useTranslation();
|
|
|
|
const selectedCountry =
|
|
countries.find((c) => c.phone === value) || countries[0];
|
|
|
|
const handleClick = () => {
|
|
setAnchorEl(menuAnchor);
|
|
};
|
|
|
|
const handleClose = () => {
|
|
setTimeout(() => {
|
|
setAnchorEl(null);
|
|
}, 0);
|
|
setTimeout(() => {
|
|
onCloseFocusRef.current?.focus();
|
|
}, 100);
|
|
setSearchTerm(''); // Reset search on close
|
|
};
|
|
|
|
const handleSelect = (country: Country) => {
|
|
onChange(country.phone);
|
|
handleClose();
|
|
};
|
|
|
|
const handleMenuEntered = () => {
|
|
// Focus the input field after the menu has finished opening
|
|
searchInputRef.current?.focus();
|
|
};
|
|
|
|
const filteredCountries = useMemo(
|
|
() =>
|
|
countries.filter(
|
|
(country) =>
|
|
t(country.label).toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
country.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
country.phone.includes(searchTerm),
|
|
),
|
|
[searchTerm, t],
|
|
);
|
|
|
|
return (
|
|
<InputAdornment
|
|
position={i18n.dir() === 'rtl' ? 'start' : 'end'}
|
|
sx={{
|
|
mx: 0,
|
|
}}
|
|
>
|
|
<Box
|
|
onClick={handleClick}
|
|
sx={{
|
|
// Animate width and opacity based on the 'show' prop
|
|
width: show ? 'auto' : 0,
|
|
opacity: show ? 1 : 0,
|
|
transition: (theme) =>
|
|
theme.transitions.create(['width', 'opacity'], {
|
|
duration: theme.transitions.duration.standard,
|
|
}),
|
|
|
|
// Prevent content from wrapping or spilling out during animation
|
|
overflow: 'hidden',
|
|
whiteSpace: 'nowrap',
|
|
|
|
// layout styles
|
|
height: '100%',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 0.25,
|
|
pl: show ? 0.25 : 0,
|
|
|
|
'&:hover': {
|
|
cursor: 'pointer',
|
|
},
|
|
}}
|
|
>
|
|
{/* This inner Box prevents the content from being squeezed during the transition */}
|
|
<ArrowDown2 size="24" variant="Bold" />
|
|
|
|
<Typography
|
|
variant="body1"
|
|
color="text.primary"
|
|
component="div"
|
|
sx={{ direction: 'rtl' }} // TODO: need to fixed for both en and fa
|
|
>
|
|
{value}
|
|
</Typography>
|
|
|
|
<ReactCountryFlag
|
|
countryCode={selectedCountry.code}
|
|
svg
|
|
style={{
|
|
height: '1.5rem',
|
|
width: '1.5rem',
|
|
// TODO: Check alignment for better styling definition
|
|
marginTop: '-2px',
|
|
marginRight: '4px',
|
|
}}
|
|
/>
|
|
|
|
<Menu
|
|
anchorEl={anchorEl}
|
|
open={open}
|
|
onClose={handleClose}
|
|
slotProps={{
|
|
list: {
|
|
sx: {
|
|
// Set the width to match the anchor element's width
|
|
width: menuWidth,
|
|
height: '18.75rem',
|
|
},
|
|
},
|
|
transition: {
|
|
onEntered: handleMenuEntered,
|
|
},
|
|
}}
|
|
anchorOrigin={{
|
|
vertical: 'bottom',
|
|
horizontal: 'left',
|
|
}}
|
|
transformOrigin={{
|
|
vertical: 'top',
|
|
horizontal: 'left',
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
position: 'sticky',
|
|
top: 0,
|
|
zIndex: 1,
|
|
p: 1,
|
|
backgroundColor: 'background.paper',
|
|
}}
|
|
>
|
|
<TextField
|
|
inputRef={searchInputRef}
|
|
size="small"
|
|
fullWidth
|
|
label={t('labels.search')}
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
/>
|
|
</Box>
|
|
|
|
{/* Can improve preformance with using virtual scrolling */}
|
|
<Box sx={{ width: '100%', maxHeight: '14.75rem', overflow: 'auto' }}>
|
|
{filteredCountries.length === 0 ? (
|
|
<MenuItem disabled>
|
|
<ListItem>
|
|
<ListItemText>{t('messages.noResualtFound')}</ListItemText>
|
|
</ListItem>
|
|
</MenuItem>
|
|
) : (
|
|
filteredCountries.map((country) => (
|
|
<MenuItem
|
|
key={country.code}
|
|
selected={country.phone === value}
|
|
onClick={() => handleSelect(country)}
|
|
>
|
|
<ListItemIcon>
|
|
<ReactCountryFlag
|
|
countryCode={country.code}
|
|
svg
|
|
style={{
|
|
height: '1.125rem',
|
|
width: '1.125rem',
|
|
}}
|
|
/>
|
|
</ListItemIcon>
|
|
<ListItemText primary={t(country.label)} />
|
|
<Typography color="text.secondary">
|
|
{country.phone}
|
|
</Typography>
|
|
</MenuItem>
|
|
))
|
|
)}
|
|
</Box>
|
|
|
|
{/* virtual scrolling */}
|
|
{/* <Virtuoso
|
|
style={{ height: '14.75rem' }} // Adjust height to account for the search bar
|
|
data={filteredCountries}
|
|
components={{
|
|
EmptyPlaceholder: () => (
|
|
<MenuItem disabled>
|
|
<ListItemText>{t('messages.noResultFound')}</ListItemText>
|
|
</MenuItem>
|
|
),
|
|
}}
|
|
initialTopMostItemIndex={countries.indexOf(selectedCountry)}
|
|
itemContent={(_, country) => (
|
|
<MenuItem
|
|
key={country.code}
|
|
selected={country.phone === value}
|
|
onClick={() => handleSelect(country)}
|
|
>
|
|
<ListItemIcon>
|
|
<ReactCountryFlag
|
|
countryCode={country.code}
|
|
svg
|
|
style={{ fontSize: '1.25em', lineHeight: '1.25em' }}
|
|
/>
|
|
</ListItemIcon>
|
|
<ListItemText primary={country.label} />
|
|
<Typography variant="body2" color="text.secondary">
|
|
{country.phone}
|
|
</Typography>
|
|
</MenuItem>
|
|
)}
|
|
/> */}
|
|
</Menu>
|
|
</Box>
|
|
</InputAdornment>
|
|
);
|
|
}
|