Open Benjamin-Lee opened 1 year ago
This is great @Benjamin-Lee! Thank you so much for contributing 😀
I've had more people asking for better forms support, but haven't had a chance to look into it yet. And to be honest it might still be a few days/weeks/years until I do.
Happy to help. I ended up changing my strategy for doing this to something so much easier: using cva
and just pulling the styles out into an input variant along with some other form-related styles into a file:
import { cva } from "class-variance-authority"
import { cn } from "src/lib/utils"
export const inputVariants = cva(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
)
export const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
{
variants: {
error: {
true: "text-destructive",
},
},
}
)
export const fieldErrorVariants = cva(
"block text-[0.8rem] font-medium text-destructive"
)
export const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
return <div ref={ref} className={cn("space-y-2", className)} {...props} />
})
FormItem.displayName = "FormItem"
Then, building a form is as easy as:
<Form onSubmit={onSubmit} className="space-y-6">
<div className="flex justify-between">
<FormItem>
<Label
name="email"
className={labelVariants()}
errorClassName={labelVariants({ error: true })}
>
First Name
</Label>
<TextField
name="firstName"
className={inputVariants()}
validation={{
required: {
value: true,
message: 'First name is required',
},
}}
autoComplete="given-name"
/>
<FieldError name="firstName" className={fieldErrorVariants()} />
</FormItem>
<FormItem>
<Label
name="lastName"
className={labelVariants()}
errorClassName={labelVariants({ error: true })}
>
Last Name
</Label>
<TextField
name="lastName"
className={inputVariants()}
validation={{
required: {
value: true,
message: 'Last name is required',
},
}}
autoComplete="family-name"
/>
<FieldError name="email" className={fieldErrorVariants()} />
</FormItem>
</div>
<FormItem>
<Label
name="email"
className={labelVariants()}
errorClassName={labelVariants({ error: true })}
>
Email address
</Label>
<EmailField
name="email"
className={inputVariants()}
ref={emailRef}
validation={{
required: {
value: true,
message: 'Email is required',
},
}}
/>
<FieldError name="email" className={fieldErrorVariants()} />
</FormItem>
<FormItem>
<Label
name="password"
className={labelVariants()}
errorClassName={labelVariants({ error: true })}
>
Password
</Label>
<PasswordField
name="password"
className={inputVariants()}
autoComplete="current-password"
validation={{
required: {
value: true,
message: 'Password is required',
},
minLength: {
value: 8,
message: 'Password must be at least 8 characters',
},
}}
/>
<FieldError name="password" className={fieldErrorVariants()} />
</FormItem>
<div>
<Submit className={cn(buttonVariants(), 'w-full')}>Login</Submit>
</div>
</Form>
Hello, I was wondering if you have a solution for the Checkbox and Switch components?
Shadcn generates more than just a single component (e.g., a button with an icon inside) for the Checkbox/Switch, so I can't simply apply the class (generated by cva) to Redwood's CheckboxField component. I also tried using Redwood's useRegister
, but it doesn't seem to work with the Shadcn component either
@Quelu I've just used shad's <Switch />
component directly
<FormField
control={form.control}
name="fanControl"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between">
<div className="space-y-0.5">
<FormLabel className="text-base">Fan Control</FormLabel>
<FormDescription>
Changes will apply to next refresh cycle
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
It works, but you don't get the full RW form integration with its error handling unfortunately
I want a better experience, but haven't prioritized that yet
Thank you so much for the response!
However, it doesn't work with the Checkbox component when it's set up to have multiple values other than a boolean.
Based on what you provided, I was able to better understand how Shadcn works with react-hook-form. I was then able to create these components, which I can now directly use in my Redwood forms:
export const CheckboxField = ({
label,
value,
name,
}: InputFieldProps & {
label: React.ReactNode;
value: string;
}) => {
return (
<Controller
name={name}
render={({ field }) => (
<div className="flex gap-4" key={name}>
<Checkbox
id={`checkbox-${value}`}
checked={field.value?.includes(value)}
onCheckedChange={(checked) => {
return checked
? field.onChange([...(field?.value || []), value])
: field.onChange(
field.value?.filter((newValue) => newValue !== value)
);
}}
/>
<ShadLabel htmlFor={`checkbox-${value}`}>{label}</ShadLabel>
</div>
)}
/>
);
};
export const CheckboxGroupField = ({
name,
options,
validation,
}: InputFieldProps & {
options: { label: React.ReactNode; value: string }[];
}) => {
return (
<Controller
name={name}
rules={validation}
render={() => (
<>
{options.map((option) => (
<CheckboxField
key={option.value}
label={option.label}
value={option.value}
name={name}
/>
))}
</>
)}
/>
);
};
export const SwitchField = ({
label,
name,
validation,
defaultValue,
}: InputFieldProps & {
label?: React.ReactNode;
}) => {
return (
<Controller
name={name}
rules={validation}
defaultValue={defaultValue}
render={({ field }) => (
<div className="flex gap-4" key={name}>
<Switch
id={`switch-${name}`}
checked={field.value}
onCheckedChange={field.onChange}
/>
{label && <ShadLabel htmlFor={`switch-${name}`}>{label}</ShadLabel>}
</div>
)}
/>
);
};
I'm trying to use shadcn/ui inside a Redwood project while also using Redwood Forms. It would be great if there were a way for the
<Input>
component from shadcn/ui to wrap the Redwood<TextField>
and its siblings. Here's my first pass at it if it's of any help: