@c2-markdown-ui/react 0.4.4 → 0.4.5

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.
Files changed (73) hide show
  1. package/README.md +24 -6
  2. package/dist/index.js +4365 -3733
  3. package/package.json +2 -2
  4. package/src/lib/MarkdownUI.tsx +47 -9
  5. package/src/lib/widgets.css +2188 -1372
  6. package/src/widgets/Button.tsx +39 -39
  7. package/src/widgets/ButtonGroup.tsx +47 -47
  8. package/src/widgets/Cascader.tsx +3 -2
  9. package/src/widgets/Chart.tsx +237 -237
  10. package/src/widgets/DatePicker.tsx +451 -434
  11. package/src/widgets/Form.tsx +512 -372
  12. package/src/widgets/Incomplete.tsx +3 -3
  13. package/src/widgets/MultipleChoiceQuestion.tsx +82 -82
  14. package/src/widgets/Quiz.tsx +320 -320
  15. package/src/widgets/Radio.tsx +57 -26
  16. package/src/widgets/Select.tsx +178 -142
  17. package/src/widgets/SelectMulti.tsx +105 -72
  18. package/src/widgets/ShortAnswerQuestion.tsx +101 -101
  19. package/src/widgets/Slider.tsx +70 -56
  20. package/src/widgets/Switch.tsx +14 -2
  21. package/src/widgets/TextInput.tsx +76 -38
  22. package/src/widgets/TreeSelect.tsx +649 -0
  23. package/src/widgets/Widget.tsx +130 -82
  24. package/src/widgets/choiceUtils.ts +90 -45
  25. package/src/widgets/index.ts +17 -16
  26. package/dist/index.d.ts +0 -2
  27. package/dist/index.d.ts.map +0 -1
  28. package/dist/lib/MarkdownUI.d.ts +0 -10
  29. package/dist/lib/MarkdownUI.d.ts.map +0 -1
  30. package/dist/lib/index.d.ts +0 -4
  31. package/dist/lib/index.d.ts.map +0 -1
  32. package/dist/widgets/Button.d.ts +0 -15
  33. package/dist/widgets/Button.d.ts.map +0 -1
  34. package/dist/widgets/ButtonGroup.d.ts +0 -11
  35. package/dist/widgets/ButtonGroup.d.ts.map +0 -1
  36. package/dist/widgets/Cascader.d.ts +0 -34
  37. package/dist/widgets/Cascader.d.ts.map +0 -1
  38. package/dist/widgets/Chart.d.ts +0 -20
  39. package/dist/widgets/Chart.d.ts.map +0 -1
  40. package/dist/widgets/DatePicker.d.ts +0 -28
  41. package/dist/widgets/DatePicker.d.ts.map +0 -1
  42. package/dist/widgets/Form.d.ts +0 -47
  43. package/dist/widgets/Form.d.ts.map +0 -1
  44. package/dist/widgets/Incomplete.d.ts +0 -3
  45. package/dist/widgets/Incomplete.d.ts.map +0 -1
  46. package/dist/widgets/MultipleChoiceQuestion.d.ts +0 -15
  47. package/dist/widgets/MultipleChoiceQuestion.d.ts.map +0 -1
  48. package/dist/widgets/NumberInput.d.ts +0 -26
  49. package/dist/widgets/NumberInput.d.ts.map +0 -1
  50. package/dist/widgets/Quiz.d.ts +0 -37
  51. package/dist/widgets/Quiz.d.ts.map +0 -1
  52. package/dist/widgets/Radio.d.ts +0 -24
  53. package/dist/widgets/Radio.d.ts.map +0 -1
  54. package/dist/widgets/Select.d.ts +0 -15
  55. package/dist/widgets/Select.d.ts.map +0 -1
  56. package/dist/widgets/SelectMulti.d.ts +0 -20
  57. package/dist/widgets/SelectMulti.d.ts.map +0 -1
  58. package/dist/widgets/ShortAnswerQuestion.d.ts +0 -16
  59. package/dist/widgets/ShortAnswerQuestion.d.ts.map +0 -1
  60. package/dist/widgets/Slider.d.ts +0 -13
  61. package/dist/widgets/Slider.d.ts.map +0 -1
  62. package/dist/widgets/Switch.d.ts +0 -17
  63. package/dist/widgets/Switch.d.ts.map +0 -1
  64. package/dist/widgets/TextInput.d.ts +0 -11
  65. package/dist/widgets/TextInput.d.ts.map +0 -1
  66. package/dist/widgets/Widget.d.ts +0 -7
  67. package/dist/widgets/Widget.d.ts.map +0 -1
  68. package/dist/widgets/chartRemoteData.d.ts +0 -18
  69. package/dist/widgets/chartRemoteData.d.ts.map +0 -1
  70. package/dist/widgets/choiceUtils.d.ts +0 -24
  71. package/dist/widgets/choiceUtils.d.ts.map +0 -1
  72. package/dist/widgets/index.d.ts +0 -18
  73. package/dist/widgets/index.d.ts.map +0 -1
