@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.
- package/dist/StudentProfile-BVfZMbnV.cjs +1 -0
- package/dist/StudentProfile-DeMxdrL3.js +3275 -0
- package/dist/assessment-toolbar/assessment-toolbar.d.ts +1 -1
- package/dist/assessment-toolbar/index.d.ts +5 -1
- package/dist/assessment-toolbar/question-header-bar.d.ts +2 -0
- package/dist/assessment-toolbar/question-materials-drawer.d.ts +2 -0
- package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
- package/dist/assessment-toolbar/timer-display.d.ts +1 -1
- package/dist/assessment-toolbar/types.d.ts +52 -4
- package/dist/assessment-toolbar/use-countdown.d.ts +43 -0
- package/dist/common/index.d.ts +3 -1
- package/dist/common/pagination.d.ts +26 -0
- package/dist/common/stepper.d.ts +6 -0
- package/dist/common/types.d.ts +38 -0
- package/dist/components.css +1 -1
- package/dist/content/attachment-list.d.ts +6 -0
- package/dist/content/audio-player.d.ts +22 -0
- package/dist/content/code-block.d.ts +30 -0
- package/dist/content/content-block.d.ts +1 -1
- package/dist/content/embed-block.d.ts +28 -0
- package/dist/content/index.d.ts +8 -1
- package/dist/content/types.d.ts +63 -0
- package/dist/curriculum/course-card.d.ts +51 -0
- package/dist/curriculum/curriculum-item.d.ts +1 -1
- 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 +597 -308
- 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 +3 -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 +5 -0
- package/dist/modules/AssignmentModule/types.d.ts +69 -0
- package/dist/modules/CertificateModule/CertificateModule.d.ts +5 -0
- package/dist/modules/CertificateModule/types.d.ts +51 -0
- 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 +5 -0
- package/dist/modules/DiscussionModule/types.d.ts +47 -0
- package/dist/modules/ExamModule/ExamModule.d.ts +5 -0
- package/dist/modules/ExamModule/types.d.ts +55 -0
- 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 +5 -0
- package/dist/modules/GradeCenterModule/types.d.ts +56 -0
- package/dist/modules/QuizModule/QuizModule.d.ts +4 -1
- package/dist/modules/QuizModule/types.d.ts +10 -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 +5 -0
- package/dist/modules/SurveyModule/types.d.ts +51 -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 +18 -0
- package/dist/modules.cjs +1 -0
- package/dist/modules.js +1834 -0
- package/dist/progress/achievement-badge.d.ts +6 -0
- package/dist/progress/activity-timeline.d.ts +6 -0
- package/dist/progress/index.d.ts +4 -1
- package/dist/progress/stat-card.d.ts +1 -1
- package/dist/progress/streak-badge.d.ts +6 -0
- package/dist/progress/types.d.ts +99 -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 +2 -2
- package/dist/questions/fill-in-the-blank.d.ts +1 -1
- package/dist/questions/hotspot.d.ts +21 -0
- package/dist/questions/index.d.ts +11 -1
- package/dist/questions/inline-choice.d.ts +21 -0
- package/dist/questions/matching.d.ts +22 -0
- package/dist/questions/multiple-choice.d.ts +1 -1
- package/dist/questions/numeric.d.ts +11 -0
- package/dist/questions/ordering.d.ts +12 -0
- package/dist/questions/question-renderer.d.ts +1 -1
- package/dist/questions/scenario.d.ts +23 -0
- package/dist/questions/scoring.d.ts +22 -0
- package/dist/questions/spreadsheet.d.ts +29 -0
- package/dist/questions/true-false.d.ts +1 -1
- package/dist/questions/types.d.ts +106 -1
- package/dist/questions/use-drag-reorder.d.ts +17 -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 +13 -5
- 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 +12 -1
- 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 +8 -0
- package/dist/sections/ForumBoard/types.d.ts +78 -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 +12 -1
- package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +8 -0
- package/dist/sections/RequirementsChecklist/types.d.ts +43 -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 +9 -0
- package/dist/sections/RubricView/types.d.ts +56 -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 +13 -1
- package/dist/sections.cjs +1 -1
- package/dist/sections.js +282 -1786
- package/dist/social/post-card.d.ts +1 -1
- package/dist/tabs-BsfVo2Bl.cjs +173 -0
- package/dist/tabs-BuY1iNJE.js +22305 -0
- package/dist/ui/alert.d.ts +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/button.d.ts +1 -1
- package/dist/ui/drawer.d.ts +84 -0
- package/dist/ui/index.d.ts +5 -0
- package/dist/ui/progress.d.ts +1 -1
- package/dist/ui/rich-text-editor.d.ts +32 -0
- package/dist/ui/rich-text-toolbar.d.ts +8 -0
- package/dist/ui/toast.d.ts +43 -0
- package/dist/utils/array-utils.d.ts +4 -0
- package/dist/utils/debounce.d.ts +5 -1
- package/dist/utils/flatten-leaves.d.ts +6 -0
- package/dist/utils/format-file-size.d.ts +1 -0
- package/dist/utils/format-timestamp.d.ts +1 -0
- package/dist/utils/is-empty-html.d.ts +5 -0
- package/dist/utils/pick-palette-color.d.ts +19 -0
- package/dist/utils/shuffle.d.ts +1 -0
- package/dist/utils/string-utils.d.ts +12 -0
- package/dist/video/types.d.ts +15 -0
- package/dist/video/video-bookmark.d.ts +1 -1
- package/dist/video/video-player.d.ts +1 -1
- package/dist/video/video-playlist-item.d.ts +1 -1
- package/dist/withProGate-BWqcKdPM.js +137 -0
- package/dist/withProGate-DX6XqKLp.cjs +1 -0
- package/package.json +40 -137
- package/src/assessment-toolbar/assessment-toolbar.tsx +54 -49
- package/src/assessment-toolbar/index.ts +6 -0
- package/src/assessment-toolbar/question-header-bar.tsx +61 -0
- package/src/assessment-toolbar/question-materials-drawer.tsx +55 -0
- package/src/assessment-toolbar/question-navigator.tsx +13 -36
- package/src/assessment-toolbar/timer-display.tsx +6 -5
- package/src/assessment-toolbar/types.ts +54 -4
- package/src/assessment-toolbar/use-countdown.ts +153 -0
- package/src/common/empty-state.tsx +1 -0
- package/src/common/index.ts +5 -0
- package/src/common/pagination.tsx +135 -0
- package/src/common/search-input.tsx +8 -6
- package/src/common/stepper.tsx +100 -0
- package/src/common/types.ts +41 -0
- package/src/content/attachment-list.tsx +92 -0
- package/src/content/audio-player.tsx +196 -0
- package/src/content/code-block.tsx +113 -0
- package/src/content/content-block.tsx +68 -2
- package/src/content/embed-block.tsx +78 -0
- package/src/content/file-upload-zone.tsx +11 -6
- package/src/content/index.ts +9 -0
- package/src/content/types.ts +46 -0
- package/src/curriculum/course-card.tsx +199 -0
- package/src/curriculum/curriculum-item.tsx +9 -5
- package/src/curriculum/curriculum-tree.tsx +20 -13
- package/src/curriculum/index.ts +2 -0
- package/src/curriculum/types.ts +2 -2
- package/src/feedback/feedback-banner.tsx +12 -14
- package/src/flashcards/flashcard-deck.tsx +1 -9
- package/src/flashcards/flashcard.tsx +29 -9
- 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 +24 -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 +314 -0
- package/src/modules/AssignmentModule/types.ts +77 -0
- package/src/modules/CertificateModule/CertificateModule.tsx +173 -0
- package/src/modules/CertificateModule/types.ts +49 -0
- package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
- package/src/modules/CourseCatalogModule/types.ts +47 -0
- package/src/modules/CoursePlayer/CoursePlayer.tsx +80 -69
- package/src/modules/DiscussionModule/DiscussionModule.tsx +145 -0
- package/src/modules/DiscussionModule/types.ts +54 -0
- package/src/modules/ExamModule/ExamModule.tsx +151 -0
- package/src/modules/ExamModule/types.ts +57 -0
- package/src/modules/FlashcardLab/FlashcardLab.tsx +39 -21
- package/src/modules/FlashcardLab/types.ts +2 -0
- package/src/modules/GradeCenterModule/GradeCenterModule.tsx +174 -0
- package/src/modules/GradeCenterModule/types.ts +65 -0
- package/src/modules/QuizModule/QuizModule.tsx +58 -178
- package/src/modules/QuizModule/types.ts +10 -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 +185 -0
- package/src/modules/SurveyModule/types.ts +53 -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 +33 -0
- package/src/progress/achievement-badge.tsx +52 -0
- package/src/progress/activity-timeline.tsx +84 -0
- package/src/progress/grade-indicator.tsx +9 -1
- package/src/progress/index.ts +7 -0
- package/src/progress/progress-ring.tsx +2 -1
- package/src/progress/stat-card.tsx +37 -18
- package/src/progress/streak-badge.tsx +35 -0
- package/src/progress/types.ts +103 -0
- package/src/provider/HydraProvider.tsx +15 -6
- package/src/questions/choice.tsx +19 -14
- package/src/questions/confidence-indicator.tsx +107 -0
- package/src/questions/essay.tsx +28 -28
- package/src/questions/fill-in-the-blank.tsx +20 -19
- package/src/questions/hotspot.tsx +154 -0
- package/src/questions/index.ts +18 -0
- package/src/questions/inline-choice.tsx +152 -0
- package/src/questions/matching.tsx +229 -0
- package/src/questions/multiple-choice.tsx +19 -14
- package/src/questions/numeric.tsx +106 -0
- package/src/questions/ordering.tsx +167 -0
- package/src/questions/question-renderer.tsx +24 -2
- package/src/questions/scenario.tsx +140 -0
- package/src/questions/scoring.ts +201 -0
- package/src/questions/spreadsheet.tsx +260 -0
- package/src/questions/true-false.tsx +19 -14
- package/src/questions/types.ts +123 -1
- package/src/questions/use-drag-reorder.ts +80 -0
- package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +66 -23
- package/src/sections/AnnouncementFeed/types.ts +15 -1
- package/src/sections/AssessmentReview/AssessmentReview.tsx +50 -2
- package/src/sections/AssessmentReview/types.ts +6 -0
- package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +44 -6
- package/src/sections/AssignmentSubmission/types.ts +6 -0
- package/src/sections/CertificateViewer/CertificateViewer.tsx +215 -60
- package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
- package/src/sections/CertificateViewer/types.ts +19 -5
- package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
- package/src/sections/CourseCatalog/types.ts +76 -0
- package/src/sections/CourseOutline/CourseOutline.tsx +45 -14
- package/src/sections/CourseOutline/types.ts +6 -0
- package/src/sections/DiscussionThread/DiscussionThread.tsx +55 -11
- 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 +125 -82
- package/src/sections/ExamSession/types.ts +12 -1
- package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
- package/src/sections/FlashcardStudySession/types.ts +6 -0
- package/src/sections/ForumBoard/ForumBoard.tsx +342 -0
- package/src/sections/ForumBoard/types.ts +81 -0
- package/src/sections/GradebookTable/GradebookTable.tsx +55 -2
- 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 +40 -13
- package/src/sections/LessonPage/types.ts +6 -0
- package/src/sections/PracticeQuiz/PracticeQuiz.tsx +119 -98
- package/src/sections/PracticeQuiz/types.ts +6 -0
- package/src/sections/ProgressDashboard/ProgressDashboard.tsx +121 -67
- package/src/sections/ProgressDashboard/types.ts +6 -0
- package/src/sections/QuizSession/QuizSession.tsx +115 -67
- package/src/sections/QuizSession/types.ts +12 -1
- package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +147 -0
- package/src/sections/RequirementsChecklist/types.ts +44 -0
- package/src/sections/ResourceLibrary/ResourceLibrary.tsx +68 -17
- package/src/sections/ResourceLibrary/types.ts +15 -1
- package/src/sections/RubricView/RubricView.tsx +174 -0
- package/src/sections/RubricView/types.ts +58 -0
- package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +58 -23
- 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 +40 -10
- 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 +42 -1
- package/src/social/post-card.tsx +8 -19
- package/src/social/user-avatar.tsx +10 -5
- package/src/styles/globals.css +52 -41
- package/src/ui/badge.tsx +8 -0
- package/src/ui/drawer.tsx +600 -0
- package/src/ui/index.ts +21 -0
- package/src/ui/progress.tsx +4 -0
- package/src/ui/rich-text-editor.tsx +119 -0
- package/src/ui/rich-text-toolbar.tsx +157 -0
- package/src/ui/toast.tsx +170 -0
- package/src/utils/array-utils.ts +17 -0
- package/src/utils/debounce.ts +8 -2
- package/src/utils/flatten-leaves.ts +17 -0
- package/src/utils/format-file-size.ts +5 -0
- package/src/utils/format-timestamp.ts +13 -0
- package/src/utils/is-empty-html.ts +7 -0
- package/src/utils/pick-palette-color.ts +33 -0
- package/src/utils/shuffle.ts +8 -0
- package/src/utils/string-utils.ts +30 -0
- package/src/video/types.ts +16 -0
- package/src/video/video-bookmark.tsx +4 -3
- package/src/video/video-chapter-list.tsx +9 -4
- package/src/video/video-player.tsx +24 -5
- package/src/video/video-playlist-item.tsx +8 -3
- package/src/video/video-thumbnail-card.tsx +4 -0
- package/src/video/video-transcript.tsx +8 -5
- package/dist/table-BrS5cDQu.js +0 -2510
- package/dist/table-D6AkBBEo.cjs +0 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { CheckCircle2, XCircle, Trophy, Clock, RotateCcw } from "lucide-react";
|
|
3
|
+
import { ProgressRing } from "../../progress/progress-ring";
|
|
4
|
+
import { StatCard } from "../../progress/stat-card";
|
|
5
|
+
import { Button } from "../../ui/button";
|
|
6
|
+
import { Badge } from "../../ui/badge";
|
|
7
|
+
import { Card, CardContent } from "../../ui/card";
|
|
8
|
+
import { Separator } from "../../ui/separator";
|
|
9
|
+
import { formatDuration } from "../../utils/format-duration";
|
|
10
|
+
import { AssessmentReview } from "../../sections/AssessmentReview/AssessmentReview";
|
|
11
|
+
import type { QuestionData, SessionAnswer } from "../../questions/types";
|
|
12
|
+
|
|
13
|
+
export interface AssessmentResultsProps {
|
|
14
|
+
title: string;
|
|
15
|
+
percentage: number;
|
|
16
|
+
passed: boolean;
|
|
17
|
+
correct: number;
|
|
18
|
+
total: number;
|
|
19
|
+
timeElapsedSeconds: number;
|
|
20
|
+
answers: SessionAnswer[];
|
|
21
|
+
questions: QuestionData[];
|
|
22
|
+
/** Allow retaking the assessment. */
|
|
23
|
+
allowRetake?: boolean;
|
|
24
|
+
onRetake?: () => void;
|
|
25
|
+
retakeLabel?: string;
|
|
26
|
+
/** Show per-question review below the summary. */
|
|
27
|
+
showReview?: boolean;
|
|
28
|
+
/** Extra badges shown next to the pass/fail badge (e.g., "Auto-submitted"). */
|
|
29
|
+
extraBadges?: ReactNode;
|
|
30
|
+
/** When true, disables interactions for preview/demo mode. @default false */
|
|
31
|
+
readOnly?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function AssessmentResults({
|
|
35
|
+
title,
|
|
36
|
+
percentage,
|
|
37
|
+
passed,
|
|
38
|
+
correct,
|
|
39
|
+
total,
|
|
40
|
+
timeElapsedSeconds,
|
|
41
|
+
answers,
|
|
42
|
+
questions,
|
|
43
|
+
allowRetake,
|
|
44
|
+
onRetake,
|
|
45
|
+
retakeLabel = "Retake",
|
|
46
|
+
showReview = true,
|
|
47
|
+
extraBadges,
|
|
48
|
+
readOnly = false,
|
|
49
|
+
}: AssessmentResultsProps) {
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<Card>
|
|
53
|
+
<CardContent className="pt-8 pb-8">
|
|
54
|
+
{/* Score summary */}
|
|
55
|
+
<div className="text-center mb-8">
|
|
56
|
+
<ProgressRing
|
|
57
|
+
value={percentage}
|
|
58
|
+
size={140}
|
|
59
|
+
strokeWidth={10}
|
|
60
|
+
color={passed ? "var(--success)" : "var(--destructive)"}
|
|
61
|
+
className="mx-auto mb-4 text-foreground"
|
|
62
|
+
/>
|
|
63
|
+
<Badge
|
|
64
|
+
variant={passed ? "success" : "destructive"}
|
|
65
|
+
className="text-sm px-3 py-1 mb-2"
|
|
66
|
+
>
|
|
67
|
+
{passed ? "Passed" : "Failed"}
|
|
68
|
+
</Badge>
|
|
69
|
+
{extraBadges}
|
|
70
|
+
<h2 className="text-xl font-bold text-foreground">{title}</h2>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{/* Stats grid */}
|
|
74
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8">
|
|
75
|
+
<StatCard
|
|
76
|
+
icon={<CheckCircle2 />}
|
|
77
|
+
label="Correct"
|
|
78
|
+
description="Questions answered right"
|
|
79
|
+
value={`${correct}/${total}`}
|
|
80
|
+
accent="var(--success)"
|
|
81
|
+
/>
|
|
82
|
+
<StatCard
|
|
83
|
+
icon={<XCircle />}
|
|
84
|
+
label="Incorrect"
|
|
85
|
+
description="Questions to review"
|
|
86
|
+
value={`${total - correct}/${total}`}
|
|
87
|
+
accent="var(--destructive)"
|
|
88
|
+
/>
|
|
89
|
+
<StatCard
|
|
90
|
+
icon={<Trophy />}
|
|
91
|
+
label="Score"
|
|
92
|
+
description="Overall percentage"
|
|
93
|
+
value={`${percentage}%`}
|
|
94
|
+
accent="var(--palette-3)"
|
|
95
|
+
/>
|
|
96
|
+
<StatCard
|
|
97
|
+
icon={<Clock />}
|
|
98
|
+
label="Time"
|
|
99
|
+
description="Total elapsed"
|
|
100
|
+
value={formatDuration(timeElapsedSeconds)}
|
|
101
|
+
accent="var(--palette-1)"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{/* Actions */}
|
|
106
|
+
{allowRetake && onRetake && !readOnly && (
|
|
107
|
+
<div className="flex justify-center mb-8">
|
|
108
|
+
<Button variant="outline" onClick={onRetake}>
|
|
109
|
+
<RotateCcw className="size-4 mr-2" />
|
|
110
|
+
{retakeLabel}
|
|
111
|
+
</Button>
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
</CardContent>
|
|
115
|
+
</Card>
|
|
116
|
+
|
|
117
|
+
{/* Per-question review */}
|
|
118
|
+
{showReview && (
|
|
119
|
+
<>
|
|
120
|
+
<Separator className="my-6" />
|
|
121
|
+
<h3 className="text-lg font-semibold text-foreground mb-4">
|
|
122
|
+
Question Review
|
|
123
|
+
</h3>
|
|
124
|
+
<AssessmentReview
|
|
125
|
+
questions={questions}
|
|
126
|
+
sessionAnswers={answers}
|
|
127
|
+
showCorrectAnswers
|
|
128
|
+
/>
|
|
129
|
+
</>
|
|
130
|
+
)}
|
|
131
|
+
</>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SessionAnswer } from "../../questions/types";
|
|
2
|
+
|
|
3
|
+
/** Shared result shape for scored assessment modules. */
|
|
4
|
+
export interface AssessmentResult {
|
|
5
|
+
answers: SessionAnswer[];
|
|
6
|
+
correct: number;
|
|
7
|
+
total: number;
|
|
8
|
+
percentage: number;
|
|
9
|
+
passed: boolean;
|
|
10
|
+
timeElapsedSeconds: number;
|
|
11
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Manages an interval-based timer that runs while `active` is true.
|
|
5
|
+
* Returns elapsed seconds and a reset function.
|
|
6
|
+
*/
|
|
7
|
+
export function useTimer(active: boolean) {
|
|
8
|
+
const [timeElapsed, setTimeElapsed] = useState(0);
|
|
9
|
+
const startTimeRef = useRef<number | null>(null);
|
|
10
|
+
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
11
|
+
const timeElapsedRef = useRef(0);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (active) {
|
|
15
|
+
startTimeRef.current = Date.now();
|
|
16
|
+
intervalRef.current = setInterval(() => {
|
|
17
|
+
if (startTimeRef.current) {
|
|
18
|
+
const next = Math.floor((Date.now() - startTimeRef.current) / 1000);
|
|
19
|
+
if (next !== timeElapsedRef.current) {
|
|
20
|
+
timeElapsedRef.current = next;
|
|
21
|
+
setTimeElapsed(next);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}, 1000);
|
|
25
|
+
} else {
|
|
26
|
+
if (intervalRef.current) {
|
|
27
|
+
clearInterval(intervalRef.current);
|
|
28
|
+
intervalRef.current = null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return () => {
|
|
32
|
+
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
33
|
+
};
|
|
34
|
+
}, [active]);
|
|
35
|
+
|
|
36
|
+
const getFinalElapsed = useCallback(() => {
|
|
37
|
+
return startTimeRef.current
|
|
38
|
+
? Math.floor((Date.now() - startTimeRef.current) / 1000)
|
|
39
|
+
: timeElapsedRef.current;
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const reset = useCallback(() => {
|
|
43
|
+
timeElapsedRef.current = 0;
|
|
44
|
+
setTimeElapsed(0);
|
|
45
|
+
startTimeRef.current = null;
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
return { timeElapsed, getFinalElapsed, reset };
|
|
49
|
+
}
|
package/src/modules/index.ts
CHANGED
|
@@ -10,3 +10,36 @@ export type {
|
|
|
10
10
|
|
|
11
11
|
export { CoursePlayer } from "./CoursePlayer/CoursePlayer";
|
|
12
12
|
export type { CoursePlayerProps, CoursePlayerItem } from "./CoursePlayer/types";
|
|
13
|
+
|
|
14
|
+
export { ExamModule } from "./ExamModule/ExamModule";
|
|
15
|
+
export type { ExamModuleProps, ExamModuleResult } from "./ExamModule/types";
|
|
16
|
+
|
|
17
|
+
export { SurveyModule } from "./SurveyModule/SurveyModule";
|
|
18
|
+
export type {
|
|
19
|
+
SurveyModuleProps,
|
|
20
|
+
SurveyModuleResult,
|
|
21
|
+
} from "./SurveyModule/types";
|
|
22
|
+
|
|
23
|
+
export { GradeCenterModule } from "./GradeCenterModule/GradeCenterModule";
|
|
24
|
+
export type { GradeCenterModuleProps } from "./GradeCenterModule/types";
|
|
25
|
+
|
|
26
|
+
export { AssignmentModule } from "./AssignmentModule/AssignmentModule";
|
|
27
|
+
export type {
|
|
28
|
+
AssignmentModuleProps,
|
|
29
|
+
AssignmentModuleResult,
|
|
30
|
+
} from "./AssignmentModule/types";
|
|
31
|
+
|
|
32
|
+
export { CertificateModule } from "./CertificateModule/CertificateModule";
|
|
33
|
+
export type { CertificateModuleProps } from "./CertificateModule/types";
|
|
34
|
+
|
|
35
|
+
export { DiscussionModule } from "./DiscussionModule/DiscussionModule";
|
|
36
|
+
export type { DiscussionModuleProps } from "./DiscussionModule/types";
|
|
37
|
+
|
|
38
|
+
export { StudentDashboardModule } from "./StudentDashboardModule/StudentDashboardModule";
|
|
39
|
+
export type { StudentDashboardModuleProps } from "./StudentDashboardModule/types";
|
|
40
|
+
|
|
41
|
+
export { CourseCatalogModule } from "./CourseCatalogModule/CourseCatalogModule";
|
|
42
|
+
export type { CourseCatalogModuleProps } from "./CourseCatalogModule/types";
|
|
43
|
+
|
|
44
|
+
export { StudentProfileModule } from "./StudentProfileModule/StudentProfileModule";
|
|
45
|
+
export type { StudentProfileModuleProps } from "./StudentProfileModule/types";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Trophy, Lock } from "lucide-react";
|
|
2
|
+
import { Card } from "../ui/card";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
import type { AchievementBadgeProps } from "./types";
|
|
5
|
+
|
|
6
|
+
const VARIANT_STYLES = {
|
|
7
|
+
default: "text-primary",
|
|
8
|
+
gold: "text-palette-3",
|
|
9
|
+
silver: "text-muted-foreground",
|
|
10
|
+
bronze: "text-palette-3/70",
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* AchievementBadge displays a single achievement or badge earned by a learner,
|
|
15
|
+
* with support for locked/earned states and metal-tier variants.
|
|
16
|
+
*/
|
|
17
|
+
export function AchievementBadge({
|
|
18
|
+
title,
|
|
19
|
+
description,
|
|
20
|
+
icon,
|
|
21
|
+
earnedDate,
|
|
22
|
+
locked = false,
|
|
23
|
+
variant = "default",
|
|
24
|
+
className,
|
|
25
|
+
style,
|
|
26
|
+
}: AchievementBadgeProps) {
|
|
27
|
+
return (
|
|
28
|
+
<Card
|
|
29
|
+
className={cn("p-2 text-center", locked && "opacity-60", className)}
|
|
30
|
+
style={style}
|
|
31
|
+
>
|
|
32
|
+
<div className="mx-auto mb-1 w-12 h-12 flex items-center justify-center">
|
|
33
|
+
{locked ? (
|
|
34
|
+
<Lock size={32} className="text-muted-foreground" />
|
|
35
|
+
) : (
|
|
36
|
+
icon ?? (
|
|
37
|
+
<Trophy size={32} className={VARIANT_STYLES[variant]} />
|
|
38
|
+
)
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
<p className="font-semibold text-sm text-foreground">{title}</p>
|
|
42
|
+
{description && (
|
|
43
|
+
<p className="text-xs text-muted-foreground">{description}</p>
|
|
44
|
+
)}
|
|
45
|
+
{earnedDate && !locked && (
|
|
46
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
47
|
+
{new Date(earnedDate).toLocaleDateString()}
|
|
48
|
+
</p>
|
|
49
|
+
)}
|
|
50
|
+
</Card>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { BookOpen, CheckCircle, Send, Award, Circle } from "lucide-react";
|
|
2
|
+
import { Button } from "../ui/button";
|
|
3
|
+
import { formatTimestamp } from "../utils/format-timestamp";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
import type { ActivityTimelineProps } from "./types";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_ICONS: Record<string, React.ElementType> = {
|
|
8
|
+
lesson_completed: BookOpen,
|
|
9
|
+
quiz_passed: CheckCircle,
|
|
10
|
+
assignment_submitted: Send,
|
|
11
|
+
badge_earned: Award,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ActivityTimeline renders a vertical timeline of activity events
|
|
16
|
+
* with icons, timestamps, and an optional "load more" action.
|
|
17
|
+
*/
|
|
18
|
+
export function ActivityTimeline({
|
|
19
|
+
events,
|
|
20
|
+
limit,
|
|
21
|
+
showLoadMore = false,
|
|
22
|
+
onLoadMore,
|
|
23
|
+
emptyMessage = "No recent activity",
|
|
24
|
+
className,
|
|
25
|
+
style,
|
|
26
|
+
}: ActivityTimelineProps) {
|
|
27
|
+
const displayedEvents = limit ? events.slice(0, limit) : events;
|
|
28
|
+
|
|
29
|
+
if (events.length === 0) {
|
|
30
|
+
return (
|
|
31
|
+
<p className={cn("text-sm text-muted-foreground", className)} style={style}>
|
|
32
|
+
{emptyMessage}
|
|
33
|
+
</p>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className={cn("relative", className)} style={style}>
|
|
39
|
+
{/* Vertical line */}
|
|
40
|
+
<div className="absolute left-3.5 top-0 bottom-0 w-px bg-border" />
|
|
41
|
+
|
|
42
|
+
{displayedEvents.map((event) => {
|
|
43
|
+
const Icon = DEFAULT_ICONS[event.type] ?? Circle;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
key={event.uid}
|
|
48
|
+
className="relative flex gap-3 pb-4 last:pb-0"
|
|
49
|
+
>
|
|
50
|
+
{/* Dot / Icon */}
|
|
51
|
+
<div className="relative z-10 shrink-0 w-7 h-7 rounded-full bg-background border border-border flex items-center justify-center">
|
|
52
|
+
{event.icon ?? (
|
|
53
|
+
<Icon size={14} className="text-muted-foreground" />
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{/* Content */}
|
|
58
|
+
<div className="flex-1 min-w-0 pt-0.5">
|
|
59
|
+
<p className="text-sm font-medium text-foreground">
|
|
60
|
+
{event.title}
|
|
61
|
+
</p>
|
|
62
|
+
{event.description && (
|
|
63
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
64
|
+
{event.description}
|
|
65
|
+
</p>
|
|
66
|
+
)}
|
|
67
|
+
<span className="text-xs text-muted-foreground">
|
|
68
|
+
{formatTimestamp(event.timestamp)}
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
})}
|
|
74
|
+
|
|
75
|
+
{showLoadMore && onLoadMore && (
|
|
76
|
+
<div className="pl-10 pt-1">
|
|
77
|
+
<Button variant="ghost" size="sm" onClick={onLoadMore}>
|
|
78
|
+
Load more
|
|
79
|
+
</Button>
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -36,7 +36,15 @@ export function GradeIndicator({
|
|
|
36
36
|
if (variant === "linear") {
|
|
37
37
|
return (
|
|
38
38
|
<div className={cn("flex flex-row items-center gap-1", className)} style={style}>
|
|
39
|
-
<div
|
|
39
|
+
<div
|
|
40
|
+
className="flex-1 bg-muted rounded-full overflow-hidden"
|
|
41
|
+
style={{ height: TRACK_HEIGHTS[size] }}
|
|
42
|
+
role="progressbar"
|
|
43
|
+
aria-valuenow={Math.round(percentage)}
|
|
44
|
+
aria-valuemin={0}
|
|
45
|
+
aria-valuemax={100}
|
|
46
|
+
aria-label={`Grade: ${letterGrade ?? `${Math.round(percentage)}%`}`}
|
|
47
|
+
>
|
|
40
48
|
<div
|
|
41
49
|
className="h-full rounded-full transition-[width] duration-300 ease-in-out"
|
|
42
50
|
style={{ width: `${percentage}%`, background: color }}
|
package/src/progress/index.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
export { ProgressRing } from "./progress-ring";
|
|
2
2
|
export { GradeIndicator } from "./grade-indicator";
|
|
3
3
|
export { StatCard } from "./stat-card";
|
|
4
|
+
export { AchievementBadge } from "./achievement-badge";
|
|
5
|
+
export { StreakBadge } from "./streak-badge";
|
|
6
|
+
export { ActivityTimeline } from "./activity-timeline";
|
|
4
7
|
export type {
|
|
5
8
|
ProgressRingProps,
|
|
6
9
|
GradeIndicatorProps,
|
|
7
10
|
StatCardProps,
|
|
11
|
+
AchievementBadgeProps,
|
|
12
|
+
StreakBadgeProps,
|
|
13
|
+
ActivityTimelineProps,
|
|
14
|
+
TimelineEvent,
|
|
8
15
|
} from "./types";
|
|
@@ -21,7 +21,7 @@ export function ProgressRing({
|
|
|
21
21
|
className={cn("relative inline-flex", className)}
|
|
22
22
|
style={{ width: `${size}px`, height: `${size}px`, ...style }}
|
|
23
23
|
>
|
|
24
|
-
<svg width={size} height={size}>
|
|
24
|
+
<svg width={size} height={size} role="img" aria-label={label ?? `${Math.round(value)}% progress`}>
|
|
25
25
|
<circle
|
|
26
26
|
cx={center}
|
|
27
27
|
cy={center}
|
|
@@ -48,6 +48,7 @@ export function ProgressRing({
|
|
|
48
48
|
<span
|
|
49
49
|
className="absolute inset-0 flex items-center justify-center font-bold text-foreground"
|
|
50
50
|
style={{ fontSize: `${size * 0.2}px` }}
|
|
51
|
+
aria-hidden="true"
|
|
51
52
|
>
|
|
52
53
|
{label ?? `${Math.round(value)}%`}
|
|
53
54
|
</span>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { memo } from "react";
|
|
1
2
|
import { TrendingUp, TrendingDown, Minus } from "lucide-react";
|
|
3
|
+
import { Card, CardContent } from "../ui/card";
|
|
2
4
|
import type { StatCardProps } from "./types";
|
|
3
5
|
import { cn } from "../lib/utils";
|
|
4
6
|
|
|
@@ -9,34 +11,51 @@ const TREND_COLORS = {
|
|
|
9
11
|
};
|
|
10
12
|
const TREND_ICONS = { up: TrendingUp, down: TrendingDown, flat: Minus };
|
|
11
13
|
|
|
12
|
-
export function StatCard({
|
|
14
|
+
export const StatCard = memo(function StatCard({
|
|
13
15
|
icon,
|
|
14
16
|
label,
|
|
17
|
+
description,
|
|
15
18
|
value,
|
|
16
19
|
subtitle,
|
|
17
20
|
trend,
|
|
21
|
+
accent,
|
|
18
22
|
className,
|
|
19
23
|
style,
|
|
20
24
|
}: StatCardProps) {
|
|
21
25
|
const TrendIcon = trend ? TREND_ICONS[trend.direction] : null;
|
|
22
26
|
|
|
23
27
|
return (
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
<Card className={cn(className)} style={style}>
|
|
29
|
+
<CardContent className="p-4">
|
|
30
|
+
{icon && (
|
|
31
|
+
<div
|
|
32
|
+
className="mb-2 w-9 h-9 rounded-lg flex items-center justify-center [&>svg]:size-5"
|
|
33
|
+
style={{
|
|
34
|
+
backgroundColor: `color-mix(in oklch, ${accent ?? "var(--primary)"} 10%, transparent)`,
|
|
35
|
+
color: accent ?? "var(--primary)",
|
|
36
|
+
}}
|
|
37
|
+
>
|
|
38
|
+
{icon}
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
<span className="text-sm font-medium text-foreground">{label}</span>
|
|
42
|
+
{description && (
|
|
43
|
+
<p className="text-xs text-muted-foreground mt-0.5 leading-snug">{description}</p>
|
|
37
44
|
)}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
<div className="flex flex-row items-baseline gap-1.5 mt-1">
|
|
46
|
+
<span className="text-3xl font-bold tracking-tight">{value}</span>
|
|
47
|
+
{trend && TrendIcon && (
|
|
48
|
+
<span className="flex flex-row items-center gap-px" style={{ color: TREND_COLORS[trend.direction] }}>
|
|
49
|
+
<TrendIcon size={14} />
|
|
50
|
+
<span className="text-xs font-semibold">
|
|
51
|
+
{trend.value > 0 ? "+" : ""}
|
|
52
|
+
{trend.value}%
|
|
53
|
+
</span>
|
|
54
|
+
</span>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
{subtitle && <span className="text-xs text-muted-foreground">{subtitle}</span>}
|
|
58
|
+
</CardContent>
|
|
59
|
+
</Card>
|
|
41
60
|
);
|
|
42
|
-
}
|
|
61
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Flame } from "lucide-react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
import type { StreakBadgeProps } from "./types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* StreakBadge displays a compact learning streak indicator with
|
|
7
|
+
* a fire icon and day count.
|
|
8
|
+
*/
|
|
9
|
+
export function StreakBadge({
|
|
10
|
+
currentStreak,
|
|
11
|
+
longestStreak,
|
|
12
|
+
unit = "days",
|
|
13
|
+
showLongest = false,
|
|
14
|
+
className,
|
|
15
|
+
style,
|
|
16
|
+
}: StreakBadgeProps) {
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
className={cn("flex items-center gap-2", className)}
|
|
20
|
+
style={style}
|
|
21
|
+
>
|
|
22
|
+
<Flame size={20} className="text-warning shrink-0" />
|
|
23
|
+
<div>
|
|
24
|
+
<span className="text-sm font-bold text-foreground">
|
|
25
|
+
{currentStreak} {unit}
|
|
26
|
+
</span>
|
|
27
|
+
{showLongest && longestStreak != null && (
|
|
28
|
+
<span className="block text-xs text-muted-foreground">
|
|
29
|
+
Longest: {longestStreak} {unit}
|
|
30
|
+
</span>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
package/src/progress/types.ts
CHANGED
|
@@ -60,12 +60,115 @@ export interface StatCardProps {
|
|
|
60
60
|
icon?: ReactNode;
|
|
61
61
|
/** Stat label */
|
|
62
62
|
label: string;
|
|
63
|
+
/** Short description displayed below the label */
|
|
64
|
+
description?: string;
|
|
63
65
|
/** Stat value */
|
|
64
66
|
value: string | number;
|
|
65
67
|
/** Secondary text below the value */
|
|
66
68
|
subtitle?: string;
|
|
67
69
|
/** Optional trend data */
|
|
68
70
|
trend?: { value: number; direction: "up" | "down" | "flat" };
|
|
71
|
+
/** Accent color CSS value for the icon background. Defaults to primary. */
|
|
72
|
+
accent?: string;
|
|
73
|
+
/** CSS class name for the root element */
|
|
74
|
+
className?: string;
|
|
75
|
+
/** Inline styles for the root element */
|
|
76
|
+
style?: React.CSSProperties;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* AchievementBadge displays a single achievement or badge earned by a learner,
|
|
81
|
+
* with support for locked/earned states and metal-tier variants.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* <AchievementBadge
|
|
85
|
+
* title="Quiz Master"
|
|
86
|
+
* description="Pass 10 quizzes with 90%+ score"
|
|
87
|
+
* variant="gold"
|
|
88
|
+
* earnedDate="2025-12-01T00:00:00Z"
|
|
89
|
+
* />
|
|
90
|
+
*/
|
|
91
|
+
export interface AchievementBadgeProps {
|
|
92
|
+
/** Achievement title */
|
|
93
|
+
title: string;
|
|
94
|
+
/** Optional description of how to earn the achievement */
|
|
95
|
+
description?: string;
|
|
96
|
+
/** Custom icon to display. Falls back to Trophy icon */
|
|
97
|
+
icon?: ReactNode;
|
|
98
|
+
/** ISO date string of when the achievement was earned */
|
|
99
|
+
earnedDate?: string;
|
|
100
|
+
/** Whether the achievement is locked (not yet earned). @default false */
|
|
101
|
+
locked?: boolean;
|
|
102
|
+
/** Visual tier variant. @default 'default' */
|
|
103
|
+
variant?: "default" | "gold" | "silver" | "bronze";
|
|
104
|
+
/** CSS class name for the root element */
|
|
105
|
+
className?: string;
|
|
106
|
+
/** Inline styles for the root element */
|
|
107
|
+
style?: React.CSSProperties;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* StreakBadge displays a compact learning streak indicator with
|
|
112
|
+
* a fire icon and day count.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* <StreakBadge currentStreak={7} longestStreak={14} showLongest />
|
|
116
|
+
*/
|
|
117
|
+
export interface StreakBadgeProps {
|
|
118
|
+
/** Current active streak count */
|
|
119
|
+
currentStreak: number;
|
|
120
|
+
/** All-time longest streak count */
|
|
121
|
+
longestStreak?: number;
|
|
122
|
+
/** Unit label for the streak count. @default "days" */
|
|
123
|
+
unit?: string;
|
|
124
|
+
/** Whether to show the longest streak subtitle. @default false */
|
|
125
|
+
showLongest?: boolean;
|
|
126
|
+
/** CSS class name for the root element */
|
|
127
|
+
className?: string;
|
|
128
|
+
/** Inline styles for the root element */
|
|
129
|
+
style?: React.CSSProperties;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A single event in an ActivityTimeline.
|
|
134
|
+
*/
|
|
135
|
+
export interface TimelineEvent {
|
|
136
|
+
/** Unique identifier */
|
|
137
|
+
uid: string;
|
|
138
|
+
/** Event type — used for default icon selection */
|
|
139
|
+
type: string;
|
|
140
|
+
/** Event title */
|
|
141
|
+
title: string;
|
|
142
|
+
/** Optional longer description */
|
|
143
|
+
description?: string;
|
|
144
|
+
/** ISO 8601 timestamp */
|
|
145
|
+
timestamp: string;
|
|
146
|
+
/** Optional custom icon (overrides type-based default) */
|
|
147
|
+
icon?: ReactNode;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* ActivityTimeline renders a vertical timeline of activity events
|
|
152
|
+
* with icons, timestamps, and an optional "load more" action.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* <ActivityTimeline
|
|
156
|
+
* events={[
|
|
157
|
+
* { uid: "1", type: "lesson_completed", title: "Completed Lesson 3", timestamp: "2025-03-01T12:00:00Z" },
|
|
158
|
+
* ]}
|
|
159
|
+
* />
|
|
160
|
+
*/
|
|
161
|
+
export interface ActivityTimelineProps {
|
|
162
|
+
/** List of activity events to display */
|
|
163
|
+
events: TimelineEvent[];
|
|
164
|
+
/** Maximum number of events to display. When set, truncates the list. */
|
|
165
|
+
limit?: number;
|
|
166
|
+
/** Whether to show a "Load more" button at the bottom. @default false */
|
|
167
|
+
showLoadMore?: boolean;
|
|
168
|
+
/** Called when the user clicks "Load more" */
|
|
169
|
+
onLoadMore?: () => void;
|
|
170
|
+
/** Message displayed when the events list is empty. @default "No recent activity" */
|
|
171
|
+
emptyMessage?: string;
|
|
69
172
|
/** CSS class name for the root element */
|
|
70
173
|
className?: string;
|
|
71
174
|
/** Inline styles for the root element */
|