2026-04-03 16:15:25 +00:00
|
|
|
import {
|
|
|
|
|
Field,
|
|
|
|
|
FieldDescription,
|
|
|
|
|
FieldError,
|
2026-04-09 18:39:54 +00:00
|
|
|
InputGroup,
|
|
|
|
|
InputGroupAddon,
|
|
|
|
|
InputGroupInput,
|
2026-04-03 16:15:25 +00:00
|
|
|
} from "@repo/shadcn-ui/components";
|
|
|
|
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
2026-04-09 18:39:54 +00:00
|
|
|
import { CalendarIcon } from "lucide-react";
|
|
|
|
|
import * as React from "react";
|
2026-04-03 16:15:25 +00:00
|
|
|
import { type FieldPath, type FieldValues, useFormContext } from "react-hook-form";
|
|
|
|
|
|
|
|
|
|
import { FormFieldLabel } from "./form-field-label.tsx";
|
2026-04-09 18:39:54 +00:00
|
|
|
import type { NativeInputProps } from "./types.ts";
|
2026-04-03 16:15:25 +00:00
|
|
|
|
2026-04-09 18:39:54 +00:00
|
|
|
type DatePickerFieldProps<TFormValues extends FieldValues> = Omit<NativeInputProps, "name"> & {
|
2026-04-03 16:15:25 +00:00
|
|
|
name: FieldPath<TFormValues>;
|
|
|
|
|
|
|
|
|
|
label?: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
|
|
|
|
|
orientation?: "vertical" | "horizontal" | "responsive";
|
|
|
|
|
|
|
|
|
|
inputClassName?: string;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-09 18:39:54 +00:00
|
|
|
export const DatePickerField = <TFormValues extends FieldValues>({
|
2026-04-03 16:15:25 +00:00
|
|
|
name,
|
2026-04-09 18:39:54 +00:00
|
|
|
|
2026-04-03 16:15:25 +00:00
|
|
|
label,
|
|
|
|
|
description,
|
2026-04-09 18:39:54 +00:00
|
|
|
|
2026-04-03 16:15:25 +00:00
|
|
|
required = false,
|
|
|
|
|
readOnly = false,
|
2026-04-09 18:39:54 +00:00
|
|
|
|
2026-04-03 16:15:25 +00:00
|
|
|
orientation = "vertical",
|
2026-04-09 18:39:54 +00:00
|
|
|
|
2026-04-03 16:15:25 +00:00
|
|
|
className,
|
|
|
|
|
inputClassName,
|
2026-04-09 18:39:54 +00:00
|
|
|
|
|
|
|
|
...inputRest
|
|
|
|
|
}: DatePickerFieldProps<TFormValues>) => {
|
|
|
|
|
const { register, formState, getFieldState } = useFormContext<TFormValues>();
|
|
|
|
|
|
|
|
|
|
const inputId = React.useId();
|
|
|
|
|
const disabled = formState.isSubmitting || inputRest.disabled;
|
|
|
|
|
|
|
|
|
|
const presetProps = {
|
|
|
|
|
type: "date",
|
|
|
|
|
autoComplete: "off",
|
|
|
|
|
spellCheck: false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const rightIcon = <CalendarIcon />;
|
|
|
|
|
|
|
|
|
|
// Obtener error del campo (tipado seguro)
|
|
|
|
|
const fieldError = getFieldState(name, formState).error;
|
2026-04-03 16:15:25 +00:00
|
|
|
|
|
|
|
|
return (
|
2026-04-09 18:39:54 +00:00
|
|
|
<Field className={cn("gap-1", className)} data-invalid={!!fieldError} orientation={orientation}>
|
|
|
|
|
{label ? (
|
|
|
|
|
<FormFieldLabel htmlFor={inputId} required={required}>
|
|
|
|
|
{label}
|
|
|
|
|
</FormFieldLabel>
|
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
|
|
<InputGroup
|
|
|
|
|
className={cn(
|
|
|
|
|
"bg-muted/50 font-medium",
|
|
|
|
|
"hover:border-ring hover:ring-ring/20 hover:ring-[3px]",
|
|
|
|
|
"focus-visible:border-ring focus-visible:ring-ring/60 focus-visible:ring-[3px]",
|
|
|
|
|
"placeholder:text-muted-foreground/50",
|
|
|
|
|
inputClassName
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<InputGroupInput
|
|
|
|
|
{...presetProps}
|
|
|
|
|
{...inputRest}
|
|
|
|
|
{...register(name)}
|
|
|
|
|
aria-invalid={!!fieldError}
|
|
|
|
|
className="placeholder:text-muted-foreground/50"
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
id={inputId}
|
|
|
|
|
readOnly={readOnly}
|
|
|
|
|
required={required}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{rightIcon && <InputGroupAddon aria-hidden="true">{rightIcon}</InputGroupAddon>}
|
|
|
|
|
</InputGroup>
|
|
|
|
|
|
|
|
|
|
{description ? (
|
|
|
|
|
<FieldDescription>{description}</FieldDescription>
|
|
|
|
|
) : (
|
|
|
|
|
<div aria-hidden="true" className="min-h-5" />
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<FieldError errors={[fieldError]} />
|
|
|
|
|
</Field>
|
2026-04-03 16:15:25 +00:00
|
|
|
);
|
2026-04-09 18:39:54 +00:00
|
|
|
};
|