diff --git a/src/components/TextField.tsx b/src/components/TextField.tsx new file mode 100644 index 0000000..6006269 --- /dev/null +++ b/src/components/TextField.tsx @@ -0,0 +1,180 @@ +import { + Ref, + ReactNode, + forwardRef, + InputHTMLAttributes, + TextareaHTMLAttributes, + PropsWithChildren, +} from "react"; + +import { cn } from "@/utils/cn"; + +const sizeMap = { + lg: "py-4 px-5 text-[1rem]", + sm: "p-2.5 text-sm", +} as const; + +interface TextElementProps { + id?: string; + label?: string; + prefix?: ReactNode; + postfix?: ReactNode; + size?: keyof typeof sizeMap; + fullWidth?: boolean; + disabled?: boolean; + validateMessage?: string; + className?: string; + wrapperClassName?: string; +} + +type InputProps = Omit< + InputHTMLAttributes, + keyof TextElementProps +> & + TextElementProps; + +type TextAreaProps = Omit< + TextareaHTMLAttributes, + keyof TextElementProps +> & + TextElementProps; + +function Field({ + id, + label, + validateMessage, + children, +}: PropsWithChildren< + Pick +>) { + return ( +
+ {label && ( + + )} + + {children} + + {validateMessage && ( +

{validateMessage}

+ )} +
+ ); +} + +const Input = forwardRef( + ( + { + id, + label, + prefix, + postfix, + fullWidth, + disabled, + wrapperClassName, + className, + size = "lg", + validateMessage, + ...rest + }: InputProps, + ref: Ref, + ) => { + const wrapperClassNames = cn( + "flex w-fit gap-1.5 border rounded-[0.375rem] border-gray-30 focus-within:border-blue-20 placeholder:text-gray-40 ", + { + "w-full": fullWidth, + "bg-gray-20 text-gray-40": disabled, + "focus-within:border-red-40": validateMessage, + }, + sizeMap[size], + wrapperClassName, + ); + + const textElementClassNames = cn( + "disabled:placeholder:text-gray-40 resize-none outline-none", + { + "flex-1": fullWidth, + }, + className, + ); + + return ( + +
+ {prefix} + + {postfix} +
+
+ ); + }, +); + +const TextArea = forwardRef( + ( + { + id, + label, + prefix, + postfix, + fullWidth, + disabled, + wrapperClassName, + className, + size = "lg", + validateMessage, + ...rest + }: TextAreaProps, + ref: Ref, + ) => { + const wrapperClassNames = cn( + "flex w-fit gap-1.5 border rounded-[0.375rem] border-gray-30 focus-within:border-blue-20 placeholder:text-gray-40 ", + { + "w-full": fullWidth, + "bg-gray-20 text-gray-40": disabled, + "focus-within:border-red-40": validateMessage, + }, + sizeMap[size], + wrapperClassName, + ); + + const textElementClassNames = cn( + "disabled:placeholder:text-gray-40 resize-none outline-none", + { + "flex-1": fullWidth, + }, + className, + ); + + return ( + +
+ {prefix} +