@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
@@ -0,0 +1,49 @@
1
+ import type { Requirement } from "../../sections/RequirementsChecklist/types";
2
+ import type { CertificateVariant } from "../../sections/CertificateViewer/types";
3
+
4
+ /**
5
+ * CertificateModule — a certificate-earning flow with requirements tracking
6
+ * and certificate display.
7
+ *
8
+ * Steps: Requirements (checklist + progress) → Certificate (CertificateViewer).
9
+ * The certificate step is only accessible when all requirements are met.
10
+ *
11
+ * @example
12
+ * <CertificateModule
13
+ * courseTitle="React Fundamentals"
14
+ * recipientName="Jane Smith"
15
+ * organizationName="HydraLMS Academy"
16
+ * requirements={requirements}
17
+ * overallProgress={100}
18
+ * />
19
+ */
20
+ export interface CertificateModuleProps {
21
+ /** Course or program title */
22
+ courseTitle: string;
23
+ /** Recipient's full name */
24
+ recipientName: string;
25
+ /** Issuing organization name */
26
+ organizationName: string;
27
+ /** Organization logo URL */
28
+ organizationLogo?: string;
29
+ /** Signatory information */
30
+ signatory?: { name: string; title: string };
31
+ /** Completion date (used on the certificate) */
32
+ completionDate?: string;
33
+ /** Certificate visual variant. @default "modern" */
34
+ certificateVariant?: CertificateVariant;
35
+ /** Completion requirements */
36
+ requirements: Requirement[];
37
+ /** Overall course progress (0-100). When omitted, derived from requirements completion. */
38
+ overallProgress?: number;
39
+ /** Called when a requirement item is clicked */
40
+ onRequirementClick?: (uid: string) => void;
41
+ /** Called when the certificate is first displayed. @default undefined */
42
+ onComplete?: () => void;
43
+ /** When true, disables interactions for preview/demo mode. @default false */
44
+ readOnly?: boolean;
45
+ /** CSS class name for the root element */
46
+ className?: string;
47
+ /** Inline styles for the root element */
48
+ style?: React.CSSProperties;
49
+ }
@@ -0,0 +1,126 @@
1
+ import { useState, useRef, useEffect } from "react";
2
+ import { ArrowLeft } from "lucide-react";
3
+ import { CourseCatalog } from "../../sections/CourseCatalog/CourseCatalog";
4
+ import { EnrollmentWizard } from "../../sections/EnrollmentWizard/EnrollmentWizard";
5
+ import { Button } from "../../ui/button";
6
+ import { Card, CardContent } from "../../ui/card";
7
+ import { cn } from "../../lib/utils";
8
+ import { withProGate } from "../../license/withProGate";
9
+ import type { CourseCatalogModuleProps } from "./types";
10
+
11
+ type InternalStep =
12
+ | { tag: "browse" }
13
+ | { tag: "enroll"; courseUid: string };
14
+
15
+ /**
16
+ * CourseCatalogModule — a browse-and-enroll course catalog module.
17
+ *
18
+ * Master-detail layout: CourseCatalog (browse) ↔ EnrollmentWizard (enroll).
19
+ * Clicking a course drills into the enrollment flow; a back button returns to browsing.
20
+ */
21
+ function CourseCatalogModuleBase({
22
+ title,
23
+ courses = [],
24
+ categories = [],
25
+ enrollmentData,
26
+ onEnroll,
27
+ onCourseOpen,
28
+ readOnly = false,
29
+ className,
30
+ style,
31
+ }: CourseCatalogModuleProps) {
32
+ const [step, setStep] = useState<InternalStep>({ tag: "browse" });
33
+ const contentRef = useRef<HTMLDivElement>(null);
34
+
35
+ useEffect(() => {
36
+ contentRef.current?.focus({ preventScroll: true });
37
+ }, [step.tag]);
38
+
39
+ function handleCourseClick(courseUid: string) {
40
+ if (enrollmentData?.[courseUid]) {
41
+ setStep({ tag: "enroll", courseUid });
42
+ } else {
43
+ onCourseOpen?.(courseUid);
44
+ }
45
+ }
46
+
47
+ function handleBack() {
48
+ setStep({ tag: "browse" });
49
+ }
50
+
51
+ function handleEnrollConfirm(courseUid: string) {
52
+ onEnroll(courseUid);
53
+ setStep({ tag: "browse" });
54
+ }
55
+
56
+ return (
57
+ <div
58
+ ref={contentRef}
59
+ tabIndex={-1}
60
+ className={cn("outline-none", className)}
61
+ style={style}
62
+ >
63
+ {title && step.tag === "browse" && (
64
+ <h2 className="text-xl font-bold text-foreground mb-4">{title}</h2>
65
+ )}
66
+
67
+ {step.tag === "enroll" ? (
68
+ /* --- Enrollment View --- */
69
+ <div>
70
+ <Button
71
+ variant="ghost"
72
+ size="sm"
73
+ onClick={handleBack}
74
+ className="mb-4"
75
+ >
76
+ <ArrowLeft className="size-4 mr-1.5" />
77
+ Back to Catalog
78
+ </Button>
79
+ {enrollmentData?.[step.courseUid] ? (
80
+ <EnrollmentWizard
81
+ course={enrollmentData[step.courseUid].course}
82
+ prerequisites={enrollmentData[step.courseUid].prerequisites}
83
+ onEnroll={handleEnrollConfirm}
84
+ onCancel={handleBack}
85
+ />
86
+ ) : (
87
+ /* Fallback when enrollment data is not yet available */
88
+ <Card>
89
+ <CardContent className="pt-6">
90
+ <h3 className="text-lg font-semibold text-foreground mb-2">
91
+ {courses.find((c) => c.uid === step.courseUid)?.title ??
92
+ "Course"}
93
+ </h3>
94
+ <p className="text-sm text-muted-foreground mb-4">
95
+ Enrollment details are loading. You can enroll directly below.
96
+ </p>
97
+ <div className="flex gap-2">
98
+ <Button variant="outline" onClick={handleBack}>
99
+ Cancel
100
+ </Button>
101
+ <Button
102
+ onClick={() => handleEnrollConfirm(step.courseUid)}
103
+ disabled={readOnly}
104
+ >
105
+ Enroll Now
106
+ </Button>
107
+ </div>
108
+ </CardContent>
109
+ </Card>
110
+ )}
111
+ </div>
112
+ ) : (
113
+ /* --- Browse View --- */
114
+ <CourseCatalog
115
+ courses={courses}
116
+ categories={categories}
117
+ onCourseClick={(course) => handleCourseClick(course.uid)}
118
+ onEnroll={(course) => handleCourseClick(course.uid)}
119
+ readOnly={readOnly}
120
+ />
121
+ )}
122
+ </div>
123
+ );
124
+ }
125
+
126
+ export const CourseCatalogModule = withProGate(CourseCatalogModuleBase, "CourseCatalogModule");
@@ -0,0 +1,47 @@
1
+ import type { CourseInfo } from "../../sections/CourseCatalog/types";
2
+ import type {
3
+ EnrollmentCourse,
4
+ Prerequisite,
5
+ } from "../../sections/EnrollmentWizard/types";
6
+
7
+ /**
8
+ * CourseCatalogModule — a browse-and-enroll course catalog module.
9
+ *
10
+ * Master-detail layout: CourseCatalog (browse) ↔ EnrollmentWizard (enroll).
11
+ * Clicking a course drills into the enrollment flow; a back button returns to browsing.
12
+ *
13
+ * @example
14
+ * <CourseCatalogModule
15
+ * title="Course Catalog"
16
+ * courses={courses}
17
+ * categories={categories}
18
+ * enrollmentData={enrollmentData}
19
+ * onEnroll={(courseUid) => handleEnroll(courseUid)}
20
+ * />
21
+ */
22
+ export interface CourseCatalogModuleProps {
23
+ /** Catalog title */
24
+ title?: string;
25
+ /** Courses to display */
26
+ courses: CourseInfo[];
27
+ /** Optional categories for filtering */
28
+ categories?: { uid: string; label: string }[];
29
+ /** Enrollment data keyed by course UID — provides course details and prerequisites for the enrollment wizard */
30
+ enrollmentData?: Record<
31
+ string,
32
+ {
33
+ course: EnrollmentCourse;
34
+ prerequisites?: Prerequisite[];
35
+ }
36
+ >;
37
+ /** Called when a user confirms enrollment */
38
+ onEnroll: (courseUid: string) => void;
39
+ /** Called when a course is opened (for lazy-loading enrollment data) */
40
+ onCourseOpen?: (courseUid: string) => void;
41
+ /** When true, disables interactions for preview/demo mode. @default false */
42
+ readOnly?: boolean;
43
+ /** CSS class name for the root element */
44
+ className?: string;
45
+ /** Inline styles for the root element */
46
+ style?: React.CSSProperties;
47
+ }
@@ -1,11 +1,18 @@
1
- import { useState, useMemo } from "react";
1
+ import { useCallback, useState, useMemo, useRef, useEffect } from "react";
2
2
  import {
3
3
  ChevronLeft,
4
4
  ChevronRight,
5
5
  Check,
6
- PanelLeftClose,
7
6
  PanelLeft,
8
7
  } from "lucide-react";
