Last active
September 29, 2023 18:19
-
-
Save devstojko/b461349507ff7aa9b5d38c011db8a922 to your computer and use it in GitHub Desktop.
remix-validated-form shadcn/ui
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type * as LabelPrimitive from "@radix-ui/react-label"; | |
import { ValidatedForm, useField } from "remix-validated-form"; | |
import React from "react"; | |
import { Label } from "./label"; | |
import { Slot } from "@radix-ui/react-slot"; | |
import { cn } from "~/lib/utils"; | |
const AppValidatedFormContext = React.createContext<{ id: string }>( | |
{} as { id: string } | |
); | |
interface CustomValidateFormProps | |
extends React.ComponentPropsWithoutRef<typeof ValidatedForm> { | |
id: string; | |
} | |
const AppValidatedForm = React.forwardRef< | |
React.ElementRef<typeof ValidatedForm>, | |
CustomValidateFormProps | |
>(({ ...props }, ref) => { | |
return ( | |
<AppValidatedFormContext.Provider value={{ id: props.id }}> | |
<ValidatedForm {...props} ref={ref} /> | |
</AppValidatedFormContext.Provider> | |
); | |
}); | |
AppValidatedForm.displayName = "appValidatedForm"; | |
type FormFieldContextValue = { | |
formId: string; | |
name: string; | |
}; | |
const FormFieldContext = React.createContext<FormFieldContextValue>( | |
{} as FormFieldContextValue | |
); | |
type AppValidatedFormFieldProps = { | |
name: string; | |
children: React.ReactNode; | |
}; | |
const AppValidatedFormField = (props: AppValidatedFormFieldProps) => { | |
const ctx = React.useContext(AppValidatedFormContext); | |
return ( | |
<FormFieldContext.Provider | |
value={{ | |
formId: ctx.id, | |
name: props.name, | |
}} | |
{...props} | |
/> | |
); | |
}; | |
type FormItemContextValue = { | |
id: string; | |
}; | |
const FormItemContext = React.createContext<FormItemContextValue>( | |
{} as FormItemContextValue | |
); | |
const AppValidatedFormItem = React.forwardRef< | |
HTMLDivElement, | |
React.HTMLAttributes<HTMLDivElement> | |
>(({ className, ...props }, ref) => { | |
const id = React.useId(); | |
return ( | |
<FormItemContext.Provider value={{ id }}> | |
<div ref={ref} className={cn("space-y-2", className)} {...props} /> | |
</FormItemContext.Provider> | |
); | |
}); | |
AppValidatedFormItem.displayName = "AppValidatedFormItem"; | |
const useAppValidatedFormField = () => { | |
const fieldContext = React.useContext(FormFieldContext); | |
const itemContext = React.useContext(FormItemContext); | |
const field = useField(fieldContext.name); | |
return { | |
id: itemContext.id, | |
name: fieldContext.name, | |
formItemId: `${itemContext.id}-form-item`, | |
formDescriptionId: `${itemContext.id}-form-item-description`, | |
formMessageId: `${itemContext.id}-form-item-message`, | |
...field, | |
}; | |
}; | |
const AppValidatedFormLabel = React.forwardRef< | |
React.ElementRef<typeof LabelPrimitive.Root>, | |
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> | |
>(({ className, ...props }, ref) => { | |
const { error, formItemId } = useAppValidatedFormField(); | |
return ( | |
<Label | |
ref={ref} | |
className={cn(error && "text-destructive", className)} | |
htmlFor={formItemId} | |
{...props} | |
/> | |
); | |
}); | |
AppValidatedFormLabel.displayName = "AppValidatedFormLabel"; | |
const AppValidatedFormControl = React.forwardRef< | |
React.ElementRef<typeof Slot>, | |
React.ComponentPropsWithoutRef<typeof Slot> | |
>(({ ...props }, ref) => { | |
const { error, formItemId, formDescriptionId, formMessageId, getInputProps } = | |
useAppValidatedFormField(); | |
return ( | |
<Slot | |
ref={ref} | |
id={formItemId} | |
aria-describedby={ | |
!error | |
? `${formDescriptionId}` | |
: `${formDescriptionId} ${formMessageId}` | |
} | |
aria-invalid={!!error} | |
{...getInputProps()} | |
{...props} | |
/> | |
); | |
}); | |
AppValidatedFormControl.displayName = "AppValidatedFormControl"; | |
const AppValidatedFormDescription = React.forwardRef< | |
HTMLParagraphElement, | |
React.HTMLAttributes<HTMLParagraphElement> | |
>(({ className, ...props }, ref) => { | |
const { formDescriptionId } = useAppValidatedFormField(); | |
return ( | |
<p | |
ref={ref} | |
id={formDescriptionId} | |
className={cn("text-sm text-muted-foreground", className)} | |
{...props} | |
/> | |
); | |
}); | |
AppValidatedFormDescription.displayName = "AppValidatedFormDescription"; | |
const AppValidatedFormMessage = React.forwardRef< | |
HTMLParagraphElement, | |
React.HTMLAttributes<HTMLParagraphElement> | |
>(({ className, children, ...props }, ref) => { | |
const { error, formMessageId } = useAppValidatedFormField(); | |
const body = error ? String(error) : children; | |
if (!body) { | |
return null; | |
} | |
return ( | |
<p | |
ref={ref} | |
id={formMessageId} | |
className={cn("text-sm font-medium text-destructive", className)} | |
{...props} | |
> | |
{body} | |
</p> | |
); | |
}); | |
AppValidatedFormMessage.displayName = "AppValidatedFormMessage"; | |
export { | |
AppValidatedForm, | |
AppValidatedFormItem, | |
AppValidatedFormLabel, | |
AppValidatedFormControl, | |
AppValidatedFormDescription, | |
AppValidatedFormMessage, | |
AppValidatedFormField, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I added App prefix for all exported components so it doesn't conflict with original one. You can rename it if you want.
Usage example: