import * as React from "react" import { AlertCircleIcon } from "lucide-react" import { cn } from "@/lib/utils" export type FormFieldLayout = "vertical" | "horizontal" | "inline" export type FormFieldDescriptionPosition = "top" | "bottom" export type FormFieldShellProps = React.ComponentProps<"div"> & { label?: React.ReactNode description?: React.ReactNode error?: React.ReactNode success?: React.ReactNode loading?: boolean required?: boolean htmlFor?: string labelId?: string descriptionId?: string errorId?: string layout?: FormFieldLayout descriptionPosition?: FormFieldDescriptionPosition labelAction?: React.ReactNode requiredIndicator?: React.ReactNode errorIcon?: React.ReactNode showErrorIcon?: boolean disabled?: boolean readOnly?: boolean labelClassName?: string labelRowClassName?: string descriptionClassName?: string errorClassName?: string contentClassName?: string } export type FormFieldShellControlProps = Pick< FormFieldShellProps, | "label" | "description" | "success" | "loading" | "required" | "className" | "htmlFor" | "labelId" | "descriptionId" | "errorId" | "layout" | "descriptionPosition" | "labelAction" | "requiredIndicator" | "errorIcon" | "showErrorIcon" | "disabled" | "readOnly" | "labelClassName" | "labelRowClassName" | "descriptionClassName" | "errorClassName" | "contentClassName" > export type FormFieldMessageState = { description?: React.ReactNode error?: React.ReactNode } export type FormFieldResolvedIds = { labelId: string descriptionId: string errorId: string describedBy?: string } function resolveFormFieldIds(id: string, state: FormFieldMessageState = {}): FormFieldResolvedIds { const labelId = `${id}-label` const descriptionId = `${id}-description` const errorId = `${id}-error` const describedBy = [state.description ? descriptionId : null, state.error ? errorId : null] .filter(Boolean) .join(" ") || undefined return { labelId, descriptionId, errorId, describedBy } } const layoutClassName: Record = { vertical: "grid gap-2", horizontal: "grid gap-2 sm:grid-cols-[minmax(0,12rem)_1fr] sm:items-start sm:gap-5", inline: "flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4", } function FormFieldShell({ className, label, description, error, success, loading = false, required = false, htmlFor, labelId, descriptionId, errorId, layout = "vertical", descriptionPosition = "top", labelAction, requiredIndicator = *, errorIcon, showErrorIcon = true, disabled = false, readOnly = false, labelClassName, labelRowClassName, descriptionClassName, errorClassName, contentClassName, children, ...props }: FormFieldShellProps) { const hasHeader = Boolean(label || labelAction) const hasDescriptionTop = description && descriptionPosition === "top" const hasDescriptionBottom = description && descriptionPosition === "bottom" const resolvedErrorIcon = errorIcon ?? const header = hasHeader ? (
{label ? ( ) : ( )} {labelAction && (
{labelAction}
)}
) : null const descriptionNode = description ? (

{description}

) : null const content = (
{children}
) const errorNode = error ? ( ) : null const successNode = success && !error ? (

{success}

) : null const loadingNode = loading ? (

Loading...

) : null return (
{layout === "horizontal" ? ( <>
{header} {hasDescriptionTop && descriptionNode}
{content} {loadingNode} {hasDescriptionBottom && descriptionNode} {successNode} {errorNode}
) : layout === "inline" ? ( <> {header}
{hasDescriptionTop && descriptionNode} {content} {loadingNode} {hasDescriptionBottom && descriptionNode} {successNode} {errorNode}
) : ( <> {header} {hasDescriptionTop && descriptionNode} {content} {loadingNode} {hasDescriptionBottom && descriptionNode} {successNode} {errorNode} )}
) } export { FormFieldShell, resolveFormFieldIds }