Presupuestador_web/client/src/components/Forms/FormQuantityField.tsx

163 lines
4.8 KiB
TypeScript
Raw Normal View History

2024-07-09 16:21:12 +00:00
import { cn } from "@/lib/utils";
import { FormControl, FormDescription, FormItem, InputProps } from "@/ui";
import { Quantity, QuantityObject } from "@shared/contexts";
import { createElement, forwardRef, useState } from "react";
import {
Controller,
FieldPath,
FieldValues,
UseControllerProps,
useFormContext,
} from "react-hook-form";
import { FormErrorMessage } from "./FormErrorMessage";
import { FormLabel, FormLabelProps } from "./FormLabel";
import { FormInputProps, FormInputWithIconProps } from "./FormProps";
export type FormQuantityFieldProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
button?: (props?: React.PropsWithChildren) => React.ReactNode;
defaultValue?: any;
} & InputProps &
FormInputProps &
Partial<FormLabelProps> &
FormInputWithIconProps &
UseControllerProps<TFieldValues, TName>;
export const FormQuantityField = forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & FormQuantityFieldProps
>((props, ref) => {
const {
name,
label,
hint,
placeholder,
description,
required,
className,
leadIcon,
trailIcon,
button,
defaultValue,
} = props;
const { control } = useFormContext();
const [precision, setPrecision] = useState<number>(Quantity.DEFAULT_PRECISION);
const transform = {
input: (value: QuantityObject) => {
const quantityOrError = Quantity.create(value);
if (quantityOrError.isFailure) {
throw quantityOrError.error;
}
const quantityValue = quantityOrError.object;
setPrecision(quantityValue.getPrecision());
return quantityValue.toString();
},
output: (event: React.ChangeEvent<HTMLInputElement>): QuantityObject => {
const value = parseFloat(event.target.value);
const output = !isNaN(value) ? value : 0;
const quantityOrError = Quantity.create({
amount: output * Math.pow(10, precision),
precision,
});
if (quantityOrError.isFailure) {
throw quantityOrError.error;
}
return quantityOrError.object.toObject();
},
};
return (
<Controller
defaultValue={defaultValue}
control={control}
name={name}
rules={{
required,
}}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
render={({ field, fieldState, formState }) => {
return (
<input
type='number'
{...field}
className='text-right'
placeholder={placeholder}
onChange={(e) => field.onChange(transform.output(e))}
value={transform.input(field.value)}
/>
);
return (
<FormItem ref={ref} className={cn(className, "space-y-3")}>
{label && <FormLabel label={label} hint={hint} required={required} />}
<div className={cn(button ? "flex" : null)}>
<div
className={cn(
leadIcon ? "relative flex items-stretch flex-grow focus-within:z-10" : ""
)}
>
{leadIcon && (
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
{createElement(
leadIcon,
{
className: "h-5 w-5 text-muted-foreground",
"aria-hidden": true,
},
null
)}
</div>
)}
<FormControl
className={cn("block", leadIcon ? "pl-10" : "", trailIcon ? "pr-10" : "")}
>
<input
type='number'
placeholder={placeholder}
className={cn(
fieldState.error ? "border-destructive focus-visible:ring-destructive" : ""
)}
{...field}
onInput={(e) => field.onChange(transform.output(e))}
value={transform.input(field.value)}
/>
</FormControl>
{trailIcon && (
<div className='absolute inset-y-0 right-0 flex items-center pl-3 pointer-events-none'>
{createElement(
trailIcon,
{
className: "h-5 w-5 text-muted-foreground",
"aria-hidden": true,
},
null
)}
</div>
)}
</div>
{button && <>{createElement(button)}</>}
</div>
{description && <FormDescription>{description}</FormDescription>}
<FormErrorMessage />
</FormItem>
);
}}
/>
);
});