Files
tm_panel/src/components/FormElements/InputGroup/index.tsx
T
AmirReza Jamali 4fb1a931c5 refactor(logo): Update main logo SVG to use currentColor
The main logo SVG has been replaced with an updated version.

The previous logo had hardcoded fill colors, making it difficult to adapt to different color schemes, such as light and dark modes.

The new SVG uses `fill="currentColor"`, which allows its color to be controlled via the parent element's CSS `color` property. This makes the logo easily themeable and more flexible for use throughout the application.
2025-09-16 18:31:33 +03:30

129 lines
3.9 KiB
TypeScript

import { cn } from "@/lib/utils";
import {
HTMLInputAutoCompleteAttribute,
type HTMLInputTypeAttribute,
useId,
} from "react";
import {
type FieldErrors,
type FieldValues,
type Path,
type UseFormRegister,
type UseFormRegisterReturn,
} from "react-hook-form";
type InputGroupProps<T extends FieldValues> = {
name: Path<T>;
label: string;
type: HTMLInputTypeAttribute;
placeholder: string;
className?: string;
fileStyleVariant?: "style1" | "style2";
required?: boolean;
disabled?: boolean;
active?: boolean;
handleChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
value?: string;
icon?: React.ReactNode;
iconPosition?: "left" | "right";
height?: "sm" | "default";
autoComplete?: HTMLInputAutoCompleteAttribute;
defaultValue?: string;
register?: UseFormRegister<T>;
errors?: FieldErrors<T>;
} & Partial<UseFormRegisterReturn>;
const InputGroup = <T extends FieldValues>({
className,
label,
type,
placeholder,
required,
disabled,
active,
handleChange,
icon,
name,
register,
errors,
autoComplete,
onChange,
onBlur,
ref,
...props
}: InputGroupProps<T>): React.ReactElement => {
const id = useId();
const error = errors && errors[name];
return (
<div className={className}>
<label
htmlFor={id}
className="text-body-sm font-medium text-dark dark:text-white"
>
{label}
{required && <span className="ml-1 select-none text-error">*</span>}
</label>
<div
className={cn(
"relative mt-3 [&_svg]:absolute [&_svg]:top-1/2 [&_svg]:-translate-y-1/2",
props.iconPosition === "left"
? "[&_svg]:left-4.5"
: "[&_svg]:right-4.5",
)}
>
<input
id={id}
type={type}
placeholder={placeholder}
name={name}
ref={ref}
autoComplete={autoComplete}
onChange={(e) => {
onChange?.(e);
handleChange?.(e);
}}
onBlur={onBlur}
{...(!onChange &&
props.value !== undefined && { value: props.value })}
{...(!onChange &&
props.defaultValue !== undefined && {
defaultValue: props.defaultValue,
})}
className={cn(
"w-full rounded-lg border-[1.5px] border-stroke bg-transparent outline-none transition focus:border-primary disabled:cursor-default disabled:bg-gray-2 data-[active=true]:border-primary dark:border-dark-3 dark:bg-dark-2 dark:focus:border-primary dark:disabled:bg-dark dark:data-[active=true]:border-primary",
// Add error styles conditionally
error && "border-red focus:border-red dark:border-red",
type === "file"
? getFileStyles(props.fileStyleVariant!)
: "px-5.5 py-3 text-dark placeholder:text-dark-6 dark:text-white",
props.iconPosition === "left" && "pl-12.5",
props.height === "sm" && "py-2.5",
)}
disabled={disabled}
data-active={active}
/>
{icon}
</div>
{error && (
<p className="text-red mt-1.5 text-sm">{error.message as string}</p>
)}
</div>
);
};
export default InputGroup;
function getFileStyles(variant: "style1" | "style2") {
switch (variant) {
case "style1":
return `file:mr-5 file:border-collapse file:cursor-pointer file:border-0 file:border-r file:border-solid file:border-stroke file:bg-[#E2E8F0] file:px-6.5 file:py-[13px] file:text-body-sm file:font-medium file:text-dark-5 file:hover:bg-primary file:hover:bg-opacity-10 dark:file:border-dark-3 dark:file:bg-white/30 dark:file:text-white`;
default:
return `file:mr-4 file:rounded file:border-[0.5px] file:border-stroke file:bg-stroke file:px-2.5 file:py-1 file:text-body-xs file:font-medium file:text-dark-5 file:focus:border-primary dark:file:border-dark-3 dark:file:bg-white/30 dark:file:text-white px-3 py-[9px]`;
}
}