Browse Source
feat: add QuestionCheckbox component for handling multiple choice questions with "Doesn't matter" option
master
feat: add QuestionCheckbox component for handling multiple choice questions with "Doesn't matter" option
master
21 changed files with 2206 additions and 466 deletions
-
5src/app/layout.tsx
-
187src/app/questions-list/[slug]/question-detail-client.tsx
-
8src/components/questions/question-button.tsx
-
96src/components/questions/question-checkbox.tsx
-
29src/components/questions/question-date.tsx
-
24src/components/questions/question-dropdown.tsx
-
29src/components/questions/question-file.tsx
-
92src/components/questions/question-number.tsx
-
22src/components/questions/question-phone.tsx
-
24src/components/questions/question-photo.tsx
-
18src/components/questions/question-progress-tracker.tsx
-
52src/components/questions/question-radio.tsx
-
23src/components/questions/question-slider.tsx
-
7src/components/questions/question-snap-list.tsx
-
42src/components/questions/question-text.tsx
-
4src/components/questions/question-title.tsx
-
8src/data/question-data.ts
-
2src/hooks/marriage/types.ts
-
8src/i18n/dictionaries.ts
-
989src/i18n/locales/en/questions.json
-
987src/i18n/locales/fa/questions.json
@ -0,0 +1,96 @@ |
|||
"use client"; |
|||
|
|||
import { useId } from "react"; |
|||
import type { QuestionField } from "@/data/question-data"; |
|||
import { useQuestionAnswers } from "./question-answer-storage"; |
|||
import QuestionTitle from "./question-title"; |
|||
|
|||
type QuestionCheckboxProps = { |
|||
question: QuestionField; |
|||
questionIndex: number; |
|||
disabled?: boolean; |
|||
}; |
|||
|
|||
export function QuestionCheckbox({ |
|||
question, |
|||
questionIndex, |
|||
disabled, |
|||
}: QuestionCheckboxProps) { |
|||
const groupId = useId(); |
|||
const options = question.extras.options; |
|||
const { getAnswerValue, setAnswerValue } = useQuestionAnswers(); |
|||
const rawValue = getAnswerValue(question, questionIndex); |
|||
|
|||
// Ensure value is an array
|
|||
const value = Array.isArray(rawValue) ? rawValue : (rawValue ? [String(rawValue)] : []); |
|||
|
|||
if (options.length === 0) { |
|||
return null; |
|||
} |
|||
|
|||
const toggleOption = (option: string) => { |
|||
let nextValue: string[]; |
|||
if (value.includes(option)) { |
|||
nextValue = value.filter((v) => v !== option); |
|||
} else { |
|||
nextValue = [...value, option]; |
|||
} |
|||
|
|||
// Handle "Doesn't matter" (مهم نیست) logic if it exists
|
|||
const doesnMatterOption = options.find(o => o.includes("مهم نیست") || o.includes("Doesn't matter")); |
|||
if (doesnMatterOption) { |
|||
if (option === doesnMatterOption) { |
|||
// If "Doesn't matter" is selected, clear everything else
|
|||
nextValue = [doesnMatterOption]; |
|||
} else if (nextValue.includes(doesnMatterOption)) { |
|||
// If something else is selected, remove "Doesn't matter"
|
|||
nextValue = nextValue.filter(v => v !== doesnMatterOption); |
|||
} |
|||
} |
|||
|
|||
setAnswerValue(question, questionIndex, nextValue.length > 0 ? nextValue : null); |
|||
}; |
|||
|
|||
return ( |
|||
<div |
|||
className={[ |
|||
"flex w-full flex-col gap-3 transition-opacity duration-200", |
|||
disabled ? "pointer-events-none opacity-30" : "", |
|||
].join(" ")} |
|||
> |
|||
<QuestionTitle question={question} /> |
|||
<div className="flex flex-col gap-3"> |
|||
{options.map((option) => { |
|||
const optionId = `checkbox-${questionIndex}-${option}`; |
|||
const isSelected = value.includes(option); |
|||
|
|||
return ( |
|||
<label |
|||
key={option} |
|||
htmlFor={optionId} |
|||
className={[ |
|||
"w-full cursor-pointer rounded-[12px] px-4 py-3.5", |
|||
"text-start text-[13px] leading-tight font-semibold transition-all duration-200", |
|||
isSelected |
|||
? "bg-[#F0445B] text-white shadow-[0_8px_20px_rgba(240,68,91,0.25)]" |
|||
: "bg-[#F5F5F5] text-[#181818] hover:bg-[#EBEBEB]", |
|||
].join(" ")} |
|||
> |
|||
<input |
|||
type="checkbox" |
|||
id={optionId} |
|||
checked={isSelected} |
|||
disabled={disabled} |
|||
onChange={() => toggleOption(option)} |
|||
className="sr-only" |
|||
/> |
|||
{option} |
|||
</label> |
|||
); |
|||
})} |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
export default QuestionCheckbox; |
|||
@ -1,33 +1,40 @@ |
|||
"use client"; |
|||
|
|||
import type { QuestionField } from "@/data/question-data"; |
|||
import { useQuestionAnswer } from "./question-answer-storage"; |
|||
import { useQuestionAnswers } from "./question-answer-storage"; |
|||
import QuestionTitle from "./question-title"; |
|||
|
|||
type QuestionDateProps = { |
|||
question: QuestionField; |
|||
questionIndex: number; |
|||
disabled?: boolean; |
|||
}; |
|||
|
|||
export function QuestionDate({ question, questionIndex }: QuestionDateProps) { |
|||
const { setValue, value } = useQuestionAnswer(question, questionIndex); |
|||
export function QuestionDate({ |
|||
question, |
|||
questionIndex, |
|||
disabled, |
|||
}: QuestionDateProps) { |
|||
const { getAnswerValue, setAnswerValue } = useQuestionAnswers(); |
|||
const value = getAnswerValue(question, questionIndex); |
|||
const dateValue = typeof value === "string" ? value : ""; |
|||
|
|||
return ( |
|||
<label data-question-type={question.type} className="block space-y-3"> |
|||
<div |
|||
className={[ |
|||
"flex w-full flex-col gap-2 transition-opacity duration-200", |
|||
disabled ? "pointer-events-none opacity-30" : "", |
|||
].join(" ")} |
|||
> |
|||
<QuestionTitle question={question} /> |
|||
<input |
|||
type="date" |
|||
required={question.required} |
|||
value={dateValue} |
|||
onChange={(event) => { |
|||
const nextValue = event.target.value; |
|||
|
|||
setValue(nextValue.length > 0 ? nextValue : null); |
|||
}} |
|||
onChange={(event) => setAnswerValue(question, questionIndex, event.target.value)} |
|||
disabled={disabled} |
|||
className="h-[54px] w-full rounded-[15px] border border-[#E7D8D5] bg-white px-4 text-[15px] text-[#181818] outline-none focus:border-[#6F6F6F]" |
|||
/> |
|||
</label> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
|
|||
@ -1,47 +1,51 @@ |
|||
"use client"; |
|||
|
|||
import type { QuestionField } from "@/data/question-data"; |
|||
import { useQuestionAnswer } from "./question-answer-storage"; |
|||
import { useQuestionAnswers } from "./question-answer-storage"; |
|||
import QuestionTitle from "./question-title"; |
|||
|
|||
type QuestionTextProps = { |
|||
question: QuestionField; |
|||
questionIndex: number; |
|||
description?: string; |
|||
disabled?: boolean; |
|||
heightClassName?: string; |
|||
}; |
|||
|
|||
export function QuestionText({ |
|||
export default function QuestionText({ |
|||
question, |
|||
questionIndex, |
|||
description, |
|||
heightClassName = "min-h-[116px]", |
|||
disabled, |
|||
heightClassName, |
|||
}: QuestionTextProps) { |
|||
const { setValue, value } = useQuestionAnswer(question, questionIndex); |
|||
const textValue = typeof value === "string" ? value : ""; |
|||
const { getAnswerValue, setAnswerValue } = useQuestionAnswers(); |
|||
const value = getAnswerValue(question, questionIndex); |
|||
|
|||
return ( |
|||
<label data-question-type={question.type} className="block space-y-3"> |
|||
<div |
|||
className={[ |
|||
"flex w-full flex-col gap-2 transition-opacity duration-200", |
|||
disabled ? "pointer-events-none opacity-30" : "", |
|||
].join(" ")} |
|||
> |
|||
<QuestionTitle question={question} /> |
|||
<textarea |
|||
required={question.required} |
|||
rows={1} |
|||
<input |
|||
type="text" |
|||
value={String(value ?? "")} |
|||
onChange={(e) => setAnswerValue(question, questionIndex, e.target.value)} |
|||
placeholder={question.extras.placeHolder} |
|||
value={textValue} |
|||
onChange={(event) => { |
|||
const nextValue = event.target.value; |
|||
|
|||
setValue(nextValue.length > 0 ? nextValue : null); |
|||
}} |
|||
className={`w-full resize-none rounded-[15px] border border-[#8B8B8B] bg-white px-4 py-3 text-[#181818] outline-none placeholder:text-[#8B8B8B] focus:border-[#6F6F6F] ${heightClassName}`} |
|||
disabled={disabled} |
|||
className={[ |
|||
"w-full rounded-[10px] bg-[#DBDBDB] px-[14px] py-[10px] text-[13px] font-semibold text-[#181818] outline-none placeholder:text-[#808080]", |
|||
heightClassName || "min-h-[116px]", |
|||
].join(" ")} |
|||
/> |
|||
{description ? ( |
|||
<span className="block text-[10px] font-semibold text-[#747474]"> |
|||
{description} |
|||
</span> |
|||
) : null} |
|||
</label> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
export default QuestionText; |
|||
989
src/i18n/locales/en/questions.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
987
src/i18n/locales/fa/questions.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue