Sistema completo para monitoreo y gestion de flotas de vehiculos con: - Backend FastAPI con PostgreSQL/TimescaleDB - Frontend React con TypeScript y TailwindCSS - App movil React Native con Expo - Soporte para dispositivos GPS, Meshtastic y celulares - Video streaming en vivo con MediaMTX - Geocercas, alertas, viajes y reportes - Autenticacion JWT y WebSockets en tiempo real Documentacion completa y guias de usuario incluidas.
135 lines
3.6 KiB
TypeScript
135 lines
3.6 KiB
TypeScript
import { forwardRef, InputHTMLAttributes, ReactNode } from 'react'
|
|
import clsx from 'clsx'
|
|
|
|
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
label?: string
|
|
error?: string
|
|
helperText?: string
|
|
leftIcon?: ReactNode
|
|
rightIcon?: ReactNode
|
|
fullWidth?: boolean
|
|
}
|
|
|
|
const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
(
|
|
{
|
|
className,
|
|
label,
|
|
error,
|
|
helperText,
|
|
leftIcon,
|
|
rightIcon,
|
|
fullWidth = true,
|
|
type = 'text',
|
|
id,
|
|
...props
|
|
},
|
|
ref
|
|
) => {
|
|
const inputId = id || label?.toLowerCase().replace(/\s+/g, '-')
|
|
|
|
return (
|
|
<div className={clsx(fullWidth && 'w-full')}>
|
|
{label && (
|
|
<label
|
|
htmlFor={inputId}
|
|
className="block text-sm font-medium text-slate-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-slate-400">
|
|
{leftIcon}
|
|
</div>
|
|
)}
|
|
<input
|
|
ref={ref}
|
|
type={type}
|
|
id={inputId}
|
|
className={clsx(
|
|
'w-full px-4 py-2.5 bg-background-800 border rounded-lg',
|
|
'text-white placeholder-slate-500',
|
|
'focus:outline-none focus:ring-2 focus:border-transparent',
|
|
'transition-all duration-200',
|
|
error
|
|
? 'border-error-500 focus:ring-error-500'
|
|
: 'border-slate-700 focus:ring-accent-500',
|
|
leftIcon && 'pl-10',
|
|
rightIcon && 'pr-10',
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
{rightIcon && (
|
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center text-slate-400">
|
|
{rightIcon}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{error && <p className="mt-1.5 text-sm text-error-500">{error}</p>}
|
|
{helperText && !error && (
|
|
<p className="mt-1.5 text-sm text-slate-500">{helperText}</p>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
)
|
|
|
|
Input.displayName = 'Input'
|
|
|
|
export default Input
|
|
|
|
// Textarea component
|
|
export interface TextareaProps
|
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
label?: string
|
|
error?: string
|
|
helperText?: string
|
|
fullWidth?: boolean
|
|
}
|
|
|
|
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
(
|
|
{ className, label, error, helperText, fullWidth = true, id, ...props },
|
|
ref
|
|
) => {
|
|
const inputId = id || label?.toLowerCase().replace(/\s+/g, '-')
|
|
|
|
return (
|
|
<div className={clsx(fullWidth && 'w-full')}>
|
|
{label && (
|
|
<label
|
|
htmlFor={inputId}
|
|
className="block text-sm font-medium text-slate-300 mb-1.5"
|
|
>
|
|
{label}
|
|
</label>
|
|
)}
|
|
<textarea
|
|
ref={ref}
|
|
id={inputId}
|
|
className={clsx(
|
|
'w-full px-4 py-2.5 bg-background-800 border rounded-lg',
|
|
'text-white placeholder-slate-500',
|
|
'focus:outline-none focus:ring-2 focus:border-transparent',
|
|
'transition-all duration-200 resize-none',
|
|
error
|
|
? 'border-error-500 focus:ring-error-500'
|
|
: 'border-slate-700 focus:ring-accent-500',
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
{error && <p className="mt-1.5 text-sm text-error-500">{error}</p>}
|
|
{helperText && !error && (
|
|
<p className="mt-1.5 text-sm text-slate-500">{helperText}</p>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
)
|
|
|
|
Textarea.displayName = 'Textarea'
|