Spaces:
Sleeping
Sleeping
| import React, { forwardRef } from 'react'; | |
| import { classNames } from '@/utils/helpers'; | |
| interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { | |
| label?: string; | |
| error?: string; | |
| hint?: string; | |
| leftIcon?: React.ReactNode; | |
| rightIcon?: React.ReactNode; | |
| variant?: 'default' | 'filled'; | |
| } | |
| export const Input = forwardRef<HTMLInputElement, InputProps>( | |
| ( | |
| { | |
| label, | |
| error, | |
| hint, | |
| leftIcon, | |
| rightIcon, | |
| variant = 'default', | |
| className, | |
| id, | |
| ...props | |
| }, | |
| ref | |
| ) => { | |
| const inputId = id || `input-${Math.random().toString(36).slice(2, 9)}`; | |
| const variantStyles = { | |
| default: 'bg-dark-900 border-dark-600', | |
| filled: 'bg-dark-700 border-transparent', | |
| }; | |
| return ( | |
| <div className="w-full"> | |
| {label && ( | |
| <label | |
| htmlFor={inputId} | |
| className="block text-sm font-medium text-dark-300 mb-1.5" | |
| > | |
| {label} | |
| </label> | |
| )} | |
| <div className="relative"> | |
| {leftIcon && ( | |
| <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-dark-500"> | |
| {leftIcon} | |
| </div> | |
| )} | |
| <input | |
| ref={ref} | |
| id={inputId} | |
| className={classNames( | |
| 'w-full px-3 py-2 border rounded-lg text-dark-100 placeholder-dark-500', | |
| 'focus:outline-none focus:border-accent-primary focus:ring-1 focus:ring-accent-primary/50', | |
| 'transition-colors duration-200', | |
| variantStyles[variant], | |
| leftIcon ? 'pl-10' : '', | |
| rightIcon ? 'pr-10' : '', | |
| error ? 'border-red-500 focus:border-red-500 focus:ring-red-500/50' : '', | |
| className | |
| )} | |
| {...props} | |
| /> | |
| {rightIcon && ( | |
| <div className="absolute inset-y-0 right-0 pr-3 flex items-center text-dark-500"> | |
| {rightIcon} | |
| </div> | |
| )} | |
| </div> | |
| {(error || hint) && ( | |
| <p | |
| className={classNames( | |
| 'mt-1.5 text-sm', | |
| error ? 'text-red-400' : 'text-dark-500' | |
| )} | |
| > | |
| {error || hint} | |
| </p> | |
| )} | |
| </div> | |
| ); | |
| } | |
| ); | |
| Input.displayName = 'Input'; | |
| interface TextareaProps | |
| extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { | |
| label?: string; | |
| error?: string; | |
| hint?: string; | |
| } | |
| export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>( | |
| ({ label, error, hint, className, id, ...props }, ref) => { | |
| const inputId = id || `textarea-${Math.random().toString(36).slice(2, 9)}`; | |
| return ( | |
| <div className="w-full"> | |
| {label && ( | |
| <label | |
| htmlFor={inputId} | |
| className="block text-sm font-medium text-dark-300 mb-1.5" | |
| > | |
| {label} | |
| </label> | |
| )} | |
| <textarea | |
| ref={ref} | |
| id={inputId} | |
| className={classNames( | |
| 'w-full px-3 py-2 bg-dark-900 border border-dark-600 rounded-lg', | |
| 'text-dark-100 placeholder-dark-500 resize-none', | |
| 'focus:outline-none focus:border-accent-primary focus:ring-1 focus:ring-accent-primary/50', | |
| 'transition-colors duration-200', | |
| error && 'border-red-500 focus:border-red-500 focus:ring-red-500/50', | |
| className | |
| )} | |
| {...props} | |
| /> | |
| {(error || hint) && ( | |
| <p | |
| className={classNames( | |
| 'mt-1.5 text-sm', | |
| error ? 'text-red-400' : 'text-dark-500' | |
| )} | |
| > | |
| {error || hint} | |
| </p> | |
| )} | |
| </div> | |
| ); | |
| } | |
| ); | |
| Textarea.displayName = 'Textarea'; | |
| export default Input; | |