@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.
- package/dist/StudentProfile-BPsZBaJj.cjs +1 -0
- package/dist/StudentProfile-Cw2p-RZn.js +3273 -0
- package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
- package/dist/assessment-toolbar/timer-display.d.ts +1 -1
- package/dist/common/index.d.ts +2 -1
- package/dist/common/pagination.d.ts +26 -0
- package/dist/common/types.d.ts +1 -0
- package/dist/components.css +1 -1
- package/dist/content/audio-player.d.ts +22 -0
- package/dist/content/code-block.d.ts +30 -0
- package/dist/content/embed-block.d.ts +28 -0
- package/dist/content/index.d.ts +6 -0
- package/dist/content/types.d.ts +24 -0
- package/dist/curriculum/course-card.d.ts +51 -0
- package/dist/curriculum/index.d.ts +2 -0
- package/dist/curriculum/types.d.ts +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +495 -439
- package/dist/license/HydraContext.d.ts +16 -0
- package/dist/license/ProBadge.d.ts +6 -0
- package/dist/license/index.d.ts +7 -0
- package/dist/license/tiers.d.ts +6 -0
- package/dist/license/useHydraLicense.d.ts +6 -0
- package/dist/license/validate.d.ts +13 -0
- package/dist/license/withProGate.d.ts +6 -0
- package/dist/modules/AssignmentModule/AssignmentModule.d.ts +4 -7
- package/dist/modules/AssignmentModule/types.d.ts +5 -1
- package/dist/modules/CertificateModule/CertificateModule.d.ts +4 -8
- package/dist/modules/CertificateModule/types.d.ts +6 -4
- package/dist/modules/CourseCatalogModule/CourseCatalogModule.d.ts +5 -0
- package/dist/modules/CourseCatalogModule/types.d.ts +43 -0
- package/dist/modules/CoursePlayer/CoursePlayer.d.ts +4 -1
- package/dist/modules/DiscussionModule/DiscussionModule.d.ts +4 -7
- package/dist/modules/ExamModule/ExamModule.d.ts +4 -7
- package/dist/modules/ExamModule/types.d.ts +5 -14
- package/dist/modules/FlashcardLab/FlashcardLab.d.ts +4 -1
- package/dist/modules/FlashcardLab/types.d.ts +2 -0
- package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +4 -8
- package/dist/modules/GradeCenterModule/types.d.ts +2 -0
- package/dist/modules/QuizModule/QuizModule.d.ts +4 -1
- package/dist/modules/QuizModule/types.d.ts +5 -14
- package/dist/modules/StudentDashboardModule/StudentDashboardModule.d.ts +5 -0
- package/dist/modules/StudentDashboardModule/types.d.ts +54 -0
- package/dist/modules/StudentProfileModule/StudentProfileModule.d.ts +5 -0
- package/dist/modules/StudentProfileModule/types.d.ts +43 -0
- package/dist/modules/SurveyModule/SurveyModule.d.ts +4 -6
- package/dist/modules/SurveyModule/types.d.ts +2 -0
- package/dist/modules/_shared/assessment-intro.d.ts +16 -0
- package/dist/modules/_shared/assessment-results.d.ts +23 -0
- package/dist/modules/_shared/types.d.ts +10 -0
- package/dist/modules/_shared/use-timer.d.ts +9 -0
- package/dist/modules/index.d.ts +6 -0
- package/dist/modules.cjs +1 -1
- package/dist/modules.js +1267 -854
- package/dist/progress/types.d.ts +2 -0
- package/dist/provider/HydraProvider.d.ts +5 -1
- package/dist/questions/choice.d.ts +1 -1
- package/dist/questions/confidence-indicator.d.ts +37 -0
- package/dist/questions/essay.d.ts +1 -1
- package/dist/questions/fill-in-the-blank.d.ts +1 -1
- package/dist/questions/hotspot.d.ts +1 -1
- package/dist/questions/index.d.ts +2 -0
- package/dist/questions/inline-choice.d.ts +1 -1
- package/dist/questions/matching.d.ts +1 -1
- package/dist/questions/multiple-choice.d.ts +1 -1
- package/dist/questions/numeric.d.ts +1 -1
- package/dist/questions/ordering.d.ts +1 -1
- package/dist/questions/question-renderer.d.ts +1 -1
- package/dist/questions/scenario.d.ts +1 -1
- package/dist/questions/spreadsheet.d.ts +1 -1
- package/dist/questions/true-false.d.ts +1 -1
- package/dist/sections/AdaptiveLearningPath/AdaptiveLearningPath.d.ts +5 -0
- package/dist/sections/AdaptiveLearningPath/path-connector.d.ts +8 -0
- package/dist/sections/AdaptiveLearningPath/path-milestone-marker.d.ts +7 -0
- package/dist/sections/AdaptiveLearningPath/path-node-card.d.ts +10 -0
- package/dist/sections/AdaptiveLearningPath/path-skill-bar.d.ts +8 -0
- package/dist/sections/AdaptiveLearningPath/types.d.ts +136 -0
- package/dist/sections/AnnouncementFeed/AnnouncementFeed.d.ts +1 -1
- package/dist/sections/AnnouncementFeed/types.d.ts +15 -1
- package/dist/sections/AssessmentReview/AssessmentReview.d.ts +1 -1
- package/dist/sections/AssessmentReview/types.d.ts +6 -0
- package/dist/sections/AssignmentSubmission/AssignmentSubmission.d.ts +1 -1
- package/dist/sections/AssignmentSubmission/types.d.ts +6 -0
- package/dist/sections/CertificateViewer/CertificateViewer.d.ts +1 -1
- package/dist/sections/CertificateViewer/certificate-variants.d.ts +42 -0
- package/dist/sections/CertificateViewer/types.d.ts +6 -0
- package/dist/sections/ContentAuthoringStudio/ContentAuthoringStudio.d.ts +5 -0
- package/dist/sections/ContentAuthoringStudio/block-editor-item.d.ts +14 -0
- package/dist/sections/ContentAuthoringStudio/block-type-picker.d.ts +12 -0
- package/dist/sections/ContentAuthoringStudio/types.d.ts +67 -0
- package/dist/sections/CourseCatalog/CourseCatalog.d.ts +2 -0
- package/dist/sections/CourseCatalog/types.d.ts +80 -0
- package/dist/sections/CourseOutline/CourseOutline.d.ts +1 -1
- package/dist/sections/CourseOutline/types.d.ts +6 -0
- package/dist/sections/DiscussionThread/DiscussionThread.d.ts +1 -1
- package/dist/sections/DiscussionThread/types.d.ts +6 -0
- package/dist/sections/EnrollmentWizard/EnrollmentWizard.d.ts +2 -0
- package/dist/sections/EnrollmentWizard/types.d.ts +66 -0
- package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
- package/dist/sections/ExamSession/types.d.ts +6 -0
- package/dist/sections/FlashcardStudySession/FlashcardStudySession.d.ts +1 -1
- package/dist/sections/FlashcardStudySession/types.d.ts +6 -0
- package/dist/sections/ForumBoard/ForumBoard.d.ts +1 -1
- package/dist/sections/ForumBoard/types.d.ts +14 -0
- package/dist/sections/GradebookTable/GradebookTable.d.ts +1 -1
- package/dist/sections/GradebookTable/types.d.ts +14 -0
- package/dist/sections/LecturePlayer/LecturePlayer.d.ts +1 -1
- package/dist/sections/LecturePlayer/types.d.ts +8 -0
- package/dist/sections/LessonPage/LessonPage.d.ts +1 -1
- package/dist/sections/LessonPage/types.d.ts +6 -0
- package/dist/sections/PracticeQuiz/PracticeQuiz.d.ts +1 -1
- package/dist/sections/PracticeQuiz/types.d.ts +6 -0
- package/dist/sections/ProgressDashboard/ProgressDashboard.d.ts +1 -1
- package/dist/sections/ProgressDashboard/types.d.ts +6 -0
- package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
- package/dist/sections/QuizSession/types.d.ts +6 -0
- package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +1 -1
- package/dist/sections/RequirementsChecklist/types.d.ts +6 -0
- package/dist/sections/ResourceLibrary/ResourceLibrary.d.ts +1 -1
- package/dist/sections/ResourceLibrary/types.d.ts +15 -1
- package/dist/sections/RubricView/RubricView.d.ts +1 -1
- package/dist/sections/RubricView/types.d.ts +6 -0
- package/dist/sections/ScrollableQuiz/ScrollableQuiz.d.ts +1 -1
- package/dist/sections/ScrollableQuiz/types.d.ts +6 -0
- package/dist/sections/StudentProfile/StudentProfile.d.ts +2 -0
- package/dist/sections/StudentProfile/types.d.ts +98 -0
- package/dist/sections/SurveyForm/SurveyForm.d.ts +1 -1
- package/dist/sections/SurveyForm/types.d.ts +6 -0
- package/dist/sections/_shared/merge-answers.d.ts +9 -0
- package/dist/sections/_shared/section-shell.d.ts +20 -0
- package/dist/sections/_shared/use-assessment-session.d.ts +30 -0
- package/dist/sections/index.d.ts +10 -0
- package/dist/sections.cjs +1 -1
- package/dist/sections.js +1361 -307
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/progress.d.ts +1 -1
- package/dist/ui/rich-text-editor.d.ts +3 -1
- package/dist/ui/toast.d.ts +43 -0
- package/dist/utils/debounce.d.ts +5 -1
- package/dist/utils/pick-palette-color.d.ts +19 -0
- package/dist/video/types.d.ts +15 -0
- package/dist/video/video-player.d.ts +1 -1
- package/dist/withProGate-BJdu1T9Y.cjs +2 -0
- package/dist/withProGate-BvFc7Jwy.js +4975 -0
- package/package.json +57 -226
- package/src/assessment-toolbar/question-navigator.tsx +10 -5
- package/src/assessment-toolbar/timer-display.tsx +4 -3
- package/src/assessment-toolbar/use-countdown.ts +1 -1
- package/src/common/empty-state.tsx +1 -0
- package/src/common/index.ts +2 -0
- package/src/common/pagination.tsx +135 -0
- package/src/common/search-input.tsx +2 -1
- package/src/common/types.ts +2 -0
- package/src/content/attachment-list.tsx +2 -0
- package/src/content/audio-player.tsx +196 -0
- package/src/content/code-block.tsx +113 -0
- package/src/content/content-block.tsx +64 -0
- package/src/content/embed-block.tsx +78 -0
- package/src/content/file-upload-zone.tsx +10 -0
- package/src/content/index.ts +6 -0
- package/src/content/types.ts +5 -0
- package/src/curriculum/course-card.tsx +199 -0
- package/src/curriculum/curriculum-item.tsx +3 -3
- package/src/curriculum/curriculum-tree.tsx +20 -13
- package/src/curriculum/index.ts +2 -0
- package/src/curriculum/types.ts +2 -2
- package/src/flashcards/flashcard.tsx +28 -8
- package/src/index.ts +3 -0
- package/src/license/HydraContext.tsx +62 -0
- package/src/license/ProBadge.tsx +43 -0
- package/src/license/index.ts +7 -0
- package/src/license/tiers.ts +34 -0
- package/src/license/useHydraLicense.ts +10 -0
- package/src/license/validate.ts +90 -0
- package/src/license/withProGate.tsx +21 -0
- package/src/modules/AssignmentModule/AssignmentModule.tsx +17 -8
- package/src/modules/AssignmentModule/types.ts +5 -1
- package/src/modules/CertificateModule/CertificateModule.tsx +21 -9
- package/src/modules/CertificateModule/types.ts +6 -4
- package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
- package/src/modules/CourseCatalogModule/types.ts +47 -0
- package/src/modules/CoursePlayer/CoursePlayer.tsx +39 -22
- package/src/modules/DiscussionModule/DiscussionModule.tsx +57 -22
- package/src/modules/ExamModule/ExamModule.tsx +64 -198
- package/src/modules/ExamModule/types.ts +5 -14
- package/src/modules/FlashcardLab/FlashcardLab.tsx +10 -5
- package/src/modules/FlashcardLab/types.ts +2 -0
- package/src/modules/GradeCenterModule/GradeCenterModule.tsx +7 -2
- package/src/modules/GradeCenterModule/types.ts +2 -0
- package/src/modules/QuizModule/QuizModule.tsx +49 -169
- package/src/modules/QuizModule/types.ts +5 -15
- package/src/modules/StudentDashboardModule/StudentDashboardModule.tsx +117 -0
- package/src/modules/StudentDashboardModule/types.ts +56 -0
- package/src/modules/StudentProfileModule/StudentProfileModule.tsx +289 -0
- package/src/modules/StudentProfileModule/types.ts +45 -0
- package/src/modules/SurveyModule/SurveyModule.tsx +9 -4
- package/src/modules/SurveyModule/types.ts +2 -0
- package/src/modules/_shared/assessment-intro.tsx +75 -0
- package/src/modules/_shared/assessment-results.tsx +133 -0
- package/src/modules/_shared/types.ts +11 -0
- package/src/modules/_shared/use-timer.ts +49 -0
- package/src/modules/index.ts +9 -0
- package/src/progress/achievement-badge.tsx +3 -3
- package/src/progress/grade-indicator.tsx +9 -1
- package/src/progress/progress-ring.tsx +2 -1
- package/src/progress/stat-card.tsx +14 -2
- package/src/progress/types.ts +2 -0
- package/src/provider/HydraProvider.tsx +15 -6
- package/src/questions/choice.tsx +13 -6
- package/src/questions/confidence-indicator.tsx +107 -0
- package/src/questions/essay.tsx +6 -4
- package/src/questions/fill-in-the-blank.tsx +8 -4
- package/src/questions/hotspot.tsx +4 -4
- package/src/questions/index.ts +2 -0
- package/src/questions/inline-choice.tsx +5 -4
- package/src/questions/matching.tsx +5 -4
- package/src/questions/multiple-choice.tsx +13 -6
- package/src/questions/numeric.tsx +8 -4
- package/src/questions/ordering.tsx +12 -4
- package/src/questions/question-renderer.tsx +3 -2
- package/src/questions/scenario.tsx +4 -4
- package/src/questions/spreadsheet.tsx +5 -4
- package/src/questions/true-false.tsx +13 -6
- package/src/sections/AdaptiveLearningPath/AdaptiveLearningPath.tsx +251 -0
- package/src/sections/AdaptiveLearningPath/path-connector.tsx +27 -0
- package/src/sections/AdaptiveLearningPath/path-milestone-marker.tsx +50 -0
- package/src/sections/AdaptiveLearningPath/path-node-card.tsx +166 -0
- package/src/sections/AdaptiveLearningPath/path-skill-bar.tsx +49 -0
- package/src/sections/AdaptiveLearningPath/types.ts +159 -0
- package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +64 -8
- package/src/sections/AnnouncementFeed/types.ts +15 -1
- package/src/sections/AssessmentReview/AssessmentReview.tsx +37 -0
- package/src/sections/AssessmentReview/types.ts +6 -0
- package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +37 -1
- package/src/sections/AssignmentSubmission/types.ts +6 -0
- package/src/sections/CertificateViewer/CertificateViewer.tsx +29 -227
- package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
- package/src/sections/CertificateViewer/types.ts +6 -0
- package/src/sections/ContentAuthoringStudio/ContentAuthoringStudio.tsx +289 -0
- package/src/sections/ContentAuthoringStudio/block-editor-item.tsx +487 -0
- package/src/sections/ContentAuthoringStudio/block-type-picker.tsx +123 -0
- package/src/sections/ContentAuthoringStudio/types.ts +67 -0
- package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
- package/src/sections/CourseCatalog/types.ts +76 -0
- package/src/sections/CourseOutline/CourseOutline.tsx +41 -0
- package/src/sections/CourseOutline/types.ts +6 -0
- package/src/sections/DiscussionThread/DiscussionThread.tsx +42 -1
- package/src/sections/DiscussionThread/types.ts +6 -0
- package/src/sections/EnrollmentWizard/EnrollmentWizard.tsx +343 -0
- package/src/sections/EnrollmentWizard/types.ts +65 -0
- package/src/sections/ExamSession/ExamSession.tsx +100 -94
- package/src/sections/ExamSession/types.ts +6 -0
- package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
- package/src/sections/FlashcardStudySession/types.ts +6 -0
- package/src/sections/ForumBoard/ForumBoard.tsx +67 -7
- package/src/sections/ForumBoard/types.ts +14 -0
- package/src/sections/GradebookTable/GradebookTable.tsx +54 -1
- package/src/sections/GradebookTable/types.ts +14 -0
- package/src/sections/LecturePlayer/LecturePlayer.tsx +63 -37
- package/src/sections/LecturePlayer/types.ts +8 -0
- package/src/sections/LessonPage/LessonPage.tsx +34 -6
- package/src/sections/LessonPage/types.ts +6 -0
- package/src/sections/PracticeQuiz/PracticeQuiz.tsx +106 -74
- package/src/sections/PracticeQuiz/types.ts +6 -0
- package/src/sections/ProgressDashboard/ProgressDashboard.tsx +64 -10
- package/src/sections/ProgressDashboard/types.ts +6 -0
- package/src/sections/QuizSession/QuizSession.tsx +71 -82
- package/src/sections/QuizSession/types.ts +6 -0
- package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +41 -1
- package/src/sections/RequirementsChecklist/types.ts +6 -0
- package/src/sections/ResourceLibrary/ResourceLibrary.tsx +64 -8
- package/src/sections/ResourceLibrary/types.ts +15 -1
- package/src/sections/RubricView/RubricView.tsx +37 -1
- package/src/sections/RubricView/types.ts +6 -0
- package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +36 -15
- package/src/sections/ScrollableQuiz/types.ts +6 -0
- package/src/sections/StudentProfile/StudentProfile.tsx +279 -0
- package/src/sections/StudentProfile/types.ts +99 -0
- package/src/sections/SurveyForm/SurveyForm.tsx +32 -5
- package/src/sections/SurveyForm/types.ts +6 -0
- package/src/sections/_shared/merge-answers.ts +22 -0
- package/src/sections/_shared/section-shell.tsx +64 -0
- package/src/sections/_shared/use-assessment-session.ts +125 -0
- package/src/sections/index.ts +40 -0
- package/src/social/user-avatar.tsx +9 -5
- package/src/styles/globals.css +39 -41
- package/src/ui/badge.tsx +8 -0
- package/src/ui/index.ts +2 -0
- package/src/ui/progress.tsx +4 -0
- package/src/ui/rich-text-editor.tsx +10 -0
- package/src/ui/rich-text-toolbar.tsx +2 -1
- package/src/ui/toast.tsx +170 -0
- package/src/utils/debounce.ts +8 -2
- package/src/utils/pick-palette-color.ts +33 -0
- package/src/video/types.ts +16 -0
- package/src/video/video-player.tsx +27 -6
- package/dist/ForumBoard-CHXU3mjC.js +0 -2207
- package/dist/ForumBoard-d1w5-r6n.cjs +0 -1
- package/dist/tabs-DRM2Iq_J.cjs +0 -172
- package/dist/tabs-Wf3h_Cx3.js +0 -21580
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { CheckCircle } from "lucide-react";
|
|
3
3
|
import { FlashcardDeck } from "../../flashcards";
|
|
4
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
4
5
|
import { Button } from "../../ui/button";
|
|
5
6
|
import { Card, CardContent } from "../../ui/card";
|
|
7
|
+
import { SectionShell } from "../_shared/section-shell";
|
|
6
8
|
import type {
|
|
7
9
|
FlashcardStudySessionProps,
|
|
8
10
|
FlashcardSessionStats,
|
|
@@ -16,6 +18,9 @@ export function FlashcardStudySession({
|
|
|
16
18
|
shuffled = false,
|
|
17
19
|
onComplete,
|
|
18
20
|
readOnly = false,
|
|
21
|
+
isLoading,
|
|
22
|
+
error,
|
|
23
|
+
onRetry,
|
|
19
24
|
className,
|
|
20
25
|
style,
|
|
21
26
|
}: FlashcardStudySessionProps) {
|
|
@@ -35,42 +40,54 @@ export function FlashcardStudySession({
|
|
|
35
40
|
setIsComplete(false);
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
if (isComplete) {
|
|
39
|
-
return (
|
|
40
|
-
<div className={cn("flex flex-col items-center", className)} style={style}>
|
|
41
|
-
<Card>
|
|
42
|
-
<CardContent className="pt-6 text-center flex flex-col items-center gap-2">
|
|
43
|
-
<CheckCircle size={48} className="text-success" />
|
|
44
|
-
<span className="text-xl font-bold text-foreground">Deck complete!</span>
|
|
45
|
-
{title && (
|
|
46
|
-
<span className="text-muted-foreground">
|
|
47
|
-
You finished <strong>{title}</strong>
|
|
48
|
-
</span>
|
|
49
|
-
)}
|
|
50
|
-
<span className="text-sm text-muted-foreground">
|
|
51
|
-
{stats.totalCards} card{stats.totalCards !== 1 ? "s" : ""} studied
|
|
52
|
-
{stats.wasShuffled ? " (shuffled)" : ""}
|
|
53
|
-
</span>
|
|
54
|
-
<Button className="mt-2" onClick={handleStudyAgain}>
|
|
55
|
-
Study Again
|
|
56
|
-
</Button>
|
|
57
|
-
</CardContent>
|
|
58
|
-
</Card>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
43
|
return (
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
44
|
+
<SectionShell
|
|
45
|
+
isLoading={isLoading}
|
|
46
|
+
error={error}
|
|
47
|
+
onRetry={onRetry}
|
|
48
|
+
className={className}
|
|
49
|
+
style={style}
|
|
50
|
+
skeleton={
|
|
51
|
+
<>
|
|
52
|
+
<Skeleton className="h-6 w-48" />
|
|
53
|
+
<Skeleton className="h-64 w-full rounded-lg" />
|
|
54
|
+
</>
|
|
55
|
+
}
|
|
56
|
+
>
|
|
57
|
+
{isComplete ? (
|
|
58
|
+
<div className={cn("flex flex-col items-center", className)} style={style}>
|
|
59
|
+
<Card>
|
|
60
|
+
<CardContent className="pt-6 text-center flex flex-col items-center gap-2">
|
|
61
|
+
<CheckCircle size={48} className="text-success" />
|
|
62
|
+
<span className="text-xl font-bold text-foreground">Deck complete!</span>
|
|
63
|
+
{title && (
|
|
64
|
+
<span className="text-muted-foreground">
|
|
65
|
+
You finished <strong>{title}</strong>
|
|
66
|
+
</span>
|
|
67
|
+
)}
|
|
68
|
+
<span className="text-sm text-muted-foreground">
|
|
69
|
+
{stats.totalCards} card{stats.totalCards !== 1 ? "s" : ""} studied
|
|
70
|
+
{stats.wasShuffled ? " (shuffled)" : ""}
|
|
71
|
+
</span>
|
|
72
|
+
<Button className="mt-2" onClick={handleStudyAgain}>
|
|
73
|
+
Study Again
|
|
74
|
+
</Button>
|
|
75
|
+
</CardContent>
|
|
76
|
+
</Card>
|
|
77
|
+
</div>
|
|
78
|
+
) : (
|
|
79
|
+
<div className={cn("flex flex-col items-center", className)} style={style}>
|
|
80
|
+
<FlashcardDeck
|
|
81
|
+
cards={cards}
|
|
82
|
+
deckName={title}
|
|
83
|
+
deckDescription={description}
|
|
84
|
+
shuffled={shuffled}
|
|
85
|
+
showProgress
|
|
86
|
+
onComplete={handleComplete}
|
|
87
|
+
readOnly={readOnly}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</SectionShell>
|
|
75
92
|
);
|
|
76
93
|
}
|
|
@@ -28,6 +28,12 @@ export interface FlashcardStudySessionProps {
|
|
|
28
28
|
onComplete?: (stats: FlashcardSessionStats) => void;
|
|
29
29
|
/** When true, disables card flipping */
|
|
30
30
|
readOnly?: boolean;
|
|
31
|
+
/** Render skeleton placeholders instead of content */
|
|
32
|
+
isLoading?: boolean;
|
|
33
|
+
/** Error message — renders an error state with optional retry */
|
|
34
|
+
error?: string | null;
|
|
35
|
+
/** Called when the user clicks retry in the error state */
|
|
36
|
+
onRetry?: () => void;
|
|
31
37
|
/** CSS class name for the root element */
|
|
32
38
|
className?: string;
|
|
33
39
|
/** Inline styles for the root element */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useState, useMemo } from "react";
|
|
1
|
+
import { useState, useMemo, memo, useCallback } from "react";
|
|
2
2
|
import {
|
|
3
|
+
AlertCircle,
|
|
3
4
|
Plus,
|
|
4
5
|
MessageSquare,
|
|
5
6
|
Heart,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
CheckCircle2,
|
|
8
9
|
ArrowUpDown,
|
|
9
10
|
} from "lucide-react";
|
|
11
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
10
12
|
import { SearchInput } from "../../common/search-input";
|
|
11
13
|
import { UserAvatar } from "../../social/user-avatar";
|
|
12
14
|
import { EmptyState } from "../../common/empty-state";
|
|
@@ -19,6 +21,7 @@ import { RichTextEditor } from "../../ui/rich-text-editor";
|
|
|
19
21
|
import { isEmptyHtml } from "../../utils/is-empty-html";
|
|
20
22
|
import { cn } from "../../lib/utils";
|
|
21
23
|
import { formatTimestamp } from "../../utils/format-timestamp";
|
|
24
|
+
import { Pagination } from "../../common/pagination";
|
|
22
25
|
import type { ForumBoardProps, ForumTopic, ForumSortOrder } from "./types";
|
|
23
26
|
|
|
24
27
|
const SORT_LABELS: Record<ForumSortOrder, string> = {
|
|
@@ -44,6 +47,13 @@ export function ForumBoard({
|
|
|
44
47
|
searchQuery = "",
|
|
45
48
|
onSearchChange,
|
|
46
49
|
readOnly,
|
|
50
|
+
isLoading,
|
|
51
|
+
error,
|
|
52
|
+
onRetry,
|
|
53
|
+
pageSize,
|
|
54
|
+
currentPage = 1,
|
|
55
|
+
totalItems,
|
|
56
|
+
onPageChange,
|
|
47
57
|
className,
|
|
48
58
|
style,
|
|
49
59
|
}: ForumBoardProps) {
|
|
@@ -117,6 +127,42 @@ export function ForumBoard({
|
|
|
117
127
|
});
|
|
118
128
|
}, [topics, activeSearch, activeSort]);
|
|
119
129
|
|
|
130
|
+
if (isLoading) {
|
|
131
|
+
return (
|
|
132
|
+
<div className={cn("space-y-4", className)} style={style}>
|
|
133
|
+
<Skeleton className="h-9 w-full" />
|
|
134
|
+
{Array.from({ length: 4 }).map((_, i) => (
|
|
135
|
+
<div key={i} className="flex items-center gap-3">
|
|
136
|
+
<Skeleton className="h-8 w-8 rounded-full" />
|
|
137
|
+
<div className="flex-1 space-y-2">
|
|
138
|
+
<Skeleton className="h-5 w-64" />
|
|
139
|
+
<Skeleton className="h-4 w-32" />
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (error) {
|
|
148
|
+
return (
|
|
149
|
+
<div className={cn("py-12", className)} style={style}>
|
|
150
|
+
<EmptyState
|
|
151
|
+
icon={<AlertCircle className="size-10 text-destructive" />}
|
|
152
|
+
title="Something went wrong"
|
|
153
|
+
description={error}
|
|
154
|
+
action={
|
|
155
|
+
onRetry ? (
|
|
156
|
+
<Button variant="outline" onClick={onRetry}>
|
|
157
|
+
Retry
|
|
158
|
+
</Button>
|
|
159
|
+
) : undefined
|
|
160
|
+
}
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
120
166
|
return (
|
|
121
167
|
<div className={cn("flex flex-col gap-4", className)} style={style}>
|
|
122
168
|
{/* Header */}
|
|
@@ -204,31 +250,45 @@ export function ForumBoard({
|
|
|
204
250
|
/>
|
|
205
251
|
) : (
|
|
206
252
|
<div className="flex flex-col gap-2">
|
|
207
|
-
{
|
|
253
|
+
{(onPageChange && pageSize
|
|
254
|
+
? sorted.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
|
255
|
+
: sorted
|
|
256
|
+
).map((topic) => (
|
|
208
257
|
<TopicRow
|
|
209
258
|
key={topic.uid}
|
|
210
259
|
topic={topic}
|
|
211
|
-
onClick={
|
|
260
|
+
onClick={onTopicClick}
|
|
212
261
|
/>
|
|
213
262
|
))}
|
|
214
263
|
</div>
|
|
215
264
|
)}
|
|
265
|
+
|
|
266
|
+
{onPageChange && pageSize && sorted.length > 0 && (
|
|
267
|
+
<Pagination
|
|
268
|
+
currentPage={currentPage}
|
|
269
|
+
totalPages={Math.ceil((totalItems ?? sorted.length) / pageSize)}
|
|
270
|
+
onPageChange={onPageChange}
|
|
271
|
+
className="mt-4"
|
|
272
|
+
/>
|
|
273
|
+
)}
|
|
216
274
|
</div>
|
|
217
275
|
);
|
|
218
276
|
}
|
|
219
277
|
|
|
220
|
-
function TopicRow({
|
|
278
|
+
const TopicRow = memo(function TopicRow({
|
|
221
279
|
topic,
|
|
222
280
|
onClick,
|
|
223
281
|
}: {
|
|
224
282
|
topic: ForumTopic;
|
|
225
|
-
onClick: () => void;
|
|
283
|
+
onClick: (uid: string) => void;
|
|
226
284
|
}) {
|
|
285
|
+
const handleClick = useCallback(() => onClick(topic.uid), [onClick, topic.uid]);
|
|
286
|
+
|
|
227
287
|
return (
|
|
228
288
|
<button
|
|
229
289
|
type="button"
|
|
230
290
|
className="w-full text-left"
|
|
231
|
-
onClick={
|
|
291
|
+
onClick={handleClick}
|
|
232
292
|
>
|
|
233
293
|
<Card
|
|
234
294
|
className={cn(
|
|
@@ -281,4 +341,4 @@ function TopicRow({
|
|
|
281
341
|
</Card>
|
|
282
342
|
</button>
|
|
283
343
|
);
|
|
284
|
-
}
|
|
344
|
+
});
|
|
@@ -37,6 +37,20 @@ export interface ForumBoardProps {
|
|
|
37
37
|
onSearchChange?: (query: string) => void;
|
|
38
38
|
/** When true, disables create and interactions */
|
|
39
39
|
readOnly?: boolean;
|
|
40
|
+
/** Render skeleton placeholders instead of content */
|
|
41
|
+
isLoading?: boolean;
|
|
42
|
+
/** Error message — renders an error state with optional retry */
|
|
43
|
+
error?: string | null;
|
|
44
|
+
/** Called when the user clicks retry in the error state */
|
|
45
|
+
onRetry?: () => void;
|
|
46
|
+
/** Number of items per page (enables pagination when set with onPageChange) */
|
|
47
|
+
pageSize?: number;
|
|
48
|
+
/** Current page (1-indexed) */
|
|
49
|
+
currentPage?: number;
|
|
50
|
+
/** Total number of items (for server-side pagination) */
|
|
51
|
+
totalItems?: number;
|
|
52
|
+
/** Called when the user navigates to a different page */
|
|
53
|
+
onPageChange?: (page: number) => void;
|
|
40
54
|
/** CSS class name for the root element */
|
|
41
55
|
className?: string;
|
|
42
56
|
/** Inline styles for the root element */
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { Fragment, useMemo, useState } from "react";
|
|
2
|
-
import { ArrowUp, ArrowDown } from "lucide-react";
|
|
2
|
+
import { ArrowUp, ArrowDown, AlertCircle } from "lucide-react";
|
|
3
3
|
import { GradeIndicator } from "../../progress";
|
|
4
4
|
import { StatusBadge, DueDateDisplay } from "../../common";
|
|
5
5
|
import { Card, CardContent } from "../../ui/card";
|
|
6
6
|
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "../../ui/table";
|
|
7
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
8
|
+
import { Button } from "../../ui/button";
|
|
9
|
+
import { EmptyState } from "../../common/empty-state";
|
|
10
|
+
import { Pagination } from "../../common/pagination";
|
|
7
11
|
import type { GradebookTableProps, GradeCategory } from "./types";
|
|
8
12
|
import { cn } from "../../lib/utils";
|
|
9
13
|
|
|
@@ -53,6 +57,13 @@ export function GradebookTable({
|
|
|
53
57
|
showCategoryTotals = true,
|
|
54
58
|
onItemClick,
|
|
55
59
|
readOnly = false,
|
|
60
|
+
isLoading,
|
|
61
|
+
error,
|
|
62
|
+
onRetry,
|
|
63
|
+
pageSize,
|
|
64
|
+
currentPage = 1,
|
|
65
|
+
totalItems,
|
|
66
|
+
onPageChange,
|
|
56
67
|
className,
|
|
57
68
|
style,
|
|
58
69
|
}: GradebookTableProps) {
|
|
@@ -104,6 +115,39 @@ export function GradebookTable({
|
|
|
104
115
|
|
|
105
116
|
const colCount = showWeights ? 5 : 4;
|
|
106
117
|
|
|
118
|
+
if (isLoading) {
|
|
119
|
+
return (
|
|
120
|
+
<div className={cn("space-y-4", className)} style={style}>
|
|
121
|
+
<Skeleton className="h-20 w-full" />
|
|
122
|
+
<Skeleton className="h-10 w-full" />
|
|
123
|
+
<Skeleton className="h-12 w-full" />
|
|
124
|
+
<Skeleton className="h-12 w-full" />
|
|
125
|
+
<Skeleton className="h-12 w-full" />
|
|
126
|
+
<Skeleton className="h-12 w-full" />
|
|
127
|
+
<Skeleton className="h-12 w-full" />
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (error) {
|
|
133
|
+
return (
|
|
134
|
+
<div className={cn("py-12", className)} style={style}>
|
|
135
|
+
<EmptyState
|
|
136
|
+
icon={<AlertCircle className="size-10 text-destructive" />}
|
|
137
|
+
title="Something went wrong"
|
|
138
|
+
description={error}
|
|
139
|
+
action={
|
|
140
|
+
onRetry ? (
|
|
141
|
+
<Button variant="outline" onClick={onRetry}>
|
|
142
|
+
Retry
|
|
143
|
+
</Button>
|
|
144
|
+
) : undefined
|
|
145
|
+
}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
107
151
|
return (
|
|
108
152
|
<div className={className} style={style}>
|
|
109
153
|
{/* Overall grade */}
|
|
@@ -224,6 +268,15 @@ export function GradebookTable({
|
|
|
224
268
|
</TableBody>
|
|
225
269
|
</Table>
|
|
226
270
|
</Card>
|
|
271
|
+
|
|
272
|
+
{onPageChange && pageSize && (
|
|
273
|
+
<Pagination
|
|
274
|
+
currentPage={currentPage}
|
|
275
|
+
totalPages={Math.ceil((totalItems ?? items.length) / pageSize)}
|
|
276
|
+
onPageChange={onPageChange}
|
|
277
|
+
className="mt-4"
|
|
278
|
+
/>
|
|
279
|
+
)}
|
|
227
280
|
</div>
|
|
228
281
|
);
|
|
229
282
|
}
|
|
@@ -27,6 +27,20 @@ export interface GradebookTableProps {
|
|
|
27
27
|
onItemClick?: (item: GradeItem) => void;
|
|
28
28
|
/** When true, disables interactions */
|
|
29
29
|
readOnly?: boolean;
|
|
30
|
+
/** Render skeleton placeholders instead of content */
|
|
31
|
+
isLoading?: boolean;
|
|
32
|
+
/** Error message — renders an error state with optional retry */
|
|
33
|
+
error?: string | null;
|
|
34
|
+
/** Called when the user clicks retry in the error state */
|
|
35
|
+
onRetry?: () => void;
|
|
36
|
+
/** Number of items per page (enables pagination when set with onPageChange) */
|
|
37
|
+
pageSize?: number;
|
|
38
|
+
/** Current page (1-indexed) */
|
|
39
|
+
currentPage?: number;
|
|
40
|
+
/** Total number of items (for server-side pagination) */
|
|
41
|
+
totalItems?: number;
|
|
42
|
+
/** Called when the user navigates to a different page */
|
|
43
|
+
onPageChange?: (page: number) => void;
|
|
30
44
|
/** CSS class name for the root element */
|
|
31
45
|
className?: string;
|
|
32
46
|
/** Inline styles for the root element */
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
1
2
|
import { VideoPlayer } from "../../video";
|
|
2
3
|
import { Card, CardHeader, CardTitle, CardContent } from "../../ui/card";
|
|
4
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
5
|
+
import { SectionShell } from "../_shared/section-shell";
|
|
3
6
|
import type { LecturePlayerProps } from "./types";
|
|
4
7
|
import { cn } from "../../lib/utils";
|
|
5
8
|
|
|
@@ -9,52 +12,75 @@ export function LecturePlayer({
|
|
|
9
12
|
layout = "horizontal",
|
|
10
13
|
notesPanelWidth = "340px",
|
|
11
14
|
notesPanelHeight = "240px",
|
|
15
|
+
isLoading,
|
|
16
|
+
error,
|
|
17
|
+
onRetry,
|
|
18
|
+
onComplete,
|
|
12
19
|
className,
|
|
13
20
|
style,
|
|
14
21
|
}: LecturePlayerProps) {
|
|
15
22
|
const isHorizontal = layout === "horizontal";
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</div>
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
+
const handleEnded = useCallback(() => {
|
|
25
|
+
video.onEnded?.();
|
|
26
|
+
onComplete?.();
|
|
27
|
+
}, [video, onComplete]);
|
|
24
28
|
|
|
25
29
|
return (
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)}
|
|
30
|
+
<SectionShell
|
|
31
|
+
isLoading={isLoading}
|
|
32
|
+
error={error}
|
|
33
|
+
onRetry={onRetry}
|
|
34
|
+
className={className}
|
|
32
35
|
style={style}
|
|
36
|
+
skeleton={
|
|
37
|
+
<>
|
|
38
|
+
<Skeleton className="aspect-video w-full rounded-lg" />
|
|
39
|
+
{notes !== undefined && (
|
|
40
|
+
<Skeleton className="h-32 w-full rounded-lg" />
|
|
41
|
+
)}
|
|
42
|
+
</>
|
|
43
|
+
}
|
|
33
44
|
>
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
height: isHorizontal ? undefined : notesPanelHeight,
|
|
45
|
-
}}
|
|
46
|
-
>
|
|
47
|
-
<CardHeader>
|
|
48
|
-
<CardTitle>Notes</CardTitle>
|
|
49
|
-
</CardHeader>
|
|
50
|
-
<CardContent>
|
|
51
|
-
{typeof notes === "string" ? (
|
|
52
|
-
<span className="text-sm text-foreground">{notes}</span>
|
|
53
|
-
) : (
|
|
54
|
-
notes
|
|
45
|
+
{!notes ? (
|
|
46
|
+
<div className={className} style={style}>
|
|
47
|
+
<VideoPlayer {...video} onEnded={handleEnded} />
|
|
48
|
+
</div>
|
|
49
|
+
) : (
|
|
50
|
+
<div
|
|
51
|
+
className={cn(
|
|
52
|
+
"flex overflow-hidden gap-4",
|
|
53
|
+
isHorizontal ? "flex-row" : "flex-col",
|
|
54
|
+
className,
|
|
55
55
|
)}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
style={style}
|
|
57
|
+
>
|
|
58
|
+
<div className="flex-1 min-w-0 min-h-0">
|
|
59
|
+
<VideoPlayer {...video} onEnded={handleEnded} />
|
|
60
|
+
</div>
|
|
61
|
+
<Card
|
|
62
|
+
className={cn(
|
|
63
|
+
"overflow-auto shrink-0 rounded-none border-0",
|
|
64
|
+
isHorizontal ? "border-l border-border" : "border-t border-border w-full",
|
|
65
|
+
)}
|
|
66
|
+
style={{
|
|
67
|
+
width: isHorizontal ? notesPanelWidth : undefined,
|
|
68
|
+
height: isHorizontal ? undefined : notesPanelHeight,
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<CardHeader>
|
|
72
|
+
<CardTitle>Notes</CardTitle>
|
|
73
|
+
</CardHeader>
|
|
74
|
+
<CardContent>
|
|
75
|
+
{typeof notes === "string" ? (
|
|
76
|
+
<span className="text-sm text-foreground">{notes}</span>
|
|
77
|
+
) : (
|
|
78
|
+
notes
|
|
79
|
+
)}
|
|
80
|
+
</CardContent>
|
|
81
|
+
</Card>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
</SectionShell>
|
|
59
85
|
);
|
|
60
86
|
}
|
|
@@ -41,6 +41,14 @@ export interface LecturePlayerProps {
|
|
|
41
41
|
* @default "240px"
|
|
42
42
|
*/
|
|
43
43
|
notesPanelHeight?: string | number;
|
|
44
|
+
/** Render skeleton placeholders instead of content */
|
|
45
|
+
isLoading?: boolean;
|
|
46
|
+
/** Error message — renders an error state with optional retry */
|
|
47
|
+
error?: string | null;
|
|
48
|
+
/** Called when the user clicks retry in the error state */
|
|
49
|
+
onRetry?: () => void;
|
|
50
|
+
/** Called when the video playback ends. */
|
|
51
|
+
onComplete?: () => void;
|
|
44
52
|
/** CSS class name for the root element */
|
|
45
53
|
className?: string;
|
|
46
54
|
/** Inline styles for the root element */
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { Check, ChevronRight, Clock } from "lucide-react";
|
|
3
3
|
import { ContentBlock } from "../../content";
|
|
4
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
5
|
+
import { EmptyState } from "../../common/empty-state";
|
|
4
6
|
import { Button } from "../../ui/button";
|
|
5
7
|
import { Card, CardContent } from "../../ui/card";
|
|
6
8
|
import { Separator } from "../../ui/separator";
|
|
9
|
+
import { SectionShell } from "../_shared/section-shell";
|
|
7
10
|
import { formatDuration } from "../../utils/format-duration";
|
|
8
11
|
import type { LessonPageProps } from "./types";
|
|
9
|
-
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
export function LessonPage({
|
|
12
15
|
title,
|
|
@@ -18,18 +21,37 @@ export function LessonPage({
|
|
|
18
21
|
estimatedDuration,
|
|
19
22
|
showDuration = true,
|
|
20
23
|
readOnly = false,
|
|
24
|
+
isLoading,
|
|
25
|
+
error,
|
|
26
|
+
onRetry,
|
|
21
27
|
className,
|
|
22
28
|
style,
|
|
23
29
|
}: LessonPageProps) {
|
|
24
|
-
const [
|
|
30
|
+
const [localCompleted, setLocalCompleted] = useState(false);
|
|
31
|
+
const completed = isCompleted || localCompleted;
|
|
25
32
|
|
|
26
33
|
function handleMarkComplete() {
|
|
27
|
-
|
|
34
|
+
setLocalCompleted(true);
|
|
28
35
|
onMarkComplete?.();
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
return (
|
|
32
|
-
<
|
|
39
|
+
<SectionShell
|
|
40
|
+
isLoading={isLoading}
|
|
41
|
+
error={error}
|
|
42
|
+
onRetry={onRetry}
|
|
43
|
+
className={className}
|
|
44
|
+
style={style}
|
|
45
|
+
skeleton={
|
|
46
|
+
<>
|
|
47
|
+
<Skeleton className="h-8 w-64" />
|
|
48
|
+
<Skeleton className="h-24 w-full" />
|
|
49
|
+
<Skeleton className="h-16 w-full" />
|
|
50
|
+
<Skeleton className="h-32 w-full" />
|
|
51
|
+
</>
|
|
52
|
+
}
|
|
53
|
+
>
|
|
54
|
+
<div>
|
|
33
55
|
{/* Header */}
|
|
34
56
|
<div className="mb-3">
|
|
35
57
|
<p className="text-2xl font-bold mb-0.5 text-foreground">{title}</p>
|
|
@@ -45,13 +67,18 @@ export function LessonPage({
|
|
|
45
67
|
|
|
46
68
|
{/* Content blocks */}
|
|
47
69
|
<div className="flex flex-col gap-3">
|
|
48
|
-
{blocks.map((block, i) => (
|
|
70
|
+
{blocks?.length ? blocks.map((block, i) => (
|
|
49
71
|
<ContentBlock
|
|
50
72
|
key={i}
|
|
51
73
|
block={block}
|
|
52
74
|
readOnly={readOnly}
|
|
53
75
|
/>
|
|
54
|
-
))
|
|
76
|
+
)) : (
|
|
77
|
+
<EmptyState
|
|
78
|
+
title="No content yet"
|
|
79
|
+
description="This lesson doesn't have any content blocks."
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
55
82
|
</div>
|
|
56
83
|
|
|
57
84
|
{/* Completion bar */}
|
|
@@ -83,5 +110,6 @@ export function LessonPage({
|
|
|
83
110
|
</CardContent>
|
|
84
111
|
</Card>
|
|
85
112
|
</div>
|
|
113
|
+
</SectionShell>
|
|
86
114
|
);
|
|
87
115
|
}
|
|
@@ -34,6 +34,12 @@ export interface LessonPageProps {
|
|
|
34
34
|
showDuration?: boolean;
|
|
35
35
|
/** When true, disables interactive elements */
|
|
36
36
|
readOnly?: boolean;
|
|
37
|
+
/** Render skeleton placeholders instead of content */
|
|
38
|
+
isLoading?: boolean;
|
|
39
|
+
/** Error message — renders an error state with optional retry */
|
|
40
|
+
error?: string | null;
|
|
41
|
+
/** Called when the user clicks retry in the error state */
|
|
42
|
+
onRetry?: () => void;
|
|
37
43
|
/** CSS class name for the root element */
|
|
38
44
|
className?: string;
|
|
39
45
|
/** Inline styles for the root element */
|