Field
Conteneur de mise en page associant un libelle, un controle, une description et un message d'erreur. Installez Field depuis le registre VLLNT UI avec la CLI shadcn.
Conteneur de mise en page associant un libelle, un controle, une description et un message d'erreur. Fait partie de la famille form dans VLLNT UI, il est publie comme entree de registre lisible par machine — copiez la source directement dans votre application avec la CLI shadcn et possedez-la, sans dependance d'execution a une bibliotheque de composants.
Apercu
Basculez entre clair et sombre pour inspecter l'apercu Storybook integre.
Installation
Ajoutez Field a votre projet avec la CLI shadcn. La source arrive dans votre code, prete a etre adaptee :
pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/field.jsonSource
"use client";
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../lib/utils";
import { Label } from "../label/label";
type FieldContextValue = {
descriptionId: string;
errorId: string;
id: string;
invalid: boolean;
};
const FieldContext = React.createContext<FieldContextValue | null>(null);
function useFieldContext(): FieldContextValue {
const context = React.use(FieldContext);
if (!context) {
throw new Error("Field subcomponents must be used within a Field");
}
return context;
}
const fieldVariants = cva("flex", {
defaultVariants: {
orientation: "vertical",
},
variants: {
orientation: {
horizontal: "flex-row items-center gap-3",
vertical: "flex-col gap-1.5",
},
},
});
/** Groups a label, control, description, and error message for one input. */
export type FieldProps = {
children: React.ReactNode;
className?: string;
invalid?: boolean;
} & VariantProps<typeof fieldVariants>;
const Field = ({
children,
className,
invalid = false,
orientation,
ref,
}: FieldProps & { ref?: React.Ref<HTMLDivElement> }) => {
const id = React.useId();
const value = React.useMemo<FieldContextValue>(
() => ({
descriptionId: `${id}-description`,
errorId: `${id}-error`,
id: `${id}-control`,
invalid,
}),
[id, invalid],
);
return (
<FieldContext.Provider value={value}>
<div
className={cn(fieldVariants({ orientation }), className)}
data-slot="field"
ref={ref}
>
{children}
</div>
</FieldContext.Provider>
);
};
Field.displayName = "Field";
/** Label wired to the field control through htmlFor. */
export type FieldLabelProps = React.ComponentPropsWithoutRef<typeof Label>;
const FieldLabel = ({
className,
ref,
...props
}: FieldLabelProps & { ref?: React.Ref<React.ComponentRef<typeof Label>> }) => {
const { id, invalid } = useFieldContext();
return (
<Label
className={cn(invalid && "text-destructive", className)}
htmlFor={id}
ref={ref}
{...props}
/>
);
};
FieldLabel.displayName = "FieldLabel";
/** Wraps the control and injects id plus aria wiring from the Field. */
export type FieldControlProps = {
children: React.ReactNode;
};
const FieldControl = ({
children,
ref,
}: FieldControlProps & { ref?: React.Ref<HTMLElement> }) => {
const { descriptionId, errorId, id, invalid } = useFieldContext();
const describedBy =
[descriptionId, invalid ? errorId : null].filter(Boolean).join(" ") ||
undefined;
return (
<Slot
aria-describedby={describedBy}
aria-invalid={invalid || undefined}
id={id}
ref={ref}
>
{children}
</Slot>
);
};
FieldControl.displayName = "FieldControl";
/** Helper text describing the field, announced to assistive tech. */
export type FieldDescriptionProps = React.ComponentPropsWithoutRef<"p">;
const FieldDescription = ({
className,
ref,
...props
}: FieldDescriptionProps & { ref?: React.Ref<HTMLParagraphElement> }) => {
const { descriptionId } = useFieldContext();
return (
<p
className={cn("text-sm text-muted-foreground", className)}
id={descriptionId}
ref={ref}
{...props}
/>
);
};
FieldDescription.displayName = "FieldDescription";
/** Validation message that renders when children are present. */
export type FieldErrorProps = React.ComponentPropsWithoutRef<"p">;
const FieldError = ({
children,
className,
ref,
...props
}: FieldErrorProps & { ref?: React.Ref<HTMLParagraphElement> }) => {
const { errorId } = useFieldContext();
if (!children) {
return null;
}
return (
<p
className={cn("text-sm font-medium text-destructive", className)}
id={errorId}
ref={ref}
role="alert"
{...props}
>
{children}
</p>
);
};
FieldError.displayName = "FieldError";
export {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
fieldVariants,
};
Stories
Explorez chaque variante et etat dans le Storybook interactif :
Apercu
Basculez entre clair et sombre pour inspecter l'apercu Storybook integre.