@hydralms/components 0.2.0 → 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 (283) hide show
  1. package/dist/StudentProfile-BVfZMbnV.cjs +1 -0
  2. package/dist/StudentProfile-DeMxdrL3.js +3275 -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 +494 -444
  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 +3 -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 +1266 -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/AnnouncementFeed/AnnouncementFeed.d.ts +1 -1
  74. package/dist/sections/AnnouncementFeed/types.d.ts +15 -1
  75. package/dist/sections/AssessmentReview/AssessmentReview.d.ts +1 -1
  76. package/dist/sections/AssessmentReview/types.d.ts +6 -0
  77. package/dist/sections/AssignmentSubmission/AssignmentSubmission.d.ts +1 -1
  78. package/dist/sections/AssignmentSubmission/types.d.ts +6 -0
  79. package/dist/sections/CertificateViewer/CertificateViewer.d.ts +1 -1
  80. package/dist/sections/CertificateViewer/certificate-variants.d.ts +42 -0
  81. package/dist/sections/CertificateViewer/types.d.ts +6 -0
  82. package/dist/sections/CourseCatalog/CourseCatalog.d.ts +2 -0
  83. package/dist/sections/CourseCatalog/types.d.ts +80 -0
  84. package/dist/sections/CourseOutline/CourseOutline.d.ts +1 -1
  85. package/dist/sections/CourseOutline/types.d.ts +6 -0
  86. package/dist/sections/DiscussionThread/DiscussionThread.d.ts +1 -1
  87. package/dist/sections/DiscussionThread/types.d.ts +6 -0
  88. package/dist/sections/EnrollmentWizard/EnrollmentWizard.d.ts +2 -0
  89. package/dist/sections/EnrollmentWizard/types.d.ts +66 -0
  90. package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
  91. package/dist/sections/ExamSession/types.d.ts +6 -0
  92. package/dist/sections/FlashcardStudySession/FlashcardStudySession.d.ts +1 -1
  93. package/dist/sections/FlashcardStudySession/types.d.ts +6 -0
  94. package/dist/sections/ForumBoard/ForumBoard.d.ts +1 -1
  95. package/dist/sections/ForumBoard/types.d.ts +14 -0
  96. package/dist/sections/GradebookTable/GradebookTable.d.ts +1 -1
  97. package/dist/sections/GradebookTable/types.d.ts +14 -0
  98. package/dist/sections/LecturePlayer/LecturePlayer.d.ts +1 -1
  99. package/dist/sections/LecturePlayer/types.d.ts +8 -0
  100. package/dist/sections/LessonPage/LessonPage.d.ts +1 -1
  101. package/dist/sections/LessonPage/types.d.ts +6 -0
  102. package/dist/sections/PracticeQuiz/PracticeQuiz.d.ts +1 -1
  103. package/dist/sections/PracticeQuiz/types.d.ts +6 -0
  104. package/dist/sections/ProgressDashboard/ProgressDashboard.d.ts +1 -1
  105. package/dist/sections/ProgressDashboard/types.d.ts +6 -0
  106. package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
  107. package/dist/sections/QuizSession/types.d.ts +6 -0
  108. package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +1 -1
  109. package/dist/sections/RequirementsChecklist/types.d.ts +6 -0
  110. package/dist/sections/ResourceLibrary/ResourceLibrary.d.ts +1 -1
  111. package/dist/sections/ResourceLibrary/types.d.ts +15 -1
  112. package/dist/sections/RubricView/RubricView.d.ts +1 -1
  113. package/dist/sections/RubricView/types.d.ts +6 -0
  114. package/dist/sections/ScrollableQuiz/ScrollableQuiz.d.ts +1 -1
  115. package/dist/sections/ScrollableQuiz/types.d.ts +6 -0
  116. package/dist/sections/StudentProfile/StudentProfile.d.ts +2 -0
  117. package/dist/sections/StudentProfile/types.d.ts +98 -0
  118. package/dist/sections/SurveyForm/SurveyForm.d.ts +1 -1
  119. package/dist/sections/SurveyForm/types.d.ts +6 -0
  120. package/dist/sections/_shared/merge-answers.d.ts +9 -0
  121. package/dist/sections/_shared/section-shell.d.ts +20 -0
  122. package/dist/sections/_shared/use-assessment-session.d.ts +30 -0
  123. package/dist/sections/index.d.ts +6 -0
  124. package/dist/sections.cjs +1 -1
  125. package/dist/sections.js +268 -307
  126. package/dist/tabs-BsfVo2Bl.cjs +173 -0
  127. package/dist/{tabs-Wf3h_Cx3.js → tabs-BuY1iNJE.js} +7532 -6807
  128. package/dist/ui/badge.d.ts +1 -1
  129. package/dist/ui/index.d.ts +2 -0
  130. package/dist/ui/progress.d.ts +1 -1
  131. package/dist/ui/rich-text-editor.d.ts +3 -1
  132. package/dist/ui/toast.d.ts +43 -0
  133. package/dist/utils/debounce.d.ts +5 -1
  134. package/dist/utils/pick-palette-color.d.ts +19 -0
  135. package/dist/video/types.d.ts +15 -0
  136. package/dist/video/video-player.d.ts +1 -1
  137. package/dist/withProGate-BWqcKdPM.js +137 -0
  138. package/dist/withProGate-DX6XqKLp.cjs +1 -0
  139. package/package.json +34 -220
  140. package/src/assessment-toolbar/question-navigator.tsx +10 -5
  141. package/src/assessment-toolbar/timer-display.tsx +4 -3
  142. package/src/assessment-toolbar/use-countdown.ts +1 -1
  143. package/src/common/empty-state.tsx +1 -0
  144. package/src/common/index.ts +2 -0
  145. package/src/common/pagination.tsx +135 -0
  146. package/src/common/search-input.tsx +2 -1
  147. package/src/common/types.ts +2 -0
  148. package/src/content/attachment-list.tsx +2 -0
  149. package/src/content/audio-player.tsx +196 -0
  150. package/src/content/code-block.tsx +113 -0
  151. package/src/content/content-block.tsx +64 -0
  152. package/src/content/embed-block.tsx +78 -0
  153. package/src/content/file-upload-zone.tsx +10 -0
  154. package/src/content/index.ts +6 -0
  155. package/src/content/types.ts +5 -0
  156. package/src/curriculum/course-card.tsx +199 -0
  157. package/src/curriculum/curriculum-item.tsx +3 -3
  158. package/src/curriculum/curriculum-tree.tsx +20 -13
  159. package/src/curriculum/index.ts +2 -0
  160. package/src/curriculum/types.ts +2 -2
  161. package/src/flashcards/flashcard.tsx +28 -8
  162. package/src/index.ts +3 -0
  163. package/src/license/HydraContext.tsx +62 -0
  164. package/src/license/ProBadge.tsx +43 -0
  165. package/src/license/index.ts +7 -0
  166. package/src/license/tiers.ts +24 -0
  167. package/src/license/useHydraLicense.ts +10 -0
  168. package/src/license/validate.ts +90 -0
  169. package/src/license/withProGate.tsx +21 -0
  170. package/src/modules/AssignmentModule/AssignmentModule.tsx +17 -8
  171. package/src/modules/AssignmentModule/types.ts +5 -1
  172. package/src/modules/CertificateModule/CertificateModule.tsx +21 -9
  173. package/src/modules/CertificateModule/types.ts +6 -4
  174. package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
  175. package/src/modules/CourseCatalogModule/types.ts +47 -0
  176. package/src/modules/CoursePlayer/CoursePlayer.tsx +37 -22
  177. package/src/modules/DiscussionModule/DiscussionModule.tsx +57 -22
  178. package/src/modules/ExamModule/ExamModule.tsx +64 -198
  179. package/src/modules/ExamModule/types.ts +5 -14
  180. package/src/modules/FlashcardLab/FlashcardLab.tsx +10 -5
  181. package/src/modules/FlashcardLab/types.ts +2 -0
  182. package/src/modules/GradeCenterModule/GradeCenterModule.tsx +7 -2
  183. package/src/modules/GradeCenterModule/types.ts +2 -0
  184. package/src/modules/QuizModule/QuizModule.tsx +49 -169
  185. package/src/modules/QuizModule/types.ts +5 -15
  186. package/src/modules/StudentDashboardModule/StudentDashboardModule.tsx +117 -0
  187. package/src/modules/StudentDashboardModule/types.ts +56 -0
  188. package/src/modules/StudentProfileModule/StudentProfileModule.tsx +289 -0
  189. package/src/modules/StudentProfileModule/types.ts +45 -0
  190. package/src/modules/SurveyModule/SurveyModule.tsx +9 -4
  191. package/src/modules/SurveyModule/types.ts +2 -0
  192. package/src/modules/_shared/assessment-intro.tsx +75 -0
  193. package/src/modules/_shared/assessment-results.tsx +133 -0
  194. package/src/modules/_shared/types.ts +11 -0
  195. package/src/modules/_shared/use-timer.ts +49 -0
  196. package/src/modules/index.ts +9 -0
  197. package/src/progress/achievement-badge.tsx +3 -3
  198. package/src/progress/grade-indicator.tsx +9 -1
  199. package/src/progress/progress-ring.tsx +2 -1
  200. package/src/progress/stat-card.tsx +8 -1
  201. package/src/progress/types.ts +2 -0
  202. package/src/provider/HydraProvider.tsx +15 -6
  203. package/src/questions/choice.tsx +13 -6
  204. package/src/questions/confidence-indicator.tsx +107 -0
  205. package/src/questions/essay.tsx +6 -4
  206. package/src/questions/fill-in-the-blank.tsx +8 -4
  207. package/src/questions/hotspot.tsx +4 -4
  208. package/src/questions/index.ts +2 -0
  209. package/src/questions/inline-choice.tsx +5 -4
  210. package/src/questions/matching.tsx +5 -4
  211. package/src/questions/multiple-choice.tsx +13 -6
  212. package/src/questions/numeric.tsx +8 -4
  213. package/src/questions/ordering.tsx +12 -4
  214. package/src/questions/question-renderer.tsx +3 -2
  215. package/src/questions/scenario.tsx +4 -4
  216. package/src/questions/spreadsheet.tsx +5 -4
  217. package/src/questions/true-false.tsx +13 -6
  218. package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +64 -8
  219. package/src/sections/AnnouncementFeed/types.ts +15 -1
  220. package/src/sections/AssessmentReview/AssessmentReview.tsx +37 -0
  221. package/src/sections/AssessmentReview/types.ts +6 -0
  222. package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +37 -1
  223. package/src/sections/AssignmentSubmission/types.ts +6 -0
  224. package/src/sections/CertificateViewer/CertificateViewer.tsx +29 -227
  225. package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
  226. package/src/sections/CertificateViewer/types.ts +6 -0
  227. package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
  228. package/src/sections/CourseCatalog/types.ts +76 -0
  229. package/src/sections/CourseOutline/CourseOutline.tsx +41 -0
  230. package/src/sections/CourseOutline/types.ts +6 -0
  231. package/src/sections/DiscussionThread/DiscussionThread.tsx +42 -1
  232. package/src/sections/DiscussionThread/types.ts +6 -0
  233. package/src/sections/EnrollmentWizard/EnrollmentWizard.tsx +343 -0
  234. package/src/sections/EnrollmentWizard/types.ts +65 -0
  235. package/src/sections/ExamSession/ExamSession.tsx +100 -94
  236. package/src/sections/ExamSession/types.ts +6 -0
  237. package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
  238. package/src/sections/FlashcardStudySession/types.ts +6 -0
  239. package/src/sections/ForumBoard/ForumBoard.tsx +59 -1
  240. package/src/sections/ForumBoard/types.ts +14 -0
  241. package/src/sections/GradebookTable/GradebookTable.tsx +54 -1
  242. package/src/sections/GradebookTable/types.ts +14 -0
  243. package/src/sections/LecturePlayer/LecturePlayer.tsx +63 -37
  244. package/src/sections/LecturePlayer/types.ts +8 -0
  245. package/src/sections/LessonPage/LessonPage.tsx +36 -5
  246. package/src/sections/LessonPage/types.ts +6 -0
  247. package/src/sections/PracticeQuiz/PracticeQuiz.tsx +106 -74
  248. package/src/sections/PracticeQuiz/types.ts +6 -0
  249. package/src/sections/ProgressDashboard/ProgressDashboard.tsx +64 -10
  250. package/src/sections/ProgressDashboard/types.ts +6 -0
  251. package/src/sections/QuizSession/QuizSession.tsx +71 -82
  252. package/src/sections/QuizSession/types.ts +6 -0
  253. package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +41 -1
  254. package/src/sections/RequirementsChecklist/types.ts +6 -0
  255. package/src/sections/ResourceLibrary/ResourceLibrary.tsx +64 -8
  256. package/src/sections/ResourceLibrary/types.ts +15 -1
  257. package/src/sections/RubricView/RubricView.tsx +37 -1
  258. package/src/sections/RubricView/types.ts +6 -0
  259. package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +36 -15
  260. package/src/sections/ScrollableQuiz/types.ts +6 -0
  261. package/src/sections/StudentProfile/StudentProfile.tsx +279 -0
  262. package/src/sections/StudentProfile/types.ts +99 -0
  263. package/src/sections/SurveyForm/SurveyForm.tsx +32 -5
  264. package/src/sections/SurveyForm/types.ts +6 -0
  265. package/src/sections/_shared/merge-answers.ts +22 -0
  266. package/src/sections/_shared/section-shell.tsx +64 -0
  267. package/src/sections/_shared/use-assessment-session.ts +125 -0
  268. package/src/sections/index.ts +22 -0
  269. package/src/social/user-avatar.tsx +9 -5
  270. package/src/styles/globals.css +39 -41
  271. package/src/ui/badge.tsx +8 -0
  272. package/src/ui/index.ts +2 -0
  273. package/src/ui/progress.tsx +4 -0
  274. package/src/ui/rich-text-editor.tsx +10 -0
  275. package/src/ui/rich-text-toolbar.tsx +2 -1
  276. package/src/ui/toast.tsx +170 -0
  277. package/src/utils/debounce.ts +8 -2
  278. package/src/utils/pick-palette-color.ts +33 -0
  279. package/src/video/types.ts +16 -0
  280. package/src/video/video-player.tsx +13 -1
  281. package/dist/ForumBoard-CHXU3mjC.js +0 -2207
  282. package/dist/ForumBoard-d1w5-r6n.cjs +0 -1
  283. package/dist/tabs-DRM2Iq_J.cjs +0 -172