8
+ import {
9
+ Drawer,
10
+ DrawerContent,
11
+ DrawerHeader,
12
+ DrawerBody,
13
+ DrawerTitle,
14
+ DrawerClose,
15
+ } from "../../ui/drawer";
9
16
  import { CourseOutline } from "../../sections/CourseOutline/CourseOutline";
10
17
  import { LessonPage } from "../../sections/LessonPage/LessonPage";
11
18
  import { LecturePlayer } from "../../sections/LecturePlayer/LecturePlayer";
@@ -13,23 +20,13 @@ import { PracticeQuiz } from "../../sections/PracticeQuiz/PracticeQuiz";
13
20
  import { EmptyState } from "../../common";
14
21
  import { Progress } from "../../ui/progress";
15
22
  import { Button } from "../../ui/button";
23
+ import { flattenLeaves } from "../../utils/flatten-leaves";
16
24
  import { cn } from "../../lib/utils";
17
25
  import type { CurriculumItem } from "../../curriculum/types";
26
+ import { withProGate } from "../../license/withProGate";
18
27
  import type { CoursePlayerProps, CoursePlayerItem } from "./types";
19
28
 
20
- function flattenLeaves(items: CurriculumItem[]): string[] {
21
- const leaves: string[] = [];
22
- for (const item of items) {
23
- if (!item.children || item.children.length === 0) {
24
- leaves.push(item.uid);
25
- } else {
26
- leaves.push(...flattenLeaves(item.children));
27
- }
28
- }
29
- return leaves;
30
- }
31
-
32
- export function CoursePlayer({
29
+ function CoursePlayerBase({
33
30
  courseTitle,
34
31
  curriculum,
35
32
  progress,
@@ -42,6 +39,7 @@ export function CoursePlayer({
42
39
  className,
43
40
  style,
44
41
  }: CoursePlayerProps) {
42
+ const contentRef = useRef<HTMLDivElement>(null);
45
43
  const leafUids = useMemo(() => flattenLeaves(curriculum), [curriculum]);
46
44
  const itemMap = useMemo(
47
45
  () => new Map(items.map((item) => [item.uid, item])),
@@ -74,29 +72,47 @@ export function CoursePlayer({
74
72
 
75
73
  const activeItem = itemMap.get(activeUid);
76
74
 
77
- function navigateTo(uid: string) {
75
+ const videoConfig = useMemo(
76
+ () => activeItem?.type === "video"
77
+ ? { src: activeItem.src, poster: activeItem.poster, title: activeItem.title }
78
+ : null,
79
+ [activeItem],
80
+ );
81
+
82
+ useEffect(() => {
83
+ contentRef.current?.focus({ preventScroll: true });
84
+ }, [activeUid]);
85
+
86
+ const onItemChangeRef = useRef(onItemChange);
87
+ onItemChangeRef.current = onItemChange;
88
+ const onItemCompleteRef = useRef(onItemComplete);
89
+ onItemCompleteRef.current = onItemComplete;
90
+
91
+ const navigateTo = useCallback((uid: string) => {
78
92
  setActiveUid(uid);
79
- onItemChange?.(uid);
80
- }
93
+ onItemChangeRef.current?.(uid);
94
+ }, []);
81
95
 
82
- function handleItemClick(item: CurriculumItem) {
96
+ const handleItemClick = useCallback((item: CurriculumItem) => {
83
97
  if (!item.children || item.children.length === 0) {
84
98
  navigateTo(item.uid);
85
99
  }
86
- }
100
+ }, [navigateTo]);
87
101
 
88
- function handleMarkComplete() {
102
+ const handleMarkComplete = useCallback(() => {
89
103
  setCompletedUids((prev) => new Set(prev).add(activeUid));
90
- onItemComplete?.(activeUid);
91
- }
104
+ onItemCompleteRef.current?.(activeUid);
105
+ }, [activeUid]);
92
106
 
93
- function handleNext() {
94
- if (hasNext) navigateTo(leafUids[currentIndex + 1]);
95
- }
107
+ const handleNext = useCallback(() => {
108
+ const idx = leafUids.indexOf(activeUid);
109
+ if (idx < leafUids.length - 1) navigateTo(leafUids[idx + 1]);
110
+ }, [leafUids, activeUid, navigateTo]);
96
111
 
97
- function handlePrevious() {
98
- if (hasPrevious) navigateTo(leafUids[currentIndex - 1]);
99
- }
112
+ const handlePrevious = useCallback(() => {
113
+ const idx = leafUids.indexOf(activeUid);
114
+ if (idx > 0) navigateTo(leafUids[idx - 1]);
115
+ }, [leafUids, activeUid, navigateTo]);
100
116
 
101
117
  // Build combined progress for CourseOutline
102
118
  const combinedProgress = useMemo(() => {
@@ -121,45 +137,40 @@ export function CoursePlayer({
121
137
  className={cn("flex h-full overflow-hidden", className)}
122
138
  style={style}
123
139
  >
124
- {/* Sidebar */}
125
- {sidebarOpen && (
126
- <aside className="w-80 shrink-0 border-r border-border overflow-y-auto bg-background">
127
- <div className="flex items-center justify-between px-3 pt-3 pb-1">
128
- <span className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
140
+ {/* Sidebar Drawer */}
141
+ <Drawer open={sidebarOpen} onOpenChange={setSidebarOpen} side="left">
142
+ <DrawerContent size="sm" scrollLock={false}>
143
+ <DrawerHeader className="flex-row items-center justify-between">
144
+ <DrawerTitle className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
129
145
  Course
130
- </span>
131
- <button
132
- type="button"
133
- className="p-1 rounded hover:bg-muted text-muted-foreground"
134
- onClick={() => setSidebarOpen(false)}
135
- >
136
- <PanelLeftClose className="size-4" />
137
- </button>
138
- </div>
139
- <CourseOutline
140
- items={curriculum}
141
- progress={combinedProgress}
142
- courseTitle={courseTitle}
143
- activeItemUid={activeUid}
144
- onItemClick={handleItemClick}
145
- readOnly={readOnly}
146
- />
147
- </aside>
148
- )}
146
+ </DrawerTitle>
147
+ <DrawerClose />
148
+ </DrawerHeader>
149
+ <DrawerBody className="px-2 pb-2">
150
+ <CourseOutline
151
+ items={curriculum}
152
+ progress={combinedProgress}
153
+ courseTitle={courseTitle}
154
+ activeItemUid={activeUid}
155
+ onItemClick={handleItemClick}
156
+ readOnly={readOnly}
157
+ />
158
+ </DrawerBody>
159
+ </DrawerContent>
160
+ </Drawer>
149
161
 
150
162
  {/* Main content area */}
151
163
  <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
152
164
  {/* Toolbar */}
153
165
  <div className="flex items-center gap-2 px-4 py-2 border-b border-border bg-background shrink-0">
154
- {!sidebarOpen && (
155
- <button
156
- type="button"
157
- className="p-1 rounded hover:bg-muted text-muted-foreground mr-1"
158
- onClick={() => setSidebarOpen(true)}
159
- >
160
- <PanelLeft className="size-4" />
161
- </button>
162
- )}
166
+ <Button
167
+ variant="ghost"
168
+ size="sm"
169
+ className="size-7 p-0 mr-1"
170
+ onClick={() => setSidebarOpen(true)}
171
+ >
172
+ <PanelLeft className="size-4" />
173
+ </Button>
163
174
  <div className="flex-1 min-w-0">
164
175
  <span className="text-sm font-semibold text-foreground truncate block">
165
176
  {activeItem?.title ?? "Select an item"}
@@ -171,9 +182,9 @@ export function CoursePlayer({
171
182
  </div>
172
183
 
173
184
  {/* Content */}
174
- <div className="flex-1 overflow-y-auto p-6">
185
+ <div ref={contentRef} tabIndex={-1} className="flex-1 overflow-y-auto p-6 outline-none">
175
186
  {activeItem ? (
176
- renderContent(activeItem, readOnly, handleMarkComplete, isCurrentCompleted, handleNext, hasNext, nextItem)
187
+ renderContent(activeItem, readOnly, handleMarkComplete, isCurrentCompleted, handleNext, hasNext, nextItem, videoConfig)
177
188
  ) : (
178
189
  <EmptyState
179
190
  title="No content selected"
@@ -237,6 +248,7 @@ function renderContent(
237
248
  onNext: () => void,
238
249
  hasNext: boolean,
239
250
  nextItem: CoursePlayerItem | null | undefined,
251
+ videoConfig: { src: string; poster?: string; title: string } | null,
240
252
  ) {
241
253
  switch (item.type) {
242
254
  case "lesson":
@@ -254,11 +266,8 @@ function renderContent(
254
266
  case "video":
255
267
  return (
256
268
  <LecturePlayer
257
- video={{
258
- src: item.src,
259
- poster: item.poster,
260
- title: item.title,
261
- }}
269
+ video={videoConfig ?? { src: "", title: item.title }}
270
+ onComplete={isCompleted ? undefined : onMarkComplete}
262
271
  />
263
272
  );
264
273
  case "quiz":
@@ -279,3 +288,5 @@ function renderContent(
279
288
  );
280
289
  }
281
290
  }
291
+
292
+ export const CoursePlayer = withProGate(CoursePlayerBase, "CoursePlayer");
@@ -0,0 +1,145 @@
1
+ import { useCallback, useState, useRef, useEffect } from "react";
2
+ import { ArrowLeft } from "lucide-react";
3
+ import { ForumBoard } from "../../sections/ForumBoard/ForumBoard";
4
+ import { DiscussionThread } from "../../sections/DiscussionThread/DiscussionThread";
5
+ import { EmptyState } from "../../common";
6
+ import { Button } from "../../ui/button";
7
+ import { cn } from "../../lib/utils";
8
+ import type { ForumSortOrder } from "../../sections/ForumBoard/types";
9
+ import { withProGate } from "../../license/withProGate";
10
+ import type { DiscussionModuleProps } from "./types";
11
+
12
+ /**
13
+ * DiscussionModule — a master-detail discussion forum with topic list and thread view.
14
+ *
15
+ * Panel-based layout: ForumBoard (topic listing) ↔ DiscussionThread (thread detail).
16
+ * Clicking a topic drills into the thread; a back button returns to the board.
17
+ */
18
+ function DiscussionModuleBase({
19
+ forumTitle,
20
+ topics = [],
21
+ currentUser,
22
+ threads = {},
23
+ onCreateTopic,
24
+ onReply,
25
+ onToggleLike,
26
+ onMarkAnswer,
27
+ onTopicOpen,
28
+ readOnly,
29
+ className,
30
+ style,
31
+ }: DiscussionModuleProps) {
32
+ const [activeTopicUid, setActiveTopicUid] = useState<string | null>(null);
33
+ const [searchQuery, setSearchQuery] = useState("");
34
+ const [sortOrder, setSortOrder] = useState<ForumSortOrder>("newest");
35
+ const contentRef = useRef<HTMLDivElement>(null);
36
+
37
+ useEffect(() => {
38
+ contentRef.current?.focus({ preventScroll: true });
39
+ }, [activeTopicUid]);
40
+
41
+ const onTopicOpenRef = useRef(onTopicOpen);
42
+ onTopicOpenRef.current = onTopicOpen;
43
+
44
+ const handleTopicClick = useCallback((topicUid: string) => {
45
+ setActiveTopicUid(topicUid);
46
+ onTopicOpenRef.current?.(topicUid);
47
+ }, []);
48
+
49
+ const handleBack = useCallback(() => {
50
+ setActiveTopicUid(null);
51
+ }, []);
52
+
53
+ const activeThread = activeTopicUid ? threads[activeTopicUid] : null;
54
+ const activeTopic = activeTopicUid
55
+ ? topics.find((t) => t.uid === activeTopicUid)
56
+ : null;
57
+
58
+ const handleReply = useCallback(
59
+ (parentUid: string, content: string) => {
60
+ if (activeTopicUid) onReply?.(activeTopicUid, parentUid, content);
61
+ },
62
+ [activeTopicUid, onReply],
63
+ );
64
+
65
+ const handleToggleLike = useCallback(
66
+ (postUid: string) => {
67
+ if (activeTopicUid) onToggleLike?.(activeTopicUid, postUid);
68
+ },
69
+ [activeTopicUid, onToggleLike],
70
+ );
71
+
72
+ const handleMarkAnswer = useCallback(
73
+ (postUid: string) => {
74
+ if (activeTopicUid) onMarkAnswer?.(activeTopicUid, postUid);
75
+ },
76
+ [activeTopicUid, onMarkAnswer],
77
+ );
78
+
79
+ return (
80
+ <div
81
+ ref={contentRef}
82
+ tabIndex={-1}
83
+ className={cn("outline-none", className)}
84
+ style={style}
85
+ >
86
+ {activeTopicUid && activeThread ? (
87
+ /* ─── Thread View ─── */
88
+ <div>
89
+ <Button
90
+ variant="ghost"
91
+ size="sm"
92
+ onClick={handleBack}
93
+ className="mb-4"
94
+ >
95
+ <ArrowLeft className="size-4 mr-1.5" />
96
+ Back to Topics
97
+ </Button>
98
+ <DiscussionThread
99
+ title={activeTopic?.title ?? ""}
100
+ rootPost={activeThread.rootPost}
101
+ replies={activeThread.replies}
102
+ currentUser={currentUser}
103
+ onReply={handleReply}
104
+ onToggleLike={onToggleLike ? handleToggleLike : undefined}
105
+ onMarkAnswer={onMarkAnswer ? handleMarkAnswer : undefined}
106
+ readOnly={readOnly}
107
+ />
108
+ </div>
109
+ ) : activeTopicUid && !activeThread ? (
110
+ /* ─── Empty Thread State ─── */
111
+ <div>
112
+ <Button
113
+ variant="ghost"
114
+ size="sm"
115
+ onClick={handleBack}
116
+ className="mb-4"
117
+ >
118
+ <ArrowLeft className="size-4 mr-1.5" />
119
+ Back to Topics
120
+ </Button>
121
+ <EmptyState
122
+ title="No discussion yet"
123
+ description="Be the first to start this conversation."
124
+ />
125
+ </div>
126
+ ) : (
127
+ /* ─── Forum Board View ─── */
128
+ <ForumBoard
129
+ title={forumTitle}
130
+ topics={topics}
131
+ currentUser={currentUser}
132
+ onTopicClick={handleTopicClick}
133
+ onCreateTopic={onCreateTopic}
134
+ sortOrder={sortOrder}
135
+ onSortChange={setSortOrder}
136
+ searchQuery={searchQuery}
137
+ onSearchChange={setSearchQuery}
138
+ readOnly={readOnly}
139
+ />
140
+ )}
141
+ </div>
142
+ );
143
+ }
144
+
145
+ export const DiscussionModule = withProGate(DiscussionModuleBase, "DiscussionModule");
@@ -0,0 +1,54 @@
1
+ import type {
2
+ DiscussionUser,
3
+ DiscussionPost,
4
+ } from "../../sections/DiscussionThread/types";
5
+ import type { ForumTopic } from "../../sections/ForumBoard/types";
6
+
7
+ /**
8
+ * DiscussionModule — a master-detail discussion forum with topic list and thread view.
9
+ *
10
+ * Panel-based layout: ForumBoard (topic list) ↔ DiscussionThread (thread detail).
11
+ * Clicking a topic drills into the thread; a back button returns to the board.
12
+ *
13
+ * @example
14
+ * <DiscussionModule
15
+ * forumTitle="Class Discussion"
16
+ * topics={topics}
17
+ * threads={threadData}
18
+ * currentUser={user}
19
+ * onCreateTopic={(title, content) => createTopic(title, content)}
20
+ * onReply={(topicUid, parentUid, content) => postReply(topicUid, parentUid, content)}
21
+ * />
22
+ */
23
+ export interface DiscussionModuleProps {
24
+ /** Forum title */
25
+ forumTitle?: string;
26
+ /** List of discussion topics */
27
+ topics: ForumTopic[];
28
+ /** The currently authenticated user */
29
+ currentUser: DiscussionUser;
30
+ /** Thread data keyed by topic UID */
31
+ threads: Record<
32
+ string,
33
+ {
34
+ rootPost: DiscussionPost;
35
+ replies: DiscussionPost[];
36
+ }
37
+ >;
38
+ /** Called when a new topic is created */
39
+ onCreateTopic?: (title: string, content: string) => void;
40
+ /** Called when a reply is posted */
41
+ onReply?: (topicUid: string, parentUid: string, content: string) => void;
42
+ /** Called when a like is toggled */
43
+ onToggleLike?: (topicUid: string, postUid: string) => void;
44
+ /** Called when a post is marked as the answer */
45
+ onMarkAnswer?: (topicUid: string, postUid: string) => void;
46
+ /** Called when a topic is opened (for lazy loading thread data) */
47
+ onTopicOpen?: (topicUid: string) => void;
48
+ /** When true, disables all interactions */
49
+ readOnly?: boolean;
50
+ /** CSS class name for the root element */
51
+ className?: string;
52
+ /** Inline styles for the root element */
53
+ style?: React.CSSProperties;
54
+ }