@hydralms/components 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/dist/StudentProfile-BPsZBaJj.cjs +1 -0
  2. package/dist/StudentProfile-Cw2p-RZn.js +3273 -0
  3. package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
  4. package/dist/assessment-toolbar/timer-display.d.ts +1 -1
  5. package/dist/common/index.d.ts +2 -1
  6. package/dist/common/pagination.d.ts +26 -0
  7. package/dist/common/types.d.ts +1 -0
  8. package/dist/components.css +1 -1
  9. package/dist/content/audio-player.d.ts +22 -0
  10. package/dist/content/code-block.d.ts +30 -0
  11. package/dist/content/embed-block.d.ts +28 -0
  12. package/dist/content/index.d.ts +6 -0
  13. package/dist/content/types.d.ts +24 -0
  14. package/dist/curriculum/course-card.d.ts +51 -0
  15. package/dist/curriculum/index.d.ts +2 -0
  16. package/dist/curriculum/types.d.ts +2 -2
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.js +495 -439
  20. package/dist/license/HydraContext.d.ts +16 -0
  21. package/dist/license/ProBadge.d.ts +6 -0
  22. package/dist/license/index.d.ts +7 -0
  23. package/dist/license/tiers.d.ts +6 -0
  24. package/dist/license/useHydraLicense.d.ts +6 -0
  25. package/dist/license/validate.d.ts +13 -0
  26. package/dist/license/withProGate.d.ts +6 -0
  27. package/dist/modules/AssignmentModule/AssignmentModule.d.ts +4 -7
  28. package/dist/modules/AssignmentModule/types.d.ts +5 -1
  29. package/dist/modules/CertificateModule/CertificateModule.d.ts +4 -8
  30. package/dist/modules/CertificateModule/types.d.ts +6 -4
  31. package/dist/modules/CourseCatalogModule/CourseCatalogModule.d.ts +5 -0
  32. package/dist/modules/CourseCatalogModule/types.d.ts +43 -0
  33. package/dist/modules/CoursePlayer/CoursePlayer.d.ts +4 -1
  34. package/dist/modules/DiscussionModule/DiscussionModule.d.ts +4 -7
  35. package/dist/modules/ExamModule/ExamModule.d.ts +4 -7
  36. package/dist/modules/ExamModule/types.d.ts +5 -14
  37. package/dist/modules/FlashcardLab/FlashcardLab.d.ts +4 -1
  38. package/dist/modules/FlashcardLab/types.d.ts +2 -0
  39. package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +4 -8
  40. package/dist/modules/GradeCenterModule/types.d.ts +2 -0
  41. package/dist/modules/QuizModule/QuizModule.d.ts +4 -1
  42. package/dist/modules/QuizModule/types.d.ts +5 -14
  43. package/dist/modules/StudentDashboardModule/StudentDashboardModule.d.ts +5 -0
  44. package/dist/modules/StudentDashboardModule/types.d.ts +54 -0
  45. package/dist/modules/StudentProfileModule/StudentProfileModule.d.ts +5 -0
  46. package/dist/modules/StudentProfileModule/types.d.ts +43 -0
  47. package/dist/modules/SurveyModule/SurveyModule.d.ts +4 -6
  48. package/dist/modules/SurveyModule/types.d.ts +2 -0
  49. package/dist/modules/_shared/assessment-intro.d.ts +16 -0
  50. package/dist/modules/_shared/assessment-results.d.ts +23 -0
  51. package/dist/modules/_shared/types.d.ts +10 -0
  52. package/dist/modules/_shared/use-timer.d.ts +9 -0
  53. package/dist/modules/index.d.ts +6 -0
  54. package/dist/modules.cjs +1 -1
  55. package/dist/modules.js +1267 -854
  56. package/dist/progress/types.d.ts +2 -0
  57. package/dist/provider/HydraProvider.d.ts +5 -1
  58. package/dist/questions/choice.d.ts +1 -1
  59. package/dist/questions/confidence-indicator.d.ts +37 -0
  60. package/dist/questions/essay.d.ts +1 -1
  61. package/dist/questions/fill-in-the-blank.d.ts +1 -1
  62. package/dist/questions/hotspot.d.ts +1 -1
  63. package/dist/questions/index.d.ts +2 -0
  64. package/dist/questions/inline-choice.d.ts +1 -1
  65. package/dist/questions/matching.d.ts +1 -1
  66. package/dist/questions/multiple-choice.d.ts +1 -1
  67. package/dist/questions/numeric.d.ts +1 -1
  68. package/dist/questions/ordering.d.ts +1 -1
  69. package/dist/questions/question-renderer.d.ts +1 -1
  70. package/dist/questions/scenario.d.ts +1 -1
  71. package/dist/questions/spreadsheet.d.ts +1 -1
  72. package/dist/questions/true-false.d.ts +1 -1
  73. package/dist/sections/AdaptiveLearningPath/AdaptiveLearningPath.d.ts +5 -0
  74. package/dist/sections/AdaptiveLearningPath/path-connector.d.ts +8 -0
  75. package/dist/sections/AdaptiveLearningPath/path-milestone-marker.d.ts +7 -0
  76. package/dist/sections/AdaptiveLearningPath/path-node-card.d.ts +10 -0
  77. package/dist/sections/AdaptiveLearningPath/path-skill-bar.d.ts +8 -0
  78. package/dist/sections/AdaptiveLearningPath/types.d.ts +136 -0
  79. package/dist/sections/AnnouncementFeed/AnnouncementFeed.d.ts +1 -1
  80. package/dist/sections/AnnouncementFeed/types.d.ts +15 -1
  81. package/dist/sections/AssessmentReview/AssessmentReview.d.ts +1 -1
  82. package/dist/sections/AssessmentReview/types.d.ts +6 -0
  83. package/dist/sections/AssignmentSubmission/AssignmentSubmission.d.ts +1 -1
  84. package/dist/sections/AssignmentSubmission/types.d.ts +6 -0
  85. package/dist/sections/CertificateViewer/CertificateViewer.d.ts +1 -1
  86. package/dist/sections/CertificateViewer/certificate-variants.d.ts +42 -0
  87. package/dist/sections/CertificateViewer/types.d.ts +6 -0
  88. package/dist/sections/ContentAuthoringStudio/ContentAuthoringStudio.d.ts +5 -0
  89. package/dist/sections/ContentAuthoringStudio/block-editor-item.d.ts +14 -0
  90. package/dist/sections/ContentAuthoringStudio/block-type-picker.d.ts +12 -0
  91. package/dist/sections/ContentAuthoringStudio/types.d.ts +67 -0
  92. package/dist/sections/CourseCatalog/CourseCatalog.d.ts +2 -0
  93. package/dist/sections/CourseCatalog/types.d.ts +80 -0
  94. package/dist/sections/CourseOutline/CourseOutline.d.ts +1 -1
  95. package/dist/sections/CourseOutline/types.d.ts +6 -0
  96. package/dist/sections/DiscussionThread/DiscussionThread.d.ts +1 -1
  97. package/dist/sections/DiscussionThread/types.d.ts +6 -0
  98. package/dist/sections/EnrollmentWizard/EnrollmentWizard.d.ts +2 -0
  99. package/dist/sections/EnrollmentWizard/types.d.ts +66 -0
  100. package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
  101. package/dist/sections/ExamSession/types.d.ts +6 -0
  102. package/dist/sections/FlashcardStudySession/FlashcardStudySession.d.ts +1 -1
  103. package/dist/sections/FlashcardStudySession/types.d.ts +6 -0
  104. package/dist/sections/ForumBoard/ForumBoard.d.ts +1 -1
  105. package/dist/sections/ForumBoard/types.d.ts +14 -0
  106. package/dist/sections/GradebookTable/GradebookTable.d.ts +1 -1
  107. package/dist/sections/GradebookTable/types.d.ts +14 -0
  108. package/dist/sections/LecturePlayer/LecturePlayer.d.ts +1 -1
  109. package/dist/sections/LecturePlayer/types.d.ts +8 -0
  110. package/dist/sections/LessonPage/LessonPage.d.ts +1 -1
  111. package/dist/sections/LessonPage/types.d.ts +6 -0
  112. package/dist/sections/PracticeQuiz/PracticeQuiz.d.ts +1 -1
  113. package/dist/sections/PracticeQuiz/types.d.ts +6 -0
  114. package/dist/sections/ProgressDashboard/ProgressDashboard.d.ts +1 -1
  115. package/dist/sections/ProgressDashboard/types.d.ts +6 -0
  116. package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
  117. package/dist/sections/QuizSession/types.d.ts +6 -0
  118. package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +1 -1
  119. package/dist/sections/RequirementsChecklist/types.d.ts +6 -0
  120. package/dist/sections/ResourceLibrary/ResourceLibrary.d.ts +1 -1
  121. package/dist/sections/ResourceLibrary/types.d.ts +15 -1
  122. package/dist/sections/RubricView/RubricView.d.ts +1 -1
  123. package/dist/sections/RubricView/types.d.ts +6 -0
  124. package/dist/sections/ScrollableQuiz/ScrollableQuiz.d.ts +1 -1
  125. package/dist/sections/ScrollableQuiz/types.d.ts +6 -0
  126. package/dist/sections/StudentProfile/StudentProfile.d.ts +2 -0
  127. package/dist/sections/StudentProfile/types.d.ts +98 -0
  128. package/dist/sections/SurveyForm/SurveyForm.d.ts +1 -1
  129. package/dist/sections/SurveyForm/types.d.ts +6 -0
  130. package/dist/sections/_shared/merge-answers.d.ts +9 -0
  131. package/dist/sections/_shared/section-shell.d.ts +20 -0
  132. package/dist/sections/_shared/use-assessment-session.d.ts +30 -0
  133. package/dist/sections/index.d.ts +10 -0
  134. package/dist/sections.cjs +1 -1
  135. package/dist/sections.js +1361 -307
  136. package/dist/ui/badge.d.ts +1 -1
  137. package/dist/ui/index.d.ts +2 -0
  138. package/dist/ui/progress.d.ts +1 -1
  139. package/dist/ui/rich-text-editor.d.ts +3 -1
  140. package/dist/ui/toast.d.ts +43 -0
  141. package/dist/utils/debounce.d.ts +5 -1
  142. package/dist/utils/pick-palette-color.d.ts +19 -0
  143. package/dist/video/types.d.ts +15 -0
  144. package/dist/video/video-player.d.ts +1 -1
  145. package/dist/withProGate-BJdu1T9Y.cjs +2 -0
  146. package/dist/withProGate-BvFc7Jwy.js +4975 -0
  147. package/package.json +57 -226
  148. package/src/assessment-toolbar/question-navigator.tsx +10 -5
  149. package/src/assessment-toolbar/timer-display.tsx +4 -3
  150. package/src/assessment-toolbar/use-countdown.ts +1 -1
  151. package/src/common/empty-state.tsx +1 -0
  152. package/src/common/index.ts +2 -0
  153. package/src/common/pagination.tsx +135 -0
  154. package/src/common/search-input.tsx +2 -1
  155. package/src/common/types.ts +2 -0
  156. package/src/content/attachment-list.tsx +2 -0
  157. package/src/content/audio-player.tsx +196 -0
  158. package/src/content/code-block.tsx +113 -0
  159. package/src/content/content-block.tsx +64 -0
  160. package/src/content/embed-block.tsx +78 -0
  161. package/src/content/file-upload-zone.tsx +10 -0
  162. package/src/content/index.ts +6 -0
  163. package/src/content/types.ts +5 -0
  164. package/src/curriculum/course-card.tsx +199 -0
  165. package/src/curriculum/curriculum-item.tsx +3 -3
  166. package/src/curriculum/curriculum-tree.tsx +20 -13
  167. package/src/curriculum/index.ts +2 -0
  168. package/src/curriculum/types.ts +2 -2
  169. package/src/flashcards/flashcard.tsx +28 -8
  170. package/src/index.ts +3 -0
  171. package/src/license/HydraContext.tsx +62 -0
  172. package/src/license/ProBadge.tsx +43 -0
  173. package/src/license/index.ts +7 -0
  174. package/src/license/tiers.ts +34 -0
  175. package/src/license/useHydraLicense.ts +10 -0
  176. package/src/license/validate.ts +90 -0
  177. package/src/license/withProGate.tsx +21 -0
  178. package/src/modules/AssignmentModule/AssignmentModule.tsx +17 -8
  179. package/src/modules/AssignmentModule/types.ts +5 -1
  180. package/src/modules/CertificateModule/CertificateModule.tsx +21 -9
  181. package/src/modules/CertificateModule/types.ts +6 -4
  182. package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
  183. package/src/modules/CourseCatalogModule/types.ts +47 -0
  184. package/src/modules/CoursePlayer/CoursePlayer.tsx +39 -22
  185. package/src/modules/DiscussionModule/DiscussionModule.tsx +57 -22
  186. package/src/modules/ExamModule/ExamModule.tsx +64 -198
  187. package/src/modules/ExamModule/types.ts +5 -14
  188. package/src/modules/FlashcardLab/FlashcardLab.tsx +10 -5
  189. package/src/modules/FlashcardLab/types.ts +2 -0
  190. package/src/modules/GradeCenterModule/GradeCenterModule.tsx +7 -2
  191. package/src/modules/GradeCenterModule/types.ts +2 -0
  192. package/src/modules/QuizModule/QuizModule.tsx +49 -169
  193. package/src/modules/QuizModule/types.ts +5 -15
  194. package/src/modules/StudentDashboardModule/StudentDashboardModule.tsx +117 -0
  195. package/src/modules/StudentDashboardModule/types.ts +56 -0
  196. package/src/modules/StudentProfileModule/StudentProfileModule.tsx +289 -0
  197. package/src/modules/StudentProfileModule/types.ts +45 -0
  198. package/src/modules/SurveyModule/SurveyModule.tsx +9 -4
  199. package/src/modules/SurveyModule/types.ts +2 -0
  200. package/src/modules/_shared/assessment-intro.tsx +75 -0
  201. package/src/modules/_shared/assessment-results.tsx +133 -0
  202. package/src/modules/_shared/types.ts +11 -0
  203. package/src/modules/_shared/use-timer.ts +49 -0
  204. package/src/modules/index.ts +9 -0
  205. package/src/progress/achievement-badge.tsx +3 -3
  206. package/src/progress/grade-indicator.tsx +9 -1
  207. package/src/progress/progress-ring.tsx +2 -1
  208. package/src/progress/stat-card.tsx +14 -2
  209. package/src/progress/types.ts +2 -0
  210. package/src/provider/HydraProvider.tsx +15 -6
  211. package/src/questions/choice.tsx +13 -6
  212. package/src/questions/confidence-indicator.tsx +107 -0
  213. package/src/questions/essay.tsx +6 -4
  214. package/src/questions/fill-in-the-blank.tsx +8 -4
  215. package/src/questions/hotspot.tsx +4 -4
  216. package/src/questions/index.ts +2 -0
  217. package/src/questions/inline-choice.tsx +5 -4
  218. package/src/questions/matching.tsx +5 -4
  219. package/src/questions/multiple-choice.tsx +13 -6
  220. package/src/questions/numeric.tsx +8 -4
  221. package/src/questions/ordering.tsx +12 -4
  222. package/src/questions/question-renderer.tsx +3 -2
  223. package/src/questions/scenario.tsx +4 -4
  224. package/src/questions/spreadsheet.tsx +5 -4
  225. package/src/questions/true-false.tsx +13 -6
  226. package/src/sections/AdaptiveLearningPath/AdaptiveLearningPath.tsx +251 -0
  227. package/src/sections/AdaptiveLearningPath/path-connector.tsx +27 -0
  228. package/src/sections/AdaptiveLearningPath/path-milestone-marker.tsx +50 -0
  229. package/src/sections/AdaptiveLearningPath/path-node-card.tsx +166 -0
  230. package/src/sections/AdaptiveLearningPath/path-skill-bar.tsx +49 -0
  231. package/src/sections/AdaptiveLearningPath/types.ts +159 -0
  232. package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +64 -8
  233. package/src/sections/AnnouncementFeed/types.ts +15 -1
  234. package/src/sections/AssessmentReview/AssessmentReview.tsx +37 -0
  235. package/src/sections/AssessmentReview/types.ts +6 -0
  236. package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +37 -1
  237. package/src/sections/AssignmentSubmission/types.ts +6 -0
  238. package/src/sections/CertificateViewer/CertificateViewer.tsx +29 -227
  239. package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
  240. package/src/sections/CertificateViewer/types.ts +6 -0
  241. package/src/sections/ContentAuthoringStudio/ContentAuthoringStudio.tsx +289 -0
  242. package/src/sections/ContentAuthoringStudio/block-editor-item.tsx +487 -0
  243. package/src/sections/ContentAuthoringStudio/block-type-picker.tsx +123 -0
  244. package/src/sections/ContentAuthoringStudio/types.ts +67 -0
  245. package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
  246. package/src/sections/CourseCatalog/types.ts +76 -0
  247. package/src/sections/CourseOutline/CourseOutline.tsx +41 -0
  248. package/src/sections/CourseOutline/types.ts +6 -0
  249. package/src/sections/DiscussionThread/DiscussionThread.tsx +42 -1
  250. package/src/sections/DiscussionThread/types.ts +6 -0
  251. package/src/sections/EnrollmentWizard/EnrollmentWizard.tsx +343 -0
  252. package/src/sections/EnrollmentWizard/types.ts +65 -0
  253. package/src/sections/ExamSession/ExamSession.tsx +100 -94
  254. package/src/sections/ExamSession/types.ts +6 -0
  255. package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
  256. package/src/sections/FlashcardStudySession/types.ts +6 -0
  257. package/src/sections/ForumBoard/ForumBoard.tsx +67 -7
  258. package/src/sections/ForumBoard/types.ts +14 -0
  259. package/src/sections/GradebookTable/GradebookTable.tsx +54 -1
  260. package/src/sections/GradebookTable/types.ts +14 -0
  261. package/src/sections/LecturePlayer/LecturePlayer.tsx +63 -37
  262. package/src/sections/LecturePlayer/types.ts +8 -0
  263. package/src/sections/LessonPage/LessonPage.tsx +34 -6
  264. package/src/sections/LessonPage/types.ts +6 -0
  265. package/src/sections/PracticeQuiz/PracticeQuiz.tsx +106 -74
  266. package/src/sections/PracticeQuiz/types.ts +6 -0
  267. package/src/sections/ProgressDashboard/ProgressDashboard.tsx +64 -10
  268. package/src/sections/ProgressDashboard/types.ts +6 -0
  269. package/src/sections/QuizSession/QuizSession.tsx +71 -82
  270. package/src/sections/QuizSession/types.ts +6 -0
  271. package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +41 -1
  272. package/src/sections/RequirementsChecklist/types.ts +6 -0
  273. package/src/sections/ResourceLibrary/ResourceLibrary.tsx +64 -8
  274. package/src/sections/ResourceLibrary/types.ts +15 -1
  275. package/src/sections/RubricView/RubricView.tsx +37 -1
  276. package/src/sections/RubricView/types.ts +6 -0
  277. package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +36 -15
  278. package/src/sections/ScrollableQuiz/types.ts +6 -0
  279. package/src/sections/StudentProfile/StudentProfile.tsx +279 -0
  280. package/src/sections/StudentProfile/types.ts +99 -0
  281. package/src/sections/SurveyForm/SurveyForm.tsx +32 -5
  282. package/src/sections/SurveyForm/types.ts +6 -0
  283. package/src/sections/_shared/merge-answers.ts +22 -0
  284. package/src/sections/_shared/section-shell.tsx +64 -0
  285. package/src/sections/_shared/use-assessment-session.ts +125 -0
  286. package/src/sections/index.ts +40 -0
  287. package/src/social/user-avatar.tsx +9 -5
  288. package/src/styles/globals.css +39 -41
  289. package/src/ui/badge.tsx +8 -0
  290. package/src/ui/index.ts +2 -0
  291. package/src/ui/progress.tsx +4 -0
  292. package/src/ui/rich-text-editor.tsx +10 -0
  293. package/src/ui/rich-text-toolbar.tsx +2 -1
  294. package/src/ui/toast.tsx +170 -0
  295. package/src/utils/debounce.ts +8 -2
  296. package/src/utils/pick-palette-color.ts +33 -0
  297. package/src/video/types.ts +16 -0
  298. package/src/video/video-player.tsx +27 -6
  299. package/dist/ForumBoard-CHXU3mjC.js +0 -2207
  300. package/dist/ForumBoard-d1w5-r6n.cjs +0 -1
  301. package/dist/tabs-DRM2Iq_J.cjs +0 -172
  302. package/dist/tabs-Wf3h_Cx3.js +0 -21580
