@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
@@ -1,23 +1,13 @@
1
- import { useState, useRef, useEffect } from "react";
2
- import {
3
- Play,
4
- RotateCcw,
5
- Clock,
6
- HelpCircle,
7
- CheckCircle2,
8
- XCircle,
9
- Trophy,
10
- } from "lucide-react";
1
+ import { useState, useRef, useEffect, useCallback } from "react";
2
+ import { Trophy } from "lucide-react";
11
3
  import { QuizSession } from "../../sections/QuizSession/QuizSession";
12
- import { AssessmentReview } from "../../sections/AssessmentReview/AssessmentReview";
13
- import { ProgressRing } from "../../progress/progress-ring";
14
- import { StatCard } from "../../progress/stat-card";
15
- import { Button } from "../../ui/button";
16
- import { Badge } from "../../ui/badge";
17
- import { Card, CardContent } from "../../ui/card";
18
- import { formatDuration } from "../../utils/format-duration";
19
4
  import { cn } from "../../lib/utils";
20
5
  import type { SessionAnswer } from "../../questions/types";
6
+ import { scoreAssessment } from "../../questions/scoring";
7
+ import { useTimer } from "../_shared/use-timer";
8
+ import { AssessmentIntro } from "../_shared/assessment-intro";
9
+ import { AssessmentResults } from "../_shared/assessment-results";
10
+ import { withProGate } from "../../license/withProGate";
21
11
  import type { QuizModuleProps, QuizModuleResult } from "./types";
22
12
 
23
13
  type InternalStep =
@@ -25,7 +15,7 @@ type InternalStep =
25
15
  | { tag: "quiz" }
26
16
  | { tag: "results"; result: QuizModuleResult };
27
17
 
