Skip to content

Instantly share code, notes, and snippets.

@allipiopereira
Created August 22, 2024 19:22
Show Gist options
  • Save allipiopereira/96c6a9f032d4c3567ace0ae90c71b698 to your computer and use it in GitHub Desktop.
Save allipiopereira/96c6a9f032d4c3567ace0ae90c71b698 to your computer and use it in GitHub Desktop.
"use client";
import { defineStepper } from "@stepperize/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm, FormProvider, useFormContext, SubmitHandler } from "react-hook-form";
import * as z from "zod";
import React, { createContext, useContext, useState, ReactNode } from "react";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
export const Stepper = defineStepper(
{ id: "general-info", title: "Informações Gerais", description: "Preencha as informações básicas do aluno" },
{ id: "address", title: "Endereço", description: "Preencha o endereço do aluno" },
{ id: "review", title: "Revisão", description: "Revise todas as informações do aluno" },
);
const generalInfoFormSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
});
const addressFormSchema = z.object({
address: z.string().min(5, {
message: "Address must be at least 5 characters.",
}),
});
type GeneralInfoFormData = z.infer<typeof generalInfoFormSchema>;
type AddressFormData = z.infer<typeof addressFormSchema>;
type FormData = GeneralInfoFormData & AddressFormData;
interface FormDataContextType {
formData: FormData;
setFormData: React.Dispatch<React.SetStateAction<FormData>>;
}
const FormDataContext = createContext<FormDataContextType | null>(null);
export function GeneralInfoForm() {
const stepper = Stepper.useStepper();
const context = useContext(FormDataContext);
if (!context) throw new Error("FormDataContext not found");
const { formData, setFormData } = context;
const formMethods = useForm<GeneralInfoFormData>({
resolver: zodResolver(generalInfoFormSchema),
defaultValues: {
username: formData.username,
},
});
const onSubmit: SubmitHandler<GeneralInfoFormData> = (data) => {
setFormData((prevData) => ({ ...prevData, ...data }));
stepper.next();
};
return (
<FormProvider {...formMethods}>
<Form {...formMethods}>
<form id={`form-${stepper.current.id}`} onSubmit={formMethods.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={formMethods.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</FormProvider>
);
}
export function AddressForm() {
const stepper = Stepper.useStepper();
const context = useContext(FormDataContext);
if (!context) throw new Error("FormDataContext not found");
const { formData, setFormData } = context;
const formMethods = useForm<AddressFormData>({
resolver: zodResolver(addressFormSchema),
defaultValues: {
address: formData.address,
},
});
const onSubmit: SubmitHandler<AddressFormData> = (data) => {
setFormData((prevData) => ({ ...prevData, ...data }));
stepper.next();
};
return (
<FormProvider {...formMethods}>
<Form {...formMethods}>
<form id={`form-${stepper.current.id}`} onSubmit={formMethods.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={formMethods.control}
name="address"
render={({ field }) => (
<FormItem>
<FormLabel>Address</FormLabel>
<FormControl>
<Input placeholder="123 Main St" {...field} />
</FormControl>
<FormDescription>
This is your address.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</FormProvider>
);
}
export function ReviewForm() {
const stepper = Stepper.useStepper();
const context = useContext(FormDataContext);
if (!context) throw new Error("FormDataContext not found");
const { formData } = context;
async function createUser() {
console.log("Creating user with data:", formData);
}
return (
<div>
<h1>Review</h1>
<div className="flex">Username: {formData.username}</div>
<div className="flex">Address: {formData.address}</div>
<button onClick={createUser}>Create User</button>
</div>
);
}
export const MySteps = () => {
const stepper = Stepper.useStepper();
return (
<>
{stepper.when("general-info", (step) => (
<GeneralInfoForm />
))}
{stepper.when("address", (step) => (
<AddressForm />
))}
{stepper.when("review", (step) => (
<ReviewForm />
))}
</>
);
};
export const MyActions = () => {
const stepper = Stepper.useStepper();
return (
<div className="flex items-center gap-2">
<button onClick={stepper.prev} disabled={stepper.isFirst}>
Previous
</button>
<button type="submit" form={`form-${stepper.current.id}`} disabled={stepper.isLast}>
{stepper.when(
"review",
() => "Finish",
() => "Next"
)}
</button>
</div>
);
};
export const MyScopedStepper = () => {
const [formData, setFormData] = useState<FormData>({
username: "",
address: "",
});
return (
<FormDataContext.Provider value={{ formData, setFormData }}>
<Stepper.Scoped>
<MySteps />
<MyActions />
</Stepper.Scoped>
</FormDataContext.Provider>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment