@hydralms/components 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/dist/StudentProfile-BPsZBaJj.cjs +1 -0
  2. package/dist/StudentProfile-Cw2p-RZn.js +3273 -0
  3. package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
  4. package/dist/assessment-toolbar/timer-display.d.ts +1 -1
  5. package/dist/common/index.d.ts +2 -1
  6. package/dist/common/pagination.d.ts +26 -0
  7. package/dist/common/types.d.ts +1 -0
  8. package/dist/components.css +1 -1
  9. package/dist/content/audio-player.d.ts +22 -0
  10. package/dist/content/code-block.d.ts +30 -0
  11. package/dist/content/embed-block.d.ts +28 -0
  12. package/dist/content/index.d.ts +6 -0
  13. package/dist/content/types.d.ts +24 -0
  14. package/dist/curriculum/course-card.d.ts +51 -0
  15. package/dist/curriculum/index.d.ts +2 -0
  16. package/dist/curriculum/types.d.ts +2 -2
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.js +495 -439
  20. package/dist/license/HydraContext.d.ts +16 -0
  21. package/dist/license/ProBadge.d.ts +6 -0
  22. package/dist/license/index.d.ts +7 -0
  23. package/dist/license/tiers.d.ts +6 -0
  24. package/dist/license/useHydraLicense.d.ts +6 -0
  25. package/dist/license/validate.d.ts +13 -0
  26. package/dist/license/withProGate.d.ts +6 -0
  27. package/dist/modules/AssignmentModule/AssignmentModule.d.ts +4 -7
  28. package/dist/modules/AssignmentModule/types.d.ts +5 -1
  29. package/dist/modules/CertificateModule/CertificateModule.d.ts +4 -8
  30. package/dist/modules/CertificateModule/types.d.ts +6 -4
  31. package/dist/modules/CourseCatalogModule/CourseCatalogModule.d.ts +5 -0
  32. package/dist/modules/CourseCatalogModule/types.d.ts +43 -0
  33. package/dist/modules/CoursePlayer/CoursePlayer.d.ts +4 -1
  34. package/dist/modules/DiscussionModule/DiscussionModule.d.ts +4 -7
  35. package/dist/modules/ExamModule/ExamModule.d.ts +4 -7
  36. package/dist/modules/ExamModule/types.d.ts +5 -14
  37. package/dist/modules/FlashcardLab/FlashcardLab.d.ts +4 -1
  38. package/dist/modules/FlashcardLab/types.d.ts +2 -0
  39. package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +4 -8
  40. package/dist/modules/GradeCenterModule/types.d.ts +2 -0
  41. package/dist/modules/QuizModule/QuizModule.d.ts +4 -1
  42. package/dist/modules/QuizModule/types.d.ts +5 -14
  43. package/dist/modules/StudentDashboardModule/StudentDashboardModule.d.ts +5 -0
  44. package/dist/modules/StudentDashboardModule/types.d.ts +54 -0
  45. package/dist/modules/StudentProfileModule/StudentProfileModule.d.ts +5 -0
  46. package/dist/modules/StudentProfileModule/types.d.ts +43 -0
  47. package/dist/modules/SurveyModule/SurveyModule.d.ts +4 -6
  48. package/dist/modules/SurveyModule/types.d.ts +2 -0
  49. package/dist/modules/_shared/assessment-intro.d.ts +16 -0
  50. package/dist/modules/_shared/assessment-results.d.ts +23 -0
  51. package/dist/modules/_shared/types.d.ts +10 -0
  52. package/dist/modules/_shared/use-timer.d.ts +9 -0
  53. package/dist/modules/index.d.ts +6 -0
  54. package/dist/modules.cjs +1 -1
  55. package/dist/modules.js +1267 -854
  56. package/dist/progress/types.d.ts +2 -0
  57. package/dist/provider/HydraProvider.d.ts +5 -1
  58. package/dist/questions/choice.d.ts +1 -1
  59. package/dist/questions/confidence-indicator.d.ts +37 -0
  60. package/dist/questions/essay.d.ts +1 -1
  61. package/dist/questions/fill-in-the-blank.d.ts +1 -1
  62. package/dist/questions/hotspot.d.ts +1 -1
  63. package/dist/questions/index.d.ts +2 -0
  64. package/dist/questions/inline-choice.d.ts +1 -1
  65. package/dist/questions/matching.d.ts +1 -1
  66. package/dist/questions/multiple-choice.d.ts +1 -1
  67. package/dist/questions/numeric.d.ts +1 -1
  68. package/dist/questions/ordering.d.ts +1 -1
  69. package/dist/questions/question-renderer.d.ts +1 -1
  70. package/dist/questions/scenario.d.ts +1 -1
  71. package/dist/questions/spreadsheet.d.ts +1 -1
  72. package/dist/questions/true-false.d.ts +1 -1
  73. package/dist/sections/AdaptiveLearningPath/AdaptiveLearningPath.d.ts +5 -0
  74. package/dist/sections/AdaptiveLearningPath/path-connector.d.ts +8 -0
  75. package/dist/sections/AdaptiveLearningPath/path-milestone-marker.d.ts +7 -0
  76. package/dist/sections/AdaptiveLearningPath/path-node-card.d.ts +10 -0
  77. package/dist/sections/AdaptiveLearningPath/path-skill-bar.d.ts +8 -0
  78. package/dist/sections/AdaptiveLearningPath/types.d.ts +136 -0
  79. package/dist/sections/AnnouncementFeed/AnnouncementFeed.d.ts +1 -1
  80. package/dist/sections/AnnouncementFeed/types.d.ts +15 -1
  81. package/dist/sections/AssessmentReview/AssessmentReview.d.ts +1 -1
  82. package/dist/sections/AssessmentReview/types.d.ts +6 -0
  83. package/dist/sections/AssignmentSubmission/AssignmentSubmission.d.ts +1 -1
  84. package/dist/sections/AssignmentSubmission/types.d.ts +6 -0
  85. package/dist/sections/CertificateViewer/CertificateViewer.d.ts +1 -1
  86. package/dist/sections/CertificateViewer/certificate-variants.d.ts +42 -0
  87. package/dist/sections/CertificateViewer/types.d.ts +6 -0
  88. package/dist/sections/ContentAuthoringStudio/ContentAuthoringStudio.d.ts +5 -0
  89. package/dist/sections/ContentAuthoringStudio/block-editor-item.d.ts +14 -0
  90. package/dist/sections/ContentAuthoringStudio/block-type-picker.d.ts +12 -0
  91. package/dist/sections/ContentAuthoringStudio/types.d.ts +67 -0
  92. package/dist/sections/CourseCatalog/CourseCatalog.d.ts +2 -0
  93. package/dist/sections/CourseCatalog/types.d.ts +80 -0
  94. package/dist/sections/CourseOutline/CourseOutline.d.ts +1 -1
  95. package/dist/sections/CourseOutline/types.d.ts +6 -0
  96. package/dist/sections/DiscussionThread/DiscussionThread.d.ts +1 -1
  97. package/dist/sections/DiscussionThread/types.d.ts +6 -0
  98. package/dist/sections/EnrollmentWizard/EnrollmentWizard.d.ts +2 -0
  99. package/dist/sections/EnrollmentWizard/types.d.ts +66 -0
  100. package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
  101. package/dist/sections/ExamSession/types.d.ts +6 -0
  102. package/dist/sections/FlashcardStudySession/FlashcardStudySession.d.ts +1 -1
  103. package/dist/sections/FlashcardStudySession/types.d.ts +6 -0
  104. package/dist/sections/ForumBoard/ForumBoard.d.ts +1 -1
  105. package/dist/sections/ForumBoard/types.d.ts +14 -0
  106. package/dist/sections/GradebookTable/GradebookTable.d.ts +1 -1
  107. package/dist/sections/GradebookTable/types.d.ts +14 -0
  108. package/dist/sections/LecturePlayer/LecturePlayer.d.ts +1 -1
  109. package/dist/sections/LecturePlayer/types.d.ts +8 -0
  110. package/dist/sections/LessonPage/LessonPage.d.ts +1 -1
  111. package/dist/sections/LessonPage/types.d.ts +6 -0
  112. package/dist/sections/PracticeQuiz/PracticeQuiz.d.ts +1 -1
  113. package/dist/sections/PracticeQuiz/types.d.ts +6 -0
  114. package/dist/sections/ProgressDashboard/ProgressDashboard.d.ts +1 -1
  115. package/dist/sections/ProgressDashboard/types.d.ts +6 -0
  116. package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
  117. package/dist/sections/QuizSession/types.d.ts +6 -0
  118. package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +1 -1
  119. package/dist/sections/RequirementsChecklist/types.d.ts +6 -0
  120. package/dist/sections/ResourceLibrary/ResourceLibrary.d.ts +1 -1
  121. package/dist/sections/ResourceLibrary/types.d.ts +15 -1
  122. package/dist/sections/RubricView/RubricView.d.ts +1 -1
  123. package/dist/sections/RubricView/types.d.ts +6 -0
  124. package/dist/sections/ScrollableQuiz/ScrollableQuiz.d.ts +1 -1
  125. package/dist/sections/ScrollableQuiz/types.d.ts +6 -0
  126. package/dist/sections/StudentProfile/StudentProfile.d.ts +2 -0
  127. package/dist/sections/StudentProfile/types.d.ts +98 -0
  128. package/dist/sections/SurveyForm/SurveyForm.d.ts +1 -1
  129. package/dist/sections/SurveyForm/types.d.ts +6 -0
  130. package/dist/sections/_shared/merge-answers.d.ts +9 -0
  131. package/dist/sections/_shared/section-shell.d.ts +20 -0
  132. package/dist/sections/_shared/use-assessment-session.d.ts +30 -0
  133. package/dist/sections/index.d.ts +10 -0
  134. package/dist/sections.cjs +1 -1
  135. package/dist/sections.js +1361 -307
  136. package/dist/ui/badge.d.ts +1 -1
  137. package/dist/ui/index.d.ts +2 -0
  138. package/dist/ui/progress.d.ts +1 -1
  139. package/dist/ui/rich-text-editor.d.ts +3 -1
  140. package/dist/ui/toast.d.ts +43 -0
  141. package/dist/utils/debounce.d.ts +5 -1
  142. package/dist/utils/pick-palette-color.d.ts +19 -0
  143. package/dist/video/types.d.ts +15 -0
  144. package/dist/video/video-player.d.ts +1 -1
  145. package/dist/withProGate-BJdu1T9Y.cjs +2 -0
  146. package/dist/withProGate-BvFc7Jwy.js +4975 -0
  147. package/package.json +57 -226
  148. package/src/assessment-toolbar/question-navigator.tsx +10 -5
  149. package/src/assessment-toolbar/timer-display.tsx +4 -3
  150. package/src/assessment-toolbar/use-countdown.ts +1 -1
  151. package/src/common/empty-state.tsx +1 -0
  152. package/src/common/index.ts +2 -0
  153. package/src/common/pagination.tsx +135 -0
  154. package/src/common/search-input.tsx +2 -1
  155. package/src/common/types.ts +2 -0
  156. package/src/content/attachment-list.tsx +2 -0
  157. package/src/content/audio-player.tsx +196 -0
  158. package/src/content/code-block.tsx +113 -0
  159. package/src/content/content-block.tsx +64 -0
  160. package/src/content/embed-block.tsx +78 -0
  161. package/src/content/file-upload-zone.tsx +10 -0
  162. package/src/content/index.ts +6 -0
  163. package/src/content/types.ts +5 -0
  164. package/src/curriculum/course-card.tsx +199 -0
  165. package/src/curriculum/curriculum-item.tsx +3 -3
  166. package/src/curriculum/curriculum-tree.tsx +20 -13
  167. package/src/curriculum/index.ts +2 -0
  168. package/src/curriculum/types.ts +2 -2
  169. package/src/flashcards/flashcard.tsx +28 -8
  170. package/src/index.ts +3 -0
  171. package/src/license/HydraContext.tsx +62 -0
  172. package/src/license/ProBadge.tsx +43 -0
  173. package/src/license/index.ts +7 -0
  174. package/src/license/tiers.ts +34 -0
  175. package/src/license/useHydraLicense.ts +10 -0
  176. package/src/license/validate.ts +90 -0
  177. package/src/license/withProGate.tsx +21 -0
  178. package/src/modules/AssignmentModule/AssignmentModule.tsx +17 -8
  179. package/src/modules/AssignmentModule/types.ts +5 -1
  180. package/src/modules/CertificateModule/CertificateModule.tsx +21 -9
  181. package/src/modules/CertificateModule/types.ts +6 -4
  182. package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
  183. package/src/modules/CourseCatalogModule/types.ts +47 -0
  184. package/src/modules/CoursePlayer/CoursePlayer.tsx +39 -22
  185. package/src/modules/DiscussionModule/DiscussionModule.tsx +57 -22
  186. package/src/modules/ExamModule/ExamModule.tsx +64 -198
  187. package/src/modules/ExamModule/types.ts +5 -14
  188. package/src/modules/FlashcardLab/FlashcardLab.tsx +10 -5
  189. package/src/modules/FlashcardLab/types.ts +2 -0
  190. package/src/modules/GradeCenterModule/GradeCenterModule.tsx +7 -2
  191. package/src/modules/GradeCenterModule/types.ts +2 -0
  192. package/src/modules/QuizModule/QuizModule.tsx +49 -169
  193. package/src/modules/QuizModule/types.ts +5 -15
  194. package/src/modules/StudentDashboardModule/StudentDashboardModule.tsx +117 -0
  195. package/src/modules/StudentDashboardModule/types.ts +56 -0
  196. package/src/modules/StudentProfileModule/StudentProfileModule.tsx +289 -0
  197. package/src/modules/StudentProfileModule/types.ts +45 -0
  198. package/src/modules/SurveyModule/SurveyModule.tsx +9 -4
  199. package/src/modules/SurveyModule/types.ts +2 -0
  200. package/src/modules/_shared/assessment-intro.tsx +75 -0
  201. package/src/modules/_shared/assessment-results.tsx +133 -0
  202. package/src/modules/_shared/types.ts +11 -0
  203. package/src/modules/_shared/use-timer.ts +49 -0
  204. package/src/modules/index.ts +9 -0
  205. package/src/progress/achievement-badge.tsx +3 -3
  206. package/src/progress/grade-indicator.tsx +9 -1
  207. package/src/progress/progress-ring.tsx +2 -1
  208. package/src/progress/stat-card.tsx +14 -2
  209. package/src/progress/types.ts +2 -0
  210. package/src/provider/HydraProvider.tsx +15 -6
  211. package/src/questions/choice.tsx +13 -6
  212. package/src/questions/confidence-indicator.tsx +107 -0
  213. package/src/questions/essay.tsx +6 -4
  214. package/src/questions/fill-in-the-blank.tsx +8 -4
  215. package/src/questions/hotspot.tsx +4 -4
  216. package/src/questions/index.ts +2 -0
  217. package/src/questions/inline-choice.tsx +5 -4
  218. package/src/questions/matching.tsx +5 -4
  219. package/src/questions/multiple-choice.tsx +13 -6
  220. package/src/questions/numeric.tsx +8 -4
  221. package/src/questions/ordering.tsx +12 -4
  222. package/src/questions/question-renderer.tsx +3 -2
  223. package/src/questions/scenario.tsx +4 -4
  224. package/src/questions/spreadsheet.tsx +5 -4
  225. package/src/questions/true-false.tsx +13 -6
  226. package/src/sections/AdaptiveLearningPath/AdaptiveLearningPath.tsx +251 -0
  227. package/src/sections/AdaptiveLearningPath/path-connector.tsx +27 -0
  228. package/src/sections/AdaptiveLearningPath/path-milestone-marker.tsx +50 -0
  229. package/src/sections/AdaptiveLearningPath/path-node-card.tsx +166 -0
  230. package/src/sections/AdaptiveLearningPath/path-skill-bar.tsx +49 -0
  231. package/src/sections/AdaptiveLearningPath/types.ts +159 -0
  232. package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +64 -8
  233. package/src/sections/AnnouncementFeed/types.ts +15 -1
  234. package/src/sections/AssessmentReview/AssessmentReview.tsx +37 -0
  235. package/src/sections/AssessmentReview/types.ts +6 -0
  236. package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +37 -1
  237. package/src/sections/AssignmentSubmission/types.ts +6 -0
  238. package/src/sections/CertificateViewer/CertificateViewer.tsx +29 -227
  239. package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
  240. package/src/sections/CertificateViewer/types.ts +6 -0
  241. package/src/sections/ContentAuthoringStudio/ContentAuthoringStudio.tsx +289 -0
  242. package/src/sections/ContentAuthoringStudio/block-editor-item.tsx +487 -0
  243. package/src/sections/ContentAuthoringStudio/block-type-picker.tsx +123 -0
  244. package/src/sections/ContentAuthoringStudio/types.ts +67 -0
  245. package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
  246. package/src/sections/CourseCatalog/types.ts +76 -0
  247. package/src/sections/CourseOutline/CourseOutline.tsx +41 -0
  248. package/src/sections/CourseOutline/types.ts +6 -0
  249. package/src/sections/DiscussionThread/DiscussionThread.tsx +42 -1
  250. package/src/sections/DiscussionThread/types.ts +6 -0
  251. package/src/sections/EnrollmentWizard/EnrollmentWizard.tsx +343 -0
  252. package/src/sections/EnrollmentWizard/types.ts +65 -0
  253. package/src/sections/ExamSession/ExamSession.tsx +100 -94
  254. package/src/sections/ExamSession/types.ts +6 -0
  255. package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
  256. package/src/sections/FlashcardStudySession/types.ts +6 -0
  257. package/src/sections/ForumBoard/ForumBoard.tsx +67 -7
  258. package/src/sections/ForumBoard/types.ts +14 -0
  259. package/src/sections/GradebookTable/GradebookTable.tsx +54 -1
  260. package/src/sections/GradebookTable/types.ts +14 -0
  261. package/src/sections/LecturePlayer/LecturePlayer.tsx +63 -37
  262. package/src/sections/LecturePlayer/types.ts +8 -0
  263. package/src/sections/LessonPage/LessonPage.tsx +34 -6
  264. package/src/sections/LessonPage/types.ts +6 -0
  265. package/src/sections/PracticeQuiz/PracticeQuiz.tsx +106 -74
  266. package/src/sections/PracticeQuiz/types.ts +6 -0
  267. package/src/sections/ProgressDashboard/ProgressDashboard.tsx +64 -10
  268. package/src/sections/ProgressDashboard/types.ts +6 -0
  269. package/src/sections/QuizSession/QuizSession.tsx +71 -82
  270. package/src/sections/QuizSession/types.ts +6 -0
  271. package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +41 -1
  272. package/src/sections/RequirementsChecklist/types.ts +6 -0
  273. package/src/sections/ResourceLibrary/ResourceLibrary.tsx +64 -8
  274. package/src/sections/ResourceLibrary/types.ts +15 -1
  275. package/src/sections/RubricView/RubricView.tsx +37 -1
  276. package/src/sections/RubricView/types.ts +6 -0
  277. package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +36 -15
  278. package/src/sections/ScrollableQuiz/types.ts +6 -0
  279. package/src/sections/StudentProfile/StudentProfile.tsx +279 -0
  280. package/src/sections/StudentProfile/types.ts +99 -0
  281. package/src/sections/SurveyForm/SurveyForm.tsx +32 -5
  282. package/src/sections/SurveyForm/types.ts +6 -0
  283. package/src/sections/_shared/merge-answers.ts +22 -0
  284. package/src/sections/_shared/section-shell.tsx +64 -0
  285. package/src/sections/_shared/use-assessment-session.ts +125 -0
  286. package/src/sections/index.ts +40 -0
  287. package/src/social/user-avatar.tsx +9 -5
  288. package/src/styles/globals.css +39 -41
  289. package/src/ui/badge.tsx +8 -0
  290. package/src/ui/index.ts +2 -0
  291. package/src/ui/progress.tsx +4 -0
  292. package/src/ui/rich-text-editor.tsx +10 -0
  293. package/src/ui/rich-text-toolbar.tsx +2 -1
  294. package/src/ui/toast.tsx +170 -0
  295. package/src/utils/debounce.ts +8 -2
  296. package/src/utils/pick-palette-color.ts +33 -0
  297. package/src/video/types.ts +16 -0
  298. package/src/video/video-player.tsx +27 -6
  299. package/dist/ForumBoard-CHXU3mjC.js +0 -2207
  300. package/dist/ForumBoard-d1w5-r6n.cjs +0 -1
  301. package/dist/tabs-DRM2Iq_J.cjs +0 -172
  302. package/dist/tabs-Wf3h_Cx3.js +0 -21580
