Component Library
Browse and explore components
Repeater Forms
A form component for adding multiple entries in repeatable structure.
Ecomm Product Form
Component Code
Copy Code
"use client";
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Icon } from "@iconify/react";
function EcommRepeaterFormCode() {
const itemSchema = z.object({
itemName: z.string({ error: "Item Name is required." }).min(1, "Item Name cannot be empty."),
quantity: z.number({ error: "Quantity is required." })
.int("Quantity must be an integer.")
.min(1, "Quantity must be at least 1."),
unitPrice: z.number({ error: "Unit Price is required." })
.min(0.01, "Unit Price must be positive."),
});
const formSchema = z.object({
items: z.array(itemSchema).min(1, "At least one item is required."),
});
const defaultValues = {
items: [{ itemName: "", quantity: 1, unitPrice: 0.01 }],
};
const form = useForm<any>({
resolver: zodResolver(formSchema),
defaultValues,
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "items",
});
const onSubmit = (data: any) => {
alert(JSON.stringify(data, null, 2));
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 max-w-full">
<div>
{fields.map((field, index) => (
<div key={field.id} className="flex flex-col sm:flex-row gap-4 mb-4 p-4 border rounded-md">
<FormField
control={form.control}
name={`items.${index}.itemName`}
render={({ field: itemField }: any) => (
<FormItem className="w-full">
<FormLabel>Item Name</FormLabel>
<FormControl>
<Input placeholder="e.g., Laptop" {...itemField} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`items.${index}.quantity`}
render={({ field: itemField }: any) => (
<FormItem className="w-full">
<FormLabel>Quantity</FormLabel>
<FormControl>
<Input
type="number"
placeholder="1"
{...itemField}
value={itemField.value === undefined || itemField.value === null ? "" : String(itemField.value)}
onChange={(event) => itemField.onChange(event.target.value === "" ? undefined : +event.target.value)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`items.${index}.unitPrice`}
render={({ field: itemField }: any) => (
<FormItem className="w-full">
<FormLabel>Unit Price</FormLabel>
<FormControl>
<Input
type="number"
placeholder="100.00"
{...itemField}
value={itemField.value === undefined || itemField.value === null ? "" : String(itemField.value)}
onChange={(event) => itemField.onChange(event.target.value === "" ? undefined : +event.target.value)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex gap-2 items-end">
<Button
type="button"
onClick={() => append({ itemName: "", quantity: 1, unitPrice: 0.01 })}
variant="outline"
className="w-fit"
>
<Icon icon="lucide:plus" className="h-4 w-4" />
</Button>
{fields.length > 1 && (
<Button
type="button"
variant="destructive"
onClick={() => remove(index)}
className="w-fit text-white"
>
<Icon icon="lucide:trash-2" className="h-4 w-4" />
</Button>
)}
</div>
</div>
))}
</div>
<p className="text-sm font-medium text-destructive mt-2">{form.formState.errors.items?.message as any}</p>
<div className="flex items-center gap-4">
<Button type="submit" className="w-fit">
Submit
</Button>
</div>
</form>
</Form>
);
}
export default EcommRepeaterFormCode;
Course Form
Component Code
Copy Code
"use client";
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";
import * as z from "zod";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Icon } from "@iconify/react";
function CourseRepeaterFormCode() {
const formSchema = z.object({
courses: z.array(
z.object({
courseName: z.string({ error: "Course Name is required." }).min(1, "Course Name is required."),
credits: z.number({ error: "Credits is required." })
.int("Credits must be an integer.")
.min(1, "Minimum credits is 1.")
.max(6, "Maximum credits is 6."),
})
).min(1, "At least one course is required."),
});
type FormValues = z.infer<typeof formSchema>;
const defaultValues: FormValues = {
courses: [{ courseName: "", credits: 0 }],
};
const form = useForm<any>({
resolver: zodResolver(formSchema),
defaultValues,
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "courses",
});
const onSubmit = async (data: any) => {
alert(JSON.stringify(data, null, 2));
};
return (
<div className="max-w-full">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<div>
{fields.map((field, index) => (
<div key={field.id} className="flex flex-col sm:flex-row gap-4 mb-4 p-4 border rounded-md">
<FormField
control={form.control}
name={`courses.${index}.courseName`}
render={({ field: courseNameField }) => (
<FormItem className="flex-grow">
<FormLabel>Course Name</FormLabel>
<FormControl>
<Input placeholder="Introduction to React" {...courseNameField} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`courses.${index}.credits`}
render={({ field: creditsField }) => (
<FormItem className="w-full sm:w-auto">
<FormLabel>Credits</FormLabel>
<FormControl>
<Input
type="number"
placeholder="3"
{...creditsField}
value={creditsField.value === undefined || creditsField.value === null ? "" : String(creditsField.value)}
onChange={event => creditsField.onChange(event.target.value === "" ? undefined : +event.target.value)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="button"
variant="outline"
onClick={() => append({ courseName: "", credits: 0 })}
className="mt-auto self-start sm:self-end"
>
<Icon icon="lucide:plus" className="h-4 w-4" />
</Button>
{fields.length > 1 && (
<Button
type="button"
variant="destructive"
onClick={() => remove(index)}
className="mt-auto self-start sm:self-end text-white"
>
<Icon icon="lucide:trash" className="h-4 w-4" />
</Button>
)}
</div>
))}
{form.formState.errors.courses && (
<p className="text-sm font-medium text-destructive mt-2">{form.formState.errors.courses?.message as any}</p>
)}
</div>
<Button type="submit" className="mt-2">Submit</Button>
</form>
</Form>
</div>
);
}
export default CourseRepeaterFormCode;
Employee Form
Component Code
Copy Code
"use client";
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { Icon } from "@iconify/react";
function EmployeeRepeaterFormCode() {
const teamMemberSchema = z.object({
name: z.string().min(1, { error: "Team member name is required." }),
role: z.string().min(1, { error: "Role is required." }),
email: z.string().email({ error: "Please enter a valid email address." }),
});
const formSchema = z.object({
teamMembers: z.array(teamMemberSchema).min(2, { error: "At least 2 team members are required." }),
});
const defaultValues = {
teamMembers: [
{ name: "", role: "", email: "" },
{ name: "", role: "", email: "" },
],
};
const form = useForm<any>({
resolver: zodResolver(formSchema),
defaultValues,
mode: "onChange",
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "teamMembers",
});
function onSubmit(data: any) {
alert(JSON.stringify(data, null, 2));
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8 max-w-full">
<div className="space-y-4">
{fields.map((field: any, index: number) => (
<div key={field.id} className="flex flex-col sm:flex-row gap-4 items-end border p-4 rounded-md">
<FormField
control={form.control}
name={`teamMembers.${index}.name`}
render={({ field: memberNameField }: any) => (
<FormItem className="w-full">
<FormLabel>Team Member Name</FormLabel>
<FormControl>
<Input placeholder="John Doe" {...memberNameField} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`teamMembers.${index}.role`}
render={({ field: memberRoleField }: any) => (
<FormItem className="w-full">
<FormLabel>Role</FormLabel>
<FormControl>
<Input placeholder="Developer" {...memberRoleField} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`teamMembers.${index}.email`}
render={({ field: memberEmailField }: any) => (
<FormItem className="w-full">
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="john.doe@example.com" {...memberEmailField} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{fields.length > 1 && (
<Button
type="button"
variant="destructive"
onClick={() => remove(index)}
className="mt-2 md:mt-0 text-white"
>
<Icon icon="mdi:trash-can-outline" className="h-4 w-4" />
</Button>
)}
</div>
))}
</div>
{form.formState.errors.teamMembers && (
<p className="text-sm font-medium text-destructive mt-2">
{form.formState.errors.teamMembers.message as any}
</p>
)}
<div className="flex justify-between gap-2">
<Button type="submit">Submit</Button>
<Button
type="button"
variant="outline"
className=""
onClick={() => append({ name: "", role: "", email: "" })}
>
<Icon icon="mdi:plus" className="mr-2 h-4 w-4" />
Add Team Member
</Button>
</div>
</form>
</Form>
);
}
export default EmployeeRepeaterFormCode;
Daily Activity Form
Component Code
Copy Code
"use client";
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Icon } from "@iconify/react";
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
function DailyActivityRepeaterFormCode() {
const activitySchema = z.object({
time: z.string({ error: "Time is required." })
.regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Time must be in HH:mm format (e.g., 09:30)."),
activityTitle: z.string({ error: "Activity Title is required." })
.min(1, "Activity Title cannot be empty."),
});
const formSchema = z.object({
activities: z.array(activitySchema)
.min(1, "Please add at least one activity."),
});
const defaultValues = {
activities: [
{ time: "09:00", activityTitle: "Morning Meeting" }
],
};
const form = useForm<any>({
resolver: zodResolver(formSchema),
defaultValues,
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "activities",
});
const onSubmit = async (data: any) => {
alert(JSON.stringify(data, null, 2));
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 max-w-full">
{fields.map((item, index) => (
<div key={item.id} className="flex flex-col sm:flex-row gap-4 border p-4 rounded-md">
<div className="flex-grow grid grid-cols-1 sm:grid-cols-2 gap-4">
<FormField
control={form.control}
name={`activities.${index}.time`}
render={({ field }) => (
<FormItem>
<FormLabel>Time (HH:mm)</FormLabel>
<FormControl>
<Input
type="time"
placeholder="HH:mm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`activities.${index}.activityTitle`}
render={({ field }) => (
<FormItem>
<FormLabel>Activity Title</FormLabel>
<FormControl>
<Input
type="text"
placeholder="e.g., Standup Meeting"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex items-end justify-end gap-2 sm:justify-start">
<Button
type="button"
onClick={() => append({ time: "", activityTitle: "" })}
className="w-10 h-10"
>
<Icon icon="mdi:plus" className="h-4 w-4 " />
</Button>
{fields.length > 1 && (
<Button
type="button"
variant="destructive"
size="icon"
onClick={() => remove(index)}
className="w-10 h-10 text-white"
>
<Icon icon="mdi:trash-can-outline" className="h-4 w-4" />
<span className="sr-only">Remove activity</span>
</Button>
)}
</div>
</div>
))}
{form.formState.errors.activities && (
<p className="text-sm font-medium text-destructive mt-2">{form.formState.errors.activities.message as any}</p>
)}
<div className="flex items-center gap-4">
<Button type="submit" className="w-fit mt-0">
Submit
</Button>
</div>
</form>
</Form>
);
}
export default DailyActivityRepeaterFormCode;