MeetUp/src/components/custom-ui/labeled-input.tsx
Dominik 8dd014ead1
All checks were successful
container-scan / Container Scan (push) Successful in 2m32s
tests / Tests (push) Successful in 3m35s
docker-build / docker (push) Successful in 1m17s
Merge pull request 'test: add event creation test' (#127)
Reviewed-on: #127
Reviewed-by: Maximilian Liebmann <lima@noreply.git.dominikstahl.dev>
2025-07-01 08:18:49 +00:00

119 lines
3.4 KiB
TypeScript

import { Input, Textarea } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import React, { ForwardRefExoticComponent, RefAttributes } from 'react';
import { Button } from '../ui/button';
import { Eye, EyeOff, LucideProps } from 'lucide-react';
import { cn } from '@/lib/utils';
export default function LabeledInput({
type,
label,
subtext,
placeholder,
value,
defaultValue,
name,
icon,
variantSize = 'default',
autocomplete,
error,
'data-cy': dataCy,
...rest
}: {
label: string;
subtext?: string;
placeholder?: string;
value?: string;
name?: string;
icon?: ForwardRefExoticComponent<
Omit<LucideProps, 'ref'> & RefAttributes<SVGSVGElement>
>;
variantSize?: 'default' | 'big' | 'textarea';
autocomplete?: string;
error?: string;
'data-cy'?: string;
} & React.InputHTMLAttributes<HTMLInputElement>) {
const [passwordVisible, setPasswordVisible] = React.useState(false);
const [inputValue, setInputValue] = React.useState(
value || defaultValue || '',
);
React.useEffect(() => {
if (value !== undefined) {
setInputValue(value);
}
}, [value]);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
if (rest.onChange) {
rest.onChange(e);
}
};
return (
<div className='grid grid-cols-1 gap-1'>
<Label htmlFor={name}>{label}</Label>
{subtext && (
<Label className='text-sm text-muted-foreground' htmlFor={name}>
{subtext}
</Label>
)}
{variantSize === 'textarea' ? (
<Textarea
placeholder={placeholder}
defaultValue={value}
id={name}
name={name}
rows={3}
data-cy={dataCy}
/>
) : (
<span className='relative'>
<Input
className={cn(
type === 'password' ? 'pr-[50px]' : '',
variantSize === 'big'
? 'h-12 file:h-10 text-lg placeholder:text-lg sm:text-2xl sm:placeholder:text-2xl'
: '',
icon && inputValue === '' ? 'pl-10' : '',
'transition-all duration-300 ease-in-out',
)}
type={passwordVisible ? 'text' : type}
placeholder={placeholder}
defaultValue={inputValue}
id={name}
name={name}
autoComplete={autocomplete}
data-cy={dataCy}
{...rest}
onChange={handleInputChange}
/>
{icon && (
<span
className={cn(
'absolute left-3 top-1/2 -translate-y-1/2 text-muted-input transition-all duration-300 ease-in-out',
inputValue === ''
? 'opacity-100 scale-100'
: 'opacity-0 scale-75 pointer-events-none',
)}
>
{React.createElement(icon)}
</span>
)}
{type === 'password' && (
<Button
className='absolute right-0 top-0 w-[36px] h-[36px]'
type='button'
variant={'outline_muted'}
onClick={() => setPasswordVisible((visible) => !visible)}
>
{passwordVisible ? <Eye /> : <EyeOff />}
</Button>
)}
</span>
)}
{error && <p className='text-red-500 text-sm mt-1'>{error}</p>}
</div>
);
}