@@ -1,320 +1,320 @@
1
- import React, { useState, useMemo } from 'react';
2
- import { parseChoices } from './choiceUtils';
3
-
4
- export interface QuizProps {
5
- title: string;
6
- questions: QuizQuestion[];
7
- showScore?: boolean;
8
- showProgress?: boolean;
9
- passingScore?: number;
10
- /** 是否禁用:级联禁用所有题目 */
11
- disabled?: boolean;
12
- onchange: (value: QuizState) => void;
13
- }
14
-
15
- interface QuizQuestion {
16
- id: string;
17
- type: "mcq" | "short-answer";
18
- question: string;
19
- points: number;
20
- // MCQ specific
21
- choices?: string[];
22
- correctAnswer?: string;
23
- // Short answer specific
24
- placeholder?: string;
25
- correctAnswers?: string[];
26
- }
27
-
28
- interface QuizAnswer {
29
- answer: string;
30
- isCorrect: boolean;
31
- points: number;
32
- submitted: boolean;
33
- }
34
-
35
- interface QuizState {
36
- answers: Record<string, QuizAnswer>;
37
- totalScore: number;
38
- maxScore: number;
39
- questionsAnswered: number;
40
- isCompleted: boolean;
41
- }
42
-
43
- export const Quiz: React.FC<QuizProps> = ({
44
- title,
45
- questions,
46
- showScore = true,
47
- showProgress = true,
48
- passingScore,
49
- disabled = false,
50
- onchange
51
- }) => {
52
- const [answers, setAnswers] = useState<Record<string, QuizAnswer>>({});
53
- const [draftInputs, setDraftInputs] = useState<Record<string, string>>({});
54
-
55
- const quizState = useMemo<QuizState>(() => {
56
- const maxScore = questions.reduce((sum, q) => sum + q.points, 0);
57
- const totalScore = Object.values(answers).reduce((sum, a) => sum + a.points, 0);
58
- const questionsAnswered = Object.keys(answers).length;
59
- const isCompleted = questionsAnswered === questions.length;
60
-
61
- return {
62
- answers,
63
- totalScore,
64
- maxScore,
65
- questionsAnswered,
66
- isCompleted
67
- };
68
- }, [answers, questions]);
69
-
70
- const handleQuestionAnswer = (questionId: string, userAnswer: string) => {
71
- if (disabled) return;
72
- const question = questions.find(q => q.id === questionId);
73
- if (!question || answers[questionId]?.submitted) return;
74
-
75
- let isCorrect = false;
76
-
77
- if (question.type === "mcq") {
78
- isCorrect = question.correctAnswer === userAnswer;
79
- } else if (question.type === "short-answer") {
80
- if (question.correctAnswers) {
81
- isCorrect = question.correctAnswers.some(
82
- correct => correct.toLowerCase() === userAnswer.toLowerCase()
83
- );
84
- }
85
- }
86
-
87
- const newAnswer: QuizAnswer = {
88
- answer: userAnswer,
89
- isCorrect,
90
- points: isCorrect ? question.points : 0,
91
- submitted: true
92
- };
93
-
94
- const newAnswers = { ...answers, [questionId]: newAnswer };
95
- setAnswers(newAnswers);
96
-
97
- // Trigger onChange with updated state
98
- const updatedState = {
99
- answers: newAnswers,
100
- totalScore: Object.values(newAnswers).reduce((sum, a) => sum + a.points, 0),
101
- maxScore: quizState.maxScore,
102
- questionsAnswered: Object.keys(newAnswers).length,
103
- isCompleted: Object.keys(newAnswers).length === questions.length
104
- };
105
-
106
- onchange(updatedState);
107
- };
108
-
109
- const getProgressPercentage = () => {
110
- return Math.round((quizState.questionsAnswered / questions.length) * 100);
111
- };
112
-
113
- const getScorePercentage = () => {
114
- return quizState.maxScore > 0 ? Math.round((quizState.totalScore / quizState.maxScore) * 100) : 0;
115
- };
116
-
117
- const isPassing = () => {
118
- return passingScore ? getScorePercentage() >= passingScore : null;
119
- };
120
-
121
- const renderMCQQuestion = (question: QuizQuestion) => {
122
- const answer = answers[question.id];
123
- const isAnswered = answer?.submitted;
124
- const hasCorrect = typeof question.correctAnswer !== 'undefined';
125
- // mcq choices 支持 key:label 格式:value(key) 是取值,label 是展示文案
126
- const items = parseChoices(question.choices ?? []);
127
- // 反馈文案:correctAnswer 是 key,展示时映射回 label
128
- const correctLabel = hasCorrect
129
- ? (items.find(i => i.value === question.correctAnswer)?.label ?? question.correctAnswer as string)
130
- : '';
131
-
132
- return (
133
- <div key={question.id} className="quiz-question">
134
- <div className="quiz-question-header">
135
- <h3 className="quiz-question-text">{question.question}</h3>
136
- <span className="quiz-question-points">{question.points} pts</span>
137
- </div>
138
-
139
- <div className="quiz-choices">
140
- {items.map((item, index) => {
141
- let choiceClass = 'quiz-choice';
142
-
143
- if (isAnswered && hasCorrect) {
144
- if (item.value === question.correctAnswer) {
145
- choiceClass += ' quiz-choice-correct';
146
- } else if (item.value === answer.answer && item.value !== question.correctAnswer) {
147
- choiceClass += ' quiz-choice-incorrect';
148
- }
149
- } else if (item.value === answer?.answer) {
150
- choiceClass += ' quiz-choice-selected';
151
- }
152
-
153
- return (
154
- <button
155
- key={index}
156
- className={choiceClass}
157
- onClick={() => handleQuestionAnswer(question.id, item.value)}
158
- disabled={disabled || isAnswered}
159
- >
160
- {item.label}
161
- </button>
162
- );
163
- })}
164
- </div>
165
-
166
- {isAnswered && (
167
- hasCorrect ? (
168
- <div className={`quiz-feedback ${answer.isCorrect ? 'correct' : 'incorrect'}`}>
169
- {answer.isCorrect
170
- ? `✓ Correct! (+${answer.points} pts)`
171
- : `✗ Incorrect. The correct answer is: ${correctLabel}`}
172
- </div>
173
- ) : (
174
- <div className="quiz-feedback">
175
- {`You selected: ${answer.answer}`}
176
- </div>
177
- )
178
- )}
179
- </div>
180
- );
181
- };
182
-
183
- const renderShortAnswerQuestion = (question: QuizQuestion) => {
184
- const answer = answers[question.id];
185
- const isAnswered = answer?.submitted;
186
- const hasAnswers = (question.correctAnswers && question.correctAnswers.length > 0) || false;
187
- const inputValue = draftInputs[question.id] ?? '';
188
-
189
- const handleSubmit = () => {
190
- if (disabled) return;
191
- if (!inputValue.trim() || isAnswered) return;
192
- handleQuestionAnswer(question.id, inputValue.trim());
193
- // Clear draft after submission
194
- setDraftInputs(prev => {
195
- const next = { ...prev };
196
- delete next[question.id];
197
- return next;
198
- });
199
- };
200
-
201
- const handleKeyDown = (e: React.KeyboardEvent) => {
202
- if (e.key === 'Enter' && !isAnswered && !disabled) {
203
- handleSubmit();
204
- }
205
- };
206
-
207
- return (
208
- <div key={question.id} className="quiz-question">
209
- <div className="quiz-question-header">
210
- <h3 className="quiz-question-text">{question.question}</h3>
211
- <span className="quiz-question-points">{question.points} pts</span>
212
- </div>
213
-
214
- <div className="quiz-input-container">
215
- <input
216
- type="text"
217
- value={isAnswered ? answer.answer : inputValue}
218
- onChange={(e) => {
219
- if (!isAnswered && !disabled) {
220
- const val = e.target.value;
221
- setDraftInputs(prev => ({ ...prev, [question.id]: val }));
222
- }
223
- }}
224
- onKeyDown={handleKeyDown}
225
- placeholder={question.placeholder || "Type your answer here..."}
226
- disabled={disabled || isAnswered}
227
- className={`quiz-input ${
228
- isAnswered
229
- ? hasAnswers
230
- ? (answer.isCorrect ? 'quiz-input-correct' : 'quiz-input-incorrect')
231
- : ''
232
- : ''
233
- }`}
234
- />
235
- {!isAnswered && (
236
- <button
237
- onClick={handleSubmit}
238
- disabled={disabled || !inputValue.trim()}
239
- className="quiz-submit"
240
- >
241
- Submit
242
- </button>
243
- )}
244
- </div>
245
-
246
- {isAnswered && (
247
- hasAnswers ? (
248
- <div className={`quiz-feedback ${answer.isCorrect ? 'correct' : 'incorrect'}`}>
249
- {answer.isCorrect
250
- ? `✓ Correct! (+${answer.points} pts)`
251
- : `✗ Incorrect. Accepted answers: ${question.correctAnswers?.join(', ') || 'N/A'}`}
252
- </div>
253
- ) : (
254
- <div className="quiz-feedback">
255
- {`Your answer: ${answer.answer}`}
256
- </div>
257
- )
258
- )}
259
- </div>
260
- );
261
- };
262
-
263
- return (
264
- <div className="widget-quiz">
265
- {/* Quiz Header */}
266
- <div className="quiz-header">
267
- <h2 className="quiz-title">{title}</h2>
268
-
269
- <div className="quiz-stats">
270
- {showProgress && (
271
- <div className="quiz-progress">
272
- <span className="quiz-progress-text">
273
- Progress: {quizState.questionsAnswered}/{questions.length} questions
274
- </span>
275
- <div className="quiz-progress-bar">
276
- <div
277
- className="quiz-progress-fill"
278
- style={{ width: `${getProgressPercentage()}%` }}
279
- />
280
- </div>
281
- </div>
282
- )}
283
-
284
- {showScore && (
285
- <div className="quiz-score">
286
- <span className="quiz-score-text">
287
- Score: {quizState.totalScore}/{quizState.maxScore} ({getScorePercentage()}%)
288
- </span>
289
- </div>
290
- )}
291
- </div>
292
- </div>
293
-
294
- {/* Questions */}
295
- <div className="quiz-questions">
296
- {questions.map((question) =>
297
- question.type === "mcq"
298
- ? renderMCQQuestion(question)
299
- : renderShortAnswerQuestion(question)
300
- )}
301
- </div>
302
-
303
- {/* Quiz Summary */}
304
- {quizState.isCompleted && (
305
- <div className="quiz-summary">
306
- <h3>Quiz Complete!</h3>
307
- <div className="quiz-final-score">
308
- Final Score: {quizState.totalScore}/{quizState.maxScore} points ({getScorePercentage()}%)
309
- </div>
310
- {passingScore && (
311
- <div className={`quiz-result ${isPassing() ? 'passed' : 'failed'}`}>
312
- {isPassing() ? '🎉 Passed!' : '❌ Failed'}
313
- (Passing: {passingScore}%)
314
- </div>
315
- )}
316
- </div>
317
- )}
318
- </div>
319
- );
320
- };
1
+ import React, { useState, useMemo } from 'react';
2
+ import { parseChoices } from './choiceUtils';
3
+
4
+ export interface QuizProps {
5
+ title: string;
6
+ questions: QuizQuestion[];
7
+ showScore?: boolean;
8
+ showProgress?: boolean;
9
+ passingScore?: number;
10
+ /** 是否禁用:级联禁用所有题目 */
11
+ disabled?: boolean;
12
+ onchange: (value: QuizState) => void;
13
+ }
14
+
15
+ interface QuizQuestion {
16
+ id: string;
17
+ type: "mcq" | "short-answer";
18
+ question: string;
19
+ points: number;
20
+ // MCQ specific
21
+ choices?: string[];
22
+ correctAnswer?: string;
23
+ // Short answer specific
24
+ placeholder?: string;
25
+ correctAnswers?: string[];
26
+ }
27
+
28
+ interface QuizAnswer {
29
+ answer: string;
30
+ isCorrect: boolean;
31
+ points: number;
32
+ submitted: boolean;
33
+ }
34
+
35
+ interface QuizState {
36
+ answers: Record<string, QuizAnswer>;
37
+ totalScore: number;
38
+ maxScore: number;
39
+ questionsAnswered: number;
40
+ isCompleted: boolean;
41
+ }
42
+
43
+ export const Quiz: React.FC<QuizProps> = ({
44
+ title,
45
+ questions,
46
+ showScore = true,
47
+ showProgress = true,
48
+ passingScore,
49
+ disabled = false,
50
+ onchange
51
+ }) => {
52
+ const [answers, setAnswers] = useState<Record<string, QuizAnswer>>({});
53
+ const [draftInputs, setDraftInputs] = useState<Record<string, string>>({});
54
+
55
+ const quizState = useMemo<QuizState>(() => {
56
+ const maxScore = questions.reduce((sum, q) => sum + q.points, 0);
57
+ const totalScore = Object.values(answers).reduce((sum, a) => sum + a.points, 0);
58
+ const questionsAnswered = Object.keys(answers).length;
59
+ const isCompleted = questionsAnswered === questions.length;
60
+
61
+ return {
62
+ answers,
63
+ totalScore,
64
+ maxScore,
65
+ questionsAnswered,
66
+ isCompleted
67
+ };
68
+ }, [answers, questions]);
69
+
70
+ const handleQuestionAnswer = (questionId: string, userAnswer: string) => {
71
+ if (disabled) return;
72
+ const question = questions.find(q => q.id === questionId);
73
+ if (!question || answers[questionId]?.submitted) return;
74
+
75
+ let isCorrect = false;
76
+
77
+ if (question.type === "mcq") {
78
+ isCorrect = question.correctAnswer === userAnswer;
79
+ } else if (question.type === "short-answer") {
80
+ if (question.correctAnswers) {
81
+ isCorrect = question.correctAnswers.some(
82
+ correct => correct.toLowerCase() === userAnswer.toLowerCase()
83
+ );
84
+ }
85
+ }
86
+
87
+ const newAnswer: QuizAnswer = {
88
+ answer: userAnswer,
89
+ isCorrect,
90
+ points: isCorrect ? question.points : 0,
91
+ submitted: true
92
+ };
93
+
94
+ const newAnswers = { ...answers, [questionId]: newAnswer };
95
+ setAnswers(newAnswers);
96
+
97
+ // Trigger onChange with updated state
98
+ const updatedState = {
99
+ answers: newAnswers,
100
+ totalScore: Object.values(newAnswers).reduce((sum, a) => sum + a.points, 0),
101
+ maxScore: quizState.maxScore,
102
+ questionsAnswered: Object.keys(newAnswers).length,
103
+ isCompleted: Object.keys(newAnswers).length === questions.length
104
+ };
105
+
106
+ onchange(updatedState);
107
+ };
108
+
109
+ const getProgressPercentage = () => {
110
+ return Math.round((quizState.questionsAnswered / questions.length) * 100);
111
+ };
112
+
113
+ const getScorePercentage = () => {
114
+ return quizState.maxScore > 0 ? Math.round((quizState.totalScore / quizState.maxScore) * 100) : 0;
115
+ };
116
+
117
+ const isPassing = () => {
118
+ return passingScore ? getScorePercentage() >= passingScore : null;
119
+ };
120
+
121
+ const renderMCQQuestion = (question: QuizQuestion) => {
122
+ const answer = answers[question.id];
123
+ const isAnswered = answer?.submitted;
124
+ const hasCorrect = typeof question.correctAnswer !== 'undefined';
125
+ // mcq choices 支持 key:label 格式:value(key) 是取值,label 是展示文案
126
+ const items = parseChoices(question.choices ?? []);
127
+ // 反馈文案:correctAnswer 是 key,展示时映射回 label
128
+ const correctLabel = hasCorrect
129
+ ? (items.find(i => i.value === question.correctAnswer)?.label ?? question.correctAnswer as string)
130
+ : '';
131
+
132
+ return (
133
+ <div key={question.id} className="quiz-question">
134
+ <div className="quiz-question-header">
135
+ <h3 className="quiz-question-text">{question.question}</h3>
136
+ <span className="quiz-question-points">{question.points} pts</span>
137
+ </div>
138
+
139
+ <div className="quiz-choices">
140
+ {items.map((item, index) => {
141
+ let choiceClass = 'quiz-choice';
142
+
143
+ if (isAnswered && hasCorrect) {
144
+ if (item.value === question.correctAnswer) {
145
+ choiceClass += ' quiz-choice-correct';
146
+ } else if (item.value === answer.answer && item.value !== question.correctAnswer) {
147
+ choiceClass += ' quiz-choice-incorrect';
148
+ }
149
+ } else if (item.value === answer?.answer) {
150
+ choiceClass += ' quiz-choice-selected';
151
+ }
152
+
153
+ return (
154
+ <button
155
+ key={index}
156
+ className={choiceClass}
157
+ onClick={() => handleQuestionAnswer(question.id, item.value)}
158
+ disabled={disabled || isAnswered}
159
+ >
160
+ {item.label}
161
+ </button>
162
+ );
163
+ })}
164
+ </div>
165
+
166
+ {isAnswered && (
167
+ hasCorrect ? (
168
+ <div className={`quiz-feedback ${answer.isCorrect ? 'correct' : 'incorrect'}`}>
169
+ {answer.isCorrect
170
+ ? `✓ Correct! (+${answer.points} pts)`
171
+ : `✗ Incorrect. The correct answer is: ${correctLabel}`}
172
+ </div>
173
+ ) : (
174
+ <div className="quiz-feedback">
175
+ {`You selected: ${answer.answer}`}
176
+ </div>
177
+ )
178
+ )}
179
+ </div>
180
+ );
181
+ };
182
+
183
+ const renderShortAnswerQuestion = (question: QuizQuestion) => {
184
+ const answer = answers[question.id];
185
+ const isAnswered = answer?.submitted;
186
+ const hasAnswers = (question.correctAnswers && question.correctAnswers.length > 0) || false;
187
+ const inputValue = draftInputs[question.id] ?? '';
188
+
189
+ const handleSubmit = () => {
190
+ if (disabled) return;
191
+ if (!inputValue.trim() || isAnswered) return;
192
+ handleQuestionAnswer(question.id, inputValue.trim());
193
+ // Clear draft after submission
194
+ setDraftInputs(prev => {
195
+ const next = { ...prev };
196
+ delete next[question.id];
197
+ return next;
198
+ });
199
+ };
200
+
201
+ const handleKeyDown = (e: React.KeyboardEvent) => {
202
+ if (e.key === 'Enter' && !isAnswered && !disabled) {
203
+ handleSubmit();
204
+ }
205
+ };
206
+
207
+ return (
208
+ <div key={question.id} className="quiz-question">
209
+ <div className="quiz-question-header">
210
+ <h3 className="quiz-question-text">{question.question}</h3>
211
+ <span className="quiz-question-points">{question.points} pts</span>
212
+ </div>
213
+
214
+ <div className="quiz-input-container">
215
+ <input
216
+ type="text"
217
+ value={isAnswered ? answer.answer : inputValue}
218
+ onChange={(e) => {
219
+ if (!isAnswered && !disabled) {
220
+ const val = e.target.value;
221
+ setDraftInputs(prev => ({ ...prev, [question.id]: val }));
222
+ }
223
+ }}
224
+ onKeyDown={handleKeyDown}
225
+ placeholder={question.placeholder || "Type your answer here..."}
226
+ disabled={disabled || isAnswered}
227
+ className={`quiz-input ${
228
+ isAnswered
229
+ ? hasAnswers
230
+ ? (answer.isCorrect ? 'quiz-input-correct' : 'quiz-input-incorrect')
231
+ : ''
232
+ : ''
233
+ }`}
234
+ />
235
+ {!isAnswered && (
236
+ <button
237
+ onClick={handleSubmit}
238
+ disabled={disabled || !inputValue.trim()}
239
+ className="quiz-submit"
240
+ >
241
+ Submit
242
+ </button>
243
+ )}
244
+ </div>
245
+
246
+ {isAnswered && (
247
+ hasAnswers ? (
248
+ <div className={`quiz-feedback ${answer.isCorrect ? 'correct' : 'incorrect'}`}>
249
+ {answer.isCorrect
250
+ ? `✓ Correct! (+${answer.points} pts)`
251
+ : `✗ Incorrect. Accepted answers: ${question.correctAnswers?.join(', ') || 'N/A'}`}
252
+ </div>
253
+ ) : (
254
+ <div className="quiz-feedback">
255
+ {`Your answer: ${answer.answer}`}
256
+ </div>
257
+ )
258
+ )}
259
+ </div>
260
+ );
261
+ };
262
+
263
+ return (
264
+ <div className="widget-quiz">
265
+ {/* Quiz Header */}
266
+ <div className="quiz-header">
267
+ <h2 className="quiz-title">{title}</h2>
268
+
269
+ <div className="quiz-stats">
270
+ {showProgress && (
271
+ <div className="quiz-progress">
272
+ <span className="quiz-progress-text">
273
+ Progress: {quizState.questionsAnswered}/{questions.length} questions
274
+ </span>
275
+ <div className="quiz-progress-bar">
276
+ <div
277
+ className="quiz-progress-fill"
278
+ style={{ width: `${getProgressPercentage()}%` }}
279
+ />
280
+ </div>
281
+ </div>
282
+ )}
283
+
284
+ {showScore && (
285
+ <div className="quiz-score">
286
+ <span className="quiz-score-text">
287
+ Score: {quizState.totalScore}/{quizState.maxScore} ({getScorePercentage()}%)
288
+ </span>
289
+ </div>
290
+ )}
291
+ </div>
292
+ </div>
293
+
294
+ {/* Questions */}
295
+ <div className="quiz-questions">
296
+ {questions.map((question) =>
297
+ question.type === "mcq"
298
+ ? renderMCQQuestion(question)
299
+ : renderShortAnswerQuestion(question)
300
+ )}
301
+ </div>
302
+
303
+ {/* Quiz Summary */}
304
+ {quizState.isCompleted && (
305
+ <div className="quiz-summary">
306
+ <h3>Quiz Complete!</h3>
307
+ <div className="quiz-final-score">
308
+ Final Score: {quizState.totalScore}/{quizState.maxScore} points ({getScorePercentage()}%)
309
+ </div>
310
+ {passingScore && (
311
+ <div className={`quiz-result ${isPassing() ? 'passed' : 'failed'}`}>
312
+ {isPassing() ? '🎉 Passed!' : '❌ Failed'}
313
+ (Passing: {passingScore}%)
314
+ </div>
315
+ )}
316
+ </div>
317
+ )}
318
+ </div>
319
+ );
320
+ };