/
viktorphp
/
front-socket
Обзор
Документация
Войти
/
viktorphp
/
front-socket
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/features/auth/ui/formContainer/client/FormContainer.tsx
248 строк
8 KB
Иванов Виктор Евгеньевич
[feature-diary]: поправить баги и верстку, добавить README.md
12 апр 2026, 20:01
12 апр 2026, 20:01
0d63759
Код
Авторство
О чём код?
'use client'; import { Children, cloneElement, FC, isValidElement, PropsWithChildren, ReactElement, } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Box, Button, FormControl, FormHelperText, Stack, TextField, TextFieldProps, Typography, } from '@mui/material'; import z from 'zod'; import { useAuthModeSignal } from '@features/auth'; import { EValidationType, validationSchema } from '@shared/types'; import { FormContainerProps } from './FormContainer.types'; const isValidationType = (value: any): value is EValidationType => { return Object.values(EValidationType).includes(value); }; export const FormContainer: FC<PropsWithChildren<FormContainerProps>> = ({ title, children, borderRadius, padding, width, height, backgroundColor, spacing, borderColor, lastChildSpacing, handleFormSubmit, action, }) => { const { registrationModeSignal, toggleMode } = useAuthModeSignal(); const childrenArray = Children.toArray(children); // Получаем массив из children // Создаем новую схему валидации только для переданных значений из name const filteredValidationSchema = childrenArray.reduce( (acc, child) => { if ( isValidElement(child) && child.props.name && isValidationType(child.props.name) ) { acc[child.props.name] = validationSchema.shape[child.props.name as EValidationType]; } return acc; }, {} as Record<string, any> ); const customValidationSchema = z.object(filteredValidationSchema); // Создаем объект defaultValues const defaultValues = childrenArray.reduce( (acc, child) => { // Проверяем, что child является React элементом, имеет props и name из EValidationType if ( isValidElement(child) && child.props.name && isValidationType(child.props.name) ) { acc[child.props.name as EValidationType] = ''; } return acc; }, {} as Record<EValidationType, string> ); const getRenderChild = (name: EValidationType | undefined) => { if (!name || (name && !Object.values(EValidationType).includes(name))) return null; const child = childrenArray.find( (child) => isValidElement(child) && child.props.name === name ); return isValidElement(child) ? child : null; }; const isTextField = ( element: React.ReactElement ): element is ReactElement<TextFieldProps> => { return element.type === TextField; }; const { control, handleSubmit, formState: { errors }, reset, } = useForm({ defaultValues: defaultValues, resolver: zodResolver(customValidationSchema), mode: 'all', // Определяет, когда будет происходить валидация формы: onChange, onBlur, onSubmit, onTouched, all // reValidateMode: 'onBlur', Определяет, когда будет происходить повторная валидация поля после того, как в нем уже была обнаружена ошибка }); const onSubmit: SubmitHandler<Record<EValidationType, string>> = (data) => { handleFormSubmit?.(data); }; return ( <Stack direction={'column'} spacing={4} width={width} height={height} sx={{ ['@media (max-width: 530px)']: { width: width ?? '320px', minWidth: width ?? '320px', }, ['@media (min-width: 531px)']: { width: width ?? '60vw', minWidth: width ?? '60vw', }, ['@media (min-width: 665px)']: { width: width ?? '400px', minWidth: width ?? '400px', }, height: height ?? 'fit-content', backgroundColor: `${backgroundColor}`, borderRadius: `${borderRadius}`, padding: `${padding}`, borderColor: `${borderColor}`, borderWidth: '1px', borderStyle: 'solid', boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.1)', // 1 - смещение по горизонтали, 2 - смещение по вертикали, 3 - размытие, 4 - цвет }} alignItems={'center'} padding={padding} borderRadius={borderRadius} > <Typography variant={'h3'} fontWeight={'bold'} style={{ letterSpacing: '0.5em', color: '#757575', WebkitTextStroke: '1px #757575', }} > {title} </Typography> <Stack direction={'column'}> <form onSubmit={handleFormSubmit ? handleSubmit(onSubmit) : undefined} action={action} > {childrenArray.map((child, index) => { const renderChild = isValidElement(child) && getRenderChild(child.props.name); return ( <Box key={index} mt={ index === childrenArray.length - 1 ? (lastChildSpacing ?? 1) * 2 : index === 0 ? 0 : spacing } > {isValidElement(child) && renderChild && child.props.name && child.props.type !== 'submit' && ( <Controller name={child.props.name} control={control} render={({ field }) => !isTextField(renderChild) ? ( <FormControl sx={{ m: 1, minWidth: 120, width: '22rem' }} error={ !!errors[ renderChild.props.name as EValidationType ] } > {cloneElement(renderChild, { ...field })} <FormHelperText> { errors[ renderChild.props.name as EValidationType ]?.message } </FormHelperText> </FormControl> ) : ( cloneElement(renderChild, { ...field, error: !!errors[ renderChild.props.name as EValidationType ], helperText: errors[renderChild.props.name as EValidationType] ?.message, sx: { width: '22rem', }, }) ) } /> )} {isValidElement(child) && child.props.type === 'submit' && cloneElement(child)} </Box> ); })} <Stack direction={'row'} justifyContent={'center'} marginTop={1}> <Button onClick={() => { toggleMode(); reset(); }} size={'large'} sx={{ fontSize: '1.1rem' }} > {registrationModeSignal.value ? 'Войти' : 'Регистрация'} </Button> </Stack> </form> </Stack> </Stack> ); };