@hydralms/components 0.1.3 → 0.3.0

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 (344) hide show
  1. package/dist/StudentProfile-BVfZMbnV.cjs +1 -0
  2. package/dist/StudentProfile-DeMxdrL3.js +3275 -0
  3. package/dist/assessment-toolbar/assessment-toolbar.d.ts +1 -1
  4. package/dist/assessment-toolbar/index.d.ts +5 -1
  5. package/dist/assessment-toolbar/question-header-bar.d.ts +2 -0
  6. package/dist/assessment-toolbar/question-materials-drawer.d.ts +2 -0
  7. package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
  8. package/dist/assessment-toolbar/timer-display.d.ts +1 -1
  9. package/dist/assessment-toolbar/types.d.ts +52 -4
  10. package/dist/assessment-toolbar/use-countdown.d.ts +43 -0
  11. package/dist/common/index.d.ts +3 -1
  12. package/dist/common/pagination.d.ts +26 -0
  13. package/dist/common/stepper.d.ts +6 -0
  14. package/dist/common/types.d.ts +38 -0
  15. package/dist/components.css +1 -1
  16. package/dist/content/attachment-list.d.ts +6 -0
  17. package/dist/content/audio-player.d.ts +22 -0
  18. package/dist/content/code-block.d.ts +30 -0
  19. package/dist/content/content-block.d.ts +1 -1
  20. package/dist/content/embed-block.d.ts +28 -0
  21. package/dist/content/index.d.ts +8 -1
  22. package/dist/content/types.d.ts +63 -0
  23. package/dist/curriculum/course-card.d.ts +51 -0
  24. package/dist/curriculum/curriculum-item.d.ts +1 -1
  25. package/dist/curriculum/index.d.ts +2 -0
  26. package/dist/curriculum/types.d.ts +2 -2
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +597 -308
  30. package/dist/license/HydraContext.d.ts +16 -0
  31. package/dist/license/ProBadge.d.ts +6 -0
  32. package/dist/license/index.d.ts +7 -0
  33. package/dist/license/tiers.d.ts +3 -0
  34. package/dist/license/useHydraLicense.d.ts +6 -0
  35. package/dist/license/validate.d.ts +13 -0
  36. package/dist/license/withProGate.d.ts +6 -0
  37. package/dist/modules/AssignmentModule/AssignmentModule.d.ts +5 -0
  38. package/dist/modules/AssignmentModule/types.d.ts +69 -0
  39. package/dist/modules/CertificateModule/CertificateModule.d.ts +5 -0
  40. package/dist/modules/CertificateModule/types.d.ts +51 -0
  41. package/dist/modules/CourseCatalogModule/CourseCatalogModule.d.ts +5 -0
  42. package/dist/modules/CourseCatalogModule/types.d.ts +43 -0
  43. package/dist/modules/CoursePlayer/CoursePlayer.d.ts +4 -1
  44. package/dist/modules/DiscussionModule/DiscussionModule.d.ts +5 -0
  45. package/dist/modules/DiscussionModule/types.d.ts +47 -0
  46. package/dist/modules/ExamModule/ExamModule.d.ts +5 -0
  47. package/dist/modules/ExamModule/types.d.ts +55 -0
  48. package/dist/modules/FlashcardLab/FlashcardLab.d.ts +4 -1
  49. package/dist/modules/FlashcardLab/types.d.ts +2 -0
  50. package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +5 -0
  51. package/dist/modules/GradeCenterModule/types.d.ts +56 -0
  52. package/dist/modules/QuizModule/QuizModule.d.ts +4 -1
  53. package/dist/modules/QuizModule/types.d.ts +10 -14
  54. package/dist/modules/StudentDashboardModule/StudentDashboardModule.d.ts +5 -0
  55. package/dist/modules/StudentDashboardModule/types.d.ts +54 -0
  56. package/dist/modules/StudentProfileModule/StudentProfileModule.d.ts +5 -0
  57. package/dist/modules/StudentProfileModule/types.d.ts +43 -0
  58. package/dist/modules/SurveyModule/SurveyModule.d.ts +5 -0
  59. package/dist/modules/SurveyModule/types.d.ts +51 -0
  60. package/dist/modules/_shared/assessment-intro.d.ts +16 -0
  61. package/dist/modules/_shared/assessment-results.d.ts +23 -0
  62. package/dist/modules/_shared/types.d.ts +10 -0
  63. package/dist/modules/_shared/use-timer.d.ts +9 -0
  64. package/dist/modules/index.d.ts +18 -0
  65. package/dist/modules.cjs +1 -0
  66. package/dist/modules.js +1834 -0
  67. package/dist/progress/achievement-badge.d.ts +6 -0
  68. package/dist/progress/activity-timeline.d.ts +6 -0
  69. package/dist/progress/index.d.ts +4 -1
  70. package/dist/progress/stat-card.d.ts +1 -1
  71. package/dist/progress/streak-badge.d.ts +6 -0
  72. package/dist/progress/types.d.ts +99 -0
  73. package/dist/provider/HydraProvider.d.ts +5 -1
  74. package/dist/questions/choice.d.ts +1 -1
  75. package/dist/questions/confidence-indicator.d.ts +37 -0
  76. package/dist/questions/essay.d.ts +2 -2
  77. package/dist/questions/fill-in-the-blank.d.ts +1 -1
  78. package/dist/questions/hotspot.d.ts +21 -0
  79. package/dist/questions/index.d.ts +11 -1
  80. package/dist/questions/inline-choice.d.ts +21 -0
  81. package/dist/questions/matching.d.ts +22 -0
  82. package/dist/questions/multiple-choice.d.ts +1 -1
  83. package/dist/questions/numeric.d.ts +11 -0
  84. package/dist/questions/ordering.d.ts +12 -0
  85. package/dist/questions/question-renderer.d.ts +1 -1
  86. package/dist/questions/scenario.d.ts +23 -0
  87. package/dist/questions/scoring.d.ts +22 -0
  88. package/dist/questions/spreadsheet.d.ts +29 -0
  89. package/dist/questions/true-false.d.ts +1 -1
  90. package/dist/questions/types.d.ts +106 -1
  91. package/dist/questions/use-drag-reorder.d.ts +17 -0
  92. package/dist/sections/AnnouncementFeed/AnnouncementFeed.d.ts +1 -1
  93. package/dist/sections/AnnouncementFeed/types.d.ts +15 -1
  94. package/dist/sections/AssessmentReview/AssessmentReview.d.ts +1 -1
  95. package/dist/sections/AssessmentReview/types.d.ts +6 -0
  96. package/dist/sections/AssignmentSubmission/AssignmentSubmission.d.ts +1 -1
  97. package/dist/sections/AssignmentSubmission/types.d.ts +6 -0
  98. package/dist/sections/CertificateViewer/CertificateViewer.d.ts +1 -1
  99. package/dist/sections/CertificateViewer/certificate-variants.d.ts +42 -0
  100. package/dist/sections/CertificateViewer/types.d.ts +13 -5
  101. package/dist/sections/CourseCatalog/CourseCatalog.d.ts +2 -0
  102. package/dist/sections/CourseCatalog/types.d.ts +80 -0
  103. package/dist/sections/CourseOutline/CourseOutline.d.ts +1 -1
  104. package/dist/sections/CourseOutline/types.d.ts +6 -0
  105. package/dist/sections/DiscussionThread/DiscussionThread.d.ts +1 -1
  106. package/dist/sections/DiscussionThread/types.d.ts +6 -0
  107. package/dist/sections/EnrollmentWizard/EnrollmentWizard.d.ts +2 -0
  108. package/dist/sections/EnrollmentWizard/types.d.ts +66 -0
  109. package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
  110. package/dist/sections/ExamSession/types.d.ts +12 -1
  111. package/dist/sections/FlashcardStudySession/FlashcardStudySession.d.ts +1 -1
  112. package/dist/sections/FlashcardStudySession/types.d.ts +6 -0
  113. package/dist/sections/ForumBoard/ForumBoard.d.ts +8 -0
  114. package/dist/sections/ForumBoard/types.d.ts +78 -0
  115. package/dist/sections/GradebookTable/GradebookTable.d.ts +1 -1
  116. package/dist/sections/GradebookTable/types.d.ts +14 -0
  117. package/dist/sections/LecturePlayer/LecturePlayer.d.ts +1 -1
  118. package/dist/sections/LecturePlayer/types.d.ts +8 -0
  119. package/dist/sections/LessonPage/LessonPage.d.ts +1 -1
  120. package/dist/sections/LessonPage/types.d.ts +6 -0
  121. package/dist/sections/PracticeQuiz/PracticeQuiz.d.ts +1 -1
  122. package/dist/sections/PracticeQuiz/types.d.ts +6 -0
  123. package/dist/sections/ProgressDashboard/ProgressDashboard.d.ts +1 -1
  124. package/dist/sections/ProgressDashboard/types.d.ts +6 -0
  125. package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
  126. package/dist/sections/QuizSession/types.d.ts +12 -1
  127. package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +8 -0
  128. package/dist/sections/RequirementsChecklist/types.d.ts +43 -0
  129. package/dist/sections/ResourceLibrary/ResourceLibrary.d.ts +1 -1
  130. package/dist/sections/ResourceLibrary/types.d.ts +15 -1
  131. package/dist/sections/RubricView/RubricView.d.ts +9 -0
  132. package/dist/sections/RubricView/types.d.ts +56 -0
  133. package/dist/sections/ScrollableQuiz/ScrollableQuiz.d.ts +1 -1
  134. package/dist/sections/ScrollableQuiz/types.d.ts +6 -0
  135. package/dist/sections/StudentProfile/StudentProfile.d.ts +2 -0
  136. package/dist/sections/StudentProfile/types.d.ts +98 -0
  137. package/dist/sections/SurveyForm/SurveyForm.d.ts +1 -1
  138. package/dist/sections/SurveyForm/types.d.ts +6 -0
  139. package/dist/sections/_shared/merge-answers.d.ts +9 -0
  140. package/dist/sections/_shared/section-shell.d.ts +20 -0
  141. package/dist/sections/_shared/use-assessment-session.d.ts +30 -0
  142. package/dist/sections/index.d.ts +13 -1
  143. package/dist/sections.cjs +1 -1
  144. package/dist/sections.js +282 -1786
  145. package/dist/social/post-card.d.ts +1 -1
  146. package/dist/tabs-BsfVo2Bl.cjs +173 -0
  147. package/dist/tabs-BuY1iNJE.js +22305 -0
  148. package/dist/ui/alert.d.ts +1 -1
  149. package/dist/ui/badge.d.ts +1 -1
  150. package/dist/ui/button.d.ts +1 -1
  151. package/dist/ui/drawer.d.ts +84 -0
  152. package/dist/ui/index.d.ts +5 -0
  153. package/dist/ui/progress.d.ts +1 -1
  154. package/dist/ui/rich-text-editor.d.ts +32 -0
  155. package/dist/ui/rich-text-toolbar.d.ts +8 -0
  156. package/dist/ui/toast.d.ts +43 -0
  157. package/dist/utils/array-utils.d.ts +4 -0
  158. package/dist/utils/debounce.d.ts +5 -1
  159. package/dist/utils/flatten-leaves.d.ts +6 -0
  160. package/dist/utils/format-file-size.d.ts +1 -0
  161. package/dist/utils/format-timestamp.d.ts +1 -0
  162. package/dist/utils/is-empty-html.d.ts +5 -0
  163. package/dist/utils/pick-palette-color.d.ts +19 -0
  164. package/dist/utils/shuffle.d.ts +1 -0
  165. package/dist/utils/string-utils.d.ts +12 -0
  166. package/dist/video/types.d.ts +15 -0
  167. package/dist/video/video-bookmark.d.ts +1 -1
  168. package/dist/video/video-player.d.ts +1 -1
  169. package/dist/video/video-playlist-item.d.ts +1 -1
  170. package/dist/withProGate-BWqcKdPM.js +137 -0
  171. package/dist/withProGate-DX6XqKLp.cjs +1 -0
  172. package/package.json +40 -137
  173. package/src/assessment-toolbar/assessment-toolbar.tsx +54 -49
  174. package/src/assessment-toolbar/index.ts +6 -0
  175. package/src/assessment-toolbar/question-header-bar.tsx +61 -0
  176. package/src/assessment-toolbar/question-materials-drawer.tsx +55 -0
  177. package/src/assessment-toolbar/question-navigator.tsx +13 -36
  178. package/src/assessment-toolbar/timer-display.tsx +6 -5
  179. package/src/assessment-toolbar/types.ts +54 -4
  180. package/src/assessment-toolbar/use-countdown.ts +153 -0
  181. package/src/common/empty-state.tsx +1 -0
  182. package/src/common/index.ts +5 -0
  183. package/src/common/pagination.tsx +135 -0
  184. package/src/common/search-input.tsx +8 -6
  185. package/src/common/stepper.tsx +100 -0
  186. package/src/common/types.ts +41 -0
  187. package/src/content/attachment-list.tsx +92 -0
  188. package/src/content/audio-player.tsx +196 -0
  189. package/src/content/code-block.tsx +113 -0
  190. package/src/content/content-block.tsx +68 -2
  191. package/src/content/embed-block.tsx +78 -0
  192. package/src/content/file-upload-zone.tsx +11 -6
  193. package/src/content/index.ts +9 -0
  194. package/src/content/types.ts +46 -0
  195. package/src/curriculum/course-card.tsx +199 -0
  196. package/src/curriculum/curriculum-item.tsx +9 -5
  197. package/src/curriculum/curriculum-tree.tsx +20 -13
  198. package/src/curriculum/index.ts +2 -0
  199. package/src/curriculum/types.ts +2 -2
  200. package/src/feedback/feedback-banner.tsx +12 -14
  201. package/src/flashcards/flashcard-deck.tsx +1 -9
  202. package/src/flashcards/flashcard.tsx +29 -9
  203. package/src/index.ts +3 -0
  204. package/src/license/HydraContext.tsx +62 -0
  205. package/src/license/ProBadge.tsx +43 -0
  206. package/src/license/index.ts +7 -0
  207. package/src/license/tiers.ts +24 -0
  208. package/src/license/useHydraLicense.ts +10 -0
  209. package/src/license/validate.ts +90 -0
  210. package/src/license/withProGate.tsx +21 -0
  211. package/src/modules/AssignmentModule/AssignmentModule.tsx +314 -0
  212. package/src/modules/AssignmentModule/types.ts +77 -0
  213. package/src/modules/CertificateModule/CertificateModule.tsx +173 -0
  214. package/src/modules/CertificateModule/types.ts +49 -0
  215. package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
  216. package/src/modules/CourseCatalogModule/types.ts +47 -0
  217. package/src/modules/CoursePlayer/CoursePlayer.tsx +80 -69
  218. package/src/modules/DiscussionModule/DiscussionModule.tsx +145 -0
  219. package/src/modules/DiscussionModule/types.ts +54 -0
  220. package/src/modules/ExamModule/ExamModule.tsx +151 -0
  221. package/src/modules/ExamModule/types.ts +57 -0
  222. package/src/modules/FlashcardLab/FlashcardLab.tsx +39 -21
  223. package/src/modules/FlashcardLab/types.ts +2 -0
  224. package/src/modules/GradeCenterModule/GradeCenterModule.tsx +174 -0
  225. package/src/modules/GradeCenterModule/types.ts +65 -0
  226. package/src/modules/QuizModule/QuizModule.tsx +58 -178
  227. package/src/modules/QuizModule/types.ts +10 -15
  228. package/src/modules/StudentDashboardModule/StudentDashboardModule.tsx +117 -0
  229. package/src/modules/StudentDashboardModule/types.ts +56 -0
  230. package/src/modules/StudentProfileModule/StudentProfileModule.tsx +289 -0
  231. package/src/modules/StudentProfileModule/types.ts +45 -0
  232. package/src/modules/SurveyModule/SurveyModule.tsx +185 -0
  233. package/src/modules/SurveyModule/types.ts +53 -0
  234. package/src/modules/_shared/assessment-intro.tsx +75 -0
  235. package/src/modules/_shared/assessment-results.tsx +133 -0
  236. package/src/modules/_shared/types.ts +11 -0
  237. package/src/modules/_shared/use-timer.ts +49 -0
  238. package/src/modules/index.ts +33 -0
  239. package/src/progress/achievement-badge.tsx +52 -0
  240. package/src/progress/activity-timeline.tsx +84 -0
  241. package/src/progress/grade-indicator.tsx +9 -1
  242. package/src/progress/index.ts +7 -0
  243. package/src/progress/progress-ring.tsx +2 -1
  244. package/src/progress/stat-card.tsx +37 -18
  245. package/src/progress/streak-badge.tsx +35 -0
  246. package/src/progress/types.ts +103 -0
  247. package/src/provider/HydraProvider.tsx +15 -6
  248. package/src/questions/choice.tsx +19 -14
  249. package/src/questions/confidence-indicator.tsx +107 -0
  250. package/src/questions/essay.tsx +28 -28
  251. package/src/questions/fill-in-the-blank.tsx +20 -19
  252. package/src/questions/hotspot.tsx +154 -0
  253. package/src/questions/index.ts +18 -0
  254. package/src/questions/inline-choice.tsx +152 -0
  255. package/src/questions/matching.tsx +229 -0
  256. package/src/questions/multiple-choice.tsx +19 -14
  257. package/src/questions/numeric.tsx +106 -0
  258. package/src/questions/ordering.tsx +167 -0
  259. package/src/questions/question-renderer.tsx +24 -2
  260. package/src/questions/scenario.tsx +140 -0
  261. package/src/questions/scoring.ts +201 -0
  262. package/src/questions/spreadsheet.tsx +260 -0
  263. package/src/questions/true-false.tsx +19 -14
  264. package/src/questions/types.ts +123 -1
  265. package/src/questions/use-drag-reorder.ts +80 -0
  266. package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +66 -23
  267. package/src/sections/AnnouncementFeed/types.ts +15 -1
  268. package/src/sections/AssessmentReview/AssessmentReview.tsx +50 -2
  269. package/src/sections/AssessmentReview/types.ts +6 -0
  270. package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +44 -6
  271. package/src/sections/AssignmentSubmission/types.ts +6 -0
  272. package/src/sections/CertificateViewer/CertificateViewer.tsx +215 -60
  273. package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
  274. package/src/sections/CertificateViewer/types.ts +19 -5
  275. package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
  276. package/src/sections/CourseCatalog/types.ts +76 -0
  277. package/src/sections/CourseOutline/CourseOutline.tsx +45 -14
  278. package/src/sections/CourseOutline/types.ts +6 -0
  279. package/src/sections/DiscussionThread/DiscussionThread.tsx +55 -11
  280. package/src/sections/DiscussionThread/types.ts +6 -0
  281. package/src/sections/EnrollmentWizard/EnrollmentWizard.tsx +343 -0
  282. package/src/sections/EnrollmentWizard/types.ts +65 -0
  283. package/src/sections/ExamSession/ExamSession.tsx +125 -82
  284. package/src/sections/ExamSession/types.ts +12 -1
  285. package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
  286. package/src/sections/FlashcardStudySession/types.ts +6 -0
  287. package/src/sections/ForumBoard/ForumBoard.tsx +342 -0
  288. package/src/sections/ForumBoard/types.ts +81 -0
  289. package/src/sections/GradebookTable/GradebookTable.tsx +55 -2
  290. package/src/sections/GradebookTable/types.ts +14 -0
  291. package/src/sections/LecturePlayer/LecturePlayer.tsx +63 -37
  292. package/src/sections/LecturePlayer/types.ts +8 -0
  293. package/src/sections/LessonPage/LessonPage.tsx +40 -13
  294. package/src/sections/LessonPage/types.ts +6 -0
  295. package/src/sections/PracticeQuiz/PracticeQuiz.tsx +119 -98
  296. package/src/sections/PracticeQuiz/types.ts +6 -0
  297. package/src/sections/ProgressDashboard/ProgressDashboard.tsx +121 -67
  298. package/src/sections/ProgressDashboard/types.ts +6 -0
  299. package/src/sections/QuizSession/QuizSession.tsx +115 -67
  300. package/src/sections/QuizSession/types.ts +12 -1
  301. package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +147 -0
  302. package/src/sections/RequirementsChecklist/types.ts +44 -0
  303. package/src/sections/ResourceLibrary/ResourceLibrary.tsx +68 -17
  304. package/src/sections/ResourceLibrary/types.ts +15 -1
  305. package/src/sections/RubricView/RubricView.tsx +174 -0
  306. package/src/sections/RubricView/types.ts +58 -0
  307. package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +58 -23
  308. package/src/sections/ScrollableQuiz/types.ts +6 -0
  309. package/src/sections/StudentProfile/StudentProfile.tsx +279 -0
  310. package/src/sections/StudentProfile/types.ts +99 -0
  311. package/src/sections/SurveyForm/SurveyForm.tsx +40 -10
  312. package/src/sections/SurveyForm/types.ts +6 -0
  313. package/src/sections/_shared/merge-answers.ts +22 -0
  314. package/src/sections/_shared/section-shell.tsx +64 -0
  315. package/src/sections/_shared/use-assessment-session.ts +125 -0
  316. package/src/sections/index.ts +42 -1
  317. package/src/social/post-card.tsx +8 -19
  318. package/src/social/user-avatar.tsx +10 -5
  319. package/src/styles/globals.css +52 -41
  320. package/src/ui/badge.tsx +8 -0
  321. package/src/ui/drawer.tsx +600 -0
  322. package/src/ui/index.ts +21 -0
  323. package/src/ui/progress.tsx +4 -0
  324. package/src/ui/rich-text-editor.tsx +119 -0
  325. package/src/ui/rich-text-toolbar.tsx +157 -0
  326. package/src/ui/toast.tsx +170 -0
  327. package/src/utils/array-utils.ts +17 -0
  328. package/src/utils/debounce.ts +8 -2
  329. package/src/utils/flatten-leaves.ts +17 -0
  330. package/src/utils/format-file-size.ts +5 -0
  331. package/src/utils/format-timestamp.ts +13 -0
  332. package/src/utils/is-empty-html.ts +7 -0
  333. package/src/utils/pick-palette-color.ts +33 -0
  334. package/src/utils/shuffle.ts +8 -0
  335. package/src/utils/string-utils.ts +30 -0
  336. package/src/video/types.ts +16 -0
  337. package/src/video/video-bookmark.tsx +4 -3
  338. package/src/video/video-chapter-list.tsx +9 -4
  339. package/src/video/video-player.tsx +24 -5
  340. package/src/video/video-playlist-item.tsx +8 -3
  341. package/src/video/video-thumbnail-card.tsx +4 -0
  342. package/src/video/video-transcript.tsx +8 -5
  343. package/dist/table-BrS5cDQu.js +0 -2510
  344. package/dist/table-D6AkBBEo.cjs +0 -1
