@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,10 +1,15 @@
1
1
  import "../styles/globals.css";
2
2
  import type { ReactNode } from "react";
3
+ import { HydraLicenseProvider } from "../license/HydraContext";
3
4
 
4
5
  export interface HydraProviderProps {
5
6
  children: ReactNode;
6
7
  /** Controls color mode. Defaults to `"dark"`. Set to `"light"` to use the light theme. */
7
8
  colorMode?: "light" | "dark";
9
+ /** HydraLMS Pro license key. Omit for free tier. */
10
+ licenseKey?: string;
11
+ /** Validation endpoint URL. When empty (default), all features are unlocked (dev mode). */
12
+ validateUrl?: string;
8
13
  className?: string;
9
14
  style?: React.CSSProperties;
10
15
  }
@@ -12,15 +17,19 @@ export interface HydraProviderProps {
12
17
  export function HydraProvider({
13
18
  children,
14
19
  colorMode = "dark",
20
+ licenseKey,
21
+ validateUrl,
15
22
  className,
16
23
  style,
17
24
  }: HydraProviderProps) {
18
25
  return (
19
- <div
20
- className={`hydra-root${colorMode === "dark" ? " dark" : ""}${className ? ` ${className}` : ""}`}
21
- style={style}
22
- >
23
- {children}
24
- </div>
26
+ <HydraLicenseProvider licenseKey={licenseKey} validateUrl={validateUrl}>
27
+ <div
28
+ className={`hydra-root${colorMode === "dark" ? " dark" : ""}${className ? ` ${className}` : ""}`}
29
+ style={style}
30
+ >
31
+ {children}
32
+ </div>
33
+ </HydraLicenseProvider>
25
34
  );
26
35
  }
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from "react";
1
+ import { useState, useMemo, memo } from "react";
2
2
  import type { QuestionProps } from "./types";
3
3
  import { Alert, AlertDescription } from "../ui/alert";
4
4
  import { cn } from "../lib/utils";
@@ -12,18 +12,21 @@ import { cn } from "../lib/utils";
12
12
  * onAnswer={(answers) => handleAnswer(answers)}
13
13
  * />
14
14
  */
15
- export const Choice = ({
15
+ export const Choice = memo(function Choice({
16
16
  question,
17
17
  sessionAnswers,
18
18
  onAnswer,
19
19
  readOnly = false,
20
20
  showCorrectAnswers = false,
21
21
  disabled = false,
22
- }: QuestionProps) => {
23
- const [selectedAnswer, setSelectedAnswer] = useState<string>("");
22
+ }: QuestionProps) {
23
+ const [selectedAnswer, setSelectedAnswer] = useState<string>(
24
+ () => sessionAnswers?.[0]?.answerUid || "",
25
+ );
24
26
 
25
- const sortedAnswers = [...(question.answers || [])].sort(
26
- (a, b) => a.sequence - b.sequence,
27
+ const sortedAnswers = useMemo(
28
+ () => [...(question.answers || [])].sort((a, b) => a.sequence - b.sequence),
29
+ [question.answers],
27
30
  );
28
31
 
29
32
  const handleChange = (uid: string) => {
@@ -33,11 +36,6 @@ export const Choice = ({
33
36
  onAnswer?.([{ uid }]);
34
37
  };
35
38
 
36
- useEffect(() => {
37
- const current = sessionAnswers?.[0]?.answerUid || "";
38
- setSelectedAnswer(current);
39
- }, [sessionAnswers]);
40
-
41
39
  const getAnswerClasses = (answerUid: string) => {
42
40
  if (!showCorrectAnswers) return "px-2";
43
41
 
@@ -55,7 +53,8 @@ export const Choice = ({
55
53
  <div className="flex flex-col gap-4">
56
54
  <div dangerouslySetInnerHTML={{ __html: question.content }} />
57
55
 
58
- <div className="flex flex-col gap-2">
56
+ <fieldset className="flex flex-col gap-2 border-0 p-0 m-0">
57
+ <legend className="sr-only">Answer choices</legend>
59
58
  {sortedAnswers.map((answer) => (
60
59
  <div
61
60
  key={answer.uid}
@@ -72,10 +71,16 @@ export const Choice = ({
72
71
  className="accent-primary m-0 shrink-0"
73
72
  />
74
73
  <span dangerouslySetInnerHTML={{ __html: answer.content }} />
74
+ {showCorrectAnswers && answer.isCorrect && (
75
+ <span className="sr-only">(Correct answer)</span>
76
+ )}
77
+ {showCorrectAnswers && selectedAnswer === answer.uid && !answer.isCorrect && (
78
+ <span className="sr-only">(Your answer — incorrect)</span>
79
+ )}
75
80
  </label>
76
81
  </div>
77
82
  ))}
78
- </div>
83
+ </fieldset>
79
84
 
80
85
  {showCorrectAnswers && question.explanation && (
81
86
  <Alert className="mt-2">
@@ -87,4 +92,4 @@ export const Choice = ({
87
92
  )}
88
93
  </div>
89
94
  );
90
- };
95
+ });
@@ -0,0 +1,107 @@
1
+ import { memo } from "react";
2
+ import type { ReactNode } from "react";
3
+ import { HelpCircle, Brain, Lightbulb } from "lucide-react";
4
+ import { cn } from "../lib/utils";
5
+
6
+ /** A single confidence level option. */
7
+ export interface ConfidenceLevel {
8
+ /** Value stored in SessionAnswer.confidence */
9
+ value: string;
10
+ /** Display label */
11
+ label: string;
12
+ /** Optional icon */
13
+ icon?: ReactNode;
14
+ }
15
+
16
+ /**
17
+ * ConfidenceIndicator lets learners self-report their answer confidence,
18
+ * supporting metacognitive assessment strategies.
19
+ *
20
+ * @example
21
+ * <ConfidenceIndicator
22
+ * value={answer.confidence}
23
+ * onChange={(level) => updateConfidence(level)}
24
+ * />
25
+ */
26
+ export interface ConfidenceIndicatorProps {
27
+ /** Currently selected confidence level value */
28
+ value: string | null;
29
+ /** Called when the user selects a confidence level */
30
+ onChange: (level: string) => void;
31
+ /** Custom confidence levels (defaults to 3 levels) */
32
+ levels?: ConfidenceLevel[];
33
+ /** When true, disables interaction */
34
+ disabled?: boolean;
35
+ /** When true, shows selected state but disables interaction */
36
+ readOnly?: boolean;
37
+ /** CSS class name for the root element */
38
+ className?: string;
39
+ /** Inline styles for the root element */
40
+ style?: React.CSSProperties;
41
+ }
42
+
43
+ const DEFAULT_LEVELS: ConfidenceLevel[] = [
44
+ {
45
+ value: "low",
46
+ label: "Guessing",
47
+ icon: <HelpCircle className="size-3.5" />,
48
+ },
49
+ {
50
+ value: "medium",
51
+ label: "Somewhat sure",
52
+ icon: <Brain className="size-3.5" />,
53
+ },
54
+ {
55
+ value: "high",
56
+ label: "Confident",
57
+ icon: <Lightbulb className="size-3.5" />,
58
+ },
59
+ ];
60
+
61
+ export const ConfidenceIndicator = memo(function ConfidenceIndicator({
62
+ value,
63
+ onChange,
64
+ levels = DEFAULT_LEVELS,
65
+ disabled = false,
66
+ readOnly = false,
67
+ className,
68
+ style,
69
+ }: ConfidenceIndicatorProps) {
70
+ const isDisabled = disabled || readOnly;
71
+
72
+ return (
73
+ <div
74
+ data-slot="confidence-indicator"
75
+ className={cn("flex items-center gap-1", className)}
76
+ style={style}
77
+ role="radiogroup"
78
+ aria-label="Confidence level"
79
+ >
80
+ {levels.map((level) => {
81
+ const isSelected = value === level.value;
82
+ return (
83
+ <button
84
+ key={level.value}
85
+ type="button"
86
+ role="radio"
87
+ aria-checked={isSelected}
88
+ aria-label={level.label}
89
+ disabled={isDisabled}
90
+ onClick={() => onChange(level.value)}
91
+ className={cn(
92
+ "inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium transition-all",
93
+ "border outline-none focus-visible:ring-2 focus-visible:ring-ring/50",
94
+ isSelected
95
+ ? "border-primary bg-primary/10 text-primary"
96
+ : "border-transparent bg-muted text-muted-foreground hover:bg-muted/80",
97
+ isDisabled && "opacity-50 pointer-events-none",
98
+ )}
99
+ >
100
+ {level.icon}
101
+ {level.label}
102
+ </button>
103
+ );
104
+ })}
105
+ </div>
106
+ );
107
+ });
@@ -1,10 +1,10 @@
1
- import { useState, useEffect, useCallback } from "react";
1
+ import { useState, useMemo, useRef, useEffect, memo } from "react";
2
2
  import { debounce } from "../utils/debounce";
3
- import { Textarea } from "../ui/textarea";
3
+ import { RichTextEditor } from "../ui/rich-text-editor";
4
4
  import type { QuestionProps } from "./types";
5
5
 
6
6
  /**
7
- * Essay renders a long-form text input question with a multiline textarea.
7
+ * Essay renders a long-form rich text input question.
8
8
  *
9
9
  * @example
10
10
  * <Essay
@@ -12,48 +12,48 @@ import type { QuestionProps } from "./types";
12
12
  * onAnswer={(answers) => handleAnswer(answers)}
13
13
  * />
14
14
  */
15
- export const Essay = ({
15
+ export const Essay = memo(function Essay({
16
16
  question,
17
17
  sessionAnswers,
18
18
  onAnswer,
19
19
  readOnly = false,
20
20
  disabled = false,
21
- }: QuestionProps) => {
22
- const [value, setValue] = useState("");
21
+ }: QuestionProps) {
22
+ const [value, setValue] = useState(() => sessionAnswers?.[0]?.content || "");
23
23
 
24
- const debouncedAnswer = useCallback(
25
- debounce((content: string) => {
26
- onAnswer?.([
27
- {
28
- uid: question.answers?.[0]?.uid || question.uid,
29
- content,
30
- },
31
- ]);
32
- }, 500),
33
- [onAnswer, question.answers, question.uid],
24
+ const onAnswerRef = useRef(onAnswer);
25
+ onAnswerRef.current = onAnswer;
26
+ const answerUidRef = useRef(question.answers?.[0]?.uid || question.uid);
27
+ answerUidRef.current = question.answers?.[0]?.uid || question.uid;
28
+
29
+ const debouncedAnswer = useMemo(
30
+ () =>
31
+ debounce((content: string) => {
32
+ onAnswerRef.current?.([{ uid: answerUidRef.current, content }]);
33
+ }, 500),
34
+ [],
34
35
  );
36
+ useEffect(() => () => debouncedAnswer.cancel(), [debouncedAnswer]);
35
37
 
36
- const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
37
- const newValue = e.target.value;
38
- setValue(newValue);
39
- debouncedAnswer(newValue);
38
+ const handleChange = (html: string) => {
39
+ setValue(html);
40
+ debouncedAnswer(html);
40
41
  };
41
42
 
42
- useEffect(() => {
43
- setValue(sessionAnswers?.[0]?.content || "");
44
- }, [sessionAnswers]);
45
-
46
43
  return (
47
44
  <div className="flex flex-col gap-2">
48
45
  <div dangerouslySetInnerHTML={{ __html: question.content }} />
49
46
 
50
- <Textarea
51
- className="min-h-30 resize-y"
47
+ <RichTextEditor
48
+ className="min-h-30"
52
49
  value={value}
53
50
  onChange={handleChange}
54
51
  placeholder="Write your response here..."
55
- disabled={readOnly || disabled}
52
+ readOnly={readOnly}
53
+ disabled={disabled}
54
+ variant="default"
55
+ ariaLabel="Essay response"
56
56
  />
57
57
  </div>
58
58
  );
59
- };
59
+ });
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useCallback } from "react";
1
+ import { useState, useMemo, useRef, useId, useEffect, memo } from "react";
2
2
  import { debounce } from "../utils/debounce";
3
3
  import { Input } from "../ui/input";
4
4
  import { Alert, AlertDescription } from "../ui/alert";
@@ -13,27 +13,30 @@ import type { QuestionProps } from "./types";
13
13
  * onAnswer={(answers) => handleAnswer(answers)}
14
14
  * />
15
15
  */
16
- export const FillInTheBlank = ({
16
+ export const FillInTheBlank = memo(function FillInTheBlank({
17
17
  question,
18
18
  sessionAnswers,
19
19
  onAnswer,
20
20
  readOnly = false,
21
21
  showCorrectAnswers = false,
22
22
  disabled = false,
23
- }: QuestionProps) => {
24
- const [value, setValue] = useState("");
23
+ }: QuestionProps) {
24
+ const inputId = useId();
25
+ const [value, setValue] = useState(() => sessionAnswers?.[0]?.content || "");
25
26
 
26
- const debouncedAnswer = useCallback(
27
- debounce((content: string) => {
28
- onAnswer?.([
29
- {
30
- uid: question.answers?.[0]?.uid || "",
31
- content,
32
- },
33
- ]);
34
- }, 300),
35
- [onAnswer, question.answers],
27
+ const onAnswerRef = useRef(onAnswer);
28
+ onAnswerRef.current = onAnswer;
29
+ const answerUidRef = useRef(question.answers?.[0]?.uid || "");
30
+ answerUidRef.current = question.answers?.[0]?.uid || "";
31
+
32
+ const debouncedAnswer = useMemo(
33
+ () =>
34
+ debounce((content: string) => {
35
+ onAnswerRef.current?.([{ uid: answerUidRef.current, content }]);
36
+ }, 300),
37
+ [],
36
38
  );
39
+ useEffect(() => () => debouncedAnswer.cancel(), [debouncedAnswer]);
37
40
 
38
41
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
39
42
  const newValue = e.target.value;
@@ -41,15 +44,13 @@ export const FillInTheBlank = ({
41
44
  debouncedAnswer(newValue);
42
45
  };
43
46
 
44
- useEffect(() => {
45
- setValue(sessionAnswers?.[0]?.content || "");
46
- }, [sessionAnswers]);
47
-
48
47
  return (
49
48
  <div className="flex flex-col gap-2">
50
49
  <div dangerouslySetInnerHTML={{ __html: question.content }} />
51
50
 
51
+ <label htmlFor={inputId} className="sr-only">Your answer</label>
52
52
  <Input
53
+ id={inputId}
53
54
  value={value}
54
55
  onChange={handleChange}
55
56
  placeholder="Type your answer here..."
@@ -66,4 +67,4 @@ export const FillInTheBlank = ({
66
67
  )}
67
68
  </div>
68
69
  );
69
- };
70
+ });
@@ -0,0 +1,154 @@
1
+ import { useState, useMemo, memo } from "react";
2
+ import { Alert, AlertDescription } from "../ui/alert";
3
+ import { cn } from "../lib/utils";
4
+ import type { QuestionProps, HotspotRegion } from "./types";
5
+
6
+ function getRegionStyle(region: HotspotRegion): React.CSSProperties {
7
+ if (region.shape === "rect") {
8
+ const [x, y, w, h] = region.coords;
9
+ return {
10
+ left: `${x}%`,
11
+ top: `${y}%`,
12
+ width: `${w}%`,
13
+ height: `${h}%`,
14
+ };
15
+ }
16
+ // circle: [cx, cy, r]
17
+ const [cx, cy, r] = region.coords;
18
+ return {
19
+ left: `${cx - r}%`,
20
+ top: `${cy - r}%`,
21
+ width: `${r * 2}%`,
22
+ height: `${r * 2}%`,
23
+ borderRadius: "50%",
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Hotspot renders an image with clickable regions for selection.
29
+ *
30
+ * Regions are positioned using percentage-based coordinates so they scale with the image.
31
+ *
32
+ * @example
33
+ * <Hotspot
34
+ * question={{
35
+ * uid: "q1",
36
+ * type: "hotspot",
37
+ * content: "Click on the heart.",
38
+ * hotspotImageUrl: "/anatomy.png",
39
+ * hotspotRegions: [
40
+ * { uid: "r1", shape: "circle", coords: [50, 40, 8], isCorrect: true, label: "Heart" },
41
+ * ],
42
+ * }}
43
+ * onAnswer={(answers) => handleAnswer(answers)}
44
+ * />
45
+ */
46
+ export const Hotspot = memo(function Hotspot({
47
+ question,
48
+ sessionAnswers,
49
+ onAnswer,
50
+ readOnly = false,
51
+ showCorrectAnswers = false,
52
+ disabled = false,
53
+ }: QuestionProps) {
54
+ const multiSelect = question.hotspotMultiSelect ?? false;
55
+
56
+ const [selected, setSelected] = useState<Set<string>>(() => {
57
+ const set = new Set<string>();
58
+ for (const sa of sessionAnswers ?? []) {
59
+ set.add(sa.answerUid);
60
+ }
61
+ return set;
62
+ });
63
+
64
+ const regions = useMemo(
65
+ () => question.hotspotRegions ?? [],
66
+ [question.hotspotRegions],
67
+ );
68
+
69
+ const handleClick = (regionUid: string) => {
70
+ if (readOnly || disabled) return;
71
+
72
+ let next: Set<string>;
73
+ if (multiSelect) {
74
+ next = new Set(selected);
75
+ if (next.has(regionUid)) {
76
+ next.delete(regionUid);
77
+ } else {
78
+ next.add(regionUid);
79
+ }
80
+ } else {
81
+ next = selected.has(regionUid) ? new Set() : new Set([regionUid]);
82
+ }
83
+
84
+ setSelected(next);
85
+ onAnswer?.([...next].map((uid) => ({ uid })));
86
+ };
87
+
88
+ const getRegionClasses = (region: HotspotRegion) => {
89
+ const isSelected = selected.has(region.uid);
90
+
91
+ if (showCorrectAnswers) {
92
+ if (region.isCorrect && isSelected) {
93
+ return "ring-3 ring-success bg-success/20 border-2 border-success";
94
+ }
95
+ if (region.isCorrect && !isSelected) {
96
+ return "ring-2 ring-success/50 border-2 border-dashed border-success/50";
97
+ }
98
+ if (!region.isCorrect && isSelected) {
99
+ return "ring-3 ring-destructive bg-destructive/20 border-2 border-destructive";
100
+ }
101
+ return "";
102
+ }
103
+
104
+ if (isSelected) {
105
+ return "ring-3 ring-primary bg-primary/20 border-2 border-primary";
106
+ }
107
+
108
+ return "hover:bg-primary/10";
109
+ };
110
+
111
+ return (
112
+ <div className="flex flex-col gap-4">
113
+ <div dangerouslySetInnerHTML={{ __html: question.content }} />
114
+
115
+ <div className="relative inline-block max-w-full">
116
+ {question.hotspotImageUrl && (
117
+ <img
118
+ src={question.hotspotImageUrl}
119
+ alt="Hotspot question image"
120
+ className="w-full h-auto rounded-md"
121
+ draggable={false}
122
+ />
123
+ )}
124
+
125
+ {regions.map((region, i) => (
126
+ <button
127
+ key={region.uid}
128
+ type="button"
129
+ onClick={() => handleClick(region.uid)}
130
+ disabled={readOnly || disabled}
131
+ aria-label={region.label || `Region ${i + 1}`}
132
+ aria-pressed={selected.has(region.uid)}
133
+ className={cn(
134
+ "absolute transition-all cursor-pointer",
135
+ "disabled:cursor-default",
136
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary",
137
+ getRegionClasses(region),
138
+ )}
139
+ style={getRegionStyle(region)}
140
+ />
141
+ ))}
142
+ </div>
143
+
144
+ {showCorrectAnswers && question.explanation && (
145
+ <Alert className="mt-2">
146
+ <AlertDescription>
147
+ <strong>Explanation:</strong>{" "}
148
+ <span dangerouslySetInnerHTML={{ __html: question.explanation }} />
149
+ </AlertDescription>
150
+ </Alert>
151
+ )}
152
+ </div>
153
+ );
154
+ });
@@ -4,11 +4,29 @@ export { Choice } from "./choice";
4
4
  export { TrueFalse } from "./true-false";
5
5
  export { FillInTheBlank } from "./fill-in-the-blank";
6
6
  export { Essay } from "./essay";
7
+ export { Numeric } from "./numeric";
8
+ export { Ordering } from "./ordering";
9
+ export { Matching } from "./matching";
10
+ export { Hotspot } from "./hotspot";
11
+ export { InlineChoice } from "./inline-choice";
12
+ export { Scenario } from "./scenario";
13
+ export { Spreadsheet } from "./spreadsheet";
14
+ export { scoreQuestion, scoreScenarioSubQuestions } from "./scoring";
15
+ export { ConfidenceIndicator } from "./confidence-indicator";
16
+ export type { ConfidenceIndicatorProps, ConfidenceLevel } from "./confidence-indicator";
7
17
 
8
18
  export type {
9
19
  QuestionProps,
10
20
  QuestionData,
11
21
  QuestionTypeEnum,
22
+ QuestionMaterial,
12
23
  AnswerOption,
13
24
  SessionAnswer,
25
+ MatchingPair,
26
+ HotspotRegion,
27
+ InlineBlank,
28
+ ScenarioScoringMode,
29
+ SpreadsheetColumn,
30
+ SpreadsheetCell,
31
+ SpreadsheetRow,
14
32
  } from "./types";