Skip to content

Commit

Permalink
refactor: FormElement to fit in schema
Browse files Browse the repository at this point in the history
  • Loading branch information
toothlessdev committed Nov 22, 2024
1 parent eb1f30a commit bc24a23
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 285 deletions.
61 changes: 37 additions & 24 deletions src/components/forms/CheckBoxWithLabelForm.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,69 @@
import { useId } from "react";
import { useCallback, useId, useMemo } from "react";

import { Checkbox } from "@/components/ui/checkbox";

import { FormState, Question } from "./FormRenderer";
import { CheckedState } from "@radix-ui/react-checkbox";

// prettier-ignore
export type CheckBoxWithLabelFormProps =
Omit<Question, "questionType" | "dataType"> &
FormState;

export const CheckBoxWithLabelForm = ({
questionId,
questionText,
options,
formState,
setFormState,
}: CheckBoxWithLabelFormProps) => {
const htmlFor = useId();
const questionIndex = formState.questions.findIndex((q) => q.questionId === questionId);

const handleCheckboxChange = useCallback(
(option: (typeof options)[number], checked: CheckedState) => {
const updatedQuestions = [...formState.questions];
const updatedOptions = [...updatedQuestions[questionIndex].options];

if (checked) {
updatedOptions.push(option);
} else {
const optionIndex = updatedOptions.findIndex((opt) => opt.value === option.value);
if (optionIndex > -1) updatedOptions.splice(optionIndex, 1);
}

updatedQuestions[questionIndex] = {
...updatedQuestions[questionIndex],
options: updatedOptions,
};

setFormState({
...formState,
questions: updatedQuestions,
});
},
[formState, questionIndex, setFormState],
);

return (
<div className="flex flex-col gap-1" data-testid="checkbox-with-label-form">
<p className="font-bold">{questionText}</p>

{options.map((option, index) => {
const isChecked = formState.questions[questionIndex].options.some(
(selectedOption) => selectedOption.value === option.value,
);

return (
<div key={index} className="flex items-center gap-1">
<Checkbox
data-testid={`checkbox_${option.label}`}
id={htmlFor}
id={`${htmlFor}_${option.label}`}
className="block w-5 h-5"
checked={formState[questionText]
.map((state) => state.value)
.includes(option.value)}
onCheckedChange={(isChecked) => {
if (isChecked) {
setFormState({
...formState,
[questionText]: [
...formState[questionText],
{ value: option.value },
],
});
} else {
setFormState({
...formState,
[questionText]: formState[questionText].filter(
(state) => state.value !== option.value,
),
});
}
}}
checked={isChecked}
onCheckedChange={(checked) => handleCheckboxChange(option, checked)}
/>
<label htmlFor={`checkbox_${option.label}`}>{option.label}</label>
<label htmlFor={`${htmlFor}_${option.label}`}>{option.label}</label>
</div>
);
})}
Expand Down
53 changes: 38 additions & 15 deletions src/components/forms/DoubleSliderWithLabelForm.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,70 @@
import { useEffect, useState } from "react";
import { useState } from "react";

import { DualRangeSlider, DualRangeSliderProps } from "../ui/slider";
import { DualRangeSlider } from "../ui/slider";
import { FormState, Question } from "./FormRenderer";

// prettier-ignore
export type DoubleSliderWithLabelFormProps =
Omit<Question, "questiontype" | "dataType"> &
Omit<Question, "questionType" | "dataType"> &
FormState;

export const DoubleSliderWithLabelForm = ({
questionId,
questionText,
options,
formState,
setFormState,
}: DoubleSliderWithLabelFormProps) => {
const min = options[0];
const max = options[options.length - 1];

const [value, setValue] = useState<number[]>([min.value, max.value]);
const question = formState.questions.find((q) => q.questionId === questionId);

return (
<div>
<p className="font-bold">{questionText}</p>
<div className="px-2">
<DualRangeSlider
data-testid="double-slider-with-label-form"
className="pt-4 pb-10 text-gray-500"
labelPosition="bottom"
defaultValue={[min.value, max.value]}
value={[...value]}
min={min.value}
max={max.value}
defaultValue={[
question?.options[0]?.value || options[0].value,
question?.options[1]?.value || options[options.length - 1].value,
]}
value={[
question?.options[0]?.value || options[0].value,
question?.options[1]?.value || options[options.length - 1].value,
]}
min={options[0].value}
max={options[options.length - 1].value}
onValueChange={(value) => {
setValue(value);
const updatedQuestions = formState.questions.map((q) =>
q.questionId === questionId
? {
...q,
options: [
{
label: options.find((opt) => opt.value === value[0])
?.label,
value: value[0],
},
{
label: options.find((opt) => opt.value === value[1])
?.label,
value: value[1],
},
],
}
: q,
);
setFormState({
...formState,
[questionText]: [{ value: value }],
questions: updatedQuestions,
});
}}
label={(value) => (
<span className="text-sm text-gray-500">
<span className="mt-2 text-sm text-gray-500">
{options.find((option) => option.value === value)?.label}
</span>
)}
labelPosition="bottom"
/>
</div>
</div>
Expand Down
6 changes: 4 additions & 2 deletions src/components/forms/FormFactory.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { CheckBoxWithLabelForm } from "./CheckBoxWithLabelForm";
import { DoubleSliderWithLabelForm } from "./DoubleSliderWithLabelForm";
import { QuestionTypes } from "./FormRenderer";
import { SelectorWithLabelForm } from "./SelectorWithLabelForm";
import { SliderWithLabelForm } from "./SliderWithLabelForm";

export interface FormFactoryProps {
questionType: keyof typeof QuestionTypes;
Expand All @@ -10,8 +12,8 @@ export const FormFactory = ({ questionType }: FormFactoryProps) => {
const FormElements = {
[QuestionTypes.selector]: SelectorWithLabelForm,
[QuestionTypes.checkbox]: CheckBoxWithLabelForm,
// [QuestionTypes.slider]: SliderWithLabelForm,
// [QuestionTypes.doubleSlider]: DoubleSliderWithLabelForm,
[QuestionTypes.slider]: SliderWithLabelForm,
[QuestionTypes.doubleSlider]: DoubleSliderWithLabelForm,
};

return FormElements[questionType];
Expand Down
11 changes: 7 additions & 4 deletions src/components/forms/FormRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { FormFactory } from "./FormFactory";
export const QuestionTypes = {
checkbox: "checkbox",
selector: "selector",
// slider: "slider",
// doubleSlider: "doubleSlider",
slider: "slider",
doubleSlider: "doubleSlider",
} as const;

export interface Option {
Expand All @@ -13,26 +13,29 @@ export interface Option {
}

export interface Question {
questionId: number;
questionText: string;
questionType: keyof typeof QuestionTypes;
dataType: string;
options: Option[];
}

export interface FormProps {
_id: string;
title: string;
description: string;
questions: Question[];
}

export interface FormState {
formState: Record<string, Option[]>;
setFormState: React.Dispatch<React.SetStateAction<Record<string, Option[]>>>;
formState: FormProps;
setFormState: React.Dispatch<React.SetStateAction<FormProps>>;
}

export type FormRendererProps = FormProps & FormState;

export const FormRenderer = ({
_id,
title,
description,
questions,
Expand Down
36 changes: 25 additions & 11 deletions src/components/forms/SelectorWithLabelForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,48 @@ import {
SelectValue,
} from "@/components/ui/select";

import { FormState, Question } from "./FormRenderer";
import { FormProps, FormState, Question, Option } from "./FormRenderer";

// prettier-ignore
export type SelectorWithLabelFormProps =
Omit<Question, "questionType" | "dataType"> &
FormState
export type SelectorWithLabelFormProps = {
questionId: number;
questionText: string;
options: Option[];
formState: FormProps;
setFormState: React.Dispatch<React.SetStateAction<FormProps>>;
};

export const SelectorWithLabelForm = ({
questionId,
questionText,
options,
formState,
setFormState,
}: SelectorWithLabelFormProps) => {
const htmlFor = useId();

const questionIndex = formState.questions.findIndex((q) => q.questionId === questionId);

const selectedValue = formState.questions[questionIndex].options[0]?.value || "";

return (
<div data-testid="selector-with-label-form">
<label className="py-1 font-bold" htmlFor={htmlFor}>
{questionText}
</label>
<Select
value={formState[questionText][0].value}
onValueChange={(option) =>
setFormState({
...formState,
[questionText]: [{ value: option }],
})
}
value={selectedValue}
onValueChange={(selectedValue) => {
const selectedOption = options.find((option) => option.value === selectedValue);
const updatedFormState = { ...formState };
updatedFormState.questions[questionIndex].options = [
{
label: selectedOption?.label || "",
value: selectedValue,
},
];
setFormState(updatedFormState);
}}
>
<SelectTrigger className="w-full rounded-xl" asChild={false}>
<SelectValue placeholder="옵션을 선택해주세요"></SelectValue>
Expand Down
51 changes: 39 additions & 12 deletions src/components/forms/SliderWithLabelForm.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useEffect, useState } from "react";

import { Slider } from "@/components/ui/slider";

import { FormState, Question } from "./FormRenderer";
Expand All @@ -7,18 +9,48 @@ export type SliderWithLabelFormProps =
Omit<Question, "questionType" | "dataType"> &
FormState;

/**
* 기본 인자
*/

export const SliderWithLabelForm = ({
questionId,
questionText,
options,
formState,
setFormState,
}: SliderWithLabelFormProps) => {
const min = options[0];
const max = options[options.length - 1];
const question = formState.questions.find((q) => q.questionId === questionId);
const [value, setValue] = useState(question?.options[0].value);

useEffect(() => {
if (question?.options[0].value !== value) {
setValue(question?.options[0].value);
}
}, [question?.options[0].value]);

const handleValueChange = (newValue: number[]) => {
const updatedValue = newValue[0];
setValue(updatedValue);

const updatedQuestions = formState.questions.map((q) =>
q.questionId === questionId
? {
...q,
options: [
{
value: updatedValue,
label:
options.find((opt) => opt.value === updatedValue)?.label ||
String(updatedValue),
},
],
}
: q,
);
setFormState({
...formState,
questions: updatedQuestions,
});
};

return (
<div>
Expand All @@ -27,14 +59,9 @@ export const SliderWithLabelForm = ({
<Slider
data-testid="slider-with-label-form"
className="pt-4 text-gray-500"
defaultValue={[formState[questionText][0].value]}
value={[formState[questionText][0].value]}
onValueChange={(value) => {
setFormState({
...formState,
[questionText]: [{ value: value[0] }],
});
}}
defaultValue={[value]}
value={[value]}
onValueChange={handleValueChange}
min={min.value}
max={max.value}
step={1}
Expand Down
Loading

0 comments on commit bc24a23

Please sign in to comment.