@@ -1,4 +1,4 @@
1
- import { useMemo, useRef } from "react";
1
+ import { useMemo, useRef, memo } from "react";
2
2
  import { Alert, AlertDescription } from "../ui/alert";
3
3
  import { Separator } from "../ui/separator";
4
4
  import { cn } from "../lib/utils";
@@ -26,14 +26,14 @@ import type { QuestionProps, SessionAnswer } from "./types";
26
26
  * onAnswer={(answers) => handleAnswer(answers)}
27
27
  * />
28
28
  */
29
- export const Scenario = ({
29
+ export const Scenario = memo(function Scenario({
30
30
  question,
31
31
  sessionAnswers,
32
32
  onAnswer,
33
33
  readOnly = false,
34
34
  showCorrectAnswers = false,
35
35
  disabled = false,
36
- }: QuestionProps) => {
36
+ }: QuestionProps) {
37
37
  const subQuestions = question.scenarioQuestions ?? [];
38
38
 
39
39
  // Track the latest answers from each sub-question in a ref so
@@ -137,4 +137,4 @@ export const Scenario = ({
137
137
  )}
138
138
  </div>
139
139
  );
140
- };
140
+ });
@@ -1,4 +1,4 @@
1
- import { useState, useMemo, useRef } from "react";
1
+ import { useState, useMemo, useRef, useEffect, memo } from "react";
2
2
  import { debounce } from "../utils/debounce";
3
3
  import { Input } from "../ui/input";
4
4
  import {
@@ -66,14 +66,14 @@ function getCellStatus(
66
66
  * onAnswer={(answers) => handleAnswer(answers)}
67
67
  * />
68
68
  */
69
- export const Spreadsheet = ({
69
+ export const Spreadsheet = memo(function Spreadsheet({
70
70
  question,
71
71
  sessionAnswers,
72
72
  onAnswer,
73
73
  readOnly = false,
74
74
  showCorrectAnswers = false,
75
75
  disabled = false,
76
- }: QuestionProps) => {
76
+ }: QuestionProps) {
77
77
  const columns = question.spreadsheetColumns ?? [];
78
78
  const rows = question.spreadsheetRows ?? [];
79
79
 
@@ -104,6 +104,7 @@ export const Spreadsheet = ({
104
104
  }, 300),
105
105
  [],
106
106
  );
107
+ useEffect(() => () => debouncedEmit.cancel(), [debouncedEmit]);
107
108
 
108
109
  const [values, setValues] = useState<Map<string, string>>(() => {
109
110
  const map = new Map<string, string>();
@@ -256,4 +257,4 @@ export const Spreadsheet = ({
256
257
  )}
257
258
  </div>
258
259
  );
259
- };
260
+ });
@@ -1,4 +1,4 @@
1
- import { useState, useMemo } 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,14 +12,14 @@ import { cn } from "../lib/utils";
12
12
  * onAnswer={(answers) => handleAnswer(answers)}
13
13
  * />
14
14
  */
15
- export const TrueFalse = ({
15
+ export const TrueFalse = memo(function TrueFalse({
16
16
  question,
17
17
  sessionAnswers,
18
18
  onAnswer,
19
19
  readOnly = false,
20
20
  showCorrectAnswers = false,
21
21
  disabled = false,
22
- }: QuestionProps) => {
22
+ }: QuestionProps) {
23
23
  const [selectedAnswer, setSelectedAnswer] = useState<string>(
24
24
  () => sessionAnswers?.[0]?.answerUid || "",
25
25
  );
@@ -53,7 +53,8 @@ export const TrueFalse = ({
53
53
  <div className="flex flex-col gap-4">
54
54
  <div dangerouslySetInnerHTML={{ __html: question.content }} />
55
55
 
56
- <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">True or False</legend>
57
58
  {sortedAnswers.map((answer) => (
58
59
  <div
59
60
  key={answer.uid}
@@ -70,10 +71,16 @@ export const TrueFalse = ({
70
71
  className="accent-primary m-0 shrink-0"
71
72
  />
72
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
+ )}
73
80
  </label>
74
81
  </div>
75
82
  ))}
76
- </div>
83
+ </fieldset>
77
84
 
78
85
  {showCorrectAnswers && question.explanation && (
79
86
  <Alert className="mt-2">
@@ -85,4 +92,4 @@ export const TrueFalse = ({
85
92
  )}
86
93
  </div>
87
94
  );
88
- };
95
+ });
@@ -1,13 +1,15 @@
1
1
  import { useMemo, useState } from "react";
2
- import { Pin } from "lucide-react";
2
+ import { AlertCircle, Pin } from "lucide-react";
3
3
  import { UserAvatar } from "../../social";
4
4
  import { EmptyState } from "../../common";
5
+ import { Skeleton } from "../../ui/skeleton";
5
6
  import { Badge } from "../../ui/badge";
6
7
  import { Button } from "../../ui/button";
7
8
  import { Card, CardContent } from "../../ui/card";
8
9
  import type { AnnouncementFeedProps } from "./types";
9
10
  import { cn } from "../../lib/utils";
10
11
  import { formatTimestamp } from "../../utils/format-timestamp";
12
+ import { Pagination } from "../../common/pagination";
11
13
 
12
14
  export function AnnouncementFeed({
13
15
  announcements,
@@ -17,6 +19,13 @@ export function AnnouncementFeed({
17
19
  previewLines = 3,
18
20
  emptyMessage = "No announcements yet",
19
21
  readOnly = false,
22
+ isLoading,
23
+ error,
24
+ onRetry,
25
+ pageSize,
26
+ currentPage = 1,
27
+ totalItems,
28
+ onPageChange,
20
29
  className,
21
30
  style,
22
31
  }: AnnouncementFeedProps) {
@@ -41,6 +50,42 @@ export function AnnouncementFeed({
41
50
  }
42
51
  }
43
52
 
53
+ if (isLoading) {
54
+ return (
55
+ <div className={cn("space-y-4", className)} style={style}>
56
+ {Array.from({ length: 3 }).map((_, i) => (
57
+ <div key={i} className="flex gap-3 items-start">
58
+ <Skeleton className="h-10 w-10 rounded-full shrink-0" />
59
+ <div className="flex-1 space-y-2">
60
+ <Skeleton className="h-5 w-48" />
61
+ <Skeleton className="h-4 w-full" />
62
+ <Skeleton className="h-4 w-full" />
63
+ </div>
64
+ </div>
65
+ ))}
66
+ </div>
67
+ );
68
+ }
69
+
70
+ if (error) {
71
+ return (
72
+ <div className={cn("py-12", className)} style={style}>
73
+ <EmptyState
74
+ icon={<AlertCircle className="size-10 text-destructive" />}
75
+ title="Something went wrong"
76
+ description={error}
77
+ action={
78
+ onRetry ? (
79
+ <Button variant="outline" onClick={onRetry}>
80
+ Retry
81
+ </Button>
82
+ ) : undefined
83
+ }
84
+ />
85
+ </div>
86
+ );
87
+ }
88
+
44
89
  if (sorted.length === 0) {
45
90
  return (
46
91
  <div className={className} style={style}>
@@ -51,7 +96,10 @@ export function AnnouncementFeed({
51
96
 
52
97
  return (
53
98
  <div className={cn("flex flex-col gap-2", className)} style={style}>
54
- {sorted.map((a) => {
99
+ {(onPageChange && pageSize
100
+ ? sorted.slice((currentPage - 1) * pageSize, currentPage * pageSize)
101
+ : sorted
102
+ ).map((a) => {
55
103
  const isExpanded = expandedUids.has(a.uid);
56
104
 
57
105
  return (
@@ -91,9 +139,9 @@ export function AnnouncementFeed({
91
139
  <span className="block text-xs text-muted-foreground mb-1">
92
140
  {a.author.displayName} · {formatTimestamp(a.createdAt)}
93
141
  </span>
94
- <span
142
+ <div
95
143
  className={cn(
96
- "text-sm text-foreground",
144
+ "text-sm text-foreground [&_p]:mb-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5",
97
145
  !isExpanded && "line-clamp-(--preview-lines) overflow-hidden",
98
146
  )}
99
147
  style={
@@ -101,10 +149,9 @@ export function AnnouncementFeed({
101
149
  ? { "--preview-lines": previewLines } as React.CSSProperties
102
150
  : undefined
103
151
  }
104
- >
105
- {a.content}
106
- </span>
107
- {!isExpanded && a.content.length > 200 && (
152
+ dangerouslySetInnerHTML={{ __html: a.content }}
153
+ />
154
+ {!isExpanded && a.content.replace(/<[^>]*>/g, "").length > 200 && (
108
155
  <Button
109
156
  variant="link"
110
157
  size="xs"
@@ -123,6 +170,15 @@ export function AnnouncementFeed({
123
170
  </Card>
124
171
  );
125
172
  })}
173
+
174
+ {onPageChange && pageSize && sorted.length > 0 && (
175
+ <Pagination
176
+ currentPage={currentPage}
177
+ totalPages={Math.ceil((totalItems ?? sorted.length) / pageSize)}
178
+ onPageChange={onPageChange}
179
+ className="mt-4"
180
+ />
181
+ )}
126
182
  </div>
127
183
  );
128
184
  }
@@ -26,6 +26,20 @@ export interface AnnouncementFeedProps {
26
26
  emptyMessage?: string;
27
27
  /** When true, disables interactions */
28
28
  readOnly?: boolean;
29
+ /** Render skeleton placeholders instead of content */
30
+ isLoading?: boolean;
31
+ /** Error message — renders an error state with optional retry */
32
+ error?: string | null;
33
+ /** Called when the user clicks retry in the error state */
34
+ onRetry?: () => void;
35
+ /** Number of items per page (enables pagination when set with onPageChange) */
36
+ pageSize?: number;
37
+ /** Current page (1-indexed) */
38
+ currentPage?: number;
39
+ /** Total number of items (for server-side pagination) */
40
+ totalItems?: number;
41
+ /** Called when the user navigates to a different page */
42
+ onPageChange?: (page: number) => void;
29
43
  /** CSS class name for the root element */
30
44
  className?: string;
31
45
  /** Inline styles for the root element */
@@ -37,7 +51,7 @@ export interface Announcement {
37
51
  uid: string;
38
52
  /** Announcement title */
39
53
  title: string;
40
- /** Announcement body content */
54
+ /** Announcement body content. May contain HTML from a rich-text editor. */
41
55
  content: string;
42
56
  /** Author information */
43
57
  author: { displayName: string; avatarUrl?: string; role?: string };
@@ -1,9 +1,13 @@
1
1
  import { useMemo } from "react";
2
+ import { AlertCircle } from "lucide-react";
2
3
  import { QuestionRenderer } from "../../questions";
3
4
  import type { QuestionData, SessionAnswer } from "../../questions/types";
4
5
  import { Badge } from "../../ui/badge";
5
6
  import { Card, CardContent } from "../../ui/card";
6
7
  import { Separator } from "../../ui/separator";
8
+ import { Skeleton } from "../../ui/skeleton";
9
+ import { Button } from "../../ui/button";
10
+ import { EmptyState } from "../../common/empty-state";
7
11
  import { cn } from "../../lib/utils";
8
12
  import type {
9
13
  AssessmentReviewProps,
@@ -140,9 +144,42 @@ export function AssessmentReview({
140
144
  score,
141
145
  questionGroups,
142
146
  showCorrectAnswers = true,
147
+ isLoading,
148
+ error,
149
+ onRetry,
143
150
  className,
144
151
  style,
145
152
  }: AssessmentReviewProps) {
153
+ if (isLoading) {
154
+ return (
155
+ <div className={cn("space-y-4", className)} style={style}>
156
+ <Skeleton className="h-16 w-full" />
157
+ <Skeleton className="h-24 w-full" />
158
+ <Skeleton className="h-24 w-full" />
159
+ <Skeleton className="h-24 w-full" />
160
+ </div>
161
+ );
162
+ }
163
+
164
+ if (error) {
165
+ return (
166
+ <div className={cn("py-12", className)} style={style}>
167
+ <EmptyState
168
+ icon={<AlertCircle className="size-10 text-destructive" />}
169
+ title="Something went wrong"
170
+ description={error}
171
+ action={
172
+ onRetry ? (
173
+ <Button variant="outline" onClick={onRetry}>
174
+ Retry
175
+ </Button>
176
+ ) : undefined
177
+ }
178
+ />
179
+ </div>
180
+ );
181
+ }
182
+
146
183
  return (
147
184
  <div className={cn(className)} style={style}>
148
185
  {score && <ScoreHeader score={score} />}
@@ -34,6 +34,12 @@ export interface AssessmentReviewProps {
34
34
  * @default true
35
35
  */
36
36
  showCorrectAnswers?: 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;
37
43
  /** CSS class name for the root element */
38
44
  className?: string;
39
45
  /** Inline styles for the root element */
@@ -1,5 +1,5 @@
1
1
  import { useState } from "react";
2
- import { Send, Save } from "lucide-react";
2
+ import { Send, Save, AlertCircle } from "lucide-react";
3
3
  import { StatusBadge, DueDateDisplay } from "../../common";
4
4
  import { FileUploadZone } from "../../content";
5
5
  import { Button } from "../../ui/button";
@@ -9,6 +9,9 @@ import { Separator } from "../../ui/separator";
9
9
  import { Card, CardContent } from "../../ui/card";
10
10
  import { Alert, AlertDescription } from "../../ui/alert";
11
11
  import { Tabs, TabsList, TabsTrigger, TabsContent } from "../../ui/tabs";
12
+ import { Skeleton } from "../../ui/skeleton";
13
+ import { EmptyState } from "../../common/empty-state";
14
+ import { cn } from "../../lib/utils";
12
15
  import type { AssignmentSubmissionProps, SubmissionData } from "./types";
13
16
 
14
17
  export function AssignmentSubmission({
@@ -25,6 +28,9 @@ export function AssignmentSubmission({
25
28
  grade,
26
29
  isSubmitting = false,
27
30
  readOnly = false,
31
+ isLoading,
32
+ error,
33
+ onRetry,
28
34
  className,
29
35
  style,
30
36
  }: AssignmentSubmissionProps) {
@@ -33,6 +39,36 @@ export function AssignmentSubmission({
33
39
  const [url, setUrl] = useState(existingSubmission?.url ?? "");
34
40
  const [activeTab, setActiveTab] = useState<"text" | "file" | "url">(submissionTypes[0]);
35
41
 
42
+ if (isLoading) {
43
+ return (
44
+ <div className={cn("space-y-4", className)} style={style}>
45
+ <Skeleton className="h-8 w-64" />
46
+ <Skeleton className="h-6 w-20" />
47
+ <Skeleton className="h-32 w-full" />
48
+ <Skeleton className="h-10 w-32" />
49
+ </div>
50
+ );
51
+ }
52
+
53
+ if (error) {
54
+ return (
55
+ <div className={cn("py-12", className)} style={style}>
56
+ <EmptyState
57
+ icon={<AlertCircle className="size-10 text-destructive" />}
58
+ title="Something went wrong"
59
+ description={error}
60
+ action={
61
+ onRetry ? (
62
+ <Button variant="outline" onClick={onRetry}>
63
+ Retry
64
+ </Button>
65
+ ) : undefined
66
+ }
67
+ />
68
+ </div>
69
+ );
70
+ }
71
+
36
72
  const isEditable = !readOnly && !["submitted", "graded"].includes(status);
37
73
 
38
74
  function getSubmissionData(): SubmissionData {
@@ -44,6 +44,12 @@ export interface AssignmentSubmissionProps {
44
44
  isSubmitting?: boolean;
45
45
  /** When true, disables all interactions */
46
46
  readOnly?: boolean;
47
+ /** Render skeleton placeholders instead of content */
48
+ isLoading?: boolean;
49
+ /** Error message — renders an error state with optional retry */
50
+ error?: string | null;
51
+ /** Called when the user clicks retry in the error state */
52
+ onRetry?: () => void;
47
53
  /** CSS class name for the root element */
48
54
  className?: string;
49
55
  /** Inline styles for the root element */