new-personal-site/components/lib/form/Input.tsx
2024-12-09 16:36:17 +01:00

137 lines
3.9 KiB
TypeScript

import React, {
DetailedHTMLProps,
InputHTMLAttributes,
LabelHTMLAttributes,
RefObject,
TextareaHTMLAttributes,
} from "react";
import { twMerge } from "tailwind-merge";
export type InputProps = DetailedHTMLProps<
InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
> &
DetailedHTMLProps<
TextareaHTMLAttributes<HTMLTextAreaElement>,
HTMLTextAreaElement
> & {
label?: string;
variant?: "normal" | "warning" | "error" | "inactive";
prefix?: string | React.ReactNode;
suffix?: string | React.ReactNode;
showLabel?: boolean;
istextarea?: boolean;
wrapperProps?: DetailedHTMLProps<
InputHTMLAttributes<HTMLDivElement>,
HTMLDivElement
>;
labelProps?: DetailedHTMLProps<
LabelHTMLAttributes<HTMLLabelElement>,
HTMLLabelElement
>;
componentRef?: RefObject<any>;
};
/**
* # Input Element
* @className twui-input
*/
export default function Input({
label,
variant,
prefix,
suffix,
componentRef,
labelProps,
wrapperProps,
showLabel,
istextarea,
...props
}: InputProps) {
const [focus, setFocus] = React.useState(false);
const targetComponent = istextarea ? (
<textarea
{...props}
className={twMerge(
"w-full outline-none bg-transparent",
"twui-textarea",
props.className
)}
ref={componentRef}
onFocus={(e) => {
setFocus(true);
props?.onFocus?.(e);
}}
onBlur={(e) => {
setFocus(false);
props?.onBlur?.(e);
}}
/>
) : (
<input
{...props}
className={twMerge(
"w-full outline-none bg-transparent",
"twui-input",
props.className
)}
ref={componentRef}
onFocus={(e) => {
setFocus(true);
props?.onFocus?.(e);
}}
onBlur={(e) => {
setFocus(false);
props?.onBlur?.(e);
}}
/>
);
return (
<div
{...wrapperProps}
className={twMerge(
"relative flex items-center gap-2 border rounded-md px-3 py-2 outline outline-1",
focus
? "border-slate-700 dark:border-white/50"
: "border-slate-300 dark:border-white/20",
focus
? "outline-slate-700 dark:outline-white/50"
: "outline-transparent",
variant == "warning" &&
"border-yellow-500 dark:border-yellow-300 outline-yellow-500 dark:outline-yellow-300",
variant == "error" &&
"border-red-500 dark:border-red-300 outline-red-500 dark:outline-red-300",
variant == "inactive" && "opacity-40 pointer-events-none",
"bg-white dark:bg-black",
wrapperProps?.className
)}
>
{showLabel && (
<label
htmlFor={props.name}
{...labelProps}
className={twMerge(
"text-xs absolute -top-2.5 left-2 text-slate-500 bg-white px-1.5 rounded-t",
"dark:text-white/60 dark:bg-black",
labelProps?.className
)}
>
{label || props.placeholder || props.name}
</label>
)}
{prefix && (
<div className="opacity-60 pointer-events-none">{prefix}</div>
)}
{targetComponent}
{suffix && (
<div className="opacity-60 pointer-events-none">{suffix}</div>
)}
</div>
);
}