@@ -0,0 +1,99 @@
1
+
2
+ /**
3
+ * StudentProfile section — a student profile overview.
4
+ *
5
+ * Displays student info header, stat cards, enrolled courses list,
6
+ * achievements grid, and certificates list.
7
+ *
8
+ * @example
9
+ * <StudentProfile
10
+ * student={{ uid: "s1", displayName: "Jane Doe", email: "jane@example.com" }}
11
+ * enrolledCourses={courses}
12
+ * achievements={badges}
13
+ * onCourseClick={(uid) => navigate(`/courses/${uid}`)}
14
+ * />
15
+ */
16
+ export interface StudentProfileProps {
17
+ /** Student information */
18
+ student: StudentInfo;
19
+ /** Enrolled courses */
20
+ enrolledCourses?: EnrolledCourse[];
21
+ /** Earned achievements */
22
+ achievements?: ProfileAchievement[];
23
+ /** Earned certificates */
24
+ certificates?: ProfileCertificate[];
25
+ /** Custom stat cards to display */
26
+ stats?: { label: string; value: string; icon?: React.ReactNode }[];
27
+ /** When false, hides the Enrolled Courses section. @default true */
28
+ showCourses?: boolean;
29
+ /** When false, hides the Achievements section. @default true */
30
+ showAchievements?: boolean;
31
+ /** Called when a course is clicked */
32
+ onCourseClick?: (courseUid: string) => void;
33
+ /** Called when a certificate is clicked */
34
+ onCertificateClick?: (certificateUid: string) => void;
35
+ /** When true, disables interactions */
36
+ readOnly?: boolean;
37
+ /** Render skeleton placeholders instead of content */
38
+ isLoading?: boolean;
39
+ /** Error message — renders an error state with optional retry */
40
+ error?: string | null;
41
+ /** Called when the user clicks retry in the error state */
42
+ onRetry?: () => void;
43
+ /** CSS class name for the root element */
44
+ className?: string;
45
+ /** Inline styles for the root element */
46
+ style?: React.CSSProperties;
47
+ }
48
+
49
+ export interface StudentInfo {
50
+ /** Unique identifier */
51
+ uid: string;
52
+ /** Display name */
53
+ displayName: string;
54
+ /** Avatar URL */
55
+ avatarUrl?: string;
56
+ /** Email address */
57
+ email?: string;
58
+ /** Short bio */
59
+ bio?: string;
60
+ /** Join date as ISO string */
61
+ joinedAt?: string;
62
+ /** Role label */
63
+ role?: string;
64
+ }
65
+
66
+ export interface EnrolledCourse {
67
+ /** Course UID */
68
+ uid: string;
69
+ /** Course title */
70
+ title: string;
71
+ /** Progress percentage (0-100) */
72
+ progress: number;
73
+ /** Last accessed as ISO string */
74
+ lastAccessedAt?: string;
75
+ }
76
+
77
+ export interface ProfileAchievement {
78
+ /** Unique identifier */
79
+ uid: string;
80
+ /** Achievement name */
81
+ name: string;
82
+ /** Achievement description */
83
+ description: string;
84
+ /** Icon URL */
85
+ iconUrl?: string;
86
+ /** Date earned as ISO string */
87
+ earnedAt: string;
88
+ }
89
+
90
+ export interface ProfileCertificate {
91
+ /** Unique identifier */
92
+ uid: string;
93
+ /** Course name */
94
+ courseName: string;
95
+ /** Date issued as ISO string */
96
+ issuedAt: string;
97
+ /** Certificate URL */
98
+ certificateUrl?: string;
99
+ }
@@ -1,16 +1,19 @@
1
1
  import { useState, useMemo } from "react";
