2025-06-03 17:27:14 +00:00
|
|
|
|
import React, { useState, useRef } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
Box,
|
|
|
|
|
InputBase,
|
|
|
|
|
IconButton,
|
|
|
|
|
Menu,
|
|
|
|
|
MenuItem,
|
|
|
|
|
Paper,
|
|
|
|
|
Popper,
|
|
|
|
|
Grow,
|
|
|
|
|
ClickAwayListener,
|
|
|
|
|
MenuList,
|
2025-06-03 18:07:37 +00:00
|
|
|
|
useTheme,
|
|
|
|
|
FormHelperText
|
2025-06-03 17:27:14 +00:00
|
|
|
|
} from '@mui/material';
|
|
|
|
|
import ArrowDownIcon from "@/assets/icons/ArrowDownIcon";
|
|
|
|
|
|
|
|
|
|
interface AgeInputWithSelectProps {
|
|
|
|
|
value: string;
|
|
|
|
|
onChange: (value: string) => void;
|
2025-06-03 18:50:07 +00:00
|
|
|
|
onErrorChange?: (isError: boolean) => void;
|
2025-06-03 17:27:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-03 18:50:07 +00:00
|
|
|
|
const AgeInputWithSelect = ({ value, onChange, onErrorChange }: AgeInputWithSelectProps) => {
|
2025-06-03 17:27:14 +00:00
|
|
|
|
const theme = useTheme();
|
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
|
const anchorRef = useRef<HTMLDivElement>(null);
|
2025-06-03 18:07:37 +00:00
|
|
|
|
const [errorType, setErrorType] = useState<'format' | 'range' | false>(false);
|
|
|
|
|
|
|
|
|
|
// Валидация: только число или диапазон число-число, и диапазон 0-150
|
|
|
|
|
const validate = (val: string) => {
|
|
|
|
|
if (!val) return false;
|
|
|
|
|
// Число (только положительное)
|
2025-06-03 18:50:07 +00:00
|
|
|
|
if (/^-?\d+$/.test(val)) {
|
2025-06-03 18:07:37 +00:00
|
|
|
|
const num = Number(val);
|
|
|
|
|
if (num < 0 || num > 150) return 'range';
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Диапазон (только положительные числа)
|
2025-06-03 18:50:07 +00:00
|
|
|
|
const rangeMatch = val.match(/^(-?\d+)-(-?\d+)$/);
|
2025-06-03 18:07:37 +00:00
|
|
|
|
if (rangeMatch) {
|
|
|
|
|
const left = Number(rangeMatch[1]);
|
|
|
|
|
const right = Number(rangeMatch[2]);
|
|
|
|
|
if (left < 0 || left > 150 || right < 0 || right > 150 || left > right) return 'range';
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return 'format';
|
|
|
|
|
};
|
2025-06-03 17:27:14 +00:00
|
|
|
|
|
|
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
2025-06-03 18:07:37 +00:00
|
|
|
|
const filtered = e.target.value.replace(/[^\d-]/g, '');
|
|
|
|
|
onChange(filtered);
|
2025-06-03 18:50:07 +00:00
|
|
|
|
const err = validate(filtered);
|
|
|
|
|
setErrorType(err);
|
|
|
|
|
if (onErrorChange) onErrorChange(!!err);
|
2025-06-03 18:07:37 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleInputBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
|
|
|
const trimmed = e.target.value.replace(/\s+/g, '');
|
|
|
|
|
onChange(trimmed);
|
2025-06-03 18:50:07 +00:00
|
|
|
|
const err = validate(trimmed);
|
|
|
|
|
setErrorType(err);
|
|
|
|
|
if (onErrorChange) onErrorChange(!!err);
|
2025-06-03 17:27:14 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSelectItemClick = (selectedValue: string) => {
|
|
|
|
|
onChange(selectedValue);
|
2025-06-03 18:07:37 +00:00
|
|
|
|
setErrorType(false);
|
2025-06-03 17:27:14 +00:00
|
|
|
|
setOpen(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleToggle = () => {
|
|
|
|
|
setOpen((prevOpen) => !prevOpen);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleClose = (event: Event) => {
|
|
|
|
|
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setOpen(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Box
|
|
|
|
|
ref={anchorRef}
|
|
|
|
|
sx={{
|
|
|
|
|
position: 'relative',
|
|
|
|
|
mt: "17px",
|
|
|
|
|
height: "48px",
|
|
|
|
|
maxWidth: "420px",
|
|
|
|
|
width: "100%",
|
|
|
|
|
borderRadius: "8px",
|
|
|
|
|
border: "1px solid #9A9AAF",
|
|
|
|
|
'&:hover': {
|
|
|
|
|
borderColor: '#B0B0B0',
|
|
|
|
|
},
|
|
|
|
|
'&:focus-within': {
|
|
|
|
|
borderColor: '#7E2AEA',
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<InputBase
|
|
|
|
|
value={value}
|
|
|
|
|
onChange={handleInputChange}
|
2025-06-03 18:07:37 +00:00
|
|
|
|
onBlur={handleInputBlur}
|
2025-06-03 17:27:14 +00:00
|
|
|
|
fullWidth
|
|
|
|
|
placeholder="Введите возраст"
|
2025-06-03 18:07:37 +00:00
|
|
|
|
inputProps={{ inputMode: 'numeric', pattern: '[0-9-]*' }}
|
2025-06-03 17:27:14 +00:00
|
|
|
|
sx={{
|
|
|
|
|
height: "100%",
|
|
|
|
|
padding: "10px 20px",
|
|
|
|
|
'& input': {
|
|
|
|
|
height: "100%",
|
|
|
|
|
width: "100%",
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2025-06-03 18:07:37 +00:00
|
|
|
|
{errorType === 'format' && (
|
|
|
|
|
<FormHelperText error sx={{ position: 'absolute', left: 0, top: '100%', mt: '2px', ml: '10px' }}>
|
|
|
|
|
можно только число или диапазон
|
|
|
|
|
</FormHelperText>
|
|
|
|
|
)}
|
|
|
|
|
{errorType === 'range' && (
|
|
|
|
|
<FormHelperText error sx={{ position: 'absolute', left: 0, top: '100%', mt: '2px', ml: '10px' }}>
|
|
|
|
|
таких возрастов нет
|
|
|
|
|
</FormHelperText>
|
|
|
|
|
)}
|
2025-06-03 17:27:14 +00:00
|
|
|
|
<IconButton
|
|
|
|
|
onClick={handleToggle}
|
|
|
|
|
sx={{
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
right: 0,
|
|
|
|
|
top: '50%',
|
|
|
|
|
transform: `translateY(-50%) rotate(${open ? 180 : 0}deg)`,
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
color: theme.palette.brightPurple.main,
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
transition: 'transform 0.2s',
|
|
|
|
|
padding: '8px'
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<ArrowDownIcon style={{ width: "18px", height: "18px" }} />
|
|
|
|
|
</IconButton>
|
|
|
|
|
|
|
|
|
|
<Popper
|
|
|
|
|
open={open}
|
|
|
|
|
anchorEl={anchorRef.current}
|
|
|
|
|
role={undefined}
|
|
|
|
|
placement="bottom-end"
|
|
|
|
|
transition
|
|
|
|
|
disablePortal
|
|
|
|
|
sx={{ zIndex: 1300 }}
|
|
|
|
|
>
|
|
|
|
|
{({ TransitionProps, placement }) => (
|
|
|
|
|
<Grow
|
|
|
|
|
{...TransitionProps}
|
|
|
|
|
style={{
|
|
|
|
|
transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Paper elevation={8}>
|
|
|
|
|
<ClickAwayListener onClickAway={handleClose}>
|
|
|
|
|
<MenuList autoFocusItem={open} id="menu-list-grow">
|
|
|
|
|
<MenuItem onClick={() => handleSelectItemClick('')}>Выберите возраст</MenuItem>
|
|
|
|
|
<MenuItem onClick={() => handleSelectItemClick('18-24')}>18-24</MenuItem>
|
|
|
|
|
<MenuItem onClick={() => handleSelectItemClick('25-34')}>25-34</MenuItem>
|
|
|
|
|
<MenuItem onClick={() => handleSelectItemClick('35-44')}>35-44</MenuItem>
|
|
|
|
|
<MenuItem onClick={() => handleSelectItemClick('45-54')}>45-54</MenuItem>
|
|
|
|
|
<MenuItem onClick={() => handleSelectItemClick('55+')}>55+</MenuItem>
|
|
|
|
|
</MenuList>
|
|
|
|
|
</ClickAwayListener>
|
|
|
|
|
</Paper>
|
|
|
|
|
</Grow>
|
|
|
|
|
)}
|
|
|
|
|
</Popper>
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default AgeInputWithSelect;
|