@quizparts/react 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Choice.d.ts +11 -0
- package/dist/components/Choice.d.ts.map +1 -0
- package/dist/components/Choice.js +36 -0
- package/dist/components/ChoiceIndicator.d.ts +10 -0
- package/dist/components/ChoiceIndicator.d.ts.map +1 -0
- package/dist/components/ChoiceIndicator.js +19 -0
- package/dist/components/Choices.d.ts +7 -0
- package/dist/components/Choices.d.ts.map +1 -0
- package/dist/components/Choices.js +8 -0
- package/dist/components/ContinueButton.d.ts +7 -0
- package/dist/components/ContinueButton.d.ts.map +1 -0
- package/dist/components/ContinueButton.js +7 -0
- package/dist/components/DefaultQuestionLayout.d.ts +16 -0
- package/dist/components/DefaultQuestionLayout.d.ts.map +1 -0
- package/dist/components/DefaultQuestionLayout.js +20 -0
- package/dist/components/Feedback.d.ts +5 -0
- package/dist/components/Feedback.d.ts.map +1 -0
- package/dist/components/Feedback.js +8 -0
- package/dist/components/Hint.d.ts +12 -0
- package/dist/components/Hint.d.ts.map +1 -0
- package/dist/components/Hint.js +11 -0
- package/dist/components/Instruction.d.ts +10 -0
- package/dist/components/Instruction.d.ts.map +1 -0
- package/dist/components/Instruction.js +16 -0
- package/dist/components/MatchPairs.d.ts +2 -0
- package/dist/components/MatchPairs.d.ts.map +1 -0
- package/dist/components/MatchPairs.js +38 -0
- package/dist/components/NextButton.d.ts +6 -0
- package/dist/components/NextButton.d.ts.map +1 -0
- package/dist/components/NextButton.js +6 -0
- package/dist/components/OrderList.d.ts +2 -0
- package/dist/components/OrderList.d.ts.map +1 -0
- package/dist/components/OrderList.js +48 -0
- package/dist/components/Progress.d.ts +5 -0
- package/dist/components/Progress.d.ts.map +1 -0
- package/dist/components/Progress.js +6 -0
- package/dist/components/ProgressBar.d.ts +6 -0
- package/dist/components/ProgressBar.d.ts.map +1 -0
- package/dist/components/ProgressBar.js +7 -0
- package/dist/components/Prompt.d.ts +5 -0
- package/dist/components/Prompt.d.ts.map +1 -0
- package/dist/components/Prompt.js +8 -0
- package/dist/components/Question.d.ts +7 -0
- package/dist/components/Question.d.ts.map +1 -0
- package/dist/components/Question.js +8 -0
- package/dist/components/QuestionBody.d.ts +8 -0
- package/dist/components/QuestionBody.d.ts.map +1 -0
- package/dist/components/QuestionBody.js +3 -0
- package/dist/components/QuestionCard.d.ts +10 -0
- package/dist/components/QuestionCard.d.ts.map +1 -0
- package/dist/components/QuestionCard.js +3 -0
- package/dist/components/QuestionCounter.d.ts +9 -0
- package/dist/components/QuestionCounter.d.ts.map +1 -0
- package/dist/components/QuestionCounter.js +9 -0
- package/dist/components/QuestionFooter.d.ts +8 -0
- package/dist/components/QuestionFooter.d.ts.map +1 -0
- package/dist/components/QuestionFooter.js +3 -0
- package/dist/components/QuestionHeader.d.ts +8 -0
- package/dist/components/QuestionHeader.d.ts.map +1 -0
- package/dist/components/QuestionHeader.js +3 -0
- package/dist/components/QuestionInput.d.ts +9 -0
- package/dist/components/QuestionInput.d.ts.map +1 -0
- package/dist/components/QuestionInput.js +33 -0
- package/dist/components/QuizComplete.d.ts +17 -0
- package/dist/components/QuizComplete.d.ts.map +1 -0
- package/dist/components/QuizComplete.js +13 -0
- package/dist/components/QuizFlow.d.ts +24 -0
- package/dist/components/QuizFlow.d.ts.map +1 -0
- package/dist/components/QuizFlow.js +15 -0
- package/dist/components/QuizRoot.d.ts +9 -0
- package/dist/components/QuizRoot.d.ts.map +1 -0
- package/dist/components/QuizRoot.js +7 -0
- package/dist/components/SentenceBuilder.d.ts +2 -0
- package/dist/components/SentenceBuilder.d.ts.map +1 -0
- package/dist/components/SentenceBuilder.js +36 -0
- package/dist/components/SubmitButton.d.ts +7 -0
- package/dist/components/SubmitButton.d.ts.map +1 -0
- package/dist/components/SubmitButton.js +7 -0
- package/dist/components/TextInput.d.ts +7 -0
- package/dist/components/TextInput.d.ts.map +1 -0
- package/dist/components/TextInput.js +22 -0
- package/dist/components/ThemeVarsProvider.d.ts +15 -0
- package/dist/components/ThemeVarsProvider.d.ts.map +1 -0
- package/dist/components/ThemeVarsProvider.js +7 -0
- package/dist/constants/defaultInstructions.d.ts +3 -0
- package/dist/constants/defaultInstructions.d.ts.map +1 -0
- package/dist/constants/defaultInstructions.js +8 -0
- package/dist/context/QuizContext.d.ts +40 -0
- package/dist/context/QuizContext.d.ts.map +1 -0
- package/dist/context/QuizContext.js +88 -0
- package/dist/hooks/useProgress.d.ts +16 -0
- package/dist/hooks/useProgress.d.ts.map +1 -0
- package/dist/hooks/useProgress.js +22 -0
- package/dist/hooks/useQuestion.d.ts +55 -0
- package/dist/hooks/useQuestion.d.ts.map +1 -0
- package/dist/hooks/useQuestion.js +77 -0
- package/dist/hooks/useQuiz.d.ts +22 -0
- package/dist/hooks/useQuiz.d.ts.map +1 -0
- package/dist/hooks/useQuiz.js +59 -0
- package/dist/hooks/useQuizContext.d.ts +2 -0
- package/dist/hooks/useQuizContext.d.ts.map +1 -0
- package/dist/hooks/useQuizContext.js +10 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +135 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useQuestion } from '../hooks/useQuestion.js';
|
|
3
|
+
import { Instruction } from './Instruction.js';
|
|
4
|
+
import { Choices } from './Choices.js';
|
|
5
|
+
import { Choice } from './Choice.js';
|
|
6
|
+
import { TextInput } from './TextInput.js';
|
|
7
|
+
import { MatchPairs } from './MatchPairs.js';
|
|
8
|
+
import { OrderList } from './OrderList.js';
|
|
9
|
+
import { SentenceBuilder } from './SentenceBuilder.js';
|
|
10
|
+
/** Renders the appropriate input for the current question type (choices, text, match pairs, order, sentence builder) with optional Instruction. */
|
|
11
|
+
export const QuestionInput = ({ showChoiceIndicator = true, instruction, }) => {
|
|
12
|
+
const { question } = useQuestion();
|
|
13
|
+
if (!question)
|
|
14
|
+
return null;
|
|
15
|
+
if (question.type === 'multiple_choice' || question.type === 'multi_select') {
|
|
16
|
+
if (!('choices' in question))
|
|
17
|
+
return null;
|
|
18
|
+
return (_jsxs(_Fragment, { children: [_jsx(Instruction, { instruction: instruction }), _jsx(Choices, { children: question.choices.map((c) => (_jsx(Choice, { choiceId: c.id, showIndicator: showChoiceIndicator }, c.id))) })] }));
|
|
19
|
+
}
|
|
20
|
+
if (question.type === 'text_input') {
|
|
21
|
+
return (_jsxs(_Fragment, { children: [_jsx(Instruction, { instruction: instruction }), _jsx(TextInput, {})] }));
|
|
22
|
+
}
|
|
23
|
+
if (question.type === 'match_pairs') {
|
|
24
|
+
return (_jsxs(_Fragment, { children: [_jsx(Instruction, { instruction: instruction }), _jsx(MatchPairs, {})] }));
|
|
25
|
+
}
|
|
26
|
+
if (question.type === 'order_items') {
|
|
27
|
+
return (_jsxs(_Fragment, { children: [_jsx(Instruction, { instruction: instruction }), _jsx(OrderList, {})] }));
|
|
28
|
+
}
|
|
29
|
+
if (question.type === 'sentence_builder') {
|
|
30
|
+
return (_jsxs(_Fragment, { children: [_jsx(Instruction, { instruction: instruction }), _jsx(SentenceBuilder, {})] }));
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface QuizCompleteProps {
|
|
3
|
+
/** Heading (default "Quiz complete!"). */
|
|
4
|
+
title?: ReactNode;
|
|
5
|
+
/** Optional message below the title. */
|
|
6
|
+
message?: ReactNode;
|
|
7
|
+
/** Label for the reset button (default "Start again"). */
|
|
8
|
+
resetLabel?: string;
|
|
9
|
+
/** Called when reset is clicked; defaults to resetQuiz from context. */
|
|
10
|
+
onReset?: () => void;
|
|
11
|
+
/** Custom content instead of title/message/reset. */
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
as?: keyof JSX.IntrinsicElements;
|
|
14
|
+
}
|
|
15
|
+
/** Renders when the quiz is complete. Use inside QuizProvider; shows title, optional message, and reset button. */
|
|
16
|
+
export declare const QuizComplete: ({ title, message, resetLabel, onReset, children, as: Component, }: QuizCompleteProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
17
|
+
//# sourceMappingURL=QuizComplete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QuizComplete.d.ts","sourceRoot":"","sources":["../../src/components/QuizComplete.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,wCAAwC;IACxC,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;CAClC;AAED,mHAAmH;AACnH,eAAO,MAAM,YAAY,GAAI,mEAO1B,iBAAiB,mDAmBnB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useQuiz } from '../hooks/useQuiz.js';
|
|
3
|
+
/** Renders when the quiz is complete. Use inside QuizProvider; shows title, optional message, and reset button. */
|
|
4
|
+
export const QuizComplete = ({ title = 'Quiz complete!', message, resetLabel = 'Start again', onReset, children, as: Component = 'div', }) => {
|
|
5
|
+
const { isComplete, resetQuiz } = useQuiz();
|
|
6
|
+
if (!isComplete)
|
|
7
|
+
return null;
|
|
8
|
+
const handleReset = onReset ?? resetQuiz;
|
|
9
|
+
if (children != null) {
|
|
10
|
+
return _jsx(Component, { "data-quiz-complete": true, children: children });
|
|
11
|
+
}
|
|
12
|
+
return (_jsxs(Component, { "data-quiz-complete": true, style: { padding: '1.5rem', textAlign: 'center' }, children: [title != null && (typeof title === 'string' ? _jsx("h2", { children: title }) : title), message != null && (typeof message === 'string' ? _jsx("p", { children: message }) : message), _jsx("button", { type: "button", onClick: () => handleReset(), style: { marginTop: '1rem' }, children: resetLabel })] }));
|
|
13
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface QuizFlowProps {
|
|
3
|
+
/** Shown when quiz is complete. */
|
|
4
|
+
completeTitle?: ReactNode;
|
|
5
|
+
/** Optional message when complete. */
|
|
6
|
+
completeMessage?: ReactNode;
|
|
7
|
+
/** Reset button label when complete (default "Start again"). */
|
|
8
|
+
resetLabel?: string;
|
|
9
|
+
/** Optional hint below the question input. */
|
|
10
|
+
hint?: ReactNode;
|
|
11
|
+
/** Submit button label before answer (default "Answer"). */
|
|
12
|
+
submitLabel?: ReactNode;
|
|
13
|
+
/** Skip button label before answer (default "Skip"). Hidden after submit. */
|
|
14
|
+
continueLabel?: ReactNode;
|
|
15
|
+
/** Next button label after submit (default "Next"). */
|
|
16
|
+
nextLabel?: ReactNode;
|
|
17
|
+
/** Whether choices show the indicator (default true). */
|
|
18
|
+
showChoiceIndicator?: boolean;
|
|
19
|
+
/** Fallback when there is no current question (e.g. empty quiz). */
|
|
20
|
+
emptyFallback?: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
/** Renders the full quiz flow: complete screen or default question layout. Use inside QuizProvider + QuizRoot. */
|
|
23
|
+
export declare const QuizFlow: ({ completeTitle, completeMessage, resetLabel, hint, submitLabel, continueLabel, nextLabel, showChoiceIndicator, emptyFallback, }: QuizFlowProps) => import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
//# sourceMappingURL=QuizFlow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QuizFlow.d.ts","sourceRoot":"","sources":["../../src/components/QuizFlow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,MAAM,WAAW,aAAa;IAC5B,mCAAmC;IACnC,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,sCAAsC;IACtC,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,uDAAuD;IACvD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,yDAAyD;IACzD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,oEAAoE;IACpE,aAAa,CAAC,EAAE,SAAS,CAAC;CAC3B;AAED,kHAAkH;AAClH,eAAO,MAAM,QAAQ,GAAI,kIAUtB,aAAa,4CA0Bf,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useQuiz } from '../hooks/useQuiz.js';
|
|
3
|
+
import { Question } from './Question.js';
|
|
4
|
+
import { DefaultQuestionLayout } from './DefaultQuestionLayout.js';
|
|
5
|
+
import { QuizComplete } from './QuizComplete.js';
|
|
6
|
+
/** Renders the full quiz flow: complete screen or default question layout. Use inside QuizProvider + QuizRoot. */
|
|
7
|
+
export const QuizFlow = ({ completeTitle = 'Quiz complete!', completeMessage, resetLabel = 'Start again', hint, submitLabel = 'Answer', continueLabel = 'Skip', nextLabel = 'Next', showChoiceIndicator = true, emptyFallback = null, }) => {
|
|
8
|
+
const { currentQuestion, isComplete } = useQuiz();
|
|
9
|
+
if (isComplete) {
|
|
10
|
+
return (_jsx(QuizComplete, { title: completeTitle, message: completeMessage, resetLabel: resetLabel }));
|
|
11
|
+
}
|
|
12
|
+
if (!currentQuestion)
|
|
13
|
+
return _jsx(_Fragment, { children: emptyFallback });
|
|
14
|
+
return (_jsx(Question, { children: _jsx(DefaultQuestionLayout, { hint: hint, submitLabel: submitLabel, continueLabel: continueLabel, nextLabel: nextLabel, showChoiceIndicator: showChoiceIndicator }) }));
|
|
15
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface QuizRootProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
/** Root element (default 'div'). Use for semantics or to attach data-quiz-root to a section. */
|
|
5
|
+
as?: keyof JSX.IntrinsicElements;
|
|
6
|
+
}
|
|
7
|
+
/** Wrapper that marks the quiz UI root (theme targets data-quiz-root). Must be inside QuizProvider. */
|
|
8
|
+
export declare const QuizRoot: ({ children, as: Component }: QuizRootProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=QuizRoot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QuizRoot.d.ts","sourceRoot":"","sources":["../../src/components/QuizRoot.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,SAAS,CAAC;IACpB,gGAAgG;IAChG,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;CAClC;AAED,uGAAuG;AACvG,eAAO,MAAM,QAAQ,GAAI,6BAAqC,aAAa,4CAG1E,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useQuizContext } from '../hooks/useQuizContext.js';
|
|
3
|
+
/** Wrapper that marks the quiz UI root (theme targets data-quiz-root). Must be inside QuizProvider. */
|
|
4
|
+
export const QuizRoot = ({ children, as: Component = 'div' }) => {
|
|
5
|
+
useQuizContext();
|
|
6
|
+
return _jsx(Component, { "data-quiz-root": true, children: children });
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SentenceBuilder.d.ts","sourceRoot":"","sources":["../../src/components/SentenceBuilder.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,eAAe,sDA4E3B,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useQuestion } from '../hooks/useQuestion.js';
|
|
4
|
+
export const SentenceBuilder = () => {
|
|
5
|
+
const { question, sentenceOrder, setSentenceOrder, isSubmitted } = useQuestion();
|
|
6
|
+
if (!question || question.type !== 'sentence_builder')
|
|
7
|
+
return null;
|
|
8
|
+
const { tiles } = question;
|
|
9
|
+
const disabled = isSubmitted;
|
|
10
|
+
const available = tiles.slice();
|
|
11
|
+
for (const s of sentenceOrder) {
|
|
12
|
+
const i = available.indexOf(s);
|
|
13
|
+
if (i >= 0)
|
|
14
|
+
available.splice(i, 1);
|
|
15
|
+
}
|
|
16
|
+
const handleAddTile = (value) => {
|
|
17
|
+
if (disabled)
|
|
18
|
+
return;
|
|
19
|
+
setSentenceOrder([...sentenceOrder, value]);
|
|
20
|
+
};
|
|
21
|
+
const handleRemoveAt = (index) => {
|
|
22
|
+
if (disabled)
|
|
23
|
+
return;
|
|
24
|
+
setSentenceOrder(sentenceOrder.filter((_, i) => i !== index));
|
|
25
|
+
};
|
|
26
|
+
return (_jsxs("div", { "data-quiz-sentence-builder": true, style: { display: 'flex', flexDirection: 'column', gap: '0.75rem' }, role: "group", "aria-label": "Sentence builder", children: [_jsxs("div", { role: "group", "aria-labelledby": "sentence-slot-label", children: [_jsx("span", { id: "sentence-slot-label", style: { fontSize: '0.875rem', fontWeight: 600 }, children: "Sentence:" }), _jsxs("div", { style: {
|
|
27
|
+
display: 'flex',
|
|
28
|
+
flexWrap: 'wrap',
|
|
29
|
+
gap: '0.25rem',
|
|
30
|
+
minHeight: '2.5rem',
|
|
31
|
+
padding: '0.5rem',
|
|
32
|
+
border: '1px solid #eee',
|
|
33
|
+
borderRadius: '4px',
|
|
34
|
+
marginTop: '0.25rem',
|
|
35
|
+
}, "data-sentence-slot": true, children: [sentenceOrder.length === 0 && (_jsx("span", { style: { color: '#999' }, children: "Tap tiles below to build the sentence" })), sentenceOrder.map((value, index) => (_jsxs("button", { type: "button", onClick: () => handleRemoveAt(index), disabled: disabled, style: { padding: '0.25rem 0.5rem', margin: '0.125rem' }, "data-sentence-tile": true, children: [value, " ", !disabled && '✕'] }, `${value}-${index}`)))] })] }), _jsxs("div", { role: "group", "aria-labelledby": "tile-bank-label", children: [_jsx("span", { id: "tile-bank-label", style: { fontSize: '0.875rem', fontWeight: 600 }, children: "Tiles:" }), _jsx("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '0.25rem', marginTop: '0.25rem' }, "data-tile-bank": true, children: available.map((value, i) => (_jsx("button", { type: "button", onClick: () => handleAddTile(value), disabled: disabled, style: { padding: '0.35rem 0.6rem' }, "data-tile": true, children: value }, `${value}-${i}`))) })] })] }));
|
|
36
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface SubmitButtonProps {
|
|
3
|
+
children?: ReactNode;
|
|
4
|
+
}
|
|
5
|
+
/** Submit button for the current question. Disabled until input is valid; use with useQuiz().canSubmit. */
|
|
6
|
+
export declare const SubmitButton: ({ children }: SubmitButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=SubmitButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubmitButton.d.ts","sourceRoot":"","sources":["../../src/components/SubmitButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,2GAA2G;AAC3G,eAAO,MAAM,YAAY,GAAI,cAAyB,iBAAiB,4CAatE,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useQuiz } from '../hooks/useQuiz.js';
|
|
3
|
+
/** Submit button for the current question. Disabled until input is valid; use with useQuiz().canSubmit. */
|
|
4
|
+
export const SubmitButton = ({ children = 'Answer' }) => {
|
|
5
|
+
const { canSubmit, submitAnswer } = useQuiz();
|
|
6
|
+
return (_jsx("button", { type: "button", "data-quiz-submit": true, ...(!canSubmit && { 'data-disabled': true }), disabled: !canSubmit, onClick: () => canSubmit && submitAnswer(), children: children }));
|
|
7
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface TextInputProps {
|
|
2
|
+
as?: keyof JSX.IntrinsicElements;
|
|
3
|
+
placeholder?: string;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare const TextInput: ({ as: Component, placeholder, disabled: disabledProp, }: TextInputProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
+
//# sourceMappingURL=TextInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextInput.d.ts","sourceRoot":"","sources":["../../src/components/TextInput.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,SAAS,GAAI,yDAIvB,cAAc,mDAqBhB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useQuestion } from '../hooks/useQuestion.js';
|
|
3
|
+
export const TextInput = ({ as: Component = 'input', placeholder, disabled: disabledProp, }) => {
|
|
4
|
+
const { question, textValue, setTextValue, isSubmitted } = useQuestion();
|
|
5
|
+
if (!question || question.type !== 'text_input')
|
|
6
|
+
return null;
|
|
7
|
+
const disabled = disabledProp ?? isSubmitted;
|
|
8
|
+
const common = {
|
|
9
|
+
'data-quiz-text-input': true,
|
|
10
|
+
'data-disabled': disabled ? true : undefined,
|
|
11
|
+
'data-submitted': isSubmitted ? true : undefined,
|
|
12
|
+
value: textValue,
|
|
13
|
+
onChange: (e) => setTextValue(e.target.value),
|
|
14
|
+
disabled,
|
|
15
|
+
placeholder,
|
|
16
|
+
'aria-label': question.prompt,
|
|
17
|
+
};
|
|
18
|
+
if (Component === 'input') {
|
|
19
|
+
return _jsx("input", { type: "text", ...common });
|
|
20
|
+
}
|
|
21
|
+
return _jsx(Component, { ...common });
|
|
22
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ThemeTokens } from '@quizparts/theme';
|
|
3
|
+
export interface ThemeVarsProviderProps {
|
|
4
|
+
/** Theme to apply as CSS custom properties (e.g. defaultTheme from @quizparts/theme). */
|
|
5
|
+
theme: ThemeTokens;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
/** Wrapper element (default 'div'). */
|
|
8
|
+
as?: keyof JSX.IntrinsicElements;
|
|
9
|
+
/** Additional inline styles merged with theme vars. */
|
|
10
|
+
style?: React.CSSProperties;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Applies theme tokens as CSS custom properties on a wrapper. Use around QuizRoot so play.css can use --qp-* vars. */
|
|
14
|
+
export declare const ThemeVarsProvider: ({ theme, children, as: Component, style, className, }: ThemeVarsProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=ThemeVarsProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeVarsProvider.d.ts","sourceRoot":"","sources":["../../src/components/ThemeVarsProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,MAAM,WAAW,sBAAsB;IACrC,yFAAyF;IACzF,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB,uCAAuC;IACvC,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;IACjC,uDAAuD;IACvD,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,uHAAuH;AACvH,eAAO,MAAM,iBAAiB,GAAI,uDAM/B,sBAAsB,4CAUxB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { tokensToCssVars } from '@quizparts/theme';
|
|
3
|
+
/** Applies theme tokens as CSS custom properties on a wrapper. Use around QuizRoot so play.css can use --qp-* vars. */
|
|
4
|
+
export const ThemeVarsProvider = ({ theme, children, as: Component = 'div', style, className, }) => {
|
|
5
|
+
const vars = tokensToCssVars(theme);
|
|
6
|
+
return (_jsx(Component, { className: className, style: { ...vars, ...style }, children: children }));
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultInstructions.d.ts","sourceRoot":"","sources":["../../src/constants/defaultInstructions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAO7D,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const DEFAULT_INSTRUCTIONS = {
|
|
2
|
+
multiple_choice: 'Choose one.',
|
|
3
|
+
multi_select: 'Select all that apply.',
|
|
4
|
+
text_input: 'Type your answer below.',
|
|
5
|
+
match_pairs: 'Match the pairs.',
|
|
6
|
+
order_items: 'Put these in order.',
|
|
7
|
+
sentence_builder: 'Build the sentence from the tiles.',
|
|
8
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { Quiz } from '@quizparts/schema';
|
|
3
|
+
import type { QuizSession } from '@quizparts/core';
|
|
4
|
+
import { getProgress } from '@quizparts/core';
|
|
5
|
+
export interface QuizContextValue {
|
|
6
|
+
session: QuizSession;
|
|
7
|
+
progress: ReturnType<typeof getProgress>;
|
|
8
|
+
submitAnswer: () => {
|
|
9
|
+
correct: boolean;
|
|
10
|
+
};
|
|
11
|
+
goToNextQuestion: () => void;
|
|
12
|
+
goToPreviousQuestion: () => void;
|
|
13
|
+
resetQuiz: () => void;
|
|
14
|
+
selectChoice: (choiceId: string) => void;
|
|
15
|
+
toggleChoice: (choiceId: string) => void;
|
|
16
|
+
setTextValue: (value: string) => void;
|
|
17
|
+
setMatchPairs: (pairs: Array<[string, string]>) => void;
|
|
18
|
+
setOrderedIds: (orderedIds: string[]) => void;
|
|
19
|
+
setSentenceOrder: (order: string[]) => void;
|
|
20
|
+
}
|
|
21
|
+
declare const QuizContext: import("react").Context<QuizContextValue | null>;
|
|
22
|
+
export interface QuizProviderProps {
|
|
23
|
+
/** Parsed quiz (from parseQuiz). */
|
|
24
|
+
quiz: Quiz;
|
|
25
|
+
/** Zero-based index to start at (default 0). */
|
|
26
|
+
initialQuestionIndex?: number;
|
|
27
|
+
/** Pre-built session (e.g. for Storybook); must match quiz. */
|
|
28
|
+
initialSession?: QuizSession;
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
/** Called after each submit with whether the answer was correct. */
|
|
31
|
+
onQuestionSubmit?: (correct: boolean) => void;
|
|
32
|
+
/** Called when the current question index changes. */
|
|
33
|
+
onQuestionChange?: (index: number) => void;
|
|
34
|
+
/** Called when all questions have been attempted. */
|
|
35
|
+
onComplete?: () => void;
|
|
36
|
+
}
|
|
37
|
+
export declare const QuizProvider: ({ quiz, initialQuestionIndex, initialSession, children, onQuestionSubmit, onQuestionChange, onComplete, }: QuizProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
export declare const getQuizContext: () => import("react").Context<QuizContextValue | null>;
|
|
39
|
+
export default QuizContext;
|
|
40
|
+
//# sourceMappingURL=QuizContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QuizContext.d.ts","sourceRoot":"","sources":["../../src/context/QuizContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAYL,WAAW,EACZ,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IACzC,YAAY,EAAE,MAAM;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACzC,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,IAAI,CAAC;IACjC,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC;IACxD,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC9C,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7C;AAED,QAAA,MAAM,WAAW,kDAA+C,CAAC;AAEjE,MAAM,WAAW,iBAAiB;IAChC,oCAAoC;IACpC,IAAI,EAAE,IAAI,CAAC;IACX,gDAAgD;IAChD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,+DAA+D;IAC/D,cAAc,CAAC,EAAE,WAAW,CAAC;IAC7B,QAAQ,EAAE,SAAS,CAAC;IACpB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,eAAO,MAAM,YAAY,GAAI,2GAQ1B,iBAAiB,4CAsHnB,CAAC;AAEF,eAAO,MAAM,cAAc,wDAAoB,CAAC;AAChD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useMemo, useState } from 'react';
|
|
3
|
+
import { createQuizSession, selectChoice, toggleChoice, setTextInput, setMatchPairs, setOrderedIds, setSentenceOrder as setSentenceOrderCore, submitAnswer, goToNextQuestion, goToPreviousQuestion, resetQuiz, getProgress, } from '@quizparts/core';
|
|
4
|
+
const QuizContext = createContext(null);
|
|
5
|
+
export const QuizProvider = ({ quiz, initialQuestionIndex = 0, initialSession, children, onQuestionSubmit, onQuestionChange, onComplete, }) => {
|
|
6
|
+
const [session, setSession] = useState(() => {
|
|
7
|
+
if (initialSession)
|
|
8
|
+
return initialSession;
|
|
9
|
+
const s = createQuizSession(quiz);
|
|
10
|
+
if (initialQuestionIndex > 0 && initialQuestionIndex < quiz.questions.length) {
|
|
11
|
+
const next = [...s.questionStates];
|
|
12
|
+
for (let i = 0; i < next.length; i++) {
|
|
13
|
+
next[i] = { ...next[i], status: i < initialQuestionIndex ? 'complete' : i === initialQuestionIndex ? 'active' : 'idle' };
|
|
14
|
+
}
|
|
15
|
+
return { ...s, currentQuestionIndex: initialQuestionIndex, questionStates: next };
|
|
16
|
+
}
|
|
17
|
+
return s;
|
|
18
|
+
});
|
|
19
|
+
const progress = useMemo(() => getProgress(session), [session]);
|
|
20
|
+
const handleSubmitAnswer = useCallback(() => {
|
|
21
|
+
const result = submitAnswer(session);
|
|
22
|
+
setSession(result.session);
|
|
23
|
+
onQuestionSubmit?.(result.correct);
|
|
24
|
+
return result;
|
|
25
|
+
}, [session, onQuestionSubmit]);
|
|
26
|
+
const handleGoToNext = useCallback(() => {
|
|
27
|
+
const next = goToNextQuestion(session);
|
|
28
|
+
setSession(next);
|
|
29
|
+
onQuestionChange?.(next.currentQuestionIndex);
|
|
30
|
+
if (next.currentQuestionIndex >= session.quiz.questions.length && progress.attemptedCount >= session.quiz.questions.length) {
|
|
31
|
+
onComplete?.();
|
|
32
|
+
}
|
|
33
|
+
}, [session, onQuestionChange, onComplete, progress.attemptedCount]);
|
|
34
|
+
const handleGoToPrevious = useCallback(() => {
|
|
35
|
+
setSession(goToPreviousQuestion(session));
|
|
36
|
+
}, [session]);
|
|
37
|
+
const handleReset = useCallback(() => {
|
|
38
|
+
setSession(resetQuiz(session));
|
|
39
|
+
}, [session]);
|
|
40
|
+
const handleSelectChoice = useCallback((choiceId) => {
|
|
41
|
+
setSession(selectChoice(session, session.currentQuestionIndex, choiceId));
|
|
42
|
+
}, [session]);
|
|
43
|
+
const handleToggleChoice = useCallback((choiceId) => {
|
|
44
|
+
setSession(toggleChoice(session, session.currentQuestionIndex, choiceId));
|
|
45
|
+
}, [session]);
|
|
46
|
+
const handleSetTextValue = useCallback((value) => {
|
|
47
|
+
setSession(setTextInput(session, session.currentQuestionIndex, value));
|
|
48
|
+
}, [session]);
|
|
49
|
+
const handleSetMatchPairs = useCallback((pairs) => {
|
|
50
|
+
setSession(setMatchPairs(session, session.currentQuestionIndex, pairs));
|
|
51
|
+
}, [session]);
|
|
52
|
+
const handleSetOrderedIds = useCallback((orderedIds) => {
|
|
53
|
+
setSession(setOrderedIds(session, session.currentQuestionIndex, orderedIds));
|
|
54
|
+
}, [session]);
|
|
55
|
+
const handleSetSentenceOrder = useCallback((order) => {
|
|
56
|
+
setSession(setSentenceOrderCore(session, session.currentQuestionIndex, order));
|
|
57
|
+
}, [session]);
|
|
58
|
+
const value = useMemo(() => ({
|
|
59
|
+
session,
|
|
60
|
+
progress,
|
|
61
|
+
submitAnswer: handleSubmitAnswer,
|
|
62
|
+
goToNextQuestion: handleGoToNext,
|
|
63
|
+
goToPreviousQuestion: handleGoToPrevious,
|
|
64
|
+
resetQuiz: handleReset,
|
|
65
|
+
selectChoice: handleSelectChoice,
|
|
66
|
+
toggleChoice: handleToggleChoice,
|
|
67
|
+
setTextValue: handleSetTextValue,
|
|
68
|
+
setMatchPairs: handleSetMatchPairs,
|
|
69
|
+
setOrderedIds: handleSetOrderedIds,
|
|
70
|
+
setSentenceOrder: handleSetSentenceOrder,
|
|
71
|
+
}), [
|
|
72
|
+
session,
|
|
73
|
+
progress,
|
|
74
|
+
handleSubmitAnswer,
|
|
75
|
+
handleGoToNext,
|
|
76
|
+
handleGoToPrevious,
|
|
77
|
+
handleReset,
|
|
78
|
+
handleSelectChoice,
|
|
79
|
+
handleToggleChoice,
|
|
80
|
+
handleSetTextValue,
|
|
81
|
+
handleSetMatchPairs,
|
|
82
|
+
handleSetOrderedIds,
|
|
83
|
+
handleSetSentenceOrder,
|
|
84
|
+
]);
|
|
85
|
+
return (_jsx(QuizContext.Provider, { value: value, children: children }));
|
|
86
|
+
};
|
|
87
|
+
export const getQuizContext = () => QuizContext;
|
|
88
|
+
export default QuizContext;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress for the current quiz. Use inside QuizProvider.
|
|
3
|
+
* @returns current, total, percent, answeredCount, score, canGoNext, canGoPrevious, isComplete.
|
|
4
|
+
*/
|
|
5
|
+
export declare const useProgress: () => {
|
|
6
|
+
current: number;
|
|
7
|
+
total: number;
|
|
8
|
+
percent: number;
|
|
9
|
+
answeredCount: number;
|
|
10
|
+
remainingCount: number;
|
|
11
|
+
score: number;
|
|
12
|
+
canGoNext: boolean;
|
|
13
|
+
canGoPrevious: boolean;
|
|
14
|
+
isComplete: boolean;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=useProgress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useProgress.d.ts","sourceRoot":"","sources":["../../src/hooks/useProgress.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;CAgBvB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useQuizContext } from './useQuizContext.js';
|
|
2
|
+
/**
|
|
3
|
+
* Progress for the current quiz. Use inside QuizProvider.
|
|
4
|
+
* @returns current, total, percent, answeredCount, score, canGoNext, canGoPrevious, isComplete.
|
|
5
|
+
*/
|
|
6
|
+
export const useProgress = () => {
|
|
7
|
+
const { progress } = useQuizContext();
|
|
8
|
+
const total = progress.total;
|
|
9
|
+
const current = progress.currentIndex + 1;
|
|
10
|
+
const percent = total > 0 ? Math.round((progress.attemptedCount / total) * 100) : 0;
|
|
11
|
+
return {
|
|
12
|
+
current,
|
|
13
|
+
total,
|
|
14
|
+
percent,
|
|
15
|
+
answeredCount: progress.attemptedCount,
|
|
16
|
+
remainingCount: Math.max(0, total - progress.attemptedCount),
|
|
17
|
+
score: progress.score,
|
|
18
|
+
canGoNext: progress.canGoNext,
|
|
19
|
+
canGoPrevious: progress.canGoPrevious,
|
|
20
|
+
isComplete: progress.attemptedCount >= total && total > 0,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current question state and input setters. Use inside QuizProvider for question-type-specific UI.
|
|
3
|
+
* @returns question, selectedChoiceId/selectedChoiceIds, textValue, matchPairs, orderedIds, sentenceOrder, feedback, selectChoice, toggleChoice, setTextValue, setMatchPairs, setOrderedIds, setSentenceOrder.
|
|
4
|
+
*/
|
|
5
|
+
export declare const useQuestion: () => {
|
|
6
|
+
question: null;
|
|
7
|
+
questionState: null;
|
|
8
|
+
status: "idle";
|
|
9
|
+
selectedChoiceId: null;
|
|
10
|
+
selectedChoiceIds: never[];
|
|
11
|
+
textValue: string;
|
|
12
|
+
isSubmitted: boolean;
|
|
13
|
+
isCorrect: boolean;
|
|
14
|
+
isIncorrect: boolean;
|
|
15
|
+
feedback: null;
|
|
16
|
+
selectChoice: () => void;
|
|
17
|
+
toggleChoice: () => void;
|
|
18
|
+
setTextValue: () => void;
|
|
19
|
+
setMatchPairs: () => void;
|
|
20
|
+
setOrderedIds: () => void;
|
|
21
|
+
setSentenceOrder: () => void;
|
|
22
|
+
matchPairs: never[];
|
|
23
|
+
orderedIds: never[];
|
|
24
|
+
sentenceOrder: never[];
|
|
25
|
+
submit: () => {
|
|
26
|
+
correct: boolean;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
question: import("@quizparts/schema").QuizQuestion;
|
|
30
|
+
questionState: import("@quizparts/core").QuestionState;
|
|
31
|
+
status: import("@quizparts/core").QuestionStatus;
|
|
32
|
+
selectedChoiceId: string | null;
|
|
33
|
+
selectedChoiceIds: string[];
|
|
34
|
+
textValue: string;
|
|
35
|
+
matchPairs: [string, string][];
|
|
36
|
+
orderedIds: string[];
|
|
37
|
+
sentenceOrder: string[];
|
|
38
|
+
isSubmitted: boolean;
|
|
39
|
+
isCorrect: boolean;
|
|
40
|
+
isIncorrect: boolean;
|
|
41
|
+
feedback: {
|
|
42
|
+
isCorrect: boolean;
|
|
43
|
+
explanation: string | undefined;
|
|
44
|
+
} | null;
|
|
45
|
+
selectChoice: (choiceId: string) => void;
|
|
46
|
+
toggleChoice: (choiceId: string) => void;
|
|
47
|
+
setTextValue: (value: string) => void;
|
|
48
|
+
setMatchPairs: (pairs: Array<[string, string]>) => void;
|
|
49
|
+
setOrderedIds: (orderedIds: string[]) => void;
|
|
50
|
+
setSentenceOrder: (order: string[]) => void;
|
|
51
|
+
submit: () => {
|
|
52
|
+
correct: boolean;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=useQuestion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQuestion.d.ts","sourceRoot":"","sources":["../../src/hooks/useQuestion.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;kBA2BmB;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoD9D,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useQuizContext } from './useQuizContext.js';
|
|
2
|
+
/**
|
|
3
|
+
* Current question state and input setters. Use inside QuizProvider for question-type-specific UI.
|
|
4
|
+
* @returns question, selectedChoiceId/selectedChoiceIds, textValue, matchPairs, orderedIds, sentenceOrder, feedback, selectChoice, toggleChoice, setTextValue, setMatchPairs, setOrderedIds, setSentenceOrder.
|
|
5
|
+
*/
|
|
6
|
+
export const useQuestion = () => {
|
|
7
|
+
const { session, submitAnswer, selectChoice, toggleChoice, setTextValue, setMatchPairs: setMatchPairsContext, setOrderedIds: setOrderedIdsContext, setSentenceOrder: setSentenceOrderContext } = useQuizContext();
|
|
8
|
+
const idx = session.currentQuestionIndex;
|
|
9
|
+
const question = session.quiz.questions[idx] ?? null;
|
|
10
|
+
const questionState = session.questionStates[idx] ?? null;
|
|
11
|
+
if (!question || !questionState) {
|
|
12
|
+
return {
|
|
13
|
+
question: null,
|
|
14
|
+
questionState: null,
|
|
15
|
+
status: 'idle',
|
|
16
|
+
selectedChoiceId: null,
|
|
17
|
+
selectedChoiceIds: [],
|
|
18
|
+
textValue: '',
|
|
19
|
+
isSubmitted: false,
|
|
20
|
+
isCorrect: false,
|
|
21
|
+
isIncorrect: false,
|
|
22
|
+
feedback: null,
|
|
23
|
+
selectChoice: () => { },
|
|
24
|
+
toggleChoice: () => { },
|
|
25
|
+
setTextValue: () => { },
|
|
26
|
+
setMatchPairs: () => { },
|
|
27
|
+
setOrderedIds: () => { },
|
|
28
|
+
setSentenceOrder: () => { },
|
|
29
|
+
matchPairs: [],
|
|
30
|
+
orderedIds: [],
|
|
31
|
+
sentenceOrder: [],
|
|
32
|
+
submit: () => ({ correct: false }),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const status = questionState.status;
|
|
36
|
+
const isSubmitted = status === 'correct' || status === 'incorrect';
|
|
37
|
+
const isCorrect = questionState.isCorrect === true;
|
|
38
|
+
const isIncorrect = questionState.isCorrect === false;
|
|
39
|
+
const selectedChoiceId = question.type === 'multiple_choice'
|
|
40
|
+
? questionState.input.selectedChoiceId ?? null
|
|
41
|
+
: null;
|
|
42
|
+
const selectedChoiceIds = question.type === 'multi_select'
|
|
43
|
+
? questionState.input.selectedChoiceIds ?? []
|
|
44
|
+
: [];
|
|
45
|
+
const textValue = question.type === 'text_input' ? (questionState.input.text ?? '') : '';
|
|
46
|
+
const matchPairs = question.type === 'match_pairs' ? (questionState.input.matchPairs ?? []) : [];
|
|
47
|
+
const orderedIds = question.type === 'order_items' ? (questionState.input.orderedIds ?? []) : [];
|
|
48
|
+
const sentenceOrder = question.type === 'sentence_builder' ? (questionState.input.sentenceOrder ?? []) : [];
|
|
49
|
+
const feedback = isSubmitted
|
|
50
|
+
? {
|
|
51
|
+
isCorrect,
|
|
52
|
+
explanation: 'explanation' in question ? question.explanation : undefined,
|
|
53
|
+
}
|
|
54
|
+
: null;
|
|
55
|
+
return {
|
|
56
|
+
question,
|
|
57
|
+
questionState,
|
|
58
|
+
status,
|
|
59
|
+
selectedChoiceId,
|
|
60
|
+
selectedChoiceIds,
|
|
61
|
+
textValue,
|
|
62
|
+
matchPairs,
|
|
63
|
+
orderedIds,
|
|
64
|
+
sentenceOrder,
|
|
65
|
+
isSubmitted,
|
|
66
|
+
isCorrect,
|
|
67
|
+
isIncorrect,
|
|
68
|
+
feedback,
|
|
69
|
+
selectChoice,
|
|
70
|
+
toggleChoice,
|
|
71
|
+
setTextValue,
|
|
72
|
+
setMatchPairs: setMatchPairsContext,
|
|
73
|
+
setOrderedIds: setOrderedIdsContext,
|
|
74
|
+
setSentenceOrder: setSentenceOrderContext,
|
|
75
|
+
submit: submitAnswer,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quiz-level state and actions. Use inside QuizProvider.
|
|
3
|
+
* @returns Current question, canSubmit, submitAnswer, goToNextQuestion, score, etc.
|
|
4
|
+
*/
|
|
5
|
+
export declare const useQuiz: () => {
|
|
6
|
+
quiz: import("@quizparts/schema").Quiz;
|
|
7
|
+
currentQuestion: import("@quizparts/schema").QuizQuestion;
|
|
8
|
+
currentQuestionIndex: number;
|
|
9
|
+
questionCount: number;
|
|
10
|
+
canSubmit: boolean;
|
|
11
|
+
isComplete: boolean;
|
|
12
|
+
score: number;
|
|
13
|
+
canGoNext: boolean;
|
|
14
|
+
canGoPrevious: boolean;
|
|
15
|
+
goToNextQuestion: () => void;
|
|
16
|
+
goToPreviousQuestion: () => void;
|
|
17
|
+
submitAnswer: () => {
|
|
18
|
+
correct: boolean;
|
|
19
|
+
};
|
|
20
|
+
resetQuiz: () => void;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=useQuiz.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQuiz.d.ts","sourceRoot":"","sources":["../../src/hooks/useQuiz.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;CAwDnB,CAAC"}
|