2
2
  import { LikertScale, StarRating } from "../../feedback";
3
+
3
4
  import { Button } from "../../ui/button";
4
5
  import { Card, CardContent } from "../../ui/card";
5
6
  import { Progress } from "../../ui/progress";
6
7
  import { Separator } from "../../ui/separator";
8
+ import { Skeleton } from "../../ui/skeleton";
7
9
  import { RichTextEditor } from "../../ui/rich-text-editor";
10
+ import { SectionShell } from "../_shared/section-shell";
8
11
  import type { SurveyFormProps, SurveyAnswer } from "./types";
9
12
 
10
13
  export function SurveyForm({
11
14
  title,
12
15
  description,
13
- questions,
16
+ questions = [],
14
17
  initialAnswers = [],
15
18
  onSubmit,
16
19
  onAnswerChange,
@@ -19,6 +22,9 @@ export function SurveyForm({
19
22
  submitLabel = "Submit Survey",
20
23
  isSubmitting = false,
21
24
  readOnly = false,
25
+ isLoading,
26
+ error,
27
+ onRetry,
22
28
  className,
23
29
  style,
24
30
  }: SurveyFormProps) {
@@ -40,11 +46,31 @@ export function SurveyForm({
40
46
  });
41
47
  }
42
48
 
43
- function getAnswer(questionUid: string): SurveyAnswer | undefined {
44
- return answers.find((a) => a.questionUid === questionUid);
45
- }
49
+ const answerMap = useMemo(() => {
50
+ const map = new Map<string, SurveyAnswer>();
51
+ for (const a of answers) {
52
+ map.set(a.questionUid, a);
53
+ }
54
+ return map;
55
+ }, [answers]);
46
56
 
47
57
  return (
58
+ <SectionShell
59
+ isLoading={isLoading}
60
+ error={error}
61
+ onRetry={onRetry}
62
+ className={className}
63
+ style={style}
64
+ skeleton={
65
+ <>
66
+ <Skeleton className="h-6 w-48" />
67
+ <Skeleton className="h-2 w-full" />
68
+ <Skeleton className="h-16 w-full" />
69
+ <Skeleton className="h-16 w-full" />
70
+ <Skeleton className="h-16 w-full" />
71
+ </>
72
+ }
73
+ >
48
74
  <div className={className} style={style}>
49
75
  <p className="text-xl font-bold text-foreground mb-2">{title}</p>
50
76
  {description && (
@@ -66,7 +92,7 @@ export function SurveyForm({
66
92
  )}
67
93
 
68
94
  {questions.map((q, index) => {
69
- const answer = getAnswer(q.uid);
95
+ const answer = answerMap.get(q.uid);
70
96
  return (
71
97
  <Card key={q.uid} className="mb-2"><CardContent className="pt-6">
72
98
  <p className="font-medium text-foreground mb-2">
@@ -179,5 +205,6 @@ export function SurveyForm({
179
205
  </Button>
180
206
  </div>
181
207
  </div>
208
+ </SectionShell>
182
209
  );
183
210
  }
@@ -38,6 +38,12 @@ export interface SurveyFormProps {
38
38
  isSubmitting?: boolean;
39
39
  /** When true, all inputs are disabled */
40
40
  readOnly?: boolean;
41
+ /** Render skeleton placeholders instead of content */
42
+ isLoading?: boolean;
43
+ /** Error message — renders an error state with optional retry */
44
+ error?: string | null;
45
+ /** Called when the user clicks retry in the error state */
46
+ onRetry?: () => void;
41
47
  /** CSS class name for the root element */
42
48
  className?: string;
43
49
  /** Inline styles for the root element */
@@ -0,0 +1,22 @@
1
+ import type { SessionAnswer } from "../../questions/types";
2
+
3
+ /**
4
+ * Replaces all existing answers for `questionUid` with `rawAnswers`,
5
+ * then invokes an optional change callback with the merged array.
6
+ */
7
+ export function mergeSessionAnswers(
8
+ prev: SessionAnswer[],
9
+ questionUid: string,
10
+ rawAnswers: { uid: string; content?: string }[],
11
+ onChange?: (merged: SessionAnswer[]) => void,
12
+ ): SessionAnswer[] {
13
+ const newAnswers: SessionAnswer[] = rawAnswers.map((a) => ({
14
+ uid: questionUid,
15
+ answerUid: a.uid,
16
+ content: a.content,
17
+ }));
18
+ const filtered = prev.filter((a) => a.uid !== questionUid);
19
+ const merged = [...filtered, ...newAnswers];
20
+ onChange?.(merged);
21
+ return merged;
22
+ }
@@ -0,0 +1,64 @@
1
+ import type { ReactNode } from "react";
2
+ import { AlertCircle } from "lucide-react";
3
+ import { EmptyState } from "../../common";
4
+ import { Button } from "../../ui/button";
5
+ import { cn } from "../../lib/utils";
6
+
7
+ export interface SectionShellProps {
8
+ isLoading?: boolean;
9
+ error?: string | null;
10
+ onRetry?: () => void;
11
+ /** Skeleton placeholder rendered when `isLoading` is true. */
12
+ skeleton?: ReactNode;
13
+ /** Extra class merged into the loading wrapper (default `"space-y-4"`). */
14
+ loadingClassName?: string;
15
+ className?: string;
16
+ style?: React.CSSProperties;
17
+ children: ReactNode;
18
+ }
19
+
20
+ /**
21
+ * Shared wrapper that handles loading skeleton and error states
22
+ * so individual sections don't repeat the same early-return blocks.
23
+ *
24
+ * When neither loading nor error, renders children directly (no extra wrapper).
25
+ */
26
+ export function SectionShell({
27
+ isLoading,
28
+ error,
29
+ onRetry,
30
+ skeleton,
31
+ loadingClassName = "space-y-4",
32
+ className,
33
+ style,
34
+ children,
35
+ }: SectionShellProps) {
36
+ if (isLoading) {
37
+ return (
38
+ <div className={cn(loadingClassName, className)} style={style}>
39
+ {skeleton}
40
+ </div>
41
+ );
42
+ }
43
+
44
+ if (error) {
45
+ return (
46
+ <div className={cn("py-12", className)} style={style}>
47
+ <EmptyState
48
+ icon={<AlertCircle className="size-10 text-destructive" />}
49
+ title="Something went wrong"
50
+ description={error}
51
+ action={
52
+ onRetry ? (
53
+ <Button variant="outline" onClick={onRetry}>
54
+ Retry
55
+ </Button>
56
+ ) : undefined
57
+ }
58
+ />
59
+ </div>
60
+ );
61
+ }
62
+
63
+ return <>{children}</>;
64
+ }
@@ -0,0 +1,125 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import type { QuestionNavigatorItem } from "../../assessment-toolbar/types";
3
+ import type { QuestionData, QuestionMaterial, SessionAnswer } from "../../questions/types";
4
+ import { mergeSessionAnswers } from "./merge-answers";
5
+
6
+ export interface UseAssessmentSessionOptions {
7
+ questions: QuestionData[];
8
+ initialAnswers?: SessionAnswer[];
9
+ onAnswerChange?: (answers: SessionAnswer[]) => void;
10
+ questionMaterials?: QuestionMaterial[];
11
+ }
12
+
13
+ export function useAssessmentSession({
14
+ questions,
15
+ initialAnswers = [],
16
+ onAnswerChange,
17
+ questionMaterials,
18
+ }: UseAssessmentSessionOptions) {
19
+ const [currentIndex, setCurrentIndex] = useState(0);
20
+ const [sessionAnswers, setSessionAnswers] = useState<SessionAnswer[]>(initialAnswers);
21
+ const [flaggedUids, setFlaggedUids] = useState<Set<string>>(new Set());
22
+ const [materialsOpen, setMaterialsOpen] = useState(false);
23
+ const questionAreaRef = useRef<HTMLDivElement>(null);
24
+ const isFirstRender = useRef(true);
25
+
26
+ // Refs for stable callbacks
27
+ const questionsRef = useRef(questions);
28
+ questionsRef.current = questions;
29
+ const currentIndexRef = useRef(currentIndex);
30
+ currentIndexRef.current = currentIndex;
31
+ const onAnswerChangeRef = useRef(onAnswerChange);
32
+ onAnswerChangeRef.current = onAnswerChange;
33
+
34
+ const currentQuestion = questions[currentIndex];
35
+
36
+ // Focus question area on navigation (skip first render)
37
+ useEffect(() => {
38
+ if (isFirstRender.current) {
39
+ isFirstRender.current = false;
40
+ return;
41
+ }
42
+ questionAreaRef.current?.focus();
43
+ }, [currentIndex]);
44
+
45
+ const currentQuestionAnswers = useMemo(
46
+ () =>
47
+ currentQuestion
48
+ ? sessionAnswers.filter((a) => a.uid === currentQuestion.uid)
49
+ : [],
50
+ [sessionAnswers, currentQuestion],
51
+ );
52
+
53
+ const currentMaterials = useMemo(
54
+ () => questionMaterials?.filter((m) => m.questionUid === currentQuestion?.uid) ?? [],
55
+ [questionMaterials, currentQuestion],
56
+ );
57
+
58
+ const navigatorItems = useMemo<QuestionNavigatorItem[]>(
59
+ () =>
60
+ questions.map((q, idx) => ({
61
+ uid: q.uid,
62
+ sequence: idx,
63
+ isFlagged: flaggedUids.has(q.uid),
64
+ isAnswered: sessionAnswers.some((a) => a.uid === q.uid),
65
+ isSkipped: false,
66
+ })),
67
+ [questions, sessionAnswers, flaggedUids],
68
+ );
69
+
70
+ const handleAnswer = useCallback(
71
+ (rawAnswers: { uid: string; content?: string }[]) => {
72
+ const q = questionsRef.current[currentIndexRef.current];
73
+ if (!q) return;
74
+ setSessionAnswers((prev) =>
75
+ mergeSessionAnswers(prev, q.uid, rawAnswers, onAnswerChangeRef.current),
76
+ );
77
+ },
78
+ [],
79
+ );
80
+
81
+ const handleNavigate = useCallback(
82
+ (uid: string) => {
83
+ const idx = questions.findIndex((q) => q.uid === uid);
84
+ if (idx !== -1) setCurrentIndex(idx);
85
+ },
86
+ [questions],
87
+ );
88
+
89
+ const handleToggleFlag = useCallback((uid: string) => {
90
+ setFlaggedUids((prev) => {
91
+ const next = new Set(prev);
92
+ if (next.has(uid)) next.delete(uid);
93
+ else next.add(uid);
94
+ return next;
95
+ });
96
+ }, []);
97
+
98
+ const goNext = useCallback(() => {
99
+ setCurrentIndex((i) => Math.min(i + 1, questionsRef.current.length - 1));
100
+ }, []);
101
+
102
+ const goPrevious = useCallback(() => {
103
+ setCurrentIndex((i) => Math.max(i - 1, 0));
104
+ }, []);
105
+
106
+ return {
107
+ currentIndex,
108
+ currentQuestion,
109
+ sessionAnswers,
110
+ flaggedUids,
111
+ materialsOpen,
112
+ setMaterialsOpen,
113
+ questionAreaRef,
114
+ currentQuestionAnswers,
115
+ currentMaterials,
116
+ navigatorItems,
117
+ handleAnswer,
118
+ handleNavigate,
119
+ handleToggleFlag,
120
+ goNext,
121
+ goPrevious,
122
+ hasNext: currentIndex < questions.length - 1,
123
+ hasPrevious: currentIndex > 0,
124
+ };
125
+ }
@@ -107,3 +107,43 @@ export type {
107
107
 
108
108
  export { ForumBoard } from "./ForumBoard/ForumBoard";
109
109
  export type { ForumBoardProps, ForumTopic, ForumSortOrder } from "./ForumBoard/types";
110
+
111
+ export { EnrollmentWizard } from "./EnrollmentWizard/EnrollmentWizard";
112
+ export type {
113
+ EnrollmentWizardProps,
114
+ EnrollmentCourse,
115
+ Prerequisite,
116
+ } from "./EnrollmentWizard/types";
117
+
118
+ export { CourseCatalog } from "./CourseCatalog/CourseCatalog";
119
+ export type {
120
+ CourseCatalogProps,
121
+ CourseInfo,
122
+ } from "./CourseCatalog/types";
123
+
124
+ export { StudentProfile } from "./StudentProfile/StudentProfile";
125
+ export type {
126
+ StudentProfileProps,
127
+ StudentInfo,
128
+ EnrolledCourse,
129
+ ProfileAchievement,
130
+ ProfileCertificate,
131
+ } from "./StudentProfile/types";
132
+
133
+ export { ContentAuthoringStudio } from "./ContentAuthoringStudio/ContentAuthoringStudio";
134
+ export type {
135
+ ContentAuthoringStudioProps,
136
+ AuthoringBlock,
137
+ } from "./ContentAuthoringStudio/types";
138
+
139
+ export { AdaptiveLearningPath } from "./AdaptiveLearningPath/AdaptiveLearningPath";
140
+ export type {
141
+ AdaptiveLearningPathProps,
142
+ PathNode,
143
+ PathNodeStatus,
144
+ PathNodeType,
145
+ PathSkill,
146
+ PathMilestone,
147
+ PathBranch,
148
+ PathProgress,
149
+ } from "./AdaptiveLearningPath/types";
@@ -13,7 +13,7 @@ const ROLE_LABELS: Record<string, string> = {
13
13
 
14
14
  const ROLE_COLORS: Record<string, string> = {
15
15
  instructor: "var(--primary)",
16
- ta: "var(--purple)",
16
+ ta: "var(--palette-0)",
17
17
  admin: "var(--destructive)",
18
18
  student: "var(--muted-foreground)",
19
19
  };
@@ -67,10 +67,14 @@ export function UserAvatar({
67
67
  )}
68
68
  </div>
69
69
  {showBadge && (
70
- <span
71
- className="absolute -bottom-0.5 -right-0.5 size-2.5 rounded-full border-2 border-background"
72
- style={{ background: ROLE_COLORS[role] ?? "var(--muted-foreground)" }}
73
- />
70
+ <>
71
+ <span
72
+ className="absolute -bottom-0.5 -right-0.5 size-2.5 rounded-full border-2 border-background"
73
+ style={{ background: ROLE_COLORS[role] ?? "var(--muted-foreground)" }}
74
+ aria-hidden="true"
75
+ />
76
+ <span className="sr-only">{ROLE_LABELS[role] ?? role}</span>
77
+ </>
74
78
  )}
75
79
  </div>
76
80
  );
@@ -1,40 +1,10 @@
1
- @import "tailwindcss";
2
-
3
- @custom-variant dark (&:is(.dark *));
4
-
5
- @theme inline {
6
- --color-background: var(--background);
7
- --color-foreground: var(--foreground);
8
- --color-card: var(--card);
9
- --color-card-foreground: var(--card-foreground);
10
- --color-popover: var(--popover);
11
- --color-popover-foreground: var(--popover-foreground);
12
- --color-primary: var(--primary);
13
- --color-primary-foreground: var(--primary-foreground);
14
- --color-secondary: var(--secondary);
15
- --color-secondary-foreground: var(--secondary-foreground);
16
- --color-muted: var(--muted);
17
- --color-muted-foreground: var(--muted-foreground);
18
- --color-accent: var(--accent);
19
- --color-accent-foreground: var(--accent-foreground);
20
- --color-destructive: var(--destructive);
21
- --color-destructive-foreground: var(--destructive-foreground);
22
- --color-border: var(--border);
23
- --color-input: var(--input);
24
- --color-ring: var(--ring);
25
- --color-success: var(--success);
26
- --color-success-foreground: var(--success-foreground);
27
- --color-warning: var(--warning);
28
- --color-warning-foreground: var(--warning-foreground);
29
- --color-info: var(--info);
30
- --color-info-foreground: var(--info-foreground);
31
- --color-purple: var(--purple);
32
- --color-teal: var(--teal);
33
- --radius-sm: calc(var(--radius) - 4px);
34
- --radius-md: calc(var(--radius) - 2px);
35
- --radius-lg: var(--radius);
36
- --radius-xl: calc(var(--radius) + 4px);
37
- }
1
+ /*
2
+ * HydraLMS design tokens & base styles.
3
+ * NOTE: This file must NOT contain @import "tailwindcss" or Tailwind
4
+ * directives (@theme, @custom-variant). Those belong in the consumer's
5
+ * CSS entry point, which should also include an @source directive
6
+ * pointing to this library's source files. See the getting-started guide.
7
+ */
38
8
 
39
9
  .hydra-root {
40
10
  --radius: 0.625rem;
@@ -65,8 +35,16 @@
65
35
  --warning-foreground: oklch(0.98 0.016 73);
66
36
  --info: oklch(0.623 0.214 259);
67
37
  --info-foreground: oklch(0.97 0.014 254);
68
- --purple: oklch(0.627 0.265 303);
69
- --teal: oklch(0.697 0.146 182);
38
+
39
+ /* Palette colors for visual variety */
40
+ --palette-0: oklch(0.627 0.265 303);
41
+ --palette-0-foreground: oklch(0.985 0 0);
42
+ --palette-1: oklch(0.680 0.162 195);
43
+ --palette-1-foreground: oklch(0.985 0 0);
44
+ --palette-2: oklch(0.720 0.185 130);
45
+ --palette-2-foreground: oklch(0.985 0 0);
46
+ --palette-3: oklch(0.700 0.180 70);
47
+ --palette-3-foreground: oklch(0.15 0 0);
70
48
 
71
49
  color: var(--foreground);
72
50
  background: var(--background);
@@ -101,8 +79,16 @@
101
79
  --warning-foreground: oklch(0.15 0.05 60);
102
80
  --info: oklch(0.623 0.214 259);
103
81
  --info-foreground: oklch(0.15 0.05 255);
104
- --purple: oklch(0.627 0.265 303);
105
- --teal: oklch(0.697 0.146 182);
82
+
83
+ /* Palette colors for visual variety */
84
+ --palette-0: oklch(0.700 0.200 303);
85
+ --palette-0-foreground: oklch(0.15 0 0);
86
+ --palette-1: oklch(0.730 0.150 195);
87
+ --palette-1-foreground: oklch(0.15 0 0);
88
+ --palette-2: oklch(0.760 0.170 130);
89
+ --palette-2-foreground: oklch(0.15 0 0);
90
+ --palette-3: oklch(0.740 0.165 70);
91
+ --palette-3-foreground: oklch(0.15 0 0);
106
92
  }
107
93
 
108
94
  /* Rich text support for flashcard back content */
@@ -136,3 +122,15 @@
136
122
  height: 0;
137
123
  pointer-events: none;
138
124
  }
125
+
126
+ /* Respect user preference for reduced motion (WCAG 2.3.3) */
127
+ @media (prefers-reduced-motion: reduce) {
128
+ .hydra-root *,
129
+ .hydra-root *::before,
130
+ .hydra-root *::after {
131
+ animation-duration: 0.01ms !important;
132
+ animation-iteration-count: 1 !important;
133
+ transition-duration: 0.01ms !important;
134
+ scroll-behavior: auto !important;
135
+ }
136
+ }
package/src/ui/badge.tsx CHANGED
@@ -24,6 +24,14 @@ const badgeVariants = cva(
24
24
  "border-transparent bg-info/15 text-info",
25
25
  muted:
26
26
  "border-transparent bg-muted text-muted-foreground",
27
+ palette0:
28
+ "border-transparent bg-palette-0/15 text-palette-0",
29
+ palette1:
30
+ "border-transparent bg-palette-1/15 text-palette-1",
31
+ palette2:
32
+ "border-transparent bg-palette-2/15 text-palette-2",
33
+ palette3:
34
+ "border-transparent bg-palette-3/15 text-palette-3",
27
35
  },
28
36
  },
29
37
  defaultVariants: {
package/src/ui/index.ts CHANGED
@@ -61,3 +61,5 @@ export { Skeleton } from "./skeleton";
61
61
  export { Tooltip, TooltipTrigger, TooltipContent } from "./tooltip";
62
62
  export { RichTextEditor } from "./rich-text-editor";
63
63
  export type { RichTextEditorProps, RichTextEditorVariant } from "./rich-text-editor";
64
+ export { ToastProvider, Toaster, useToast, toastVariants } from "./toast";
65
+ export type { ToastOptions, ToastVariant } from "./toast";
@@ -10,6 +10,10 @@ const progressVariants = cva("h-full rounded-full transition-all duration-300",
10
10
  success: "bg-success",
11
11
  warning: "bg-warning",
12
12
  info: "bg-info",
13
+ palette0: "bg-palette-0",
14
+ palette1: "bg-palette-1",
15
+ palette2: "bg-palette-2",
16
+ palette3: "bg-palette-3",
13
17
  },
14
18
  size: {
15
19
  sm: "",
@@ -22,6 +22,8 @@ export interface RichTextEditorProps {
22
22
  disabled?: boolean;
23
23
  /** `"default"` shows full toolbar; `"minimal"` shows only basic formatting */
24
24
  variant?: RichTextEditorVariant;
25
+ /** Accessible label for screen readers (defaults to "Rich text editor") */
26
+ ariaLabel?: string;
25
27
  className?: string;
26
28
  style?: React.CSSProperties;
27
29
  }
@@ -45,6 +47,7 @@ export function RichTextEditor({
45
47
  readOnly = false,
46
48
  disabled = false,
47
49
  variant = "default",
50
+ ariaLabel = "Rich text editor",
48
51
  className,
49
52
  style,
50
53
  }: RichTextEditorProps) {
@@ -60,6 +63,13 @@ export function RichTextEditor({
60
63
  ],
61
64
  content: value,
62
65
  editable: !readOnly && !disabled,
66
+ editorProps: {
67
+ attributes: {
68
+ "aria-label": ariaLabel,
69
+ role: "textbox",
70
+ "aria-multiline": "true",
71
+ },
72
+ },
63
73
  onUpdate: ({ editor: e }) => {
64
74
  onChange?.(e.getHTML());
65
75
  },
@@ -39,6 +39,7 @@ function ToolbarButton({
39
39
  onClick={onClick}
40
40
  disabled={disabled}
41
41
  title={title}
42
+ aria-label={title}
42
43
  className={cn(
43
44
  "inline-flex items-center justify-center rounded-sm p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
44
45
  active && "bg-muted text-foreground",
@@ -72,7 +73,7 @@ export function RichTextToolbar({
72
73
  }
73
74
 
74
75
  return (
75
- <div className="flex items-center gap-0.5 border-b border-border px-2 py-1.5">
76
+ <div className="flex items-center gap-0.5 border-b border-border px-2 py-1.5" role="toolbar" aria-label="Text formatting">
76
77
  <ToolbarButton
77
78
  active={editor.isActive("bold")}
78
79
  onClick={() => editor.chain().focus().toggleBold().run()}