@@ -0,0 +1,170 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useRef,
7
+ useState,
8
+ type ReactNode,
9
+ } from "react";
10
+ import { createPortal } from "react-dom";
11
+ import { cva } from "class-variance-authority";
12
+ import { X, CheckCircle2, AlertCircle, AlertTriangle, Info } from "lucide-react";
13
+ import { cn } from "../lib/utils";
14
+
15
+ const toastVariants = cva(
16
+ "pointer-events-auto relative flex w-full items-start gap-3 rounded-lg border p-4 pr-8 shadow-lg text-sm transition-all",
17
+ {
18
+ variants: {
19
+ variant: {
20
+ success:
21
+ "border-success/50 bg-success/5 text-success [&>svg]:text-success",
22
+ error:
23
+ "border-destructive/50 bg-destructive/5 text-destructive [&>svg]:text-destructive",
24
+ warning:
25
+ "border-warning/50 bg-warning/5 text-warning [&>svg]:text-warning",
26
+ info: "border-info/50 bg-info/5 text-info [&>svg]:text-info",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "info",
31
+ },
32
+ },
33
+ );
34
+
35
+ type ToastVariant = "success" | "error" | "warning" | "info";
36
+
37
+ /** Options for creating a toast notification. */
38
+ interface ToastOptions {
39
+ /** Toast message */
40
+ message: string;
41
+ /** Optional title */
42
+ title?: string;
43
+ /** Visual variant */
44
+ variant?: ToastVariant;
45
+ /** Auto-dismiss duration in ms (default 5000, 0 = no auto-dismiss) */
46
+ duration?: number;
47
+ }
48
+
49
+ interface ToastItem extends ToastOptions {
50
+ id: string;
51
+ }
52
+
53
+ interface ToastContextValue {
54
+ toast: (options: ToastOptions) => void;
55
+ }
56
+
57
+ const ToastContext = createContext<ToastContextValue | null>(null);
58
+
59
+ const VARIANT_ICONS: Record<ToastVariant, React.ElementType> = {
60
+ success: CheckCircle2,
61
+ error: AlertCircle,
62
+ warning: AlertTriangle,
63
+ info: Info,
64
+ };
65
+
66
+ function ToastCard({
67
+ item,
68
+ onDismiss,
69
+ }: {
70
+ item: ToastItem;
71
+ onDismiss: (id: string) => void;
72
+ }) {
73
+ const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
74
+ const duration = item.duration ?? 5000;
75
+ const Icon = VARIANT_ICONS[item.variant ?? "info"];
76
+
77
+ useEffect(() => {
78
+ if (duration > 0) {
79
+ timerRef.current = setTimeout(() => onDismiss(item.id), duration);
80
+ }
81
+ return () => clearTimeout(timerRef.current);
82
+ }, [duration, item.id, onDismiss]);
83
+
84
+ return (
85
+ <div
86
+ data-slot="toast"
87
+ role="status"
88
+ aria-live="polite"
89
+ className={cn(toastVariants({ variant: item.variant ?? "info" }))}
90
+ >
91
+ <Icon className="size-4 shrink-0 translate-y-0.5" />
92
+ <div className="flex-1 space-y-1">
93
+ {item.title && (
94
+ <p className="font-medium leading-none">{item.title}</p>
95
+ )}
96
+ <p className="text-sm opacity-90">{item.message}</p>
97
+ </div>
98
+ <button
99
+ type="button"
100
+ onClick={() => onDismiss(item.id)}
101
+ aria-label="Dismiss notification"
102
+ className="absolute right-2 top-2 rounded-md p-1 opacity-70 hover:opacity-100 transition-opacity"
103
+ >
104
+ <X className="size-3.5" />
105
+ </button>
106
+ </div>
107
+ );
108
+ }
109
+
110
+ const MAX_VISIBLE = 3;
111
+ let idCounter = 0;
112
+
113
+ /**
114
+ * ToastProvider wraps your app to enable toast notifications.
115
+ * Renders a fixed-position toast container via portal.
116
+ *
117
+ * @example
118
+ * <ToastProvider>
119
+ * <App />
120
+ * </ToastProvider>
121
+ */
122
+ function ToastProvider({ children }: { children: ReactNode }) {
123
+ const [toasts, setToasts] = useState<ToastItem[]>([]);
124
+
125
+ const toast = useCallback((options: ToastOptions) => {
126
+ const id = `toast-${++idCounter}`;
127
+ setToasts((prev) => [...prev.slice(-(MAX_VISIBLE - 1)), { ...options, id }]);
128
+ }, []);
129
+
130
+ const dismiss = useCallback((id: string) => {
131
+ setToasts((prev) => prev.filter((t) => t.id !== id));
132
+ }, []);
133
+
134
+ return (
135
+ <ToastContext.Provider value={{ toast }}>
136
+ {children}
137
+ {typeof document !== "undefined" &&
138
+ createPortal(
139
+ <div
140
+ data-slot="toaster"
141
+ className="fixed top-4 right-4 z-50 flex flex-col gap-2 w-80 pointer-events-none"
142
+ >
143
+ {toasts.map((item) => (
144
+ <ToastCard key={item.id} item={item} onDismiss={dismiss} />
145
+ ))}
146
+ </div>,
147
+ document.body,
148
+ )}
149
+ </ToastContext.Provider>
150
+ );
151
+ }
152
+
153
+ /**
154
+ * Hook to trigger toast notifications. Must be used within a ToastProvider.
155
+ *
156
+ * @example
157
+ * const { toast } = useToast();
158
+ * toast({ message: "Saved!", variant: "success" });
159
+ */
160
+ function useToast(): ToastContextValue {
161
+ const ctx = useContext(ToastContext);
162
+ if (!ctx) throw new Error("useToast must be used within a ToastProvider");
163
+ return ctx;
164
+ }
165
+
166
+ /** Convenience alias for ToastProvider. */
167
+ const Toaster = ToastProvider;
168
+
169
+ export { ToastProvider, Toaster, useToast, toastVariants };
170
+ export type { ToastOptions, ToastVariant };
@@ -1,10 +1,16 @@
1
+ type Cancellable<T extends (...args: Parameters<T>) => void> = ((
2
+ ...args: Parameters<T>
3
+ ) => void) & { cancel: () => void };
4
+
1
5
  export function debounce<T extends (...args: Parameters<T>) => void>(
2
6
  fn: T,
3
7
  delay: number,
4
- ): (...args: Parameters<T>) => void {
8
+ ): Cancellable<T> {
5
9
  let timer: ReturnType<typeof setTimeout>;
6
- return (...args: Parameters<T>) => {
10
+ const debounced = (...args: Parameters<T>) => {
7
11
  clearTimeout(timer);
8
12
  timer = setTimeout(() => fn(...args), delay);
9
13
  };
14
+ debounced.cancel = () => clearTimeout(timer);
15
+ return debounced;
10
16
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Palette CSS variable names matching --palette-0 through --palette-3
3
+ * defined in globals.css.
4
+ */
5
+ export const PALETTE_VARS = [
6
+ "var(--palette-0)",
7
+ "var(--palette-1)",
8
+ "var(--palette-2)",
9
+ "var(--palette-3)",
10
+ ] as const;
11
+
12
+ /** Number of palette colors available. */
13
+ export const PALETTE_COUNT = PALETTE_VARS.length;
14
+
15
+ /**
16
+ * Pick a palette CSS variable by cycling through the 4 palette colors.
17
+ * Useful for assigning varied colors to list items by index.
18
+ */
19
+ export function pickPaletteColor(index: number): string {
20
+ return PALETTE_VARS[((index % PALETTE_COUNT) + PALETTE_COUNT) % PALETTE_COUNT];
21
+ }
22
+
23
+ /** Palette variant name for CVA-based components (badge, progress). */
24
+ export type PaletteVariant = "palette0" | "palette1" | "palette2" | "palette3";
25
+
26
+ /**
27
+ * Pick a palette variant name by cycling through the 4 palette variants.
28
+ * Useful for assigning varied CVA variants to list items by index.
29
+ */
30
+ export function pickPaletteVariant(index: number): PaletteVariant {
31
+ const i = ((index % PALETTE_COUNT) + PALETTE_COUNT) % PALETTE_COUNT;
32
+ return `palette${i}` as PaletteVariant;
33
+ }
@@ -9,6 +9,20 @@
9
9
  * onEnded={() => markLessonComplete()}
10
10
  * />
11
11
  */
12
+ /** A text track (captions, subtitles) for the video element. */
13
+ export interface VideoTrack {
14
+ /** URL of the track file (e.g., .vtt) */
15
+ src: string;
16
+ /** Track kind */
17
+ kind: "captions" | "subtitles" | "descriptions" | "chapters" | "metadata";
18
+ /** Human-readable track label (e.g., "English") */
19
+ label: string;
20
+ /** BCP 47 language code (e.g., "en") */
21
+ srcLang: string;
22
+ /** Whether this track should be active by default */
23
+ default?: boolean;
24
+ }
25
+
12
26
  export interface VideoPlayerProps {
13
27
  /** URL of the video source */
14
28
  src?: string;
@@ -30,6 +44,8 @@ export interface VideoPlayerProps {
30
44
  readOnly?: boolean;
31
45
  /** Aspect ratio of the video container */
32
46
  aspectRatio?: "16/9" | "4/3" | "1/1";
47
+ /** Array of text tracks (captions, subtitles) for the video */
48
+ tracks?: VideoTrack[];
33
49
  /** CSS class name for the root element */
34
50
  className?: string;
35
51
  /** Inline styles for the root element */
@@ -1,4 +1,4 @@
1
- import { useRef, useCallback } from "react";
1
+ import { useRef, useCallback, useMemo } from "react";
2
2
  import { Video, Play } from "lucide-react";
3
3
  import type { VideoPlayerProps } from "./types";
4
4
  import { cn } from "../lib/utils";
@@ -14,12 +14,22 @@ export const VideoPlayer = ({
14
14
  onTimeUpdate,
15
15
  readOnly = false,
16
16
  aspectRatio = "16/9",
17
+ tracks,
17
18
  className,
18
19
  style,
19
20
  }: VideoPlayerProps) => {
20
21
  const videoRef = useRef<HTMLVideoElement>(null);
21
22
  const lastReportedTime = useRef(-1);
22
23
 
24
+ const containerStyle = useMemo(
25
+ () => ({ aspectRatio, ...style }),
26
+ [aspectRatio, style],
27
+ );
28
+ const videoStyle = useMemo(
29
+ () => ({ width: "100%" as const, aspectRatio, display: "block" as const }),
30
+ [aspectRatio],
31
+ );
32
+
23
33
  const handleTimeUpdate = useCallback(() => {
24
34
  const video = videoRef.current;
25
35
  if (video && onTimeUpdate) {
@@ -35,7 +45,7 @@ export const VideoPlayer = ({
35
45
  return (
36
46
  <div
37
47
  className={cn("relative overflow-hidden rounded-lg border border-border bg-muted", className)}
38
- style={{ aspectRatio, ...style }}
48
+ style={containerStyle}
39
49
  >
40
50
  <div className="absolute inset-0 flex flex-col items-center justify-center gap-2">
41
51
  <div className="flex items-center justify-center size-12 rounded-full bg-muted-foreground/10">
@@ -53,14 +63,14 @@ export const VideoPlayer = ({
53
63
  return (
54
64
  <div
55
65
  className={cn("relative overflow-hidden rounded-lg", className)}
56
- style={{ aspectRatio, ...style }}
66
+ style={containerStyle}
57
67
  >
58
68
  {poster ? (
59
69
  <img
60
70
  src={poster}
61
71
  alt={title || "Video poster"}
62
72
  loading="lazy"
63
- style={{ width: "100%", height: "100%", objectFit: "cover" }}
73
+ className="w-full h-full object-cover"
64
74
  />
65
75
  ) : (
66
76
  <div className="absolute inset-0 flex items-center justify-center bg-muted">
@@ -102,8 +112,19 @@ export const VideoPlayer = ({
102
112
  onPause={onPause}
103
113
  onEnded={onEnded}
104
114
  onTimeUpdate={handleTimeUpdate}
105
- style={{ width: "100%", aspectRatio, display: "block" }}
106
- />
115
+ style={videoStyle}
116
+ >
117
+ {tracks?.map((track) => (
118
+ <track
119
+ key={`${track.kind}-${track.srcLang}`}
120
+ src={track.src}
121
+ kind={track.kind}
122
+ label={track.label}
123
+ srcLang={track.srcLang}
124
+ default={track.default}
125
+ />
126
+ ))}
127
+ </video>
107
128
  </div>
108
129
  </div>
109
130
  );