@@ -0,0 +1,133 @@
1
+ import type { ReactNode } from "react";
2
+ import { CheckCircle2, XCircle, Trophy, Clock, RotateCcw } from "lucide-react";
3
+ import { ProgressRing } from "../../progress/progress-ring";
4
+ import { StatCard } from "../../progress/stat-card";
5
+ import { Button } from "../../ui/button";
6
+ import { Badge } from "../../ui/badge";
7
+ import { Card, CardContent } from "../../ui/card";
8
+ import { Separator } from "../../ui/separator";
9
+ import { formatDuration } from "../../utils/format-duration";
10
+ import { AssessmentReview } from "../../sections/AssessmentReview/AssessmentReview";
11
+ import type { QuestionData, SessionAnswer } from "../../questions/types";
12
+
13
+ export interface AssessmentResultsProps {
14
+ title: string;
15
+ percentage: number;
16
+ passed: boolean;
17
+ correct: number;
18
+ total: number;
19
+ timeElapsedSeconds: number;
20
+ answers: SessionAnswer[];
21
+ questions: QuestionData[];
22
+ /** Allow retaking the assessment. */
23
+ allowRetake?: boolean;
24
+ onRetake?: () => void;
25
+ retakeLabel?: string;
26
+ /** Show per-question review below the summary. */
27
+ showReview?: boolean;
28
+ /** Extra badges shown next to the pass/fail badge (e.g., "Auto-submitted"). */
29
+ extraBadges?: ReactNode;
30
+ /** When true, disables interactions for preview/demo mode. @default false */
31
+ readOnly?: boolean;
32
+ }
33
+
34
+ export function AssessmentResults({
35
+ title,
36
+ percentage,
37
+ passed,
38
+ correct,
39
+ total,
40
+ timeElapsedSeconds,
41
+ answers,
42
+ questions,
43
+ allowRetake,
44
+ onRetake,
45
+ retakeLabel = "Retake",
46
+ showReview = true,
47
+ extraBadges,
48
+ readOnly = false,
49
+ }: AssessmentResultsProps) {
50
+ return (
51
+ <>
52
+ <Card>
53
+ <CardContent className="pt-8 pb-8">
54
+ {/* Score summary */}
55
+ <div className="text-center mb-8">
56
+ <ProgressRing
57
+ value={percentage}
58
+ size={140}
59
+ strokeWidth={10}
60
+ color={passed ? "var(--success)" : "var(--destructive)"}
61
+ className="mx-auto mb-4 text-foreground"
62
+ />
63
+ <Badge
64
+ variant={passed ? "success" : "destructive"}
65
+ className="text-sm px-3 py-1 mb-2"
66
+ >
67
+ {passed ? "Passed" : "Failed"}
68
+ </Badge>
69
+ {extraBadges}
70
+ <h2 className="text-xl font-bold text-foreground">{title}</h2>
71
+ </div>
72
+
73
+ {/* Stats grid */}
74
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8">
75
+ <StatCard
76
+ icon={<CheckCircle2 />}
77
+ label="Correct"
78
+ description="Questions answered right"
79
+ value={`${correct}/${total}`}
80
+ accent="var(--success)"
81
+ />
82
+ <StatCard
83
+ icon={<XCircle />}
84
+ label="Incorrect"
85
+ description="Questions to review"
86
+ value={`${total - correct}/${total}`}
87
+ accent="var(--destructive)"
88
+ />
89
+ <StatCard
90
+ icon={<Trophy />}
91
+ label="Score"
92
+ description="Overall percentage"
93
+ value={`${percentage}%`}
94
+ accent="var(--palette-3)"
95
+ />
96
+ <StatCard
97
+ icon={<Clock />}
98
+ label="Time"
99
+ description="Total elapsed"
100
+ value={formatDuration(timeElapsedSeconds)}
101
+ accent="var(--palette-1)"
102
+ />
103
+ </div>
104
+
105
+ {/* Actions */}
106
+ {allowRetake && onRetake && !readOnly && (
107
+ <div className="flex justify-center mb-8">
108
+ <Button variant="outline" onClick={onRetake}>
109
+ <RotateCcw className="size-4 mr-2" />
110
+ {retakeLabel}
111
+ </Button>
112
+ </div>
113
+ )}
114
+ </CardContent>
115
+ </Card>
116
+
117
+ {/* Per-question review */}
118
+ {showReview && (
119
+ <>
120
+ <Separator className="my-6" />
121
+ <h3 className="text-lg font-semibold text-foreground mb-4">
122
+ Question Review
123
+ </h3>
124
+ <AssessmentReview
125
+ questions={questions}
126
+ sessionAnswers={answers}
127
+ showCorrectAnswers
128
+ />
129
+ </>
130
+ )}
131
+ </>
132
+ );
133
+ }
@@ -0,0 +1,11 @@
1
+ import type { SessionAnswer } from "../../questions/types";
2
+
3
+ /** Shared result shape for scored assessment modules. */
4
+ export interface AssessmentResult {
5
+ answers: SessionAnswer[];
6
+ correct: number;
7
+ total: number;
8
+ percentage: number;
9
+ passed: boolean;
10
+ timeElapsedSeconds: number;
11
+ }
@@ -0,0 +1,49 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+
3
+ /**
4
+ * Manages an interval-based timer that runs while `active` is true.
5
+ * Returns elapsed seconds and a reset function.
6
+ */
7
+ export function useTimer(active: boolean) {
8
+ const [timeElapsed, setTimeElapsed] = useState(0);
9
+ const startTimeRef = useRef<number | null>(null);
10
+ const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
11
+ const timeElapsedRef = useRef(0);
12
+
13
+ useEffect(() => {
14
+ if (active) {
15
+ startTimeRef.current = Date.now();
16
+ intervalRef.current = setInterval(() => {
17
+ if (startTimeRef.current) {
18
+ const next = Math.floor((Date.now() - startTimeRef.current) / 1000);
19
+ if (next !== timeElapsedRef.current) {
20
+ timeElapsedRef.current = next;
21
+ setTimeElapsed(next);
22
+ }
23
+ }
24
+ }, 1000);
25
+ } else {
26
+ if (intervalRef.current) {
27
+ clearInterval(intervalRef.current);
28
+ intervalRef.current = null;
29
+ }
30
+ }
31
+ return () => {
32
+ if (intervalRef.current) clearInterval(intervalRef.current);
33
+ };
34
+ }, [active]);
35
+
36
+ const getFinalElapsed = useCallback(() => {
37
+ return startTimeRef.current
38
+ ? Math.floor((Date.now() - startTimeRef.current) / 1000)
39
+ : timeElapsedRef.current;
40
+ }, []);
41
+
42
+ const reset = useCallback(() => {
43
+ timeElapsedRef.current = 0;
44
+ setTimeElapsed(0);
45
+ startTimeRef.current = null;
46
+ }, []);
47
+
48
+ return { timeElapsed, getFinalElapsed, reset };
49
+ }
@@ -10,3 +10,36 @@ export type {
10
10
 
11
11
  export { CoursePlayer } from "./CoursePlayer/CoursePlayer";
12
12
  export type { CoursePlayerProps, CoursePlayerItem } from "./CoursePlayer/types";
13
+
14
+ export { ExamModule } from "./ExamModule/ExamModule";
15
+ export type { ExamModuleProps, ExamModuleResult } from "./ExamModule/types";
16
+
17
+ export { SurveyModule } from "./SurveyModule/SurveyModule";
18
+ export type {
19
+ SurveyModuleProps,
20
+ SurveyModuleResult,
21
+ } from "./SurveyModule/types";
22
+
23
+ export { GradeCenterModule } from "./GradeCenterModule/GradeCenterModule";
24
+ export type { GradeCenterModuleProps } from "./GradeCenterModule/types";
25
+
26
+ export { AssignmentModule } from "./AssignmentModule/AssignmentModule";
27
+ export type {
28
+ AssignmentModuleProps,
29
+ AssignmentModuleResult,
30
+ } from "./AssignmentModule/types";
31
+
32
+ export { CertificateModule } from "./CertificateModule/CertificateModule";
33
+ export type { CertificateModuleProps } from "./CertificateModule/types";
34
+
35
+ export { DiscussionModule } from "./DiscussionModule/DiscussionModule";
36
+ export type { DiscussionModuleProps } from "./DiscussionModule/types";
37
+
38
+ export { StudentDashboardModule } from "./StudentDashboardModule/StudentDashboardModule";
39
+ export type { StudentDashboardModuleProps } from "./StudentDashboardModule/types";
40
+
41
+ export { CourseCatalogModule } from "./CourseCatalogModule/CourseCatalogModule";
42
+ export type { CourseCatalogModuleProps } from "./CourseCatalogModule/types";
43
+
44
+ export { StudentProfileModule } from "./StudentProfileModule/StudentProfileModule";
45
+ export type { StudentProfileModuleProps } from "./StudentProfileModule/types";
@@ -0,0 +1,52 @@
1
+ import { Trophy, Lock } from "lucide-react";
2
+ import { Card } from "../ui/card";
3
+ import { cn } from "../lib/utils";
4
+ import type { AchievementBadgeProps } from "./types";
5
+
6
+ const VARIANT_STYLES = {
7
+ default: "text-primary",
8
+ gold: "text-palette-3",
9
+ silver: "text-muted-foreground",
10
+ bronze: "text-palette-3/70",
11
+ } as const;
12
+
13
+ /**
14
+ * AchievementBadge displays a single achievement or badge earned by a learner,
15
+ * with support for locked/earned states and metal-tier variants.
16
+ */
17
+ export function AchievementBadge({
18
+ title,
19
+ description,
20
+ icon,
21
+ earnedDate,
22
+ locked = false,
23
+ variant = "default",
24
+ className,
25
+ style,
26
+ }: AchievementBadgeProps) {
27
+ return (
28
+ <Card
29
+ className={cn("p-2 text-center", locked && "opacity-60", className)}
30
+ style={style}
31
+ >
32
+ <div className="mx-auto mb-1 w-12 h-12 flex items-center justify-center">
33
+ {locked ? (
34
+ <Lock size={32} className="text-muted-foreground" />
35
+ ) : (
36
+ icon ?? (
37
+ <Trophy size={32} className={VARIANT_STYLES[variant]} />
38
+ )
39
+ )}
40
+ </div>
41
+ <p className="font-semibold text-sm text-foreground">{title}</p>
42
+ {description && (
43
+ <p className="text-xs text-muted-foreground">{description}</p>
44
+ )}
45
+ {earnedDate && !locked && (
46
+ <p className="text-xs text-muted-foreground mt-0.5">
47
+ {new Date(earnedDate).toLocaleDateString()}
48
+ </p>
49
+ )}
50
+ </Card>
51
+ );
52
+ }
@@ -0,0 +1,84 @@
1
+ import { BookOpen, CheckCircle, Send, Award, Circle } from "lucide-react";
2
+ import { Button } from "../ui/button";
3
+ import { formatTimestamp } from "../utils/format-timestamp";
4
+ import { cn } from "../lib/utils";
5
+ import type { ActivityTimelineProps } from "./types";
6
+
7
+ const DEFAULT_ICONS: Record<string, React.ElementType> = {
8
+ lesson_completed: BookOpen,
9
+ quiz_passed: CheckCircle,
10
+ assignment_submitted: Send,
11
+ badge_earned: Award,
12
+ };
13
+
14
+ /**
15
+ * ActivityTimeline renders a vertical timeline of activity events
16
+ * with icons, timestamps, and an optional "load more" action.
17
+ */
18
+ export function ActivityTimeline({
19
+ events,
20
+ limit,
21
+ showLoadMore = false,
22
+ onLoadMore,
23
+ emptyMessage = "No recent activity",
24
+ className,
25
+ style,
26
+ }: ActivityTimelineProps) {
27
+ const displayedEvents = limit ? events.slice(0, limit) : events;
28
+
29
+ if (events.length === 0) {
30
+ return (
31
+ <p className={cn("text-sm text-muted-foreground", className)} style={style}>
32
+ {emptyMessage}
33
+ </p>
34
+ );
35
+ }
36
+
37
+ return (
38
+ <div className={cn("relative", className)} style={style}>
39
+ {/* Vertical line */}
40
+ <div className="absolute left-3.5 top-0 bottom-0 w-px bg-border" />
41
+
42
+ {displayedEvents.map((event) => {
43
+ const Icon = DEFAULT_ICONS[event.type] ?? Circle;
44
+
45
+ return (
46
+ <div
47
+ key={event.uid}
48
+ className="relative flex gap-3 pb-4 last:pb-0"
49
+ >
50
+ {/* Dot / Icon */}
51
+ <div className="relative z-10 shrink-0 w-7 h-7 rounded-full bg-background border border-border flex items-center justify-center">
52
+ {event.icon ?? (
53
+ <Icon size={14} className="text-muted-foreground" />
54
+ )}
55
+ </div>
56
+
57
+ {/* Content */}
58
+ <div className="flex-1 min-w-0 pt-0.5">
59
+ <p className="text-sm font-medium text-foreground">
60
+ {event.title}
61
+ </p>
62
+ {event.description && (
63
+ <p className="text-xs text-muted-foreground mt-0.5">
64
+ {event.description}
65
+ </p>
66
+ )}
67
+ <span className="text-xs text-muted-foreground">
68
+ {formatTimestamp(event.timestamp)}
69
+ </span>
70
+ </div>
71
+ </div>
72
+ );
73
+ })}
74
+
75
+ {showLoadMore && onLoadMore && (
76
+ <div className="pl-10 pt-1">
77
+ <Button variant="ghost" size="sm" onClick={onLoadMore}>
78
+ Load more
79
+ </Button>
80
+ </div>
81
+ )}
82
+ </div>
83
+ );
84
+ }
@@ -36,7 +36,15 @@ export function GradeIndicator({
36
36
  if (variant === "linear") {
37
37
  return (
38
38
  <div className={cn("flex flex-row items-center gap-1", className)} style={style}>
39
- <div className="flex-1 bg-muted rounded-full overflow-hidden" style={{ height: TRACK_HEIGHTS[size] }}>
39
+ <div
40
+ className="flex-1 bg-muted rounded-full overflow-hidden"
41
+ style={{ height: TRACK_HEIGHTS[size] }}
42
+ role="progressbar"
43
+ aria-valuenow={Math.round(percentage)}
44
+ aria-valuemin={0}
45
+ aria-valuemax={100}
46
+ aria-label={`Grade: ${letterGrade ?? `${Math.round(percentage)}%`}`}
47
+ >
40
48
  <div
41
49
  className="h-full rounded-full transition-[width] duration-300 ease-in-out"
42
50
  style={{ width: `${percentage}%`, background: color }}
@@ -1,8 +1,15 @@
1
1
  export { ProgressRing } from "./progress-ring";
2
2
  export { GradeIndicator } from "./grade-indicator";
3
3
  export { StatCard } from "./stat-card";
4
+ export { AchievementBadge } from "./achievement-badge";
5
+ export { StreakBadge } from "./streak-badge";
6
+ export { ActivityTimeline } from "./activity-timeline";
4
7
  export type {
5
8
  ProgressRingProps,
6
9
  GradeIndicatorProps,
7
10
  StatCardProps,
11
+ AchievementBadgeProps,
12
+ StreakBadgeProps,
13
+ ActivityTimelineProps,
14
+ TimelineEvent,
8
15
  } from "./types";
@@ -21,7 +21,7 @@ export function ProgressRing({
21
21
  className={cn("relative inline-flex", className)}
22
22
  style={{ width: `${size}px`, height: `${size}px`, ...style }}
23
23
  >
24
- <svg width={size} height={size}>
24
+ <svg width={size} height={size} role="img" aria-label={label ?? `${Math.round(value)}% progress`}>
25
25
  <circle
26
26
  cx={center}
27
27
  cy={center}
@@ -48,6 +48,7 @@ export function ProgressRing({
48
48
  <span
49
49
  className="absolute inset-0 flex items-center justify-center font-bold text-foreground"
50
50
  style={{ fontSize: `${size * 0.2}px` }}
51
+ aria-hidden="true"
51
52
  >
52
53
  {label ?? `${Math.round(value)}%`}
53
54
  </span>
@@ -1,4 +1,6 @@
1
+ import { memo } from "react";
1
2
  import { TrendingUp, TrendingDown, Minus } from "lucide-react";
3
+ import { Card, CardContent } from "../ui/card";
2
4
  import type { StatCardProps } from "./types";
3
5
  import { cn } from "../lib/utils";
4
6
 
@@ -9,34 +11,51 @@ const TREND_COLORS = {
9
11
  };
10
12
  const TREND_ICONS = { up: TrendingUp, down: TrendingDown, flat: Minus };
11
13
 
12
- export function StatCard({
14
+ export const StatCard = memo(function StatCard({
13
15
  icon,
14
16
  label,
17
+ description,
15
18
  value,
16
19
  subtitle,
17
20
  trend,
21
+ accent,
18
22
  className,
19
23
  style,
20
24
  }: StatCardProps) {
21
25
  const TrendIcon = trend ? TREND_ICONS[trend.direction] : null;
22
26
 
23
27
  return (
24
- <div className={cn("rounded-md border border-border p-4", className)} style={style}>
25
- {icon && <div className="mb-1 text-primary [&>svg]:size-6">{icon}</div>}
26
- <span className="text-xs text-muted-foreground">{label}</span>
27
- <div className="flex flex-row items-baseline gap-1">
28
- <span className="text-2xl font-bold">{value}</span>
29
- {trend && TrendIcon && (
30
- <span className="flex flex-row items-center gap-px" style={{ color: TREND_COLORS[trend.direction] }}>
31
- <TrendIcon size={14} />
32
- <span className="text-xs font-semibold">
33
- {trend.value > 0 ? "+" : ""}
34
- {trend.value}%
35
- </span>
36
- </span>
28
+ <Card className={cn(className)} style={style}>
29
+ <CardContent className="p-4">
30
+ {icon && (
31
+ <div
32
+ className="mb-2 w-9 h-9 rounded-lg flex items-center justify-center [&>svg]:size-5"
33
+ style={{
34
+ backgroundColor: `color-mix(in oklch, ${accent ?? "var(--primary)"} 10%, transparent)`,
35
+ color: accent ?? "var(--primary)",
36
+ }}
37
+ >
38
+ {icon}
39
+ </div>
40
+ )}
41
+ <span className="text-sm font-medium text-foreground">{label}</span>
42
+ {description && (
43
+ <p className="text-xs text-muted-foreground mt-0.5 leading-snug">{description}</p>
37
44
  )}
38
- </div>
39
- {subtitle && <span className="text-xs text-muted-foreground">{subtitle}</span>}
40
- </div>
45
+ <div className="flex flex-row items-baseline gap-1.5 mt-1">
46
+ <span className="text-3xl font-bold tracking-tight">{value}</span>
47
+ {trend && TrendIcon && (
48
+ <span className="flex flex-row items-center gap-px" style={{ color: TREND_COLORS[trend.direction] }}>
49
+ <TrendIcon size={14} />
50
+ <span className="text-xs font-semibold">
51
+ {trend.value > 0 ? "+" : ""}
52
+ {trend.value}%
53
+ </span>
54
+ </span>
55
+ )}
56
+ </div>
57
+ {subtitle && <span className="text-xs text-muted-foreground">{subtitle}</span>}
58
+ </CardContent>
59
+ </Card>
41
60
  );
42
- }
61
+ });
@@ -0,0 +1,35 @@
1
+ import { Flame } from "lucide-react";
2
+ import { cn } from "../lib/utils";
3
+ import type { StreakBadgeProps } from "./types";
4
+
5
+ /**
6
+ * StreakBadge displays a compact learning streak indicator with
7
+ * a fire icon and day count.
8
+ */
9
+ export function StreakBadge({
10
+ currentStreak,
11
+ longestStreak,
12
+ unit = "days",
13
+ showLongest = false,
14
+ className,
15
+ style,
16
+ }: StreakBadgeProps) {
17
+ return (
18
+ <div
19
+ className={cn("flex items-center gap-2", className)}
20
+ style={style}
21
+ >
22
+ <Flame size={20} className="text-warning shrink-0" />
23
+ <div>
24
+ <span className="text-sm font-bold text-foreground">
25
+ {currentStreak} {unit}
26
+ </span>
27
+ {showLongest && longestStreak != null && (
28
+ <span className="block text-xs text-muted-foreground">
29
+ Longest: {longestStreak} {unit}
30
+ </span>
31
+ )}
32
+ </div>
33
+ </div>
34
+ );
35
+ }
@@ -60,12 +60,115 @@ export interface StatCardProps {
60
60
  icon?: ReactNode;
61
61
  /** Stat label */
62
62
  label: string;
63
+ /** Short description displayed below the label */
64
+ description?: string;
63
65
  /** Stat value */
64
66
  value: string | number;
65
67
  /** Secondary text below the value */
66
68
  subtitle?: string;
67
69
  /** Optional trend data */
68
70
  trend?: { value: number; direction: "up" | "down" | "flat" };
71
+ /** Accent color CSS value for the icon background. Defaults to primary. */
72
+ accent?: string;
73
+ /** CSS class name for the root element */
74
+ className?: string;
75
+ /** Inline styles for the root element */
76
+ style?: React.CSSProperties;
77
+ }
78
+
79
+ /**
80
+ * AchievementBadge displays a single achievement or badge earned by a learner,
81
+ * with support for locked/earned states and metal-tier variants.
82
+ *
83
+ * @example
84
+ * <AchievementBadge
85
+ * title="Quiz Master"
86
+ * description="Pass 10 quizzes with 90%+ score"
87
+ * variant="gold"
88
+ * earnedDate="2025-12-01T00:00:00Z"
89
+ * />
90
+ */
91
+ export interface AchievementBadgeProps {
92
+ /** Achievement title */
93
+ title: string;
94
+ /** Optional description of how to earn the achievement */
95
+ description?: string;
96
+ /** Custom icon to display. Falls back to Trophy icon */
97
+ icon?: ReactNode;
98
+ /** ISO date string of when the achievement was earned */
99
+ earnedDate?: string;
100
+ /** Whether the achievement is locked (not yet earned). @default false */
101
+ locked?: boolean;
102
+ /** Visual tier variant. @default 'default' */
103
+ variant?: "default" | "gold" | "silver" | "bronze";
104
+ /** CSS class name for the root element */
105
+ className?: string;
106
+ /** Inline styles for the root element */
107
+ style?: React.CSSProperties;
108
+ }
109
+
110
+ /**
111
+ * StreakBadge displays a compact learning streak indicator with
112
+ * a fire icon and day count.
113
+ *
114
+ * @example
115
+ * <StreakBadge currentStreak={7} longestStreak={14} showLongest />
116
+ */
117
+ export interface StreakBadgeProps {
118
+ /** Current active streak count */
119
+ currentStreak: number;
120
+ /** All-time longest streak count */
121
+ longestStreak?: number;
122
+ /** Unit label for the streak count. @default "days" */
123
+ unit?: string;
124
+ /** Whether to show the longest streak subtitle. @default false */
125
+ showLongest?: boolean;
126
+ /** CSS class name for the root element */
127
+ className?: string;
128
+ /** Inline styles for the root element */
129
+ style?: React.CSSProperties;
130
+ }
131
+
132
+ /**
133
+ * A single event in an ActivityTimeline.
134
+ */
135
+ export interface TimelineEvent {
136
+ /** Unique identifier */
137
+ uid: string;
138
+ /** Event type — used for default icon selection */
139
+ type: string;
140
+ /** Event title */
141
+ title: string;
142
+ /** Optional longer description */
143
+ description?: string;
144
+ /** ISO 8601 timestamp */
145
+ timestamp: string;
146
+ /** Optional custom icon (overrides type-based default) */
147
+ icon?: ReactNode;
148
+ }
149
+
150
+ /**
151
+ * ActivityTimeline renders a vertical timeline of activity events
152
+ * with icons, timestamps, and an optional "load more" action.
153
+ *
154
+ * @example
155
+ * <ActivityTimeline
156
+ * events={[
157
+ * { uid: "1", type: "lesson_completed", title: "Completed Lesson 3", timestamp: "2025-03-01T12:00:00Z" },
158
+ * ]}
159
+ * />
160
+ */
161
+ export interface ActivityTimelineProps {
162
+ /** List of activity events to display */
163
+ events: TimelineEvent[];
164
+ /** Maximum number of events to display. When set, truncates the list. */
165
+ limit?: number;
166
+ /** Whether to show a "Load more" button at the bottom. @default false */
167
+ showLoadMore?: boolean;
168
+ /** Called when the user clicks "Load more" */
169
+ onLoadMore?: () => void;
170
+ /** Message displayed when the events list is empty. @default "No recent activity" */
171
+ emptyMessage?: string;
69
172
  /** CSS class name for the root element */
70
173
  className?: string;
71
174
  /** Inline styles for the root element */