28
- export function QuizModule({
18
+ function QuizModuleBase({
29
19
  title,
30
20
  description,
31
21
  questions,
@@ -34,119 +24,56 @@ export function QuizModule({
34
24
  allowRetake = true,
35
25
  onComplete,
36
26
  showReview = true,
27
+ questionMaterials,
28
+ readOnly = false,
37
29
  className,
38
30
  style,
39
31
  }: QuizModuleProps) {
40
32
  const [step, setStep] = useState<InternalStep>({ tag: "intro" });
41
- const [timeElapsed, setTimeElapsed] = useState(0);
42
- const startTimeRef = useRef<number | null>(null);
43
- const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
33
+ const contentRef = useRef<HTMLDivElement>(null);
34
+ const { timeElapsed, getFinalElapsed, reset: resetTimer } = useTimer(step.tag === "quiz");
35
+
36
+ const onCompleteRef = useRef(onComplete);
37
+ onCompleteRef.current = onComplete;
44
38
 
45
- // Timer for quiz step
46
39
  useEffect(() => {
47
- if (step.tag === "quiz") {
48
- startTimeRef.current = Date.now();
49
- intervalRef.current = setInterval(() => {
50
- if (startTimeRef.current) {
51
- setTimeElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000));
52
- }
53
- }, 1000);
54
- } else {
55
- if (intervalRef.current) {
56
- clearInterval(intervalRef.current);
57
- intervalRef.current = null;
58
- }
59
- }
60
- return () => {
61
- if (intervalRef.current) clearInterval(intervalRef.current);
62
- };
40
+ contentRef.current?.focus({ preventScroll: true });
63
41
  }, [step.tag]);
64
42
 
65
- function scoreAnswers(answers: SessionAnswer[]): QuizModuleResult {
66
- let correct = 0;
67
- for (const q of questions) {
68
- const userAnswers = answers.filter((a) => a.uid === q.uid);
69
- const correctUids = new Set(
70
- (q.answers ?? []).filter((a) => a.isCorrect).map((a) => a.uid),
71
- );
72
- const userUids = new Set(userAnswers.map((a) => a.answerUid));
73
-
74
- if (
75
- correctUids.size > 0 &&
76
- correctUids.size === userUids.size &&
77
- [...correctUids].every((uid) => userUids.has(uid))
78
- ) {
79
- correct++;
80
- }
81
- }
82
-
83
- const total = questions.length;
84
- const percentage = total > 0 ? Math.round((correct / total) * 100) : 0;
85
- const elapsed = startTimeRef.current
86
- ? Math.floor((Date.now() - startTimeRef.current) / 1000)
87
- : timeElapsed;
88
-
89
- return {
43
+ const handleSubmit = useCallback((answers: SessionAnswer[]) => {
44
+ const { correct, total, percentage } = scoreAssessment(questions, answers);
45
+ const result: QuizModuleResult = {
90
46
  answers,
91
47
  correct,
92
48
  total,
93
49
  percentage,
94
50
  passed: passingScore !== undefined ? percentage >= passingScore : true,
95
- timeElapsedSeconds: elapsed,
51
+ timeElapsedSeconds: getFinalElapsed(),
96
52
  };
97
- }
98
-
99
- function handleSubmit(answers: SessionAnswer[]) {
100
- const result = scoreAnswers(answers);
101
53
  setStep({ tag: "results", result });
102
- onComplete?.(result);
103
- }
54
+ onCompleteRef.current?.(result);
55
+ }, [questions, passingScore, getFinalElapsed]);
104
56
 
105
- function handleRetake() {
106
- setTimeElapsed(0);
107
- startTimeRef.current = null;
57
+ const handleRetake = useCallback(() => {
58
+ resetTimer();
108
59
  setStep({ tag: "intro" });
109
- }
60
+ }, [resetTimer]);
110
61
 
111
62
  // ─── Intro Screen ───
112
63
  if (step.tag === "intro") {
113
64
  return (
114
- <div className={cn("max-w-2xl mx-auto", className)} style={style}>
115
- <Card>
116
- <CardContent className="pt-8 pb-8 text-center">
117
- <div className="mx-auto mb-4 w-14 h-14 rounded-full bg-primary/10 flex items-center justify-center">
118
- <Trophy className="size-7 text-primary" />
119
- </div>
120
- <h2 className="text-2xl font-bold text-foreground mb-2">{title}</h2>
121
- {description && (
122
- <p className="text-muted-foreground mb-6 max-w-md mx-auto">
123
- {description}
124
- </p>
125
- )}
126
- <div className="flex flex-wrap justify-center gap-4 mb-8">
127
- <div className="flex items-center gap-2 text-sm text-muted-foreground">
128
- <HelpCircle className="size-4" />
129
- <span>{questions.length} questions</span>
130
- </div>
131
- {timeLimitSeconds && (
132
- <div className="flex items-center gap-2 text-sm text-muted-foreground">
133
- <Clock className="size-4" />
134
- <span>{formatDuration(timeLimitSeconds)} time limit</span>
135
- </div>
136
- )}
137
- {passingScore !== undefined && (
138
- <div className="flex items-center gap-2 text-sm text-muted-foreground">
139
- <CheckCircle2 className="size-4" />
140
- <span>{passingScore}% to pass</span>
141
- </div>
142
- )}
143
- </div>
144
- <Button size="lg" onClick={() => setStep({ tag: "quiz" })}>
145
- <Play className="size-4 mr-2" />
146
- Start Quiz
147
- </Button>
148
- </CardContent>
149
- </Card>
65
+ <div ref={contentRef} tabIndex={-1} className={cn("max-w-2xl mx-auto outline-none", className)} style={style}>
66
+ <AssessmentIntro
67
+ icon={<Trophy className="size-7 text-primary" />}
68
+ title={title}
69
+ description={description}
70
+ questionCount={questions.length}
71
+ timeLimitSeconds={timeLimitSeconds}
72
+ passingScore={passingScore}
73
+ startLabel="Start Quiz"
74
+ onStart={() => setStep({ tag: "quiz" })}
75
+ readOnly={readOnly}
76
+ />
150
77
  </div>
151
78
  );
152
79
  }
@@ -154,12 +81,14 @@ export function QuizModule({
154
81
  // ─── Quiz Screen ───
155
82
  if (step.tag === "quiz") {
156
83
  return (
157
- <div className={cn(className)} style={style}>
84
+ <div ref={contentRef} tabIndex={-1} className={cn("outline-none", className)} style={style}>
158
85
  <QuizSession
159
86
  questions={questions}
160
87
  onSubmit={handleSubmit}
161
88
  timeElapsedSeconds={timeElapsed}
162
89
  timeLimitSeconds={timeLimitSeconds}
90
+ questionMaterials={questionMaterials}
91
+ readOnly={readOnly}
163
92
  />
164
93
  </div>
165
94
  );
@@ -167,75 +96,26 @@ export function QuizModule({
167
96
 
168
97
  // ─── Results Screen ───
169
98
  const { result } = step;
170
- const passed = result.passed;
171
99
 
172
100
  return (
173
- <div className={cn(className)} style={style}>
174
- {/* Score summary */}
175
- <div className="text-center mb-8">
176
- <ProgressRing
177
- value={result.percentage}
178
- size={140}
179
- strokeWidth={10}
180
- color={passed ? "var(--success)" : "var(--destructive)"}
181
- className="mx-auto mb-4 text-foreground"
182
- />
183
- <Badge
184
- variant={passed ? "success" : "destructive"}
185
- className="text-sm px-3 py-1 mb-2"
186
- >
187
- {passed ? "Passed" : "Failed"}
188
- </Badge>
189
- <h2 className="text-xl font-bold text-foreground">{title}</h2>
190
- </div>
191
-
192
- {/* Stats grid */}
193
- <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8">
194
- <StatCard
195
- icon={<CheckCircle2 />}
196
- label="Correct"
197
- value={`${result.correct}/${result.total}`}
198
- />
199
- <StatCard
200
- icon={<XCircle />}
201
- label="Incorrect"
202
- value={`${result.total - result.correct}/${result.total}`}
203
- />
204
- <StatCard
205
- icon={<Trophy />}
206
- label="Score"
207
- value={`${result.percentage}%`}
208
- />
209
- <StatCard
210
- icon={<Clock />}
211
- label="Time"
212
- value={formatDuration(result.timeElapsedSeconds)}
213
- />
214
- </div>
215
-
216
- {/* Actions */}
217
- {allowRetake && (
218
- <div className="flex justify-center mb-8">
219
- <Button variant="outline" onClick={handleRetake}>
220
- <RotateCcw className="size-4 mr-2" />
221
- Retake Quiz
222
- </Button>
223
- </div>
224
- )}
225
-
226
- {/* Per-question review */}
227
- {showReview && (
228
- <div>
229
- <h3 className="text-lg font-semibold text-foreground mb-4">
230
- Question Review
231
- </h3>
232
- <AssessmentReview
233
- questions={questions}
234
- sessionAnswers={result.answers}
235
- showCorrectAnswers
236
- />
237
- </div>
238
- )}
101
+ <div ref={contentRef} tabIndex={-1} className={cn("max-w-2xl mx-auto outline-none", className)} style={style}>
102
+ <AssessmentResults
103
+ title={title}
104
+ percentage={result.percentage}
105
+ passed={result.passed}
106
+ correct={result.correct}
107
+ total={result.total}
108
+ timeElapsedSeconds={result.timeElapsedSeconds}
109
+ answers={result.answers}
110
+ questions={questions}
111
+ allowRetake={allowRetake}
112
+ onRetake={handleRetake}
113
+ retakeLabel="Retake Quiz"
114
+ showReview={showReview}
115
+ readOnly={readOnly}
116
+ />
239
117
  </div>
240
118
  );
241
119
  }
120
+
121
+ export const QuizModule = withProGate(QuizModuleBase, "QuizModule");
@@ -1,4 +1,5 @@
1
- import type { QuestionData, SessionAnswer } from "../../questions/types";
1
+ import type { QuestionData, QuestionMaterial } from "../../questions/types";
2
+ import type { AssessmentResult } from "../_shared/types";
2
3
 
3
4
  /**
4
5
  * QuizModule — a complete multi-step assessment experience.
@@ -34,23 +35,17 @@ export interface QuizModuleProps {
34
35
  onComplete?: (result: QuizModuleResult) => void;
35
36
  /** Whether to show correct/incorrect answer highlighting in the review. @default true */
36
37
  showReview?: boolean;
38
+ /**
39
+ * Related materials keyed by question UID. When provided, a "Materials"
40
+ * button appears in the question header, opening a drawer with content blocks.
41
+ */
42
+ questionMaterials?: QuestionMaterial[];
43
+ /** When true, disables interactions for preview/demo mode. @default false */
44
+ readOnly?: boolean;
37
45
  /** CSS class name for the root element */
38
46
  className?: string;
39
47
  /** Inline styles for the root element */
40
48
  style?: React.CSSProperties;
41
49
  }
42
50
 
43
- export interface QuizModuleResult {
44
- /** The user's submitted answers */
45
- answers: SessionAnswer[];
46
- /** Number of correct answers */
47
- correct: number;
48
- /** Total number of questions */
49
- total: number;
50
- /** Score as a percentage (0-100) */
51
- percentage: number;
52
- /** Whether the user passed (only meaningful when passingScore is set) */
53
- passed: boolean;
54
- /** Total time taken in seconds */
55
- timeElapsedSeconds: number;
56
- }
51
+ export interface QuizModuleResult extends AssessmentResult {}
@@ -0,0 +1,117 @@
1
+ import { useRef, useEffect } from "react";
2
+ import { BookOpen, Bell, BarChart3 } from "lucide-react";
3
+ import { ProgressDashboard } from "../../sections/ProgressDashboard/ProgressDashboard";
4
+ import { CourseCatalog } from "../../sections/CourseCatalog/CourseCatalog";
5
+ import { AnnouncementFeed } from "../../sections/AnnouncementFeed/AnnouncementFeed";
6
+ import { ProgressRing } from "../../progress/progress-ring";
7
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "../../ui/tabs";
8
+ import { Separator } from "../../ui/separator";
9
+ import { cn } from "../../lib/utils";
10
+ import { withProGate } from "../../license/withProGate";
11
+ import type { StudentDashboardModuleProps } from "./types";
12
+
13
+ /**
14
+ * StudentDashboardModule — a multi-tab student home page.
15
+ *
16
+ * Combines ProgressDashboard, CourseCatalog, and AnnouncementFeed in a tabbed layout
17
+ * to provide a complete student landing page experience.
18
+ */
19
+ function StudentDashboardModuleBase({
20
+ studentName,
21
+ overallProgress,
22
+ totalTimeSpent,
23
+ modules,
24
+ recentActivity,
25
+ streak,
26
+ achievements,
27
+ enrolledCourses,
28
+ onCourseClick,
29
+ announcements = [],
30
+ onAnnouncementSelect,
31
+ onMarkRead,
32
+ readOnly = false,
33
+ className,
34
+ style,
35
+ }: StudentDashboardModuleProps) {
36
+ const contentRef = useRef<HTMLDivElement>(null);
37
+
38
+ useEffect(() => {
39
+ contentRef.current?.focus({ preventScroll: true });
40
+ }, []);
41
+
42
+ const unreadCount = announcements.filter((a) => !a.isRead).length;
43
+
44
+ return (
45
+ <div
46
+ ref={contentRef}
47
+ tabIndex={-1}
48
+ className={cn("outline-none", className)}
49
+ style={style}
50
+ >
51
+ {/* Header */}
52
+ <div className="flex items-center justify-between mb-6">
53
+ <h2 className="text-xl font-bold text-foreground">
54
+ Welcome back, {studentName}
55
+ </h2>
56
+ <ProgressRing value={overallProgress} size={48} strokeWidth={4} />
57
+ </div>
58
+
59
+ <Separator className="mb-6" />
60
+
61
+ {/* Tabs */}
62
+ <Tabs defaultValue="overview">
63
+ <TabsList className="mb-6">
64
+ <TabsTrigger value="overview">
65
+ <BarChart3 className="size-4 mr-1.5" />
66
+ Overview
67
+ </TabsTrigger>
68
+ <TabsTrigger value="courses">
69
+ <BookOpen className="size-4 mr-1.5" />
70
+ My Courses
71
+ </TabsTrigger>
72
+ <TabsTrigger value="announcements">
73
+ <Bell className="size-4 mr-1.5" />
74
+ Announcements
75
+ {unreadCount > 0 && (
76
+ <span className="ml-1.5 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground text-xs font-medium min-w-5 h-5 px-1.5">
77
+ {unreadCount}
78
+ </span>
79
+ )}
80
+ </TabsTrigger>
81
+ </TabsList>
82
+
83
+ <TabsContent value="overview">
84
+ <ProgressDashboard
85
+ overallProgress={overallProgress}
86
+ totalTimeSpent={totalTimeSpent}
87
+ modules={modules}
88
+ recentActivity={recentActivity}
89
+ streak={streak}
90
+ achievements={achievements}
91
+ />
92
+ </TabsContent>
93
+
94
+ <TabsContent value="courses">
95
+ <CourseCatalog
96
+ courses={enrolledCourses}
97
+ onCourseClick={(course) => !readOnly && onCourseClick(course.uid)}
98
+ showSearch
99
+ viewMode="grid"
100
+ readOnly={readOnly}
101
+ />
102
+ </TabsContent>
103
+
104
+ <TabsContent value="announcements">
105
+ <AnnouncementFeed
106
+ announcements={announcements}
107
+ onSelect={onAnnouncementSelect}
108
+ onMarkRead={onMarkRead}
109
+ readOnly={readOnly}
110
+ />
111
+ </TabsContent>
112
+ </Tabs>
113
+ </div>
114
+ );
115
+ }
116
+
117
+ export const StudentDashboardModule = withProGate(StudentDashboardModuleBase, "StudentDashboardModule");
@@ -0,0 +1,56 @@
1
+ import type { CourseInfo } from "../../sections/CourseCatalog/types";
2
+ import type { Announcement } from "../../sections/AnnouncementFeed/types";
3
+ import type {
4
+ ModuleProgress,
5
+ ActivityItem,
6
+ Achievement,
7
+ } from "../../sections/ProgressDashboard/types";
8
+
9
+ /**
10
+ * StudentDashboardModule — a multi-tab student home page.
11
+ *
12
+ * Combines ProgressDashboard, CourseCatalog, and AnnouncementFeed in a tabbed layout
13
+ * to provide a complete student landing page experience.
14
+ *
15
+ * @example
16
+ * <StudentDashboardModule
17
+ * studentName="Jane"
18
+ * overallProgress={72}
19
+ * totalTimeSpent={14400}
20
+ * modules={[]}
21
+ * enrolledCourses={courses}
22
+ * onCourseClick={(uid) => navigate(`/courses/${uid}`)}
23
+ * />
24
+ */
25
+ export interface StudentDashboardModuleProps {
26
+ /** Student's display name */
27
+ studentName: string;
28
+ /** Overall progress percentage (0-100) */
29
+ overallProgress: number;
30
+ /** Total time spent in seconds */
31
+ totalTimeSpent: number;
32
+ /** Per-module progress data */
33
+ modules: ModuleProgress[];
34
+ /** Recent activity items */
35
+ recentActivity?: ActivityItem[];
36
+ /** Streak data */
37
+ streak?: { currentDays: number; longestDays: number };
38
+ /** Achievements */
39
+ achievements?: Achievement[];
40
+ /** Enrolled courses for the courses tab */
41
+ enrolledCourses: CourseInfo[];
42
+ /** Called when a course is clicked */
43
+ onCourseClick: (courseUid: string) => void;
44
+ /** Announcements for the announcements tab */
45
+ announcements?: Announcement[];
46
+ /** Called when an announcement is selected */
47
+ onAnnouncementSelect?: (announcement: Announcement) => void;
48
+ /** Called when an announcement is marked as read */
49
+ onMarkRead?: (uid: string) => void;
50
+ /** When true, disables interactions for preview/demo mode. @default false */
51
+ readOnly?: boolean;
52
+ /** CSS class name for the root element */
53
+ className?: string;
54
+ /** Inline styles for the root element */
55
+ style?: React.CSSProperties;
56
+ }