@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
@@ -1,8 +1,10 @@
1
1
  import { useState } from "react";
2
2
  import { CheckCircle } from "lucide-react";
3
3
  import { FlashcardDeck } from "../../flashcards";
4
+ import { Skeleton } from "../../ui/skeleton";
4
5
  import { Button } from "../../ui/button";
5
6
  import { Card, CardContent } from "../../ui/card";
7
+ import { SectionShell } from "../_shared/section-shell";
6
8
  import type {
7
9
  FlashcardStudySessionProps,
8
10
  FlashcardSessionStats,
@@ -16,6 +18,9 @@ export function FlashcardStudySession({
16
18
  shuffled = false,
17
19
  onComplete,
18
20
  readOnly = false,
21
+ isLoading,
22
+ error,
23
+ onRetry,
19
24
  className,
20
25
  style,
21
26
  }: FlashcardStudySessionProps) {
@@ -35,42 +40,54 @@ export function FlashcardStudySession({
35
40
  setIsComplete(false);
36
41
  }
37
42
 
38
- if (isComplete) {
39
- return (
40
- <div className={cn("flex flex-col items-center", className)} style={style}>
41
- <Card>
42
- <CardContent className="pt-6 text-center flex flex-col items-center gap-2">
43
- <CheckCircle size={48} className="text-success" />
44
- <span className="text-xl font-bold text-foreground">Deck complete!</span>
45
- {title && (
46
- <span className="text-muted-foreground">
47
- You finished <strong>{title}</strong>
48
- </span>
49
- )}
50
- <span className="text-sm text-muted-foreground">
51
- {stats.totalCards} card{stats.totalCards !== 1 ? "s" : ""} studied
52
- {stats.wasShuffled ? " (shuffled)" : ""}
53
- </span>
54
- <Button className="mt-2" onClick={handleStudyAgain}>
55
- Study Again
56
- </Button>
57
- </CardContent>
58
- </Card>
59
- </div>
60
- );
61
- }
62
-
63
43
  return (
64
- <div className={cn("flex flex-col items-center", className)} style={style}>
65
- <FlashcardDeck
66
- cards={cards}
67
- deckName={title}
68
- deckDescription={description}
69
- shuffled={shuffled}
70
- showProgress
71
- onComplete={handleComplete}
72
- readOnly={readOnly}
73
- />
74
- </div>
44
+ <SectionShell
45
+ isLoading={isLoading}
46
+ error={error}
47
+ onRetry={onRetry}
48
+ className={className}
49
+ style={style}
50
+ skeleton={
51
+ <>
52
+ <Skeleton className="h-6 w-48" />
53
+ <Skeleton className="h-64 w-full rounded-lg" />
54
+ </>
55
+ }
56
+ >
57
+ {isComplete ? (
58
+ <div className={cn("flex flex-col items-center", className)} style={style}>
59
+ <Card>
60
+ <CardContent className="pt-6 text-center flex flex-col items-center gap-2">
61
+ <CheckCircle size={48} className="text-success" />
62
+ <span className="text-xl font-bold text-foreground">Deck complete!</span>
63
+ {title && (
64
+ <span className="text-muted-foreground">
65
+ You finished <strong>{title}</strong>
66
+ </span>
67
+ )}
68
+ <span className="text-sm text-muted-foreground">
69
+ {stats.totalCards} card{stats.totalCards !== 1 ? "s" : ""} studied
70
+ {stats.wasShuffled ? " (shuffled)" : ""}
71
+ </span>
72
+ <Button className="mt-2" onClick={handleStudyAgain}>
73
+ Study Again
74
+ </Button>
75
+ </CardContent>
76
+ </Card>
77
+ </div>
78
+ ) : (
79
+ <div className={cn("flex flex-col items-center", className)} style={style}>
80
+ <FlashcardDeck
81
+ cards={cards}
82
+ deckName={title}
83
+ deckDescription={description}
84
+ shuffled={shuffled}
85
+ showProgress
86
+ onComplete={handleComplete}
87
+ readOnly={readOnly}
88
+ />
89
+ </div>
90
+ )}
91
+ </SectionShell>
75
92
  );
76
93
  }
@@ -28,6 +28,12 @@ export interface FlashcardStudySessionProps {
28
28
  onComplete?: (stats: FlashcardSessionStats) => void;
29
29
  /** When true, disables card flipping */
30
30
  readOnly?: boolean;
31
+ /** Render skeleton placeholders instead of content */
32
+ isLoading?: boolean;
33
+ /** Error message — renders an error state with optional retry */
34
+ error?: string | null;
35
+ /** Called when the user clicks retry in the error state */
36
+ onRetry?: () => void;
31
37
  /** CSS class name for the root element */
32
38
  className?: string;
33
39
  /** Inline styles for the root element */
@@ -1,5 +1,6 @@
1
- import { useState, useMemo } from "react";
1
+ import { useState, useMemo, memo, useCallback } from "react";
2
2
  import {
3
+ AlertCircle,
3
4
  Plus,
4
5
  MessageSquare,
5
6
  Heart,
@@ -7,6 +8,7 @@ import {
7
8
  CheckCircle2,
8
9
  ArrowUpDown,
9
10
  } from "lucide-react";
11
+ import { Skeleton } from "../../ui/skeleton";
10
12
  import { SearchInput } from "../../common/search-input";
11
13
  import { UserAvatar } from "../../social/user-avatar";
12
14
  import { EmptyState } from "../../common/empty-state";
@@ -19,6 +21,7 @@ import { RichTextEditor } from "../../ui/rich-text-editor";
19
21
  import { isEmptyHtml } from "../../utils/is-empty-html";
20
22
  import { cn } from "../../lib/utils";
21
23
  import { formatTimestamp } from "../../utils/format-timestamp";
24
+ import { Pagination } from "../../common/pagination";
22
25
  import type { ForumBoardProps, ForumTopic, ForumSortOrder } from "./types";
23
26
 
24
27
  const SORT_LABELS: Record<ForumSortOrder, string> = {
@@ -44,6 +47,13 @@ export function ForumBoard({
44
47
  searchQuery = "",
45
48
  onSearchChange,
46
49
  readOnly,
50
+ isLoading,
51
+ error,
52
+ onRetry,
53
+ pageSize,
54
+ currentPage = 1,
55
+ totalItems,
56
+ onPageChange,
47
57
  className,
48
58
  style,
49
59
  }: ForumBoardProps) {
@@ -117,6 +127,42 @@ export function ForumBoard({
117
127
  });
118
128
  }, [topics, activeSearch, activeSort]);
119
129
 
130
+ if (isLoading) {
131
+ return (
132
+ <div className={cn("space-y-4", className)} style={style}>
133
+ <Skeleton className="h-9 w-full" />
134
+ {Array.from({ length: 4 }).map((_, i) => (
135
+ <div key={i} className="flex items-center gap-3">
136
+ <Skeleton className="h-8 w-8 rounded-full" />
137
+ <div className="flex-1 space-y-2">
138
+ <Skeleton className="h-5 w-64" />
139
+ <Skeleton className="h-4 w-32" />
140
+ </div>
141
+ </div>
142
+ ))}
143
+ </div>
144
+ );
145
+ }
146
+
147
+ if (error) {
148
+ return (
149
+ <div className={cn("py-12", className)} style={style}>
150
+ <EmptyState
151
+ icon={<AlertCircle className="size-10 text-destructive" />}
152
+ title="Something went wrong"
153
+ description={error}
154
+ action={
155
+ onRetry ? (
156
+ <Button variant="outline" onClick={onRetry}>
157
+ Retry
158
+ </Button>
159
+ ) : undefined
160
+ }
161
+ />
162
+ </div>
163
+ );
164
+ }
165
+
120
166
  return (
121
167
  <div className={cn("flex flex-col gap-4", className)} style={style}>
122
168
  {/* Header */}
@@ -204,31 +250,45 @@ export function ForumBoard({
204
250
  />
205
251
  ) : (
206
252
  <div className="flex flex-col gap-2">
207
- {sorted.map((topic) => (
253
+ {(onPageChange && pageSize
254
+ ? sorted.slice((currentPage - 1) * pageSize, currentPage * pageSize)
255
+ : sorted
256
+ ).map((topic) => (
208
257
  <TopicRow
209
258
  key={topic.uid}
210
259
  topic={topic}
211
- onClick={() => onTopicClick(topic.uid)}
260
+ onClick={onTopicClick}
212
261
  />
213
262
  ))}
214
263
  </div>
215
264
  )}
265
+
266
+ {onPageChange && pageSize && sorted.length > 0 && (
267
+ <Pagination
268
+ currentPage={currentPage}
269
+ totalPages={Math.ceil((totalItems ?? sorted.length) / pageSize)}
270
+ onPageChange={onPageChange}
271
+ className="mt-4"
272
+ />
273
+ )}
216
274
  </div>
217
275
  );
218
276
  }
219
277
 
220
- function TopicRow({
278
+ const TopicRow = memo(function TopicRow({
221
279
  topic,
222
280
  onClick,
223
281
  }: {
224
282
  topic: ForumTopic;
225
- onClick: () => void;
283
+ onClick: (uid: string) => void;
226
284
  }) {
285
+ const handleClick = useCallback(() => onClick(topic.uid), [onClick, topic.uid]);
286
+
227
287
  return (
228
288
  <button
229
289
  type="button"
230
290
  className="w-full text-left"
231
- onClick={onClick}
291
+ onClick={handleClick}
232
292
  >
233
293
  <Card
234
294
  className={cn(
@@ -281,4 +341,4 @@ function TopicRow({
281
341
  </Card>
282
342
  </button>
283
343
  );
284
- }
344
+ });
@@ -37,6 +37,20 @@ export interface ForumBoardProps {
37
37
  onSearchChange?: (query: string) => void;
38
38
  /** When true, disables create and interactions */
39
39
  readOnly?: boolean;
40
+ /** Render skeleton placeholders instead of content */
41
+ isLoading?: boolean;
42
+ /** Error message — renders an error state with optional retry */
43
+ error?: string | null;
44
+ /** Called when the user clicks retry in the error state */
45
+ onRetry?: () => void;
46
+ /** Number of items per page (enables pagination when set with onPageChange) */
47
+ pageSize?: number;
48
+ /** Current page (1-indexed) */
49
+ currentPage?: number;
50
+ /** Total number of items (for server-side pagination) */
51
+ totalItems?: number;
52
+ /** Called when the user navigates to a different page */
53
+ onPageChange?: (page: number) => void;
40
54
  /** CSS class name for the root element */
41
55
  className?: string;
42
56
  /** Inline styles for the root element */
@@ -1,9 +1,13 @@
1
1
  import { Fragment, useMemo, useState } from "react";
2
- import { ArrowUp, ArrowDown } from "lucide-react";
2
+ import { ArrowUp, ArrowDown, AlertCircle } from "lucide-react";
3
3
  import { GradeIndicator } from "../../progress";
4
4
  import { StatusBadge, DueDateDisplay } from "../../common";
5
5
  import { Card, CardContent } from "../../ui/card";
6
6
  import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "../../ui/table";
7
+ import { Skeleton } from "../../ui/skeleton";
8
+ import { Button } from "../../ui/button";
9
+ import { EmptyState } from "../../common/empty-state";
10
+ import { Pagination } from "../../common/pagination";
7
11
  import type { GradebookTableProps, GradeCategory } from "./types";
8
12
  import { cn } from "../../lib/utils";
9
13
 
@@ -53,6 +57,13 @@ export function GradebookTable({
53
57
  showCategoryTotals = true,
54
58
  onItemClick,
55
59
  readOnly = false,
60
+ isLoading,
61
+ error,
62
+ onRetry,
63
+ pageSize,
64
+ currentPage = 1,
65
+ totalItems,
66
+ onPageChange,
56
67
  className,
57
68
  style,
58
69
  }: GradebookTableProps) {
@@ -104,6 +115,39 @@ export function GradebookTable({
104
115
 
105
116
  const colCount = showWeights ? 5 : 4;
106
117
 
118
+ if (isLoading) {
119
+ return (
120
+ <div className={cn("space-y-4", className)} style={style}>
121
+ <Skeleton className="h-20 w-full" />
122
+ <Skeleton className="h-10 w-full" />
123
+ <Skeleton className="h-12 w-full" />
124
+ <Skeleton className="h-12 w-full" />
125
+ <Skeleton className="h-12 w-full" />
126
+ <Skeleton className="h-12 w-full" />
127
+ <Skeleton className="h-12 w-full" />
128
+ </div>
129
+ );
130
+ }
131
+
132
+ if (error) {
133
+ return (
134
+ <div className={cn("py-12", className)} style={style}>
135
+ <EmptyState
136
+ icon={<AlertCircle className="size-10 text-destructive" />}
137
+ title="Something went wrong"
138
+ description={error}
139
+ action={
140
+ onRetry ? (
141
+ <Button variant="outline" onClick={onRetry}>
142
+ Retry
143
+ </Button>
144
+ ) : undefined
145
+ }
146
+ />
147
+ </div>
148
+ );
149
+ }
150
+
107
151
  return (
108
152
  <div className={className} style={style}>
109
153
  {/* Overall grade */}
@@ -224,6 +268,15 @@ export function GradebookTable({
224
268
  </TableBody>
225
269
  </Table>
226
270
  </Card>
271
+
272
+ {onPageChange && pageSize && (
273
+ <Pagination
274
+ currentPage={currentPage}
275
+ totalPages={Math.ceil((totalItems ?? items.length) / pageSize)}
276
+ onPageChange={onPageChange}
277
+ className="mt-4"
278
+ />
279
+ )}
227
280
  </div>
228
281
  );
229
282
  }
@@ -27,6 +27,20 @@ export interface GradebookTableProps {
27
27
  onItemClick?: (item: GradeItem) => void;
28
28
  /** When true, disables interactions */
29
29
  readOnly?: boolean;
30
+ /** Render skeleton placeholders instead of content */
31
+ isLoading?: boolean;
32
+ /** Error message — renders an error state with optional retry */
33
+ error?: string | null;
34
+ /** Called when the user clicks retry in the error state */
35
+ onRetry?: () => void;
36
+ /** Number of items per page (enables pagination when set with onPageChange) */
37
+ pageSize?: number;
38
+ /** Current page (1-indexed) */
39
+ currentPage?: number;
40
+ /** Total number of items (for server-side pagination) */
41
+ totalItems?: number;
42
+ /** Called when the user navigates to a different page */
43
+ onPageChange?: (page: number) => void;
30
44
  /** CSS class name for the root element */
31
45
  className?: string;
32
46
  /** Inline styles for the root element */
@@ -1,5 +1,8 @@
1
+ import { useCallback } from "react";
1
2
  import { VideoPlayer } from "../../video";
2
3
  import { Card, CardHeader, CardTitle, CardContent } from "../../ui/card";
4
+ import { Skeleton } from "../../ui/skeleton";
5
+ import { SectionShell } from "../_shared/section-shell";
3
6
  import type { LecturePlayerProps } from "./types";
4
7
  import { cn } from "../../lib/utils";
5
8
 
@@ -9,52 +12,75 @@ export function LecturePlayer({
9
12
  layout = "horizontal",
10
13
  notesPanelWidth = "340px",
11
14
  notesPanelHeight = "240px",
15
+ isLoading,
16
+ error,
17
+ onRetry,
18
+ onComplete,
12
19
  className,
13
20
  style,
14
21
  }: LecturePlayerProps) {
15
22
  const isHorizontal = layout === "horizontal";
16
23
 
17
- if (!notes) {
18
- return (
19
- <div className={className} style={style}>
20
- <VideoPlayer {...video} />
21
- </div>
22
- );
23
- }
24
+ const handleEnded = useCallback(() => {
25
+ video.onEnded?.();
26
+ onComplete?.();
27
+ }, [video, onComplete]);
24
28
 
25
29
  return (
26
- <div
27
- className={cn(
28
- "flex overflow-hidden gap-4",
29
- isHorizontal ? "flex-row" : "flex-col",
30
- className,
31
- )}
30
+ <SectionShell
31
+ isLoading={isLoading}
32
+ error={error}
33
+ onRetry={onRetry}
34
+ className={className}
32
35
  style={style}
36
+ skeleton={
37
+ <>
38
+ <Skeleton className="aspect-video w-full rounded-lg" />
39
+ {notes !== undefined && (
40
+ <Skeleton className="h-32 w-full rounded-lg" />
41
+ )}
42
+ </>
43
+ }
33
44
  >
34
- <div className="flex-1 min-w-0 min-h-0">
35
- <VideoPlayer {...video} />
36
- </div>
37
- <Card
38
- className={cn(
39
- "overflow-auto shrink-0 rounded-none border-0",
40
- isHorizontal ? "border-l border-border" : "border-t border-border w-full",
41
- )}
42
- style={{
43
- width: isHorizontal ? notesPanelWidth : undefined,
44
- height: isHorizontal ? undefined : notesPanelHeight,
45
- }}
46
- >
47
- <CardHeader>
48
- <CardTitle>Notes</CardTitle>
49
- </CardHeader>
50
- <CardContent>
51
- {typeof notes === "string" ? (
52
- <span className="text-sm text-foreground">{notes}</span>
53
- ) : (
54
- notes
45
+ {!notes ? (
46
+ <div className={className} style={style}>
47
+ <VideoPlayer {...video} onEnded={handleEnded} />
48
+ </div>
49
+ ) : (
50
+ <div
51
+ className={cn(
52
+ "flex overflow-hidden gap-4",
53
+ isHorizontal ? "flex-row" : "flex-col",
54
+ className,
55
55
  )}
56
- </CardContent>
57
- </Card>
58
- </div>
56
+ style={style}
57
+ >
58
+ <div className="flex-1 min-w-0 min-h-0">
59
+ <VideoPlayer {...video} onEnded={handleEnded} />
60
+ </div>
61
+ <Card
62
+ className={cn(
63
+ "overflow-auto shrink-0 rounded-none border-0",
64
+ isHorizontal ? "border-l border-border" : "border-t border-border w-full",
65
+ )}
66
+ style={{
67
+ width: isHorizontal ? notesPanelWidth : undefined,
68
+ height: isHorizontal ? undefined : notesPanelHeight,
69
+ }}
70
+ >
71
+ <CardHeader>
72
+ <CardTitle>Notes</CardTitle>
73
+ </CardHeader>
74
+ <CardContent>
75
+ {typeof notes === "string" ? (
76
+ <span className="text-sm text-foreground">{notes}</span>
77
+ ) : (
78
+ notes
79
+ )}
80
+ </CardContent>
81
+ </Card>
82
+ </div>
83
+ )}
84
+ </SectionShell>
59
85
  );
60
86
  }
@@ -41,6 +41,14 @@ export interface LecturePlayerProps {
41
41
  * @default "240px"
42
42
  */
43
43
  notesPanelHeight?: string | number;
44
+ /** Render skeleton placeholders instead of content */
45
+ isLoading?: boolean;
46
+ /** Error message — renders an error state with optional retry */
47
+ error?: string | null;
48
+ /** Called when the user clicks retry in the error state */
49
+ onRetry?: () => void;
50
+ /** Called when the video playback ends. */
51
+ onComplete?: () => void;
44
52
  /** CSS class name for the root element */
45
53
  className?: string;
46
54
  /** Inline styles for the root element */
@@ -1,12 +1,15 @@
1
1
  import { useState } from "react";
2
2
  import { Check, ChevronRight, Clock } from "lucide-react";
3
3
  import { ContentBlock } from "../../content";
4
+ import { Skeleton } from "../../ui/skeleton";
5
+ import { EmptyState } from "../../common/empty-state";
4
6
  import { Button } from "../../ui/button";
5
7
  import { Card, CardContent } from "../../ui/card";
6
8
  import { Separator } from "../../ui/separator";
9
+ import { SectionShell } from "../_shared/section-shell";
7
10
  import { formatDuration } from "../../utils/format-duration";
8
11
  import type { LessonPageProps } from "./types";
9
- import { cn } from "../../lib/utils";
12
+
10
13
 
11
14
  export function LessonPage({
12
15
  title,
@@ -18,18 +21,37 @@ export function LessonPage({
18
21
  estimatedDuration,
19
22
  showDuration = true,
20
23
  readOnly = false,
24
+ isLoading,
25
+ error,
26
+ onRetry,
21
27
  className,
22
28
  style,
23
29
  }: LessonPageProps) {
24
- const [completed, setCompleted] = useState(isCompleted);
30
+ const [localCompleted, setLocalCompleted] = useState(false);
31
+ const completed = isCompleted || localCompleted;
25
32
 
26
33
  function handleMarkComplete() {
27
- setCompleted(true);
34
+ setLocalCompleted(true);
28
35
  onMarkComplete?.();
29
36
  }
30
37
 
31
38
  return (
32
- <div className={cn(className)} style={style}>
39
+ <SectionShell
40
+ isLoading={isLoading}
41
+ error={error}
42
+ onRetry={onRetry}
43
+ className={className}
44
+ style={style}
45
+ skeleton={
46
+ <>
47
+ <Skeleton className="h-8 w-64" />
48
+ <Skeleton className="h-24 w-full" />
49
+ <Skeleton className="h-16 w-full" />
50
+ <Skeleton className="h-32 w-full" />
51
+ </>
52
+ }
53
+ >
54
+ <div>
33
55
  {/* Header */}
34
56
  <div className="mb-3">
35
57
  <p className="text-2xl font-bold mb-0.5 text-foreground">{title}</p>
@@ -45,13 +67,18 @@ export function LessonPage({
45
67
 
46
68
  {/* Content blocks */}
47
69
  <div className="flex flex-col gap-3">
48
- {blocks.map((block, i) => (
70
+ {blocks?.length ? blocks.map((block, i) => (
49
71
  <ContentBlock
50
72
  key={i}
51
73
  block={block}
52
74
  readOnly={readOnly}
53
75
  />
54
- ))}
76
+ )) : (
77
+ <EmptyState
78
+ title="No content yet"
79
+ description="This lesson doesn't have any content blocks."
80
+ />
81
+ )}
55
82
  </div>
56
83
 
57
84
  {/* Completion bar */}
@@ -83,5 +110,6 @@ export function LessonPage({
83
110
  </CardContent>
84
111
  </Card>
85
112
  </div>
113
+ </SectionShell>
86
114
  );
87
115
  }
@@ -34,6 +34,12 @@ export interface LessonPageProps {
34
34
  showDuration?: boolean;
35
35
  /** When true, disables interactive elements */
36
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;
37
43
  /** CSS class name for the root element */
38
44
  className?: string;
39
45
  /** Inline styles for the root element */