110 lines
3.0 KiB
TypeScript
110 lines
3.0 KiB
TypeScript
// packages/rdx-ui/src/components/form/switch-field.tsx
|
|
import {
|
|
Field,
|
|
FieldContent,
|
|
FieldDescription,
|
|
FieldError,
|
|
Switch,
|
|
} from "@repo/shadcn-ui/components";
|
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
|
import * as React from "react";
|
|
import { Controller, type FieldPath, type FieldValues, useFormContext } from "react-hook-form";
|
|
|
|
import { FormFieldLabel } from "./form-field-label.tsx";
|
|
|
|
type SwitchFieldProps<TFormValues extends FieldValues> = {
|
|
name: FieldPath<TFormValues>;
|
|
|
|
label?: string | React.ReactNode;
|
|
description?: string;
|
|
reserveDescriptionSpace?: boolean;
|
|
|
|
onCheckedChange?: (checked: boolean) => void;
|
|
|
|
disabled?: boolean;
|
|
required?: boolean;
|
|
readOnly?: boolean;
|
|
|
|
orientation?: "vertical" | "horizontal" | "responsive";
|
|
|
|
className?: string;
|
|
inputClassName?: string;
|
|
};
|
|
|
|
export const SwitchField = <TFormValues extends FieldValues>({
|
|
name,
|
|
|
|
label,
|
|
description,
|
|
reserveDescriptionSpace = false,
|
|
|
|
onCheckedChange,
|
|
|
|
disabled = false,
|
|
required = false,
|
|
readOnly = false,
|
|
|
|
orientation = "horizontal",
|
|
|
|
className,
|
|
inputClassName,
|
|
}: SwitchFieldProps<TFormValues>) => {
|
|
const { control, formState } = useFormContext<TFormValues>();
|
|
|
|
const inputId = React.useId();
|
|
const descriptionId = description ? `${inputId}-description` : undefined;
|
|
|
|
const isDisabled = disabled || readOnly || formState.isSubmitting;
|
|
|
|
return (
|
|
<Controller
|
|
control={control}
|
|
name={name}
|
|
render={({ field, fieldState }) => {
|
|
const hasError = Boolean(fieldState.error);
|
|
return (
|
|
<Field
|
|
// Radix UI renderiza un input nativo absoluto para Switch.
|
|
// Este containing block evita que genere overflow a nivel de documento.
|
|
className={cn("gap-2 relative", className)}
|
|
data-invalid={hasError}
|
|
orientation={orientation}
|
|
>
|
|
<FieldContent className="gap-1">
|
|
<FormFieldLabel htmlFor={inputId} required={required}>
|
|
{label}
|
|
</FormFieldLabel>
|
|
|
|
{description ? (
|
|
<FieldDescription id={descriptionId}>{description}</FieldDescription>
|
|
) : reserveDescriptionSpace ? (
|
|
<div aria-hidden="true" className="min-h-5" />
|
|
) : null}
|
|
|
|
<FieldError errors={[fieldState.error]} />
|
|
</FieldContent>
|
|
|
|
<Switch
|
|
aria-describedby={descriptionId}
|
|
aria-invalid={hasError || undefined}
|
|
aria-required={required || undefined}
|
|
checked={field.value === true}
|
|
className={cn(inputClassName)}
|
|
disabled={isDisabled}
|
|
id={inputId}
|
|
name={field.name}
|
|
onBlur={field.onBlur}
|
|
onCheckedChange={(checked) => {
|
|
field.onChange(checked);
|
|
onCheckedChange?.(checked);
|
|
}}
|
|
ref={field.ref}
|
|
required={required}
|
|
/>
|
|
</Field>
|
|
);
|
|
}}
|
|
/>
|
|
);
|
|
};
|