Form
Puente entre react-hook-form y los primitivos. FormField/FormControl.
Forms@base-ui/reactreact-hook-form
Installation
npx shadcn@latest add @saastro/formPreview

Source Code
/**
* Minimal shadcn-style form primitives — just enough surface for
* @saastro/forms to wire react-hook-form fields. Standard shadcn ships
* this file via `shadcn add form`, but at the time of writing the
* registry doesn't expose it under that name from this project's
* `components.json`. Hand-rolling it keeps the bundle predictable.
*
* Exposes:
* - Form: el `FormProvider` de react-hook-form, re-exportado con el nombre
* que usa shadcn. No es un primitivo de Base UI y no lo pretende: es el
* contexto que `FormField` necesita para encontrar el formulario. Faltaba,
* y sin él los consumidores tienen que importarlo de react-hook-form por su
* cuenta, que es justo la dependencia que este fichero existe para tapar.
* - FormField: Controller wrapper that exposes `field` props to children
* - FormControl: reenvía id/aria-* al input subyacente. Con Radix esto era
* `Slot`; en Base UI el equivalente es el hook `useRender`, que fusiona las
* props sobre el hijo. Se mantiene hecho a mano A PROPÓSITO: el `FormControl`
* oficial de shadcn usa `useFormField()` y exige un `FormItem` alrededor,
* que @saastro/forms no monta — usarlo reventaría en runtime.
* NO regenerar este fichero con `shadcn add form`.
*
* The remaining named primitives @saastro/forms looks up (Field,
* FieldLabel, FieldDescription, FieldError) come from the standard
* shadcn `field.tsx`.
*/
import * as React from 'react';
import {
Controller,
FormProvider,
type ControllerProps,
type FieldPath,
type FieldValues,
} from 'react-hook-form';
import { useRender } from '@base-ui/react/use-render';
/** El contexto de react-hook-form, con el nombre que espera shadcn. */
export const Form = FormProvider;
/**
* EL SEGUNDO GENÉRICO NO ES ADORNO: SIN ÉL, `field.value` MIENTE.
*
* Faltaba `TName`, y sin él `ControllerProps` no puede estrechar por el `name=`
* de cada campo: `field.value` sale como la UNIÓN de todos los campos del
* formulario. En un diálogo que mezcla textos con un booleano, eso es
* `string | boolean`, y el error aparece lejos de aquí — en el `value` del
* `Input`, del `Select` y del `Switch`, ocho a la vez y todos en el fichero del
* consumidor. Medido: con el genérico, cero.
*
* Es la firma que trae el shadcn oficial.
*/
export function FormField<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>(props: ControllerProps<TFieldValues, TName>) {
return <Controller {...props} />;
}
export interface FormControlProps extends React.ComponentProps<'div'> {
children?: React.ReactElement;
}
export const FormControl = React.forwardRef<HTMLElement, FormControlProps>(
function FormControl({ children, ...props }, ref) {
return useRender({
render: children as React.ReactElement,
props,
ref: ref as React.Ref<HTMLElement>,
});
},
);
export const FormItem = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
function FormItem({ className, ...props }, ref) {
return <div ref={ref} className={className} {...props} />;
},
);
export const FormLabel = React.forwardRef<HTMLLabelElement, React.ComponentProps<'label'>>(
function FormLabel({ className, ...props }, ref) {
return <label ref={ref} className={className} {...props} />;
},
);
export const FormMessage = React.forwardRef<HTMLParagraphElement, React.ComponentProps<'p'>>(
function FormMessage({ className, children, ...props }, ref) {
if (!children) return null;
return (
<p ref={ref} className={className} {...props}>
{children}
</p>
);
},
);
export const FormDescription = FormMessage;Registry Setup
Add the Saastro registry to your components.json:
{
"registries": {
"@saastro": "https://ui.saastro.io/r/{name}.json"
}
}