Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Use Generic Type for Input Components #55

Merged
merged 1 commit into from
Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions frontend/src/components/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
"use client";
import { UseFormRegister } from "react-hook-form";
import { FieldValues, Path, UseFormRegister } from "react-hook-form";

import { cn } from "../lib/utils";

import OtherCheckbox from "./OtherCheckbox";
import { FormData } from "./StudentForm/types";

type CheckboxProps = {
type CheckboxProps<T extends FieldValues> = {
options: string[];
className?: string;
name: keyof FormData;
name: Path<T>;
defaultValue?: string[];
defaultOtherValue?: string;
register: UseFormRegister<FormData>;
register: UseFormRegister<T>;
};

export function Checkbox({
export function Checkbox<T extends FieldValues>({
options,
className,
name,
register,
defaultValue,
defaultOtherValue,
}: CheckboxProps) {
}: CheckboxProps<T>) {
return (
<div className={cn("sm:min-w-2/5 min-w-4/5 grid gap-x-5 gap-y-5 sm:grid-cols-3", className)}>
{options.map((item, index) => {
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/components/OtherCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { useEffect, useState } from "react";
import { UseFormRegister } from "react-hook-form";
import { FieldValues, Path, UseFormRegister } from "react-hook-form";

import { FormData } from "./StudentForm/types";
import { Textfield } from "./Textfield";

type OtherCheckboxProps = {
register: UseFormRegister<FormData>;
type OtherCheckboxProps<T extends FieldValues> = {
register: UseFormRegister<T>;
defaultOtherValue?: string;
};

export default function OtherCheckbox({ register, defaultOtherValue = "" }: OtherCheckboxProps) {
export default function OtherCheckbox<T extends FieldValues>({
register,
defaultOtherValue = "",
}: OtherCheckboxProps<T>) {
const [checked, setChecked] = useState(defaultOtherValue !== "");

//Revert other checkbox when clicked outside
Expand All @@ -32,7 +34,7 @@ export default function OtherCheckbox({ register, defaultOtherValue = "" }: Othe
<Textfield
className="animate-in fade-in"
register={register}
name="other"
name={"other" as Path<T>}
label="Other"
placeholder="Type Here..."
defaultValue={defaultOtherValue}
Expand Down
15 changes: 10 additions & 5 deletions frontend/src/components/Radio.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { FieldValues, UseFormRegister } from "react-hook-form";
import { FieldValues, Path, UseFormRegister } from "react-hook-form";

import { cn } from "../lib/utils";

type RadioProps = {
type RadioProps<T extends FieldValues> = {
options: string[];
className?: string;
name: string;
register: UseFormRegister<FieldValues>;
name: Path<T>;
register: UseFormRegister<T>;
};

export default function Radio({ options, register, name, className }: RadioProps) {
export default function Radio<T extends FieldValues>({
options,
register,
name,
className,
}: RadioProps<T>) {
return (
<div className={cn("grid gap-3", className)}>
{options.map((option, index) => {
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/StudentForm/ContactInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import { UseFormRegister } from "react-hook-form";
import { cn } from "../../lib/utils";
import { Textfield } from "../Textfield";

import { FormData, StudentFormData } from "./types";
import { StudentData, StudentFormData } from "./types";

type ContactRole = "student" | "emergency" | "serviceCoordinator";

type PersonalInfoField = "firstName" | "lastName" | "email" | "phoneNumber";

type ContactInfoProps = {
register: UseFormRegister<FormData>;
register: UseFormRegister<StudentFormData>;
classname?: string;
type: "add" | "edit";
data: StudentFormData | null;
data: StudentData | null;
};

type FieldProps = {
Expand All @@ -25,7 +25,7 @@ type FieldProps = {
};

type FinalFieldProps = {
name: keyof FormData;
name: keyof StudentFormData;
label: string;
placeholder: string;
type?: string;
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/StudentForm/StudentBackground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { cn } from "../../lib/utils";
import { Checkbox } from "../Checkbox";
import { Textfield } from "../Textfield";

import { FormData, StudentFormData } from "./types";
import { StudentData, StudentFormData } from "./types";

type StudentBackgroundProps = {
register: UseFormRegister<FormData>;
register: UseFormRegister<StudentFormData>;
classname?: string;
setCalendarValue: UseFormSetValue<FormData>;
data: StudentFormData | null;
setCalendarValue: UseFormSetValue<StudentFormData>;
data: StudentData | null;
};

const dietaryList = ["Nuts", "Eggs", "Seafood", "Pollen", "Dairy", "Other"];
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/StudentForm/StudentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { cn } from "../../lib/utils";
import { Checkbox } from "../Checkbox";
import { Textfield } from "../Textfield";

import { FormData, StudentFormData } from "./types";
import { StudentData, StudentFormData } from "./types";

type StudentInfoProps = {
register: UseFormRegister<FormData>;
register: UseFormRegister<StudentFormData>;
classname?: string;
setCalendarValue: UseFormSetValue<FormData>;
data: StudentFormData | null;
setCalendarValue: UseFormSetValue<StudentFormData>;
data: StudentData | null;
};

const regularPrograms = ["Intro", "ENTR"];
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/StudentForm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type Contact = {
phoneNumber: string;
};

export type StudentFormData = {
export type StudentData = {
student: Contact;
emergency: Contact;
serviceCoordinator: Contact;
Expand All @@ -20,7 +20,7 @@ export type StudentFormData = {
otherString: string;
};

export type FormData = {
export type StudentFormData = {
student_name: string;
student_last: string;
student_email: string;
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/components/StudentFormButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Button } from "./Button";
import ContactInfo from "./StudentForm/ContactInfo";
import StudentBackground from "./StudentForm/StudentBackground";
import StudentInfo from "./StudentForm/StudentInfo";
import { FormData, StudentFormData } from "./StudentForm/types";
import { StudentData, StudentFormData } from "./StudentForm/types";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "./ui/dialog";

type BaseProps = {
Expand All @@ -16,12 +16,12 @@ type BaseProps = {

type EditProps = BaseProps & {
type: "edit";
data: StudentFormData | null;
data: StudentData | null;
};

type AddProps = BaseProps & {
type: "add";
data?: StudentFormData | null;
data?: StudentData | null;
};

type StudentFormProps = EditProps | AddProps;
Expand All @@ -31,10 +31,10 @@ export default function StudentFormButton({
data = null,
classname,
}: StudentFormProps) {
const { register, setValue: setCalendarValue, reset, handleSubmit } = useForm<FormData>();
const { register, setValue: setCalendarValue, reset, handleSubmit } = useForm<StudentFormData>();

const onSubmit: SubmitHandler<FormData> = (formData: FormData) => {
const transformedData: StudentFormData = {
const onSubmit: SubmitHandler<StudentFormData> = (formData: StudentFormData) => {
const transformedData: StudentData = {
student: {
firstName: formData.student_name,
lastName: formData.student_last,
Expand Down
32 changes: 15 additions & 17 deletions frontend/src/components/Textfield.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,45 @@
"use client";

import { useEffect, useState } from "react";
import { UseFormRegister, UseFormSetValue } from "react-hook-form";
import { FieldValues, Path, PathValue, UseFormRegister, UseFormSetValue } from "react-hook-form";

import { Calendar } from "../components/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "../components/ui/popover";
import { cn } from "../lib/utils";

import { FormData } from "./StudentForm/types";

type BaseProps = {
register: UseFormRegister<FormData>;
name: keyof FormData;
type BaseProps<T extends FieldValues> = {
register: UseFormRegister<T>;
name: Path<T>;
label?: string;
type?: string;
placeholder: string;
defaultValue?: string;
className?: string;
};

type WithCalendarProps = BaseProps & {
calendar: true; // When calendar is true, setCalendarValue is required
setCalendarValue: UseFormSetValue<FormData>;
type WithCalendarProps<T extends FieldValues> = BaseProps<T> & {
calendar?: true; // When calendar is false or not provided, setCalendarValue is optional
setCalendarValue?: UseFormSetValue<T>;
};

type WithoutCalendarProps = BaseProps & {
type WithoutCalendarProps<T extends FieldValues> = BaseProps<T> & {
calendar?: false; // When calendar is false or not provided, setCalendarValue is optional
setCalendarValue?: UseFormSetValue<FormData>;
setCalendarValue?: UseFormSetValue<T>;
};

type TextFieldProps = WithCalendarProps | WithoutCalendarProps;
type TextFieldProps<T extends FieldValues> = WithCalendarProps<T> | WithoutCalendarProps<T>;

export function Textfield({
export function Textfield<T extends FieldValues>({
register,
setCalendarValue,
label,
name,
name, //Must be a key in form data type specified in useForm hook
placeholder,
calendar = false,
className,
type = "text",
defaultValue = "",
}: TextFieldProps) {
}: TextFieldProps<T>) {
const [date, setDate] = useState<Date>();

useEffect(() => {
Expand All @@ -51,7 +49,7 @@ export function Textfield({
month: "2-digit",
day: "2-digit",
});
setCalendarValue(name, parsedDate);
setCalendarValue(name, parsedDate as PathValue<T, Path<T>>);
}
}, [date]);

Expand All @@ -64,7 +62,7 @@ export function Textfield({
)}
>
<input
{...register(name)}
{...register(name as Path<T>)}
className="focus-visible:out w-full appearance-none bg-inherit px-2 placeholder-pia_accent outline-none"
id={label + placeholder}
type={type}
Expand Down
35 changes: 32 additions & 3 deletions frontend/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
import { useForm } from "react-hook-form";

import { Button } from "../components/Button";
import { Checkbox } from "../components/Checkbox";
import StudentFormButton from "../components/StudentFormButton";
import { Textfield } from "../components/Textfield";
import sampleStudentData from "../sampleStudentData.json";

type FruitData = {
fruits: string[];
favoriteFruit: string;
};

export default function Home() {
const { register, handleSubmit, reset } = useForm<FruitData>();

const onSubmit = (formData: FruitData) => {
console.log(formData);
reset();
};

return (
<div className="grid w-40 gap-5">
<StudentFormButton type="edit" data={sampleStudentData} />
<StudentFormButton type="add" />
<div className="w-1/2">
<div className="flex gap-5">
<StudentFormButton type="edit" data={sampleStudentData} />
<StudentFormButton type="add" />
</div>

{/* Example */}
<div className="mt-5">
<h2 className="text-2xl font-bold">Example</h2>
<form className="grid gap-5" onSubmit={handleSubmit(onSubmit)}>
<Checkbox name="fruits" register={register} options={["apples", "oranges", "bananas"]} />
<Textfield name="favoriteFruit" register={register} placeholder="Favorite Fruit" />
<Button label="Submit" />
</form>
</div>
</div>
);
}
Loading