fix: base component dirs, remove duplicate component instance
This commit is contained in:
225
src/components/CountryCodeSelector.tsx
Normal file
225
src/components/CountryCodeSelector.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
import {
|
||||
Box,
|
||||
InputAdornment,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
TextField,
|
||||
} 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';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
import { LTRTypography } from '@/components/common/LTRTypography';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
|
||||
interface CountryCodeSelectorProps {
|
||||
show: boolean;
|
||||
value: CountryCode;
|
||||
onChange: (newValue: CountryCode) => void;
|
||||
menuAnchor: HTMLElement | null;
|
||||
onCloseFocusRef: RefObject<HTMLInputElement | null>;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function CountryCodeSelector({
|
||||
show,
|
||||
value,
|
||||
onChange,
|
||||
menuAnchor,
|
||||
onCloseFocusRef,
|
||||
disabled = false,
|
||||
}: 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(['country', 'common']);
|
||||
|
||||
const selectedCountry =
|
||||
countries.find((c) => c.phone === value) || countries[0];
|
||||
|
||||
const handleClick = () => {
|
||||
if (disabled) return;
|
||||
setAnchorEl(menuAnchor);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setTimeout(() => setAnchorEl(null), 0);
|
||||
setTimeout(() => {
|
||||
onCloseFocusRef.current?.focus();
|
||||
}, 100);
|
||||
setSearchTerm('');
|
||||
};
|
||||
|
||||
const handleSelect = (country: Country) => {
|
||||
onChange(country.phone);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleMenuEntered = () => {
|
||||
searchInputRef.current?.focus();
|
||||
};
|
||||
|
||||
const filteredCountries = useMemo(
|
||||
() =>
|
||||
countries.filter(
|
||||
(country) =>
|
||||
t(country.label, { ns: 'country' })
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()) ||
|
||||
country.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
country.phone.includes(searchTerm),
|
||||
),
|
||||
[searchTerm, t],
|
||||
);
|
||||
|
||||
const initialIndex = Math.max(
|
||||
0,
|
||||
filteredCountries.findIndex((c) => c.phone === value),
|
||||
);
|
||||
|
||||
return (
|
||||
<InputAdornment
|
||||
position={'end'}
|
||||
sx={{
|
||||
mx: 0,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
onClick={handleClick}
|
||||
sx={{
|
||||
width: show ? 'auto' : 0,
|
||||
opacity: show ? 1 : 0,
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(['width', 'opacity'], {
|
||||
duration: theme.transitions.duration.standard,
|
||||
}),
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: i18n.dir() === 'ltr' ? 'row-reverse' : 'row',
|
||||
gap: 0.25,
|
||||
pl: show ? 0.25 : 0,
|
||||
|
||||
'&:hover': {
|
||||
cursor: disabled ? 'default' : 'pointer',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* This inner Box prevents the content from being squeezed during the transition */}
|
||||
<Icon
|
||||
Component={ArrowDown2}
|
||||
color={disabled ? 'text.disabled' : undefined}
|
||||
size="medium"
|
||||
variant="Bold"
|
||||
/>
|
||||
|
||||
<LTRTypography
|
||||
variant="body1"
|
||||
color={disabled ? 'text.disabled' : 'text.primary'}
|
||||
>
|
||||
{value}
|
||||
</LTRTypography>
|
||||
|
||||
<ReactCountryFlag
|
||||
countryCode={selectedCountry.code}
|
||||
svg
|
||||
style={{
|
||||
height: '1.5rem',
|
||||
width: '1.5rem',
|
||||
marginTop: '-2px',
|
||||
marginRight: '4px',
|
||||
opacity: disabled ? 0.4 : 1,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
width: menuWidth,
|
||||
maxHeight: '20rem',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
},
|
||||
transition: { onEntered: handleMenuEntered },
|
||||
}}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
||||
>
|
||||
<Box
|
||||
tabIndex={-1}
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
p: 1,
|
||||
backgroundColor: 'background.paper',
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
inputRef={searchInputRef}
|
||||
size="small"
|
||||
fullWidth
|
||||
label={t('labels.search', { ns: 'common' })}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box component="div" sx={{ width: '100%', height: '25rem' }}>
|
||||
{filteredCountries.length === 0 ? (
|
||||
<MenuItem disabled>
|
||||
<ListItem>
|
||||
<ListItemText>
|
||||
{t('messages.noResualtFound', { ns: 'common' })}
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<Virtuoso
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
data={filteredCountries}
|
||||
initialTopMostItemIndex={initialIndex}
|
||||
itemContent={(_, 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, { ns: 'country' })}
|
||||
/>
|
||||
<LTRTypography color="text.secondary">
|
||||
{country.phone}
|
||||
</LTRTypography>
|
||||
</MenuItem>
|
||||
)}
|
||||
computeItemKey={(_, c) => c.code}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Menu>
|
||||
</Box>
|
||||
</InputAdornment>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,25 @@
|
||||
import LogoSvg from '@/assets/logo.svg';
|
||||
import LogoIconSvg from '@/assets/logo_icon.svg';
|
||||
import { Box, type SxProps } from '@mui/material';
|
||||
|
||||
function Logo() {
|
||||
return <img src={LogoSvg} style={{ width: '150px', height: 'auto' }} />;
|
||||
interface LogoProps {
|
||||
boxSx?: SxProps;
|
||||
isIcon?: boolean;
|
||||
}
|
||||
|
||||
function Logo({ boxSx, isIcon = false }: LogoProps) {
|
||||
// TODO: handle transition
|
||||
return (
|
||||
<Box
|
||||
component="img"
|
||||
src={isIcon ? LogoIconSvg : LogoSvg}
|
||||
sx={{
|
||||
width: isIcon ? '30px' : '120px',
|
||||
height: 'auto',
|
||||
...boxSx,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Logo;
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Alert, Snackbar, type AlertColor } from '@mui/material';
|
||||
import { type PropsWithChildren } from 'react';
|
||||
|
||||
export interface ToastProps extends PropsWithChildren {
|
||||
color: AlertColor | undefined;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const Toast = ({ color, open, onClose, children }: ToastProps) => {
|
||||
return (
|
||||
<Snackbar
|
||||
sx={{ width: (t) => `calc(100% - ${t.spacing(6)})`, maxWidth: '396px' }}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Alert
|
||||
onClose={onClose}
|
||||
severity={color}
|
||||
variant="filled"
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{children}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
);
|
||||
};
|
||||
@@ -85,9 +85,7 @@ export default function ProductsMenu({
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<Box sx={{ width: 69 }}>
|
||||
<product.LogoComponent />
|
||||
</Box>
|
||||
<Box sx={{ width: 69 }}>{product.LogoComponent}</Box>
|
||||
</Stack>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user