@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,289 @@
1
+ import { useState, useCallback, useMemo, useRef } from "react";
2
+ import { Eye, Pencil, Save } from "lucide-react";
3
+ import { Button } from "../../ui/button";
4
+ import { Input } from "../../ui/input";
5
+ import { Skeleton } from "../../ui/skeleton";
6
+ import { ContentBlock } from "../../content";
7
+ import { EmptyState } from "../../common/empty-state";
8
+ import { SectionShell } from "../_shared/section-shell";
9
+ import { useDragReorder } from "../../questions/use-drag-reorder";
10
+ import { withProGate } from "../../license/withProGate";
11
+ import type { LessonBlock } from "../../content/types";
12
+ import type { ContentAuthoringStudioProps, AuthoringBlock } from "./types";
13
+ import { BlockEditorItem } from "./block-editor-item";
14
+ import { BlockTypePicker } from "./block-type-picker";
15
+
16
+ let idCounter = 0;
17
+ function genId() {
18
+ return `ab_${++idCounter}_${Date.now().toString(36)}`;
19
+ }
20
+
21
+ function toAuthoring(blocks: LessonBlock[]): AuthoringBlock[] {
22
+ return blocks.map((block) => ({ id: genId(), block }));
23
+ }
24
+
25
+ function createEmptyBlock(type: LessonBlock["type"]): LessonBlock {
26
+ switch (type) {
27
+ case "richtext":
28
+ return { type: "richtext", html: "" };
29
+ case "heading":
30
+ return { type: "heading", text: "", level: 2 };
31
+ case "image":
32
+ return { type: "image", src: "", alt: "" };
33
+ case "video":
34
+ return { type: "video", video: { src: "" } };
35
+ case "code":
36
+ return { type: "code", code: "", language: "javascript" };
37
+ case "callout":
38
+ return { type: "callout", content: "", variant: "info" };
39
+ case "audio":
40
+ return { type: "audio", src: "" };
41
+ case "embed":
42
+ return { type: "embed", src: "" };
43
+ case "table":
44
+ return { type: "table", headers: ["Column 1", "Column 2"], rows: [["", ""]] };
45
+ case "file":
46
+ return { type: "file", files: [] };
47
+ case "divider":
48
+ return { type: "divider" };
49
+ case "question":
50
+ return {
51
+ type: "question",
52
+ question: {
53
+ uid: genId(),
54
+ type: "multiple_choice",
55
+ content: "",
56
+ answers: [],
57
+ },
58
+ };
59
+ case "flashcards":
60
+ return { type: "flashcards", cards: [] };
61
+ case "custom":
62
+ return { type: "custom", render: null };
63
+ default:
64
+ return { type: "richtext", html: "" };
65
+ }
66
+ }
67
+
68
+ /**
69
+ * ContentAuthoringStudio — A premium block-based content editor for creating
70
+ * lessons. Produces `LessonBlock[]` compatible with `LessonPage`.
71
+ *
72
+ * Supports adding, editing, removing, reordering, and previewing content blocks
73
+ * including rich text, headings, images, videos, code, callouts, and more.
74
+ */
75
+ function ContentAuthoringStudioBase({
76
+ title: titleProp = "",
77
+ blocks: blocksProp,
78
+ onBlocksChange,
79
+ onTitleChange,
80
+ onSave,
81
+ showPreviewToggle = true,
82
+ allowedBlockTypes,
83
+ readOnly = false,
84
+ isLoading,
85
+ error,
86
+ onRetry,
87
+ className,
88
+ style,
89
+ }: ContentAuthoringStudioProps) {
90
+ const [title, setTitle] = useState(titleProp);
91
+ const [items, setItems] = useState<AuthoringBlock[]>(() =>
92
+ toAuthoring(blocksProp ?? []),
93
+ );
94
+ const [mode, setMode] = useState<"edit" | "preview">(readOnly ? "preview" : "edit");
95
+
96
+ const rawBlocks = useMemo(() => items.map((i) => i.block), [items]);
97
+
98
+ const emitChange = useCallback(
99
+ (nextItems: AuthoringBlock[]) => {
100
+ setItems(nextItems);
101
+ onBlocksChange?.(nextItems.map((i) => i.block));
102
+ },
103
+ [onBlocksChange],
104
+ );
105
+
106
+ /* ── Block operations ──────────────────────────── */
107
+
108
+ const itemsRef = useRef(items);
109
+ itemsRef.current = items;
110
+
111
+ function handleAddBlock(type: LessonBlock["type"], atIndex: number) {
112
+ const newItem: AuthoringBlock = { id: genId(), block: createEmptyBlock(type) };
113
+ const next = [...items];
114
+ next.splice(atIndex, 0, newItem);
115
+ emitChange(next);
116
+ }
117
+
118
+ const handleUpdateBlock = useCallback((id: string, block: LessonBlock) => {
119
+ emitChange(itemsRef.current.map((i) => (i.id === id ? { ...i, block } : i)));
120
+ }, [emitChange]);
121
+
122
+ const handleRemoveBlock = useCallback((id: string) => {
123
+ emitChange(itemsRef.current.filter((i) => i.id !== id));
124
+ }, [emitChange]);
125
+
126
+ const handleDuplicateBlock = useCallback((id: string) => {
127
+ const current = itemsRef.current;
128
+ const index = current.findIndex((i) => i.id === id);
129
+ if (index === -1) return;
130
+ const copy: AuthoringBlock = {
131
+ id: genId(),
132
+ block: structuredClone(current[index].block),
133
+ };
134
+ const next = [...current];
135
+ next.splice(index + 1, 0, copy);
136
+ emitChange(next);
137
+ }, [emitChange]);
138
+
139
+ const handleToggleCollapse = useCallback((id: string) => {
140
+ setItems((prev) =>
141
+ prev.map((i) =>
142
+ i.id === id ? { ...i, collapsed: !i.collapsed } : i,
143
+ ),
144
+ );
145
+ }, []);
146
+
147
+ function handleTitleChange(value: string) {
148
+ setTitle(value);
149
+ onTitleChange?.(value);
150
+ }
151
+
152
+ function handleSave() {
153
+ onSave?.({ title, blocks: rawBlocks });
154
+ }
155
+
156
+ /* ── Drag-and-drop ─────────────────────────────── */
157
+
158
+ const { getDragProps, dragIndex, dragOverIndex } = useDragReorder({
159
+ items,
160
+ onReorder: emitChange,
161
+ disabled: readOnly || mode === "preview",
162
+ });
163
+
164
+ /* ── Render ────────────────────────────────────── */
165
+
166
+ const isEditing = mode === "edit" && !readOnly;
167
+
168
+ return (
169
+ <SectionShell
170
+ isLoading={isLoading}
171
+ error={error}
172
+ onRetry={onRetry}
173
+ className={className}
174
+ style={style}
175
+ skeleton={
176
+ <>
177
+ <Skeleton className="h-10 w-64" />
178
+ <Skeleton className="h-6 w-32" />
179
+ <Skeleton className="h-32 w-full" />
180
+ <Skeleton className="h-24 w-full" />
181
+ <Skeleton className="h-32 w-full" />
182
+ </>
183
+ }
184
+ >
185
+ <div>
186
+ {/* Toolbar */}
187
+ <div className="flex items-center gap-2 mb-4">
188
+ {isEditing ? (
189
+ <Input
190
+ value={title}
191
+ onChange={(e) => handleTitleChange(e.target.value)}
192
+ placeholder="Lesson title..."
193
+ className="text-lg font-semibold flex-1 h-10"
194
+ />
195
+ ) : (
196
+ <h2 className="text-xl font-bold flex-1 text-foreground">{title || "Untitled Lesson"}</h2>
197
+ )}
198
+
199
+ {showPreviewToggle && !readOnly && (
200
+ <Button
201
+ variant="outline"
202
+ size="sm"
203
+ onClick={() => setMode(mode === "edit" ? "preview" : "edit")}
204
+ className="gap-1.5"
205
+ >
206
+ {mode === "edit" ? (
207
+ <>
208
+ <Eye className="size-3.5" /> Preview
209
+ </>
210
+ ) : (
211
+ <>
212
+ <Pencil className="size-3.5" /> Edit
213
+ </>
214
+ )}
215
+ </Button>
216
+ )}
217
+
218
+ {onSave && !readOnly && (
219
+ <Button size="sm" onClick={handleSave} className="gap-1.5">
220
+ <Save className="size-3.5" /> Save
221
+ </Button>
222
+ )}
223
+ </div>
224
+
225
+ {/* Content */}
226
+ {isEditing ? (
227
+ <div className="space-y-1">
228
+ {/* Top insertion point */}
229
+ <div className="flex justify-center py-1">
230
+ <BlockTypePicker
231
+ onSelect={(type) => handleAddBlock(type, 0)}
232
+ allowedTypes={allowedBlockTypes}
233
+ />
234
+ </div>
235
+
236
+ {items.length === 0 && (
237
+ <EmptyState
238
+ title="No content blocks"
239
+ description="Click 'Add block' above to start building your lesson."
240
+ />
241
+ )}
242
+
243
+ {items.map((item, index) => (
244
+ <div key={item.id}>
245
+ <BlockEditorItem
246
+ item={item}
247
+ onChange={handleUpdateBlock}
248
+ onRemove={handleRemoveBlock}
249
+ onDuplicate={handleDuplicateBlock}
250
+ onToggleCollapse={handleToggleCollapse}
251
+ dragProps={getDragProps(index)}
252
+ isDragging={dragIndex === index}
253
+ isDragOver={dragOverIndex === index}
254
+ />
255
+
256
+ {/* Insertion point after each block */}
257
+ <div className="flex justify-center py-1">
258
+ <BlockTypePicker
259
+ onSelect={(type) => handleAddBlock(type, index + 1)}
260
+ allowedTypes={allowedBlockTypes}
261
+ />
262
+ </div>
263
+ </div>
264
+ ))}
265
+ </div>
266
+ ) : (
267
+ /* Preview mode */
268
+ <div className="flex flex-col gap-3">
269
+ {items.length > 0 ? (
270
+ items.map((item) => (
271
+ <ContentBlock key={item.id} block={item.block} readOnly />
272
+ ))
273
+ ) : (
274
+ <EmptyState
275
+ title="No content yet"
276
+ description="Switch to edit mode to add content blocks."
277
+ />
278
+ )}
279
+ </div>
280
+ )}
281
+ </div>
282
+ </SectionShell>
283
+ );
284
+ }
285
+
286
+ export const ContentAuthoringStudio = withProGate(
287
+ ContentAuthoringStudioBase,
288
+ "ContentAuthoringStudio",
289
+ );