Skip to content
Open
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
6 changes: 5 additions & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,6 @@
"prettier --write",
"git add"
]
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
100 changes: 100 additions & 0 deletions frontend/src/components/QuestionComponents/Dropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useState } from 'react';
import tw from 'twin.macro';
import { ChevronDownIcon, CheckIcon } from '@heroicons/react/24/solid';

interface DropdownOption {
id: string | number;
label: string;
}

interface DropdownProps {
id: number;
question: string;
description?: string;
options: DropdownOption[];
required?: boolean;
defaultValue?: string | number;
onChange?: (value: string | number) => void;
onSubmit?: (questionId: number, value: string | number) => void;
disabled?: boolean;
}

const Dropdown: React.FC<DropdownProps> = ({
id,
question,
description,
options,
required = false,
defaultValue,
onChange,
onSubmit,
disabled = false,
}) => {
const [value, setValue] = useState<string | number | undefined>(defaultValue);
const [isOpen, setIsOpen] = useState(false);

const handleSelect = (optionId: string | number) => {
setValue(optionId);
setIsOpen(false);

if (onChange) onChange(optionId);
if (onSubmit) onSubmit(id, optionId);
};

const selectedOption = options.find(option => option.id === value);

return (
<div tw="mb-6 max-w-4xl w-full">
<div tw="flex items-center mb-1">
<label tw="text-lg font-medium text-gray-900">{question}</label>
{required && <span tw="ml-1 text-red-500">*</span>}
</div>

{description && (
<p tw="mb-2 text-sm text-gray-600">{description}</p>
)}

<div tw="relative">
<button
type="button"
onClick={() => !disabled && setIsOpen(!isOpen)}
disabled={disabled}
tw="w-full py-1 px-4 text-left rounded-md border-2 border-gray-400 shadow-md bg-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 hover:border-blue-400"
>
<div tw="flex items-center justify-between">
<span>{selectedOption ? selectedOption.label : 'Select an option'}</span>
<ChevronDownIcon tw="h-3 w-5 text-gray-400" />
</div>
</button>

{isOpen && (
<div tw="absolute z-10 mt-1 w-full bg-white shadow-lg rounded-md py-1 max-h-60 overflow-auto border-2 border-gray-300">
{options.map((option) => {
// Check if this option is selected
const isSelected = option.id === value;

return (
<div
key={option.id}
onClick={() => handleSelect(option.id)}
// Basic styling for all options
tw="px-4 py-1 cursor-pointer hover:bg-blue-50 text-sm flex items-center justify-between"
// Apply conditional styling based on selection state
css={isSelected ? tw`bg-blue-50 font-medium` : undefined}
>
<span>{option.label}</span>
{/* Show checkmark for selected option */}
{isSelected && (
<CheckIcon tw="h-4 w-4 text-blue-500 ml-2" />
)}
</div>
);
})}
</div>
)}
</div>
</div>
);
};

export default Dropdown;
78 changes: 78 additions & 0 deletions frontend/src/components/QuestionComponents/MultiChoice/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { useState } from 'react';
import tw from 'twin.macro';

interface Option {
id: string | number;
label: string;
}

interface MultiChoiceProps {
id: number;
question: string;
description?: string;
options: Option[];
required?: boolean;
defaultValue?: string | number;
onChange?: (value: string | number) => void;
onSubmit?: (questionId: number, value: string | number) => void;
disabled?: boolean;
}

const MultiChoice: React.FC<MultiChoiceProps> = ({
id,
question,
description,
options,
required = false,
defaultValue,
onChange,
onSubmit,
disabled = false,
}) => {
const [selectedOption, setSelectedOption] = useState<string | number | undefined>(defaultValue);

const handleChange = (optionId: string | number) => {
setSelectedOption(optionId);

if (onChange) onChange(optionId);
if (onSubmit) onSubmit(id, optionId);
};

return (
<div tw="mb-6">
<div tw="flex items-center mb-1">
<label tw="text-lg font-medium text-gray-900">{question}</label>
{required && <span tw="ml-1 text-red-500">*</span>}
</div>

{description && (
<p tw="mb-2 text-sm text-gray-600">{description}</p>
)}

<div tw="space-y-2">
{options.map((option) => (
<div key={option.id} tw="flex items-center">
<input
type="radio"
id={`option-${id}-${option.id}`}
name={`question-${id}`}
value={option.id.toString()}
checked={selectedOption === option.id}
onChange={() => handleChange(option.id)}
disabled={disabled}
tw="h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500"
/>
<label
htmlFor={`option-${id}-${option.id}`}
tw="ml-3 block text-sm font-medium text-gray-700"
>
{option.label}
</label>
</div>
))}
</div>
</div>
);
};

export default MultiChoice;
86 changes: 86 additions & 0 deletions frontend/src/components/QuestionComponents/MultiSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { useState } from 'react';
import tw from 'twin.macro';

interface Option {
id: string | number;
label: string;
}

interface MultiSelectProps {
id: number;
question: string;
description?: string;
options: Option[];
required?: boolean;
defaultValue?: Array<string | number>;
onChange?: (value: Array<string | number>) => void;
onSubmit?: (questionId: number, value: Array<string | number>) => void;
disabled?: boolean;
}

const MultiSelect: React.FC<MultiSelectProps> = ({
id,
question,
description,
options,
required = false,
defaultValue = [],
onChange,
onSubmit,
disabled = false,
}) => {
const [selectedOptions, setSelectedOptions] = useState<Array<string | number>>(defaultValue);

const handleChange = (optionId: string | number) => {
let newSelectedOptions: Array<string | number> = [];

if (selectedOptions.includes(optionId)) {
newSelectedOptions = selectedOptions.filter(id => id !== optionId);
} else {
newSelectedOptions = [...selectedOptions, optionId];
}

setSelectedOptions(newSelectedOptions);

if (onChange) onChange(newSelectedOptions);
if (onSubmit) onSubmit(id, newSelectedOptions);
};

return (
<div tw="mb-6">
<div tw="flex items-center mb-1">
<label tw="text-lg font-medium text-gray-900">{question}</label>
{required && <span tw="ml-1 text-red-500">*</span>}
</div>

{description && (
<p tw="mb-2 text-sm text-gray-600">{description}</p>
)}

<div tw="space-y-2">
{options.map((option) => (
<div key={option.id} tw="flex items-center">
<input
type="checkbox"
id={`option-${id}-${option.id}`}
name={`question-${id}-${option.id}`}
value={option.id.toString()}
checked={selectedOptions.includes(option.id)}
onChange={() => handleChange(option.id)}
disabled={disabled}
tw="h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500"
/>
<label
htmlFor={`option-${id}-${option.id}`}
tw="ml-3 block text-sm font-medium text-gray-700"
>
{option.label}
</label>
</div>
))}
</div>
</div>
);
};

export default MultiSelect;
Loading