/
lizardking
/
react_project
Обзор
Документация
Войти
/
lizardking
/
react_project
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/shared/ui/Input/Input.tsx
123 строки
4 KB
LizardKing
Task 5 - styles
17 окт 2025, 15:20
17 окт 2025, 15:20
578bb65
Код
Авторство
О чём код?
import { AnimatePresence, motion, useAnimationControls } from 'framer-motion' import React, { forwardRef, memo, useEffect, type ComponentPropsWithoutRef, type Ref } from 'react' import type { FieldError } from 'react-hook-form' type Props = ComponentPropsWithoutRef<'input'> & { id: string title?: string error?: string | FieldError | null | undefined errorMessage?: string | null | undefined buttonAction?: () => void buttonTitle?: string buttonClassName?: string } function InputInner( { id, title, error, errorMessage, buttonAction, buttonTitle = 'Удалить', buttonClassName = '', className = '', ...rest }: Props, forwardedRef: Ref<HTMLInputElement>, ) { const hasError = Boolean(error) const extractedErrorText = typeof error === 'string' ? error : (error as FieldError | null | undefined)?.message ?? null const errorText = extractedErrorText ?? errorMessage ?? null const baseClass = 'bg-elevated text-fg placeholder:text-muted border border-border ' + 'rounded-[--radius-md] h-11 pl-3 focus:outline-none focus:ring-2 focus:ring-accent ' + 'aria-[invalid=true]:border-rose-400 aria-[invalid=true]:ring-rose-400 shadow-inner w-full' const inputClassName = `${baseClass} ${buttonAction ? 'pr-24' : ''} ${className}` const actionButtonClassName = ` absolute right-1.5 top-1/2 -translate-y-1/2 bg-surface border border-border rounded-[--radius-md] px-3 py-1 text-sm text-muted hover:text-fg hover:bg-elevated focus:outline-none focus:ring-2 focus:ring-accent transition ${buttonClassName} ` const errorId = `${id}-error` const animationControls = useAnimationControls() useEffect(() => { if (!hasError) return void animationControls.start({ x: [0, -6, 6, -4, 4, -2, 2, 0], transition: { duration: 0.35, ease: 'easeOut' }, }) }, [hasError, errorText, animationControls]) const errorVariants = { hidden: { opacity: 0, y: -4 }, visible: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -4 }, } return ( <div className="space-y-2"> {title && ( <label htmlFor={id} className="block text-sm font-medium text-muted"> {title} </label> )} <motion.div className="relative" animate={animationControls}> <input id={id} aria-invalid={hasError} aria-describedby={hasError ? errorId : undefined} className={inputClassName} ref={forwardedRef} {...rest} /> {buttonAction && ( <button type="button" onClick={buttonAction} aria-label={buttonTitle} className={actionButtonClassName}> {buttonTitle} </button> )} <AnimatePresence initial={false} mode="wait"> {hasError && errorText && ( <motion.div key={String(errorText)} id={errorId} role="alert" aria-live="polite" initial="hidden" animate="visible" exit="exit" variants={errorVariants} transition={{ duration: 0.18, ease: 'easeOut' }} className="absolute left-0 top-full mt-1 z-10 max-w-[calc(100%-0.5rem)] rounded-[--radius-md] bg-red-500/80 text-white text-xs px-2 py-1 shadow-lg backdrop-blur-[1px]"> {errorText} </motion.div> )} </AnimatePresence> </motion.div> <div className="h-5" aria-hidden="true" /> </div> ) } const InputComponent = memo(forwardRef<HTMLInputElement, Props>(InputInner)) InputComponent.displayName = 'Input' export const Input = InputComponent