Commit ·
529c982
1
Parent(s): afdd8ce
Refactor question creation card
Browse files- src/app/page.tsx +1 -1
- src/components/ParameterInput.tsx +55 -64
- src/components/question-creation-card/ParameterInput.tsx +87 -0
- src/components/{QuestionParameterForm.tsx → question-creation-card/QuestionParameterForm.tsx} +1 -1
- src/components/question-creation-card/index.ts +6 -0
- src/components/question-creation-card/inputs/CheckboxInput.tsx +55 -0
- src/components/question-creation-card/inputs/RangeInput.tsx +61 -0
- src/components/question-creation-card/inputs/SelectInput.tsx +68 -0
- src/components/question-creation-card/inputs/TextInput.tsx +62 -0
- src/components/question-creation-card/inputs/index.ts +12 -0
- src/{types/questionConfig.ts → config/questionTypes.ts} +1 -24
src/app/page.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
import { useState } from 'react';
|
| 4 |
import { useChat } from '@ai-sdk/react';
|
| 5 |
-
import QuestionParameterForm from '@/components/
|
| 6 |
import { QuestionType, QuestionParameters, GeneratedQuestion } from '@/types/quiz';
|
| 7 |
|
| 8 |
const QUESTION_TYPES: QuestionType[] = [
|
|
|
|
| 2 |
|
| 3 |
import { useState } from 'react';
|
| 4 |
import { useChat } from '@ai-sdk/react';
|
| 5 |
+
import { QuestionParameterForm } from '@/components/question-creation-card';
|
| 6 |
import { QuestionType, QuestionParameters, GeneratedQuestion } from '@/types/quiz';
|
| 7 |
|
| 8 |
const QUESTION_TYPES: QuestionType[] = [
|
src/components/ParameterInput.tsx
CHANGED
|
@@ -1,93 +1,84 @@
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { ParameterConfig } from '@/types/questionConfig';
|
|
|
|
| 4 |
|
| 5 |
interface ParameterInputProps {
|
| 6 |
config: ParameterConfig;
|
| 7 |
value: any;
|
| 8 |
onChange: (value: any) => void;
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
-
export default function ParameterInput({
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
| 17 |
|
| 18 |
switch (type) {
|
| 19 |
case 'select':
|
| 20 |
return (
|
| 21 |
-
<
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
<option key={option.value.toString()} value={option.value.toString()}>
|
| 33 |
-
{option.label}
|
| 34 |
-
</option>
|
| 35 |
-
))}
|
| 36 |
-
</select>
|
| 37 |
-
</div>
|
| 38 |
);
|
| 39 |
|
| 40 |
case 'range':
|
| 41 |
return (
|
| 42 |
-
<
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
</div>
|
| 56 |
-
</div>
|
| 57 |
);
|
| 58 |
|
| 59 |
case 'input':
|
| 60 |
return (
|
| 61 |
-
<
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
<div className="text-xs text-gray-500 mt-1">{helpText}</div>
|
| 72 |
-
)}
|
| 73 |
-
</div>
|
| 74 |
);
|
| 75 |
|
| 76 |
case 'checkbox':
|
| 77 |
return (
|
| 78 |
-
<
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
<div className="text-xs text-gray-500 ml-2">{helpText}</div>
|
| 89 |
-
)}
|
| 90 |
-
</div>
|
| 91 |
);
|
| 92 |
|
| 93 |
default:
|
|
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { ParameterConfig } from '@/types/questionConfig';
|
| 4 |
+
import { SelectInput, RangeInput, TextInput, CheckboxInput } from './inputs';
|
| 5 |
|
| 6 |
interface ParameterInputProps {
|
| 7 |
config: ParameterConfig;
|
| 8 |
value: any;
|
| 9 |
onChange: (value: any) => void;
|
| 10 |
+
className?: string;
|
| 11 |
+
disabled?: boolean;
|
| 12 |
}
|
| 13 |
|
| 14 |
+
export default function ParameterInput({
|
| 15 |
+
config,
|
| 16 |
+
value,
|
| 17 |
+
onChange,
|
| 18 |
+
className = '',
|
| 19 |
+
disabled = false
|
| 20 |
+
}: ParameterInputProps) {
|
| 21 |
+
const { type, label, options, min, max, step, placeholder, helpText, key } = config;
|
| 22 |
|
| 23 |
switch (type) {
|
| 24 |
case 'select':
|
| 25 |
return (
|
| 26 |
+
<SelectInput
|
| 27 |
+
label={label}
|
| 28 |
+
value={value}
|
| 29 |
+
defaultValue={config.defaultValue}
|
| 30 |
+
options={options || []}
|
| 31 |
+
onChange={onChange}
|
| 32 |
+
className={className}
|
| 33 |
+
disabled={disabled}
|
| 34 |
+
placeholder={placeholder}
|
| 35 |
+
helpText={helpText}
|
| 36 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
);
|
| 38 |
|
| 39 |
case 'range':
|
| 40 |
return (
|
| 41 |
+
<RangeInput
|
| 42 |
+
label={label}
|
| 43 |
+
value={value}
|
| 44 |
+
defaultValue={config.defaultValue}
|
| 45 |
+
min={min || 1}
|
| 46 |
+
max={max || 10}
|
| 47 |
+
step={step}
|
| 48 |
+
onChange={onChange}
|
| 49 |
+
className={className}
|
| 50 |
+
disabled={disabled}
|
| 51 |
+
valueUnit={helpText}
|
| 52 |
+
showValue={true}
|
| 53 |
+
/>
|
|
|
|
|
|
|
| 54 |
);
|
| 55 |
|
| 56 |
case 'input':
|
| 57 |
return (
|
| 58 |
+
<TextInput
|
| 59 |
+
label={label}
|
| 60 |
+
value={value}
|
| 61 |
+
defaultValue={config.defaultValue}
|
| 62 |
+
onChange={onChange}
|
| 63 |
+
className={className}
|
| 64 |
+
disabled={disabled}
|
| 65 |
+
placeholder={placeholder}
|
| 66 |
+
helpText={helpText}
|
| 67 |
+
/>
|
|
|
|
|
|
|
|
|
|
| 68 |
);
|
| 69 |
|
| 70 |
case 'checkbox':
|
| 71 |
return (
|
| 72 |
+
<CheckboxInput
|
| 73 |
+
label={label}
|
| 74 |
+
value={value}
|
| 75 |
+
defaultValue={config.defaultValue}
|
| 76 |
+
onChange={onChange}
|
| 77 |
+
className={className}
|
| 78 |
+
disabled={disabled}
|
| 79 |
+
helpText={helpText}
|
| 80 |
+
id={key}
|
| 81 |
+
/>
|
|
|
|
|
|
|
|
|
|
| 82 |
);
|
| 83 |
|
| 84 |
default:
|
src/components/question-creation-card/ParameterInput.tsx
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { ParameterConfig } from '@/types/questionConfig';
|
| 4 |
+
import { SelectInput, RangeInput, TextInput, CheckboxInput } from './inputs';
|
| 5 |
+
|
| 6 |
+
interface ParameterInputProps {
|
| 7 |
+
config: ParameterConfig;
|
| 8 |
+
value: any;
|
| 9 |
+
onChange: (value: any) => void;
|
| 10 |
+
className?: string;
|
| 11 |
+
disabled?: boolean;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export default function ParameterInput({
|
| 15 |
+
config,
|
| 16 |
+
value,
|
| 17 |
+
onChange,
|
| 18 |
+
className = '',
|
| 19 |
+
disabled = false
|
| 20 |
+
}: ParameterInputProps) {
|
| 21 |
+
const { type, label, options, min, max, step, placeholder, helpText, key } = config;
|
| 22 |
+
|
| 23 |
+
switch (type) {
|
| 24 |
+
case 'select':
|
| 25 |
+
return (
|
| 26 |
+
<SelectInput
|
| 27 |
+
label={label}
|
| 28 |
+
value={value}
|
| 29 |
+
defaultValue={config.defaultValue}
|
| 30 |
+
options={options || []}
|
| 31 |
+
onChange={onChange}
|
| 32 |
+
className={className}
|
| 33 |
+
disabled={disabled}
|
| 34 |
+
placeholder={placeholder}
|
| 35 |
+
helpText={helpText}
|
| 36 |
+
/>
|
| 37 |
+
);
|
| 38 |
+
|
| 39 |
+
case 'range':
|
| 40 |
+
return (
|
| 41 |
+
<RangeInput
|
| 42 |
+
label={label}
|
| 43 |
+
value={value}
|
| 44 |
+
defaultValue={config.defaultValue}
|
| 45 |
+
min={min || 1}
|
| 46 |
+
max={max || 10}
|
| 47 |
+
step={step}
|
| 48 |
+
onChange={onChange}
|
| 49 |
+
className={className}
|
| 50 |
+
disabled={disabled}
|
| 51 |
+
valueUnit={helpText}
|
| 52 |
+
showValue={true}
|
| 53 |
+
/>
|
| 54 |
+
);
|
| 55 |
+
|
| 56 |
+
case 'input':
|
| 57 |
+
return (
|
| 58 |
+
<TextInput
|
| 59 |
+
label={label}
|
| 60 |
+
value={value}
|
| 61 |
+
defaultValue={config.defaultValue}
|
| 62 |
+
onChange={onChange}
|
| 63 |
+
className={className}
|
| 64 |
+
disabled={disabled}
|
| 65 |
+
placeholder={placeholder}
|
| 66 |
+
helpText={helpText}
|
| 67 |
+
/>
|
| 68 |
+
);
|
| 69 |
+
|
| 70 |
+
case 'checkbox':
|
| 71 |
+
return (
|
| 72 |
+
<CheckboxInput
|
| 73 |
+
label={label}
|
| 74 |
+
value={value}
|
| 75 |
+
defaultValue={config.defaultValue}
|
| 76 |
+
onChange={onChange}
|
| 77 |
+
className={className}
|
| 78 |
+
disabled={disabled}
|
| 79 |
+
helpText={helpText}
|
| 80 |
+
id={key}
|
| 81 |
+
/>
|
| 82 |
+
);
|
| 83 |
+
|
| 84 |
+
default:
|
| 85 |
+
return null;
|
| 86 |
+
}
|
| 87 |
+
}
|
src/components/{QuestionParameterForm.tsx → question-creation-card/QuestionParameterForm.tsx}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { QuestionType, QuestionParameters } from '@/types/quiz';
|
| 4 |
-
import { questionTypeConfigs, defaultQuestionTypeConfig } from '@/
|
| 5 |
import ParameterInput from './ParameterInput';
|
| 6 |
|
| 7 |
interface QuestionParameterFormProps {
|
|
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { QuestionType, QuestionParameters } from '@/types/quiz';
|
| 4 |
+
import { questionTypeConfigs, defaultQuestionTypeConfig } from '@/config/questionTypes';
|
| 5 |
import ParameterInput from './ParameterInput';
|
| 6 |
|
| 7 |
interface QuestionParameterFormProps {
|
src/components/question-creation-card/index.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Question Creation Card Components
|
| 2 |
+
export { default as QuestionParameterForm } from './QuestionParameterForm';
|
| 3 |
+
export { default as ParameterInput } from './ParameterInput';
|
| 4 |
+
|
| 5 |
+
// Re-export input components for convenience
|
| 6 |
+
export * from './inputs';
|
src/components/question-creation-card/inputs/CheckboxInput.tsx
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
export interface CheckboxInputProps {
|
| 4 |
+
label: string;
|
| 5 |
+
value?: boolean;
|
| 6 |
+
defaultValue?: boolean;
|
| 7 |
+
onChange: (value: boolean) => void;
|
| 8 |
+
className?: string;
|
| 9 |
+
disabled?: boolean;
|
| 10 |
+
helpText?: string;
|
| 11 |
+
required?: boolean;
|
| 12 |
+
id?: string;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
export default function CheckboxInput({
|
| 16 |
+
label,
|
| 17 |
+
value,
|
| 18 |
+
defaultValue = false,
|
| 19 |
+
onChange,
|
| 20 |
+
className = '',
|
| 21 |
+
disabled = false,
|
| 22 |
+
helpText,
|
| 23 |
+
required = false,
|
| 24 |
+
id,
|
| 25 |
+
}: CheckboxInputProps) {
|
| 26 |
+
const currentValue = value ?? defaultValue;
|
| 27 |
+
const inputId = id || `checkbox-${Math.random().toString(36).substr(2, 9)}`;
|
| 28 |
+
|
| 29 |
+
return (
|
| 30 |
+
<div className={`flex items-start ${className}`}>
|
| 31 |
+
<div className="flex items-center h-5">
|
| 32 |
+
<input
|
| 33 |
+
type="checkbox"
|
| 34 |
+
id={inputId}
|
| 35 |
+
checked={currentValue}
|
| 36 |
+
onChange={(e) => onChange(e.target.checked)}
|
| 37 |
+
disabled={disabled}
|
| 38 |
+
required={required}
|
| 39 |
+
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
| 40 |
+
/>
|
| 41 |
+
</div>
|
| 42 |
+
<div className="ml-3">
|
| 43 |
+
<label htmlFor={inputId} className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
| 44 |
+
{label}
|
| 45 |
+
{required && <span className="text-red-500 ml-1">*</span>}
|
| 46 |
+
</label>
|
| 47 |
+
{helpText && (
|
| 48 |
+
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
| 49 |
+
{helpText}
|
| 50 |
+
</div>
|
| 51 |
+
)}
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
);
|
| 55 |
+
}
|
src/components/question-creation-card/inputs/RangeInput.tsx
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
export interface RangeInputProps {
|
| 4 |
+
label: string;
|
| 5 |
+
value?: number;
|
| 6 |
+
defaultValue?: number;
|
| 7 |
+
min: number;
|
| 8 |
+
max: number;
|
| 9 |
+
step?: number;
|
| 10 |
+
onChange: (value: number) => void;
|
| 11 |
+
className?: string;
|
| 12 |
+
disabled?: boolean;
|
| 13 |
+
showValue?: boolean;
|
| 14 |
+
valueUnit?: string;
|
| 15 |
+
helpText?: string;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
export default function RangeInput({
|
| 19 |
+
label,
|
| 20 |
+
value,
|
| 21 |
+
defaultValue = 1,
|
| 22 |
+
min,
|
| 23 |
+
max,
|
| 24 |
+
step = 1,
|
| 25 |
+
onChange,
|
| 26 |
+
className = '',
|
| 27 |
+
disabled = false,
|
| 28 |
+
showValue = true,
|
| 29 |
+
valueUnit = '',
|
| 30 |
+
helpText,
|
| 31 |
+
}: RangeInputProps) {
|
| 32 |
+
const currentValue = value ?? defaultValue;
|
| 33 |
+
|
| 34 |
+
return (
|
| 35 |
+
<div className={className}>
|
| 36 |
+
<label className="block text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">
|
| 37 |
+
{label}
|
| 38 |
+
</label>
|
| 39 |
+
<input
|
| 40 |
+
type="range"
|
| 41 |
+
min={min}
|
| 42 |
+
max={max}
|
| 43 |
+
step={step}
|
| 44 |
+
value={currentValue}
|
| 45 |
+
onChange={(e) => onChange(parseInt(e.target.value))}
|
| 46 |
+
disabled={disabled}
|
| 47 |
+
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
| 48 |
+
/>
|
| 49 |
+
{showValue && (
|
| 50 |
+
<div className="text-center text-sm text-gray-600 dark:text-gray-400 mt-1">
|
| 51 |
+
{currentValue} {valueUnit} {helpText}
|
| 52 |
+
</div>
|
| 53 |
+
)}
|
| 54 |
+
{!showValue && helpText && (
|
| 55 |
+
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
| 56 |
+
{helpText}
|
| 57 |
+
</div>
|
| 58 |
+
)}
|
| 59 |
+
</div>
|
| 60 |
+
);
|
| 61 |
+
}
|
src/components/question-creation-card/inputs/SelectInput.tsx
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
export interface SelectOption {
|
| 4 |
+
value: string | number;
|
| 5 |
+
label: string;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export interface SelectInputProps {
|
| 9 |
+
label: string;
|
| 10 |
+
value?: string | number;
|
| 11 |
+
defaultValue?: string | number;
|
| 12 |
+
options: SelectOption[];
|
| 13 |
+
onChange: (value: string | number) => void;
|
| 14 |
+
className?: string;
|
| 15 |
+
disabled?: boolean;
|
| 16 |
+
placeholder?: string;
|
| 17 |
+
helpText?: string;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
export default function SelectInput({
|
| 21 |
+
label,
|
| 22 |
+
value,
|
| 23 |
+
defaultValue,
|
| 24 |
+
options,
|
| 25 |
+
onChange,
|
| 26 |
+
className = '',
|
| 27 |
+
disabled = false,
|
| 28 |
+
placeholder,
|
| 29 |
+
helpText,
|
| 30 |
+
}: SelectInputProps) {
|
| 31 |
+
const currentValue = value ?? defaultValue;
|
| 32 |
+
const baseClasses = "w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed";
|
| 33 |
+
|
| 34 |
+
return (
|
| 35 |
+
<div className={className}>
|
| 36 |
+
<label className="block text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">
|
| 37 |
+
{label}
|
| 38 |
+
</label>
|
| 39 |
+
<select
|
| 40 |
+
value={currentValue?.toString() || ''}
|
| 41 |
+
onChange={(e) => {
|
| 42 |
+
const selectedOption = options.find(opt => opt.value.toString() === e.target.value);
|
| 43 |
+
if (selectedOption) {
|
| 44 |
+
onChange(selectedOption.value);
|
| 45 |
+
}
|
| 46 |
+
}}
|
| 47 |
+
disabled={disabled}
|
| 48 |
+
className={`${baseClasses} bg-white dark:bg-gray-700 text-gray-900 dark:text-white border-gray-300 dark:border-gray-600`}
|
| 49 |
+
>
|
| 50 |
+
{placeholder && (
|
| 51 |
+
<option value="" disabled>
|
| 52 |
+
{placeholder}
|
| 53 |
+
</option>
|
| 54 |
+
)}
|
| 55 |
+
{options.map((option) => (
|
| 56 |
+
<option key={option.value.toString()} value={option.value.toString()}>
|
| 57 |
+
{option.label}
|
| 58 |
+
</option>
|
| 59 |
+
))}
|
| 60 |
+
</select>
|
| 61 |
+
{helpText && (
|
| 62 |
+
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
| 63 |
+
{helpText}
|
| 64 |
+
</div>
|
| 65 |
+
)}
|
| 66 |
+
</div>
|
| 67 |
+
);
|
| 68 |
+
}
|
src/components/question-creation-card/inputs/TextInput.tsx
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
export interface TextInputProps {
|
| 4 |
+
label: string;
|
| 5 |
+
value?: string;
|
| 6 |
+
defaultValue?: string;
|
| 7 |
+
onChange: (value: string) => void;
|
| 8 |
+
type?: 'text' | 'email' | 'password' | 'url' | 'tel';
|
| 9 |
+
className?: string;
|
| 10 |
+
disabled?: boolean;
|
| 11 |
+
placeholder?: string;
|
| 12 |
+
helpText?: string;
|
| 13 |
+
required?: boolean;
|
| 14 |
+
maxLength?: number;
|
| 15 |
+
minLength?: number;
|
| 16 |
+
pattern?: string;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
export default function TextInput({
|
| 20 |
+
label,
|
| 21 |
+
value,
|
| 22 |
+
defaultValue = '',
|
| 23 |
+
onChange,
|
| 24 |
+
type = 'text',
|
| 25 |
+
className = '',
|
| 26 |
+
disabled = false,
|
| 27 |
+
placeholder,
|
| 28 |
+
helpText,
|
| 29 |
+
required = false,
|
| 30 |
+
maxLength,
|
| 31 |
+
minLength,
|
| 32 |
+
pattern,
|
| 33 |
+
}: TextInputProps) {
|
| 34 |
+
const currentValue = value ?? defaultValue;
|
| 35 |
+
const baseClasses = "w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed";
|
| 36 |
+
|
| 37 |
+
return (
|
| 38 |
+
<div className={className}>
|
| 39 |
+
<label className="block text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">
|
| 40 |
+
{label}
|
| 41 |
+
{required && <span className="text-red-500 ml-1">*</span>}
|
| 42 |
+
</label>
|
| 43 |
+
<input
|
| 44 |
+
type={type}
|
| 45 |
+
value={currentValue}
|
| 46 |
+
placeholder={placeholder}
|
| 47 |
+
onChange={(e) => onChange(e.target.value)}
|
| 48 |
+
disabled={disabled}
|
| 49 |
+
required={required}
|
| 50 |
+
maxLength={maxLength}
|
| 51 |
+
minLength={minLength}
|
| 52 |
+
pattern={pattern}
|
| 53 |
+
className={`${baseClasses} bg-white dark:bg-gray-700 text-gray-900 dark:text-white border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400`}
|
| 54 |
+
/>
|
| 55 |
+
{helpText && (
|
| 56 |
+
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
| 57 |
+
{helpText}
|
| 58 |
+
</div>
|
| 59 |
+
)}
|
| 60 |
+
</div>
|
| 61 |
+
);
|
| 62 |
+
}
|
src/components/question-creation-card/inputs/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Independent input components
|
| 2 |
+
export { default as SelectInput } from './SelectInput';
|
| 3 |
+
export type { SelectInputProps, SelectOption } from './SelectInput';
|
| 4 |
+
|
| 5 |
+
export { default as RangeInput } from './RangeInput';
|
| 6 |
+
export type { RangeInputProps } from './RangeInput';
|
| 7 |
+
|
| 8 |
+
export { default as TextInput } from './TextInput';
|
| 9 |
+
export type { TextInputProps } from './TextInput';
|
| 10 |
+
|
| 11 |
+
export { default as CheckboxInput } from './CheckboxInput';
|
| 12 |
+
export type { CheckboxInputProps } from './CheckboxInput';
|
src/{types/questionConfig.ts → config/questionTypes.ts}
RENAMED
|
@@ -1,27 +1,4 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
export interface ParameterOption {
|
| 4 |
-
value: string | number;
|
| 5 |
-
label: string;
|
| 6 |
-
}
|
| 7 |
-
|
| 8 |
-
export interface ParameterConfig {
|
| 9 |
-
key: string;
|
| 10 |
-
label: string;
|
| 11 |
-
type: ParameterType;
|
| 12 |
-
defaultValue?: any;
|
| 13 |
-
options?: ParameterOption[];
|
| 14 |
-
min?: number;
|
| 15 |
-
max?: number;
|
| 16 |
-
step?: number;
|
| 17 |
-
placeholder?: string;
|
| 18 |
-
helpText?: string;
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
export interface QuestionTypeConfig {
|
| 22 |
-
id: string;
|
| 23 |
-
parameters: ParameterConfig[];
|
| 24 |
-
}
|
| 25 |
|
| 26 |
// Configuration for each question type
|
| 27 |
export const questionTypeConfigs: Record<string, QuestionTypeConfig> = {
|
|
|
|
| 1 |
+
import { QuestionTypeConfig } from '@/types/questionConfig';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
// Configuration for each question type
|
| 4 |
export const questionTypeConfigs: Record<string, QuestionTypeConfig> = {
|