{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"form","title":"Form","type":"registry:ui","description":"Building forms with React Hook Form and Zod.","dependencies":["@radix-ui/react-label","@radix-ui/react-slot","lucide-react","react-hook-form"],"registryDependencies":["utils","https://fysk.dev/r/fysk-provider.json"],"files":[{"path":"fysk/form.tsx","type":"registry:ui","content":"\"use client\"\r\nimport * as React from \"react\"\r\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\r\nimport { Slot } from \"@radix-ui/react-slot\"\r\nimport { Loader2, CheckCircle2, XCircle, X } from \"lucide-react\"\r\nimport {\r\n    useFormContext,\r\n    Controller,\r\n    FormProvider,\r\n    type ControllerProps,\r\n    type FieldPath,\r\n    type FieldValues\r\n} from \"react-hook-form\"\r\nimport { cn } from \"@/lib/utils\"\r\nimport { useFyskConfig } from \"@/components/fysk-provider\"\r\n\r\n\r\n// Types\r\n\r\ntype FormState = \"idle\" | \"loading\" | \"success\" | \"error\"\r\n\r\ninterface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {\r\n    state?: FormState\r\n    error?: React.ReactNode // For manual/server-side error messages\r\n    afterSubmission?: React.ReactNode\r\n    onCloseAfterSubmission?: () => void\r\n    LoadingIcon?: React.ReactNode\r\n    SuccessIcon?: React.ReactNode\r\n    ErrorIcon?: React.ReactNode\r\n    CloseIcon?: React.ReactNode\r\n}\r\n\r\n// Context Types\r\n\r\ntype FormFieldContextValue<\r\n    TFieldValues extends FieldValues = FieldValues,\r\n    TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n> = {\r\n    name: TName\r\n}\r\n\r\ntype FormItemContextValue = {\r\n    id: string\r\n}\r\n\r\n// Contexts\r\n\r\nconst FormFieldContext = React.createContext<FormFieldContextValue>(\r\n    {} as FormFieldContextValue\r\n)\r\n\r\nconst FormItemContext = React.createContext<FormItemContextValue>({ id: \"\" })\r\n\r\n// useFormField Hook\r\n\r\n/**\r\n * Hook to access the current form field's state and metadata.\r\n * Must be used within a `FormField` and `FormItem` component.\r\n *\r\n * @returns An object containing:\r\n * - `id` - The unique ID for the form item\r\n * - `name` - The field name registered with react-hook-form\r\n * - `formItemId` - ID for the form item element\r\n * - `formDescriptionId` - ID for the description element (for aria-describedby)\r\n * - `formMessageId` - ID for the error message element\r\n * - `error` - The current field error (if any)\r\n * - `invalid` - Whether the field has a validation error\r\n * - `isDirty` - Whether the field value has been modified\r\n * - `isTouched` - Whether the field has been touched/focused\r\n *\r\n * @example\r\n * ```tsx\r\n * const CustomInput = () => {\r\n *   const { error, formItemId } = useFormField()\r\n *   return (\r\n *     <input\r\n *       id={formItemId}\r\n *       className={error ? \"border-red-500\" : \"\"}\r\n *     />\r\n *   )\r\n * }\r\n * ```\r\n */\r\nconst useFormField = () => {\r\n    const fieldContext = React.useContext(FormFieldContext)\r\n    const itemContext = React.useContext(FormItemContext)\r\n    const { getFieldState, formState } = useFormContext()\r\n\r\n    if (!fieldContext.name) {\r\n        throw new Error(\"useFormField must be used within a FormField component\")\r\n    }\r\n\r\n    const fieldState = getFieldState(fieldContext.name, formState)\r\n    const { id } = itemContext\r\n\r\n    return {\r\n        id,\r\n        name: fieldContext.name,\r\n        formItemId: `${id}-form-item`,\r\n        formDescriptionId: `${id}-form-item-description`,\r\n        formMessageId: `${id}-form-item-message`,\r\n        ...fieldState,\r\n    }\r\n}\r\n\r\n// Form Component\r\n\r\n/**\r\n * A form wrapper component with built-in state management for loading,\r\n * success, and error states. Includes overlay displays for submission feedback.\r\n *\r\n * @param state - The current form state: \"idle\" | \"loading\" | \"success\" | \"error\"\r\n * @param afterSubmission - Content to display after successful form submission\r\n * @param onCloseAfterSubmission - Callback when the after-submission overlay is closed\r\n * @param LoadingIcon - Custom loading icon (defaults to Loader2)\r\n * @param SuccessIcon - Custom success icon (defaults to CheckCircle2)\r\n * @param ErrorIcon - Custom error icon (defaults to XCircle)\r\n * @param CloseIcon - Custom close icon (defaults to X)\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage\r\n * <Form onSubmit={handleSubmit}>\r\n *   <FormItem>\r\n *     <FormLabel>Email</FormLabel>\r\n *     <FormControl>\r\n *       <Input type=\"email\" />\r\n *     </FormControl>\r\n *   </FormItem>\r\n *   <Button type=\"submit\">Submit</Button>\r\n * </Form>\r\n *\r\n * // With react-hook-form and Zod validation\r\n * const formSchema = z.object({\r\n *   email: z.string().email(\"Invalid email\"),\r\n *   password: z.string().min(8, \"Min 8 characters\"),\r\n * })\r\n *\r\n * function LoginForm() {\r\n *   const [formState, setFormState] = useState<\"idle\" | \"loading\" | \"success\" | \"error\">(\"idle\")\r\n *   const form = useForm<z.infer<typeof formSchema>>({\r\n *     resolver: zodResolver(formSchema),\r\n *     defaultValues: { email: \"\", password: \"\" },\r\n *   })\r\n *\r\n *   const onSubmit = async (data) => {\r\n *     setFormState(\"loading\")\r\n *     try {\r\n *       await submitForm(data)\r\n *       setFormState(\"success\")\r\n *     } catch {\r\n *       setFormState(\"error\")\r\n *     }\r\n *   }\r\n *\r\n *   return (\r\n *     <FormProvider {...form}>\r\n *       <Form\r\n *         state={formState}\r\n *         afterSubmission=\"Thank you for signing up!\"\r\n *         onSubmit={form.handleSubmit(onSubmit)}\r\n *       >\r\n *         <FormField\r\n *           control={form.control}\r\n *           name=\"email\"\r\n *           render={({ field }) => (\r\n *             <FormItem>\r\n *               <FormLabel>Email</FormLabel>\r\n *               <FormControl>\r\n *                 <Input placeholder=\"you@example.com\" {...field} />\r\n *               </FormControl>\r\n *               <FormMessage />\r\n *             </FormItem>\r\n *           )}\r\n *         />\r\n *         <Button type=\"submit\">Sign In</Button>\r\n *       </Form>\r\n *     </FormProvider>\r\n *   )\r\n * }\r\n * ```\r\n */\r\nconst Form = React.forwardRef<HTMLFormElement, FormProps>(\r\n    (props, ref) => {\r\n        const {\r\n            className,\r\n            state,\r\n            afterSubmission,\r\n            onCloseAfterSubmission,\r\n            children,\r\n            LoadingIcon,\r\n            SuccessIcon,\r\n            ErrorIcon,\r\n            CloseIcon,\r\n            error: customError,\r\n            ...remainingProps\r\n        } = props;\r\n\r\n        const [showOverlay, setShowOverlay] = React.useState(false)\r\n        const config = useFyskConfig()\r\n\r\n        React.useEffect(() => {\r\n            if (state === \"success\" && afterSubmission) {\r\n                setShowOverlay(true)\r\n            }\r\n        }, [state, afterSubmission])\r\n\r\n        const handleClose = () => {\r\n            setShowOverlay(false)\r\n            onCloseAfterSubmission?.()\r\n        }\r\n\r\n        const finalLoadingIcon = LoadingIcon || config.icons?.loading || <Loader2 className=\"animate-spin\" />\r\n        const finalCloseIcon = CloseIcon || config.icons?.close || <X />\r\n        const finalSuccessIcon = SuccessIcon || config.icons?.success || <CheckCircle2 />\r\n        const finalErrorIcon = ErrorIcon || config.icons?.error || <XCircle />\r\n\r\n        // Extract react-hook-form props to avoid passing to native <form>\r\n        const {\r\n            control,\r\n            handleSubmit,\r\n            register,\r\n            watch,\r\n            setValue,\r\n            getValues,\r\n            reset,\r\n            resetField,\r\n            unregister,\r\n            trigger,\r\n            setError,\r\n            clearErrors,\r\n            setFocus,\r\n            getFieldState,\r\n            formState,\r\n            subscribe,\r\n            ...htmlProps\r\n        } = remainingProps as any;\r\n\r\n        // Detect if we have a form instance\r\n        const isRHF = !!control && !!handleSubmit;\r\n        const methods = isRHF ? {\r\n            control, handleSubmit, register, watch, setValue, getValues,\r\n            reset, resetField, unregister, trigger, setError, clearErrors,\r\n            setFocus, getFieldState, formState\r\n        } : null;\r\n\r\n        const formContent = (\r\n            <div className=\"relative w-full\">\r\n                <form\r\n                    ref={ref}\r\n                    className={cn(\r\n                        \"space-y-6 transition-all duration-300\",\r\n                        state === \"loading\" && \"pointer-events-none opacity-40\",\r\n                        className\r\n                    )}\r\n                    {...htmlProps}\r\n                >\r\n                    {children}\r\n                </form>\r\n\r\n                {/* Loading State - Minimal Dark Overlay */}\r\n                {state === \"loading\" && (\r\n                    <div className=\"absolute inset-x-[-4px] inset-y-[-4px] flex items-center justify-center bg-black/5 dark:bg-black/20 rounded-lg z-10 animate-in fade-in duration-200\">\r\n                        <div className=\"flex flex-col items-center gap-2 p-4 bg-background border border-border/50 shadow-lg rounded-xl\">\r\n                            <span className=\"[&_svg]:size-6 text-primary\">{finalLoadingIcon}</span>\r\n                            <span className=\"text-[13px] font-medium text-muted-foreground\">Please wait...</span>\r\n                        </div>\r\n                    </div>\r\n                )}\r\n\r\n                {/* Success Feedback - Clean Layout */}\r\n                {showOverlay && afterSubmission && state === \"success\" && (\r\n                    <div\r\n                        className=\"absolute inset-0 flex items-center justify-center bg-black/40 dark:bg-black/60 rounded-lg z-20 animate-in fade-in zoom-in-95 duration-300\"\r\n                        role=\"dialog\"\r\n                        aria-modal=\"true\"\r\n                    >\r\n                        <div className=\"relative max-w-sm w-[calc(100%-2rem)] p-8 bg-card rounded-2xl shadow-2xl border border-border/50 text-center animate-in zoom-in-95 duration-300\">\r\n                            <button\r\n                                type=\"button\"\r\n                                onClick={handleClose}\r\n                                className=\"absolute top-4 right-4 p-2 rounded-full hover:bg-accent text-muted-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring\"\r\n                                aria-label=\"Close\"\r\n                                autoFocus\r\n                            >\r\n                                <span className=\"[&_svg]:size-4\">{finalCloseIcon}</span>\r\n                            </button>\r\n\r\n                            <div className=\"flex justify-center mb-6\">\r\n                                <div className=\"p-4 bg-emerald-500/10 rounded-full ring-8 ring-emerald-500/5\">\r\n                                    <span className=\"[&_svg]:size-10 text-emerald-500\">{finalSuccessIcon}</span>\r\n                                </div>\r\n                            </div>\r\n\r\n                            <div className=\"space-y-2\">\r\n                                <h4 className=\"text-xl font-semibold tracking-tight\">Submission Successful</h4>\r\n                                <div className=\"text-sm text-muted-foreground leading-relaxed\">\r\n                                    {typeof afterSubmission === \"string\" ? (\r\n                                        <p>{afterSubmission}</p>\r\n                                    ) : (\r\n                                        afterSubmission\r\n                                    )}\r\n                                </div>\r\n                            </div>\r\n                        </div>\r\n                    </div>\r\n                )}\r\n\r\n                {/* Global Error Summary */}\r\n                {(state === \"error\" || customError) && (\r\n                    <div\r\n                        className=\"mt-6 p-4 bg-destructive/5 border border-destructive/20 rounded-xl flex items-start gap-3 animate-in slide-in-from-top-2 duration-300\"\r\n                        role=\"alert\"\r\n                    >\r\n                        <span className=\"mt-0.5 [&_svg]:size-5 text-destructive shrink-0\">{finalErrorIcon}</span>\r\n                        <div className=\"space-y-1\">\r\n                            <p className=\"text-sm font-semibold text-destructive leading-tight\">Something went wrong</p>\r\n                            <div className=\"text-[13px] text-destructive/80 leading-relaxed\">\r\n                                {customError || \"There was an error submitting the form. Please check your data and try again.\"}\r\n                            </div>\r\n                        </div>\r\n                    </div>\r\n                )}\r\n            </div>\r\n        )\r\n\r\n        if (isRHF && methods) {\r\n            return (\r\n                <FormProvider {...(methods as any)}>\r\n                    {formContent}\r\n                </FormProvider>\r\n            )\r\n        }\r\n\r\n        return formContent;\r\n    }\r\n)\r\nForm.displayName = \"Form\"\r\n\r\n// FormField Component\r\n\r\n/**\r\n * A wrapper around react-hook-form's Controller that provides context\r\n * to child components. Use this to connect form inputs to react-hook-form.\r\n *\r\n * @template TFieldValues - The type of your form values (inferred from Zod schema)\r\n * @template TName - The field path/name type\r\n *\r\n * @param control - The form control object from useForm()\r\n * @param name - The field name (must match a key in your form schema)\r\n * @param render - Render function that receives field props and state\r\n *\r\n * @example\r\n * ```tsx\r\n * import { useForm } from \"react-hook-form\"\r\n * import { zodResolver } from \"@hookform/resolvers/zod\"\r\n * import * as z from \"zod\"\r\n *\r\n * const schema = z.object({\r\n *   username: z.string().min(3, \"Username must be at least 3 characters\"),\r\n * })\r\n *\r\n * function MyForm() {\r\n *   const form = useForm<z.infer<typeof schema>>({\r\n *     resolver: zodResolver(schema),\r\n *   })\r\n *\r\n *   return (\r\n *     <FormProvider {...form}>\r\n *       <Form onSubmit={form.handleSubmit(onSubmit)}>\r\n *         <FormField\r\n *           control={form.control}\r\n *           name=\"username\"\r\n *           render={({ field }) => (\r\n *             <FormItem>\r\n *               <FormLabel>Username</FormLabel>\r\n *               <FormControl>\r\n *                 <Input {...field} />\r\n *               </FormControl>\r\n *               <FormDescription>\r\n *                 This will be your public display name.\r\n *               </FormDescription>\r\n *               <FormMessage />\r\n *             </FormItem>\r\n *           )}\r\n *         />\r\n *       </Form>\r\n *     </FormProvider>\r\n *   )\r\n * }\r\n * ```\r\n */\r\nconst FormField = <\r\n    TFieldValues extends FieldValues = FieldValues,\r\n    TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n>({\r\n    ...props\r\n}: ControllerProps<TFieldValues, TName>) => {\r\n    return (\r\n        <FormFieldContext.Provider value={{ name: props.name }}>\r\n            <Controller {...props} />\r\n        </FormFieldContext.Provider>\r\n    )\r\n}\r\n\r\n// FormItem Component\r\n\r\n/**\r\n * A container for a single form field. Provides spacing and context\r\n * for associating labels, descriptions, and error messages.\r\n *\r\n * @example\r\n * ```tsx\r\n * <FormItem>\r\n *   <FormLabel>Email</FormLabel>\r\n *   <FormControl>\r\n *     <Input type=\"email\" />\r\n *   </FormControl>\r\n *   <FormDescription>Enter your email address</FormDescription>\r\n *   <FormMessage />\r\n * </FormItem>\r\n * ```\r\n */\r\nconst FormItem = React.forwardRef<\r\n    HTMLDivElement,\r\n    React.HTMLAttributes<HTMLDivElement>\r\n>(({ className, ...props }, ref) => {\r\n    const id = React.useId()\r\n    return (\r\n        <FormItemContext.Provider value={{ id }}>\r\n            <div ref={ref} className={cn(\"space-y-2\", className)} {...props} />\r\n        </FormItemContext.Provider>\r\n    )\r\n})\r\nFormItem.displayName = \"FormItem\"\r\n\r\n// FormLabel Component\r\n\r\n/**\r\n * A label for form inputs. Automatically associates with the input\r\n * via the FormItem context.\r\n *\r\n * @example\r\n * ```tsx\r\n * <FormItem>\r\n *   <FormLabel>Password</FormLabel>\r\n *   <FormControl>\r\n *     <Input type=\"password\" />\r\n *   </FormControl>\r\n * </FormItem>\r\n * ```\r\n */\r\nconst FormLabel = React.forwardRef<\r\n    React.ComponentRef<typeof LabelPrimitive.Root>,\r\n    React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\r\n>(({ className, ...props }, ref) => {\r\n    const { id } = React.useContext(FormItemContext)\r\n\r\n    // Try to get error state if within FormField context\r\n    let hasError = false\r\n    try {\r\n        const { error } = useFormField()\r\n        hasError = !!error\r\n    } catch {\r\n        // Not within FormField context, that's okay for basic usage\r\n    }\r\n\r\n    return (\r\n        <LabelPrimitive.Root\r\n            ref={ref}\r\n            className={cn(\r\n                \"text-sm font-medium leading-normal peer-disabled:cursor-not-allowed peer-disabled:opacity-70\",\r\n                hasError && \"text-destructive\",\r\n                className\r\n            )}\r\n            htmlFor={`${id}-form-item`}\r\n            {...props}\r\n        />\r\n    )\r\n})\r\nFormLabel.displayName = \"FormLabel\"\r\n\r\n// FormControl Component\r\n\r\n/**\r\n * Wraps form inputs to provide proper accessibility attributes.\r\n * Uses Radix Slot to merge props with the child element.\r\n *\r\n * When used within a FormField, automatically adds:\r\n * - Unique ID for label association\r\n * - aria-describedby for description and error message\r\n * - aria-invalid when field has validation errors\r\n *\r\n * @example\r\n * ```tsx\r\n * <FormControl>\r\n *   <Input type=\"email\" placeholder=\"Enter your email\" />\r\n * </FormControl>\r\n *\r\n * // With react-hook-form field\r\n * <FormField\r\n *   control={form.control}\r\n *   name=\"email\"\r\n *   render={({ field }) => (\r\n *     <FormItem>\r\n *       <FormLabel>Email</FormLabel>\r\n *       <FormControl>\r\n *         <Input {...field} />\r\n *       </FormControl>\r\n *       <FormMessage />\r\n *     </FormItem>\r\n *   )}\r\n * />\r\n * ```\r\n */\r\nconst FormControl = React.forwardRef<\r\n    React.ComponentRef<typeof Slot>,\r\n    React.ComponentPropsWithoutRef<typeof Slot>\r\n>(({ ...props }, ref) => {\r\n    const { id } = React.useContext(FormItemContext)\r\n\r\n    // Try to get form field context for react-hook-form integration\r\n    let formFieldProps = {}\r\n    try {\r\n        const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\r\n        formFieldProps = {\r\n            id: formItemId,\r\n            \"aria-describedby\": !error\r\n                ? formDescriptionId\r\n                : `${formDescriptionId} ${formMessageId}`,\r\n            \"aria-invalid\": !!error,\r\n        }\r\n    } catch {\r\n        // Not within FormField context - use basic ID association\r\n        formFieldProps = {\r\n            id: `${id}-form-item`,\r\n            \"aria-describedby\": `${id}-form-item-description`,\r\n        }\r\n    }\r\n\r\n    return (\r\n        <Slot\r\n            ref={ref}\r\n            {...formFieldProps}\r\n            {...props}\r\n        />\r\n    )\r\n})\r\nFormControl.displayName = \"FormControl\"\r\n\r\n// FormDescription Component\r\n\r\n/**\r\n * Provides additional context or instructions for a form field.\r\n * Automatically associated with the input via aria-describedby.\r\n *\r\n * @example\r\n * ```tsx\r\n * <FormItem>\r\n *   <FormLabel>Password</FormLabel>\r\n *   <FormControl>\r\n *     <Input type=\"password\" />\r\n *   </FormControl>\r\n *   <FormDescription>\r\n *     Must be at least 8 characters with one uppercase letter.\r\n *   </FormDescription>\r\n * </FormItem>\r\n * ```\r\n */\r\nconst FormDescription = React.forwardRef<\r\n    HTMLParagraphElement,\r\n    React.HTMLAttributes<HTMLParagraphElement>\r\n>(({ className, ...props }, ref) => {\r\n    const { id } = React.useContext(FormItemContext)\r\n    return (\r\n        <p\r\n            ref={ref}\r\n            id={`${id}-form-item-description`}\r\n            className={cn(\"text-sm text-muted-foreground\", className)}\r\n            {...props}\r\n        />\r\n    )\r\n})\r\nFormDescription.displayName = \"FormDescription\"\r\n\r\n// FormMessage Component\r\n\r\n/**\r\n * Displays validation error messages for a form field.\r\n * When used within a FormField, automatically displays the\r\n * error message from react-hook-form validation.\r\n *\r\n * @param state - Visual state: \"error\" (red) or \"success\" (green). Defaults to \"error\".\r\n * @param children - Custom message content (overrides auto-detected error)\r\n *\r\n * @example\r\n * ```tsx\r\n * // Auto-display validation errors from react-hook-form\r\n * <FormField\r\n *   control={form.control}\r\n *   name=\"email\"\r\n *   render={({ field }) => (\r\n *     <FormItem>\r\n *       <FormLabel>Email</FormLabel>\r\n *       <FormControl>\r\n *         <Input {...field} />\r\n *       </FormControl>\r\n *       <FormMessage /> {/* Automatically shows Zod validation errors *\\/}\r\n *     </FormItem>\r\n *   )}\r\n * />\r\n *\r\n * // Manual message\r\n * <FormMessage state=\"success\">Email is available!</FormMessage>\r\n *\r\n * // Custom error message\r\n * <FormMessage>Please enter a valid email address</FormMessage>\r\n * ```\r\n */\r\nconst FormMessage = React.forwardRef<\r\n    HTMLParagraphElement,\r\n    React.HTMLAttributes<HTMLParagraphElement> & { state?: \"error\" | \"success\" }\r\n>(({ className, state = \"error\", children, ...props }, ref) => {\r\n    const { id } = React.useContext(FormItemContext)\r\n\r\n    // Try to get error from react-hook-form context\r\n    let errorMessage: string | undefined\r\n    let messageId = `${id}-form-item-message`\r\n\r\n    try {\r\n        const { error, formMessageId } = useFormField()\r\n        errorMessage = error?.message\r\n        messageId = formMessageId\r\n    } catch {\r\n        // Not within FormField context, that's okay\r\n    }\r\n\r\n    // Use children if provided, otherwise use error from form state\r\n    const body = children || errorMessage\r\n\r\n    if (!body) return null\r\n\r\n    return (\r\n        <p\r\n            ref={ref}\r\n            id={messageId}\r\n            aria-live=\"polite\"\r\n            className={cn(\r\n                \"text-[13px] font-medium leading-relaxed\",\r\n                state === \"error\" ? \"text-destructive\" : \"text-emerald-500\",\r\n                className\r\n            )}\r\n            {...props}\r\n        >\r\n            {body}\r\n        </p>\r\n    )\r\n})\r\nFormMessage.displayName = \"FormMessage\"\r\n\r\n// Exports\r\n\r\nexport {\r\n    Form,\r\n    FormField,\r\n    FormItem,\r\n    FormLabel,\r\n    FormControl,\r\n    FormDescription,\r\n    FormMessage,\r\n    FormProvider,\r\n    useFormField,\r\n}\r\n\r\n// Re-export types for convenience\r\nexport type { FormState, FormProps }\r\n"}]}