@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,26 +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
- function formatTimestamp(iso: string): string {
12
- const date = new Date(iso);
13
- const now = new Date();
14
- const diffMs = now.getTime() - date.getTime();
15
- const diffMins = Math.floor(diffMs / 60000);
16
- if (diffMins < 1) return "Just now";
17
- if (diffMins < 60) return `${diffMins}m ago`;
18
- const diffHours = Math.floor(diffMins / 60);
19
- if (diffHours < 24) return `${diffHours}h ago`;
20
- const diffDays = Math.floor(diffHours / 24);
21
- if (diffDays < 7) return `${diffDays}d ago`;
22
- return date.toLocaleDateString();
23
- }
11
+ import { formatTimestamp } from "../../utils/format-timestamp";
12
+ import { Pagination } from "../../common/pagination";
24
13
 
25
14
  export function AnnouncementFeed({
26
15
  announcements,
@@ -30,6 +19,13 @@ export function AnnouncementFeed({
30
19
  previewLines = 3,
31
20
  emptyMessage = "No announcements yet",
32
21
  readOnly = false,
22
+ isLoading,
23
+ error,
24
+ onRetry,
25
+ pageSize,
26
+ currentPage = 1,
27
+ totalItems,
28
+ onPageChange,
33
29
  className,
34
30
  style,
35
31
  }: AnnouncementFeedProps) {
@@ -54,6 +50,42 @@ export function AnnouncementFeed({
54
50
  }
55
51
  }
56
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
+
57
89
  if (sorted.length === 0) {
58
90
  return (
59
91
  <div className={className} style={style}>
@@ -64,7 +96,10 @@ export function AnnouncementFeed({
64
96
 
65
97
  return (
66
98
  <div className={cn("flex flex-col gap-2", className)} style={style}>
67
- {sorted.map((a) => {
99
+ {(onPageChange && pageSize
100
+ ? sorted.slice((currentPage - 1) * pageSize, currentPage * pageSize)
101
+ : sorted
102
+ ).map((a) => {
68
103
  const isExpanded = expandedUids.has(a.uid);
69
104
 
70
105
  return (
@@ -77,7 +112,7 @@ export function AnnouncementFeed({
77
112
  )}
78
113
  onClick={() => onSelect && !readOnly ? onSelect(a) : toggleExpand(a.uid)}
79
114
  >
80
- <CardContent className="pt-4 pb-4">
115
+ <CardContent className="py-4">
81
116
  <div className="flex gap-1.5 items-start">
82
117
  {showAvatars && (
83
118
  <UserAvatar
@@ -104,9 +139,9 @@ export function AnnouncementFeed({
104
139
  <span className="block text-xs text-muted-foreground mb-1">
105
140
  {a.author.displayName} · {formatTimestamp(a.createdAt)}
106
141
  </span>
107
- <span
142
+ <div
108
143
  className={cn(
109
- "text-sm text-foreground",
144
+ "text-sm text-foreground [&_p]:mb-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5",
110
145
  !isExpanded && "line-clamp-(--preview-lines) overflow-hidden",
111
146
  )}
112
147
  style={
@@ -114,10 +149,9 @@ export function AnnouncementFeed({
114
149
  ? { "--preview-lines": previewLines } as React.CSSProperties
115
150
  : undefined
116
151
  }
117
- >
118
- {a.content}
119
- </span>
120
- {!isExpanded && a.content.length > 200 && (
152
+ dangerouslySetInnerHTML={{ __html: a.content }}
153
+ />
154
+ {!isExpanded && a.content.replace(/<[^>]*>/g, "").length > 200 && (
121
155
  <Button
122
156
  variant="link"
123
157
  size="xs"
@@ -136,6 +170,15 @@ export function AnnouncementFeed({
136
170
  </Card>
137
171
  );
138
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
+ )}
139
182
  </div>
140
183
  );
141
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,8 +1,13 @@
1
+ import { useMemo } from "react";
2
+ import { AlertCircle } from "lucide-react";
1
3
  import { QuestionRenderer } from "../../questions";
2
4
  import type { QuestionData, SessionAnswer } from "../../questions/types";
3
5
  import { Badge } from "../../ui/badge";
4
6
  import { Card, CardContent } from "../../ui/card";
5
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";
6
11
  import { cn } from "../../lib/utils";
7
12
  import type {
8
13
  AssessmentReviewProps,
@@ -25,7 +30,7 @@ function ScoreHeader({
25
30
  <Card className="mb-3">
26
31
  <CardContent className="pt-6">
27
32
  <div className="flex flex-wrap items-center gap-2">
28
- <div>
33
+ <div className="flex items-baseline gap-2">
29
34
  <span className="text-2xl font-bold leading-none text-foreground">{pct}%</span>
30
35
  <span className="text-sm text-muted-foreground">
31
36
  {score.correct} of {score.total} correct
@@ -56,6 +61,16 @@ function QuestionList({
56
61
  sessionAnswers: SessionAnswer[];
57
62
  showCorrectAnswers: boolean;
58
63
  }) {
64
+ const answersByQuestion = useMemo(() => {
65
+ const map = new Map<string, SessionAnswer[]>();
66
+ for (const a of sessionAnswers) {
67
+ const list = map.get(a.uid);
68
+ if (list) list.push(a);
69
+ else map.set(a.uid, [a]);
70
+ }
71
+ return map;
72
+ }, [sessionAnswers]);
73
+
59
74
  return (
60
75
  <div className="flex flex-col gap-3">
61
76
  {questions.map((question, idx) => (
@@ -69,7 +84,7 @@ function QuestionList({
69
84
  <CardContent className="pt-4 pb-4">
70
85
  <QuestionRenderer
71
86
  question={question}
72
- sessionAnswers={sessionAnswers.filter((a) => a.uid === question.uid)}
87
+ sessionAnswers={answersByQuestion.get(question.uid) ?? []}
73
88
  readOnly
74
89
  showCorrectAnswers={showCorrectAnswers}
75
90
  />
@@ -129,9 +144,42 @@ export function AssessmentReview({
129
144
  score,
130
145
  questionGroups,
131
146
  showCorrectAnswers = true,
147
+ isLoading,
148
+ error,
149
+ onRetry,
132
150
  className,
133
151
  style,
134
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
+
135
183
  return (
136
184
  <div className={cn(className)} style={style}>
137
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,14 +1,17 @@
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";
6
- import { Textarea } from "../../ui/textarea";
6
+ import { RichTextEditor } from "../../ui/rich-text-editor";
7
7
  import { Input } from "../../ui/input";
8
8
  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 {
@@ -99,11 +135,12 @@ export function AssignmentSubmission({
99
135
  </TabsList>
100
136
  {submissionTypes.includes("text") && (
101
137
  <TabsContent value="text">
102
- <Textarea
138
+ <RichTextEditor
103
139
  className="min-h-45"
104
140
  placeholder="Type your submission..."
105
141
  value={textContent}
106
- onChange={(e) => setTextContent(e.target.value)}
142
+ onChange={(html) => setTextContent(html)}
143
+ variant="default"
107
144
  />
108
145
  </TabsContent>
109
146
  )}
@@ -132,11 +169,12 @@ export function AssignmentSubmission({
132
169
  ) : (
133
170
  <>
134
171
  {submissionTypes.includes("text") && (
135
- <Textarea
172
+ <RichTextEditor
136
173
  className="min-h-45 mb-2"
137
174
  placeholder="Type your submission..."
138
175
  value={textContent}
139
- onChange={(e) => setTextContent(e.target.value)}
176
+ onChange={(html) => setTextContent(html)}
177
+ variant="default"
140
178
  />
141
179
  )}
142
180
  {submissionTypes.includes("file") && (
@@ -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 */