Skip to content

Instantly share code, notes, and snippets.

@nishimweprince
Created June 20, 2024 14:51
Show Gist options
  • Save nishimweprince/3d79889bd8d90c36821f1f05f9c8e123 to your computer and use it in GitHub Desktop.
Save nishimweprince/3d79889bd8d90c36821f1f05f9c8e123 to your computer and use it in GitHub Desktop.
A reusable component for Form Input.
import {
ChangeEvent,
FC,
LegacyRef,
MouseEventHandler,
ReactNode,
useEffect,
useRef,
} from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { Link } from 'react-router-dom';
import DatePicker from './DatePicker';
import { countriesList } from '../../constants/countries';
interface InputProps {
label?: string;
placeholder?: string;
className?: string;
required?: boolean;
onChange?: ((e: ChangeEvent<HTMLInputElement>) => void) | undefined;
defaultValue?: string | number;
submit?: boolean;
type?: string;
value?: string | number;
suffixIcon?: IconProp;
prefixIcon?: IconProp;
suffixIconHandler?: MouseEventHandler<HTMLAnchorElement> | undefined;
name?: string;
suffixIconPrimary?: boolean;
prefixIconHandler?: MouseEventHandler<HTMLAnchorElement> | undefined;
prefixIconPrimary?: boolean;
prefixText?: string | ReactNode;
defaultChecked?: boolean | undefined;
accept?: string;
min?: string | number;
readOnly?: boolean;
multiple?: boolean;
labelClassName?: string;
range?: boolean;
selectionType?: 'date' | 'month' | 'year' | 'recurringDate';
}
const Input: FC<InputProps> = ({
type = 'text',
label = null,
placeholder,
className,
required = false,
value,
onChange,
defaultValue,
suffixIcon = null,
suffixIconHandler,
suffixIconPrimary = false,
prefixIcon = null,
prefixIconHandler,
prefixText = null,
name,
accept = '*',
min,
readOnly = false,
labelClassName = '',
multiple = false,
defaultChecked = false,
selectionType,
}) => {
const hiddenFileInput = useRef<HTMLButtonElement>(null);
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
if (!defaultValue && !value && ref?.current) {
ref.current.value = '';
} else if ((defaultValue || value) && ref?.current) {
ref.current.value = String(defaultValue || value);
}
}, [defaultValue, value]);
if (['checkbox', 'radio'].includes(type)) {
return (
<label className="flex items-center gap-2 text-[13px]">
<input
type={type}
name={name}
value={value}
defaultChecked={defaultChecked}
onChange={onChange}
className={`w-4 h-4 border-[1.5px] rounded-xl cursor-pointer border-secondary outline-none focus:outline-none accent-primary focus:border-[1.6px] focus:border-primary ease-in-out duration-50 ${className}`}
/>
<p className={`${label ? 'flex' : 'hidden'} text-[13px]`}>{label}</p>
</label>
);
}
if (type === 'file') {
const handleClick = () => {
hiddenFileInput.current?.click();
};
return (
<menu className="text-[12px] w-fit">
<button
type="button"
onClick={handleClick}
className={`!bg-primary !text-white hover:!bg-primary hover:!text-white !shadow-sm py-[5px] w-full text-[12px] text-center max-[800px]:!text-[14px] px-8 rounded-md cursor-pointer ease-in-out duration-400 hover:scale-[1.005] ${className}`}
>
Choose file{multiple ? 's' : ''}
</button>
<input
ref={hiddenFileInput as LegacyRef<HTMLInputElement> | undefined}
type={type}
multiple={multiple}
accept={accept}
onChange={onChange}
className="hidden"
/>
</menu>
);
}
if (type === 'tel') {
return (
<label className="flex flex-col w-full gap-1">
<p className="flex items-center gap-1">
{label}{' '}
<span className={`${required ? 'flex' : 'hidden'} text-red-600`}>
*
</span>
</p>
<menu className="relative flex items-center gap-0">
<span className="absolute inset-y-0 start-0 flex items-center ps-3.5">
<select
className="w-full !text-[12px]"
>
{countriesList?.map((country) => {
return (
<option key={country?.dial_code} value={country?.dial_code}>
{`${country?.code} ${country?.dial_code}`}
</option>
);
})}
</select>
</span>
<input
name={name}
onChange={onChange}
className="ps-[96px] py-[7px] px-4 font-normal placeholder:!font-light placeholder:text-[13px] text-[14px] flex items-center w-full rounded-lg border-[1.5px] border-secondary border-opacity-50 outline-none focus:outline-none focus:border-[1.6px] focus:border-primary ease-in-out duration-50"
type="text"
value={value}
ref={ref}
/>
</menu>
</label>
);
}
if (['date'].includes(type)) {
return (
<label className={`flex flex-col gap-[5px] w-full ${labelClassName}`}>
<p
className={`${
label ? 'flex items-center gap-[5px] text-[14px]' : 'hidden'
}`}
>
{label}{' '}
<span className={required ? 'text-[14px] text-red-600' : 'hidden'}>
*
</span>
</p>
<DatePicker selectionType={selectionType} onChange={onChange} value={value as Date | undefined} />
</label>
);
}
return (
<label className={`flex flex-col gap-[5px] w-full ${labelClassName}`}>
<p
className={`${
label ? 'flex items-center gap-[5px] text-[14px]' : 'hidden'
}`}
>
{label}{' '}
<span className={required ? 'text-[14px] text-red-600' : 'hidden'}>
*
</span>
</p>
{!prefixIcon && !prefixText && !suffixIcon && (
<input
defaultValue={defaultValue}
min={min}
value={value}
type={type || 'text'}
readOnly={readOnly}
name={name}
ref={ref}
onChange={onChange}
placeholder={readOnly ? '' : placeholder}
className={`py-[7px] px-4 font-normal placeholder:!font-light placeholder:text-[13px] text-[14px] flex items-center w-full rounded-lg border-[1.5px] border-secondary border-opacity-50 outline-none focus:outline-none focus:border-[1.6px] focus:border-primary ease-in-out duration-50 ${className} ${
readOnly &&
'!border-[.1px] !border-background hover:cursor-default focus:!border-background'
}`}
/>
)}
<section className="relative w-full">
{(prefixIcon || prefixText) && (
<menu className={`${labelClassName} relative w-full`}>
<label className="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
<Link to={'#'} onClick={prefixIconHandler} className="text-secondary">
{prefixIcon && (
<FontAwesomeIcon className="text-current" icon={prefixIcon} />
)}
{prefixText && <p className="text-[14px]">{prefixText}</p>}
</Link>
</label>
<input
defaultValue={defaultValue}
value={value}
type={type || 'text'}
readOnly={readOnly}
name={name}
ref={ref}
onChange={onChange}
placeholder={readOnly ? '' : placeholder}
className={`py-[7px] px-6 font-normal placeholder:!font-light placeholder:text-[13px] text-[14px] flex items-center w-full rounded-lg border-[1.5px] border-secondary border-opacity-50 outline-none focus:outline-none focus:border-[1.6px] focus:border-primary ease-in-out duration-50 ${className}
${prefixIcon && 'ps-10'} ${prefixText ? 'ps-[3.6rem]' : ''} ${
readOnly &&
'!border-[.1px] !border-background hover:cursor-default focus:!border-background'
}`}
/>
</menu>
)}
{suffixIcon && (
<menu className="flex items-center">
<Link
to={'#'}
onClick={suffixIconHandler}
className={`${
!suffixIcon && 'hidden'
} absolute top-0 end-0 p-2.5 px-3.5 text-sm font-medium h-full rounded-e-lg border focus:outline-none ${
suffixIconPrimary
? 'bg-primary text-white border-primary border-l-none'
: 'border-secondary border-opacity-50 bg-white text-primary border-l-none'
}`}
>
<FontAwesomeIcon icon={suffixIcon || faSearch} />
</Link>
<input
defaultValue={defaultValue}
value={value}
type={type || 'text'}
onChange={onChange}
readOnly={readOnly}
name={name}
ref={ref}
placeholder={readOnly ? '' : placeholder}
className={`${
prefixText && '!ml-16 !w-[85%]'
} py-[7px] px-4 font-normal placeholder:!font-light placeholder:text-[13px] text-[14px] flex items-center w-full rounded-lg border-[1.5px] border-secondary border-opacity-50 outline-none focus:outline-none focus:border-[1.6px] focus:border-primary ease-in-out duration-50 ${className} ${
prefixIcon &&
'!ml-[45px] !w-[90%] !border-l-none !rounded-l-none !ps-3.5'
} ${
readOnly &&
'!border-[.1px] !border-background hover:cursor-default focus:!border-background'
}`}
/>
</menu>
)}
</section>
</label>
);
};
export default Input;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment