@hydralms/components 0.2.0 → 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/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 +494 -444
- 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 +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 +1266 -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/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/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 +6 -0
- package/dist/sections.cjs +1 -1
- package/dist/sections.js +268 -307
- package/dist/tabs-BsfVo2Bl.cjs +173 -0
- package/dist/{tabs-Wf3h_Cx3.js → tabs-BuY1iNJE.js} +7532 -6807
- 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-BWqcKdPM.js +137 -0
- package/dist/withProGate-DX6XqKLp.cjs +1 -0
- package/package.json +34 -220
- 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 +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 +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 +37 -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 +8 -1
- 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/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/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 +59 -1
- 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 +36 -5
- 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 +22 -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 +13 -1
- package/dist/ForumBoard-CHXU3mjC.js +0 -2207
- package/dist/ForumBoard-d1w5-r6n.cjs +0 -1
- package/dist/tabs-DRM2Iq_J.cjs +0 -172
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo, useRef } from "react";
|
|
1
|
+
import { useMemo, useRef, memo } from "react";
|
|
2
2
|
import { Alert, AlertDescription } from "../ui/alert";
|
|
3
3
|
import { Separator } from "../ui/separator";
|
|
4
4
|
import { cn } from "../lib/utils";
|
|
@@ -26,14 +26,14 @@ import type { QuestionProps, SessionAnswer } from "./types";
|
|
|
26
26
|
* onAnswer={(answers) => handleAnswer(answers)}
|
|
27
27
|
* />
|
|
28
28
|
*/
|
|
29
|
-
export const Scenario = ({
|
|
29
|
+
export const Scenario = memo(function Scenario({
|
|
30
30
|
question,
|
|
31
31
|
sessionAnswers,
|
|
32
32
|
onAnswer,
|
|
33
33
|
readOnly = false,
|
|
34
34
|
showCorrectAnswers = false,
|
|
35
35
|
disabled = false,
|
|
36
|
-
}: QuestionProps)
|
|
36
|
+
}: QuestionProps) {
|
|
37
37
|
const subQuestions = question.scenarioQuestions ?? [];
|
|
38
38
|
|
|
39
39
|
// Track the latest answers from each sub-question in a ref so
|
|
@@ -137,4 +137,4 @@ export const Scenario = ({
|
|
|
137
137
|
)}
|
|
138
138
|
</div>
|
|
139
139
|
);
|
|
140
|
-
};
|
|
140
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo, useRef } from "react";
|
|
1
|
+
import { useState, useMemo, useRef, useEffect, memo } from "react";
|
|
2
2
|
import { debounce } from "../utils/debounce";
|
|
3
3
|
import { Input } from "../ui/input";
|
|
4
4
|
import {
|
|
@@ -66,14 +66,14 @@ function getCellStatus(
|
|
|
66
66
|
* onAnswer={(answers) => handleAnswer(answers)}
|
|
67
67
|
* />
|
|
68
68
|
*/
|
|
69
|
-
export const Spreadsheet = ({
|
|
69
|
+
export const Spreadsheet = memo(function Spreadsheet({
|
|
70
70
|
question,
|
|
71
71
|
sessionAnswers,
|
|
72
72
|
onAnswer,
|
|
73
73
|
readOnly = false,
|
|
74
74
|
showCorrectAnswers = false,
|
|
75
75
|
disabled = false,
|
|
76
|
-
}: QuestionProps)
|
|
76
|
+
}: QuestionProps) {
|
|
77
77
|
const columns = question.spreadsheetColumns ?? [];
|
|
78
78
|
const rows = question.spreadsheetRows ?? [];
|
|
79
79
|
|
|
@@ -104,6 +104,7 @@ export const Spreadsheet = ({
|
|
|
104
104
|
}, 300),
|
|
105
105
|
[],
|
|
106
106
|
);
|
|
107
|
+
useEffect(() => () => debouncedEmit.cancel(), [debouncedEmit]);
|
|
107
108
|
|
|
108
109
|
const [values, setValues] = useState<Map<string, string>>(() => {
|
|
109
110
|
const map = new Map<string, string>();
|
|
@@ -256,4 +257,4 @@ export const Spreadsheet = ({
|
|
|
256
257
|
)}
|
|
257
258
|
</div>
|
|
258
259
|
);
|
|
259
|
-
};
|
|
260
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo } from "react";
|
|
1
|
+
import { useState, useMemo, memo } from "react";
|
|
2
2
|
import type { QuestionProps } from "./types";
|
|
3
3
|
import { Alert, AlertDescription } from "../ui/alert";
|
|
4
4
|
import { cn } from "../lib/utils";
|
|
@@ -12,14 +12,14 @@ import { cn } from "../lib/utils";
|
|
|
12
12
|
* onAnswer={(answers) => handleAnswer(answers)}
|
|
13
13
|
* />
|
|
14
14
|
*/
|
|
15
|
-
export const TrueFalse = ({
|
|
15
|
+
export const TrueFalse = memo(function TrueFalse({
|
|
16
16
|
question,
|
|
17
17
|
sessionAnswers,
|
|
18
18
|
onAnswer,
|
|
19
19
|
readOnly = false,
|
|
20
20
|
showCorrectAnswers = false,
|
|
21
21
|
disabled = false,
|
|
22
|
-
}: QuestionProps)
|
|
22
|
+
}: QuestionProps) {
|
|
23
23
|
const [selectedAnswer, setSelectedAnswer] = useState<string>(
|
|
24
24
|
() => sessionAnswers?.[0]?.answerUid || "",
|
|
25
25
|
);
|
|
@@ -53,7 +53,8 @@ export const TrueFalse = ({
|
|
|
53
53
|
<div className="flex flex-col gap-4">
|
|
54
54
|
<div dangerouslySetInnerHTML={{ __html: question.content }} />
|
|
55
55
|
|
|
56
|
-
<
|
|
56
|
+
<fieldset className="flex flex-col gap-2 border-0 p-0 m-0">
|
|
57
|
+
<legend className="sr-only">True or False</legend>
|
|
57
58
|
{sortedAnswers.map((answer) => (
|
|
58
59
|
<div
|
|
59
60
|
key={answer.uid}
|
|
@@ -70,10 +71,16 @@ export const TrueFalse = ({
|
|
|
70
71
|
className="accent-primary m-0 shrink-0"
|
|
71
72
|
/>
|
|
72
73
|
<span dangerouslySetInnerHTML={{ __html: answer.content }} />
|
|
74
|
+
{showCorrectAnswers && answer.isCorrect && (
|
|
75
|
+
<span className="sr-only">(Correct answer)</span>
|
|
76
|
+
)}
|
|
77
|
+
{showCorrectAnswers && selectedAnswer === answer.uid && !answer.isCorrect && (
|
|
78
|
+
<span className="sr-only">(Your answer — incorrect)</span>
|
|
79
|
+
)}
|
|
73
80
|
</label>
|
|
74
81
|
</div>
|
|
75
82
|
))}
|
|
76
|
-
</
|
|
83
|
+
</fieldset>
|
|
77
84
|
|
|
78
85
|
{showCorrectAnswers && question.explanation && (
|
|
79
86
|
<Alert className="mt-2">
|
|
@@ -85,4 +92,4 @@ export const TrueFalse = ({
|
|
|
85
92
|
)}
|
|
86
93
|
</div>
|
|
87
94
|
);
|
|
88
|
-
};
|
|
95
|
+
});
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
|
-
import { Pin } from "lucide-react";
|
|
2
|
+
import { AlertCircle, Pin } from "lucide-react";
|
|
3
3
|
import { UserAvatar } from "../../social";
|
|
4
4
|
import { EmptyState } from "../../common";
|
|
5
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
5
6
|
import { Badge } from "../../ui/badge";
|
|
6
7
|
import { Button } from "../../ui/button";
|
|
7
8
|
import { Card, CardContent } from "../../ui/card";
|
|
8
9
|
import type { AnnouncementFeedProps } from "./types";
|
|
9
10
|
import { cn } from "../../lib/utils";
|
|
10
11
|
import { formatTimestamp } from "../../utils/format-timestamp";
|
|
12
|
+
import { Pagination } from "../../common/pagination";
|
|
11
13
|
|
|
12
14
|
export function AnnouncementFeed({
|
|
13
15
|
announcements,
|
|
@@ -17,6 +19,13 @@ export function AnnouncementFeed({
|
|
|
17
19
|
previewLines = 3,
|
|
18
20
|
emptyMessage = "No announcements yet",
|
|
19
21
|
readOnly = false,
|
|
22
|
+
isLoading,
|
|
23
|
+
error,
|
|
24
|
+
onRetry,
|
|
25
|
+
pageSize,
|
|
26
|
+
currentPage = 1,
|
|
27
|
+
totalItems,
|
|
28
|
+
onPageChange,
|
|
20
29
|
className,
|
|
21
30
|
style,
|
|
22
31
|
}: AnnouncementFeedProps) {
|
|
@@ -41,6 +50,42 @@ export function AnnouncementFeed({
|
|
|
41
50
|
}
|
|
42
51
|
}
|
|
43
52
|
|
|
53
|
+
if (isLoading) {
|
|
54
|
+
return (
|
|
55
|
+
<div className={cn("space-y-4", className)} style={style}>
|
|
56
|
+
{Array.from({ length: 3 }).map((_, i) => (
|
|
57
|
+
<div key={i} className="flex gap-3 items-start">
|
|
58
|
+
<Skeleton className="h-10 w-10 rounded-full shrink-0" />
|
|
59
|
+
<div className="flex-1 space-y-2">
|
|
60
|
+
<Skeleton className="h-5 w-48" />
|
|
61
|
+
<Skeleton className="h-4 w-full" />
|
|
62
|
+
<Skeleton className="h-4 w-full" />
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (error) {
|
|
71
|
+
return (
|
|
72
|
+
<div className={cn("py-12", className)} style={style}>
|
|
73
|
+
<EmptyState
|
|
74
|
+
icon={<AlertCircle className="size-10 text-destructive" />}
|
|
75
|
+
title="Something went wrong"
|
|
76
|
+
description={error}
|
|
77
|
+
action={
|
|
78
|
+
onRetry ? (
|
|
79
|
+
<Button variant="outline" onClick={onRetry}>
|
|
80
|
+
Retry
|
|
81
|
+
</Button>
|
|
82
|
+
) : undefined
|
|
83
|
+
}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
44
89
|
if (sorted.length === 0) {
|
|
45
90
|
return (
|
|
46
91
|
<div className={className} style={style}>
|
|
@@ -51,7 +96,10 @@ export function AnnouncementFeed({
|
|
|
51
96
|
|
|
52
97
|
return (
|
|
53
98
|
<div className={cn("flex flex-col gap-2", className)} style={style}>
|
|
54
|
-
{
|
|
99
|
+
{(onPageChange && pageSize
|
|
100
|
+
? sorted.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
|
101
|
+
: sorted
|
|
102
|
+
).map((a) => {
|
|
55
103
|
const isExpanded = expandedUids.has(a.uid);
|
|
56
104
|
|
|
57
105
|
return (
|
|
@@ -91,9 +139,9 @@ export function AnnouncementFeed({
|
|
|
91
139
|
<span className="block text-xs text-muted-foreground mb-1">
|
|
92
140
|
{a.author.displayName} · {formatTimestamp(a.createdAt)}
|
|
93
141
|
</span>
|
|
94
|
-
<
|
|
142
|
+
<div
|
|
95
143
|
className={cn(
|
|
96
|
-
"text-sm text-foreground",
|
|
144
|
+
"text-sm text-foreground [&_p]:mb-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5",
|
|
97
145
|
!isExpanded && "line-clamp-(--preview-lines) overflow-hidden",
|
|
98
146
|
)}
|
|
99
147
|
style={
|
|
@@ -101,10 +149,9 @@ export function AnnouncementFeed({
|
|
|
101
149
|
? { "--preview-lines": previewLines } as React.CSSProperties
|
|
102
150
|
: undefined
|
|
103
151
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
{!isExpanded && a.content.length > 200 && (
|
|
152
|
+
dangerouslySetInnerHTML={{ __html: a.content }}
|
|
153
|
+
/>
|
|
154
|
+
{!isExpanded && a.content.replace(/<[^>]*>/g, "").length > 200 && (
|
|
108
155
|
<Button
|
|
109
156
|
variant="link"
|
|
110
157
|
size="xs"
|
|
@@ -123,6 +170,15 @@ export function AnnouncementFeed({
|
|
|
123
170
|
</Card>
|
|
124
171
|
);
|
|
125
172
|
})}
|
|
173
|
+
|
|
174
|
+
{onPageChange && pageSize && sorted.length > 0 && (
|
|
175
|
+
<Pagination
|
|
176
|
+
currentPage={currentPage}
|
|
177
|
+
totalPages={Math.ceil((totalItems ?? sorted.length) / pageSize)}
|
|
178
|
+
onPageChange={onPageChange}
|
|
179
|
+
className="mt-4"
|
|
180
|
+
/>
|
|
181
|
+
)}
|
|
126
182
|
</div>
|
|
127
183
|
);
|
|
128
184
|
}
|
|
@@ -26,6 +26,20 @@ export interface AnnouncementFeedProps {
|
|
|
26
26
|
emptyMessage?: string;
|
|
27
27
|
/** When true, disables interactions */
|
|
28
28
|
readOnly?: boolean;
|
|
29
|
+
/** Render skeleton placeholders instead of content */
|
|
30
|
+
isLoading?: boolean;
|
|
31
|
+
/** Error message — renders an error state with optional retry */
|
|
32
|
+
error?: string | null;
|
|
33
|
+
/** Called when the user clicks retry in the error state */
|
|
34
|
+
onRetry?: () => void;
|
|
35
|
+
/** Number of items per page (enables pagination when set with onPageChange) */
|
|
36
|
+
pageSize?: number;
|
|
37
|
+
/** Current page (1-indexed) */
|
|
38
|
+
currentPage?: number;
|
|
39
|
+
/** Total number of items (for server-side pagination) */
|
|
40
|
+
totalItems?: number;
|
|
41
|
+
/** Called when the user navigates to a different page */
|
|
42
|
+
onPageChange?: (page: number) => void;
|
|
29
43
|
/** CSS class name for the root element */
|
|
30
44
|
className?: string;
|
|
31
45
|
/** Inline styles for the root element */
|
|
@@ -37,7 +51,7 @@ export interface Announcement {
|
|
|
37
51
|
uid: string;
|
|
38
52
|
/** Announcement title */
|
|
39
53
|
title: string;
|
|
40
|
-
/** Announcement body content */
|
|
54
|
+
/** Announcement body content. May contain HTML from a rich-text editor. */
|
|
41
55
|
content: string;
|
|
42
56
|
/** Author information */
|
|
43
57
|
author: { displayName: string; avatarUrl?: string; role?: string };
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
+
import { AlertCircle } from "lucide-react";
|
|
2
3
|
import { QuestionRenderer } from "../../questions";
|
|
3
4
|
import type { QuestionData, SessionAnswer } from "../../questions/types";
|
|
4
5
|
import { Badge } from "../../ui/badge";
|
|
5
6
|
import { Card, CardContent } from "../../ui/card";
|
|
6
7
|
import { Separator } from "../../ui/separator";
|
|
8
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
9
|
+
import { Button } from "../../ui/button";
|
|
10
|
+
import { EmptyState } from "../../common/empty-state";
|
|
7
11
|
import { cn } from "../../lib/utils";
|
|
8
12
|
import type {
|
|
9
13
|
AssessmentReviewProps,
|
|
@@ -140,9 +144,42 @@ export function AssessmentReview({
|
|
|
140
144
|
score,
|
|
141
145
|
questionGroups,
|
|
142
146
|
showCorrectAnswers = true,
|
|
147
|
+
isLoading,
|
|
148
|
+
error,
|
|
149
|
+
onRetry,
|
|
143
150
|
className,
|
|
144
151
|
style,
|
|
145
152
|
}: AssessmentReviewProps) {
|
|
153
|
+
if (isLoading) {
|
|
154
|
+
return (
|
|
155
|
+
<div className={cn("space-y-4", className)} style={style}>
|
|
156
|
+
<Skeleton className="h-16 w-full" />
|
|
157
|
+
<Skeleton className="h-24 w-full" />
|
|
158
|
+
<Skeleton className="h-24 w-full" />
|
|
159
|
+
<Skeleton className="h-24 w-full" />
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (error) {
|
|
165
|
+
return (
|
|
166
|
+
<div className={cn("py-12", className)} style={style}>
|
|
167
|
+
<EmptyState
|
|
168
|
+
icon={<AlertCircle className="size-10 text-destructive" />}
|
|
169
|
+
title="Something went wrong"
|
|
170
|
+
description={error}
|
|
171
|
+
action={
|
|
172
|
+
onRetry ? (
|
|
173
|
+
<Button variant="outline" onClick={onRetry}>
|
|
174
|
+
Retry
|
|
175
|
+
</Button>
|
|
176
|
+
) : undefined
|
|
177
|
+
}
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
146
183
|
return (
|
|
147
184
|
<div className={cn(className)} style={style}>
|
|
148
185
|
{score && <ScoreHeader score={score} />}
|
|
@@ -34,6 +34,12 @@ export interface AssessmentReviewProps {
|
|
|
34
34
|
* @default true
|
|
35
35
|
*/
|
|
36
36
|
showCorrectAnswers?: 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 */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import { Send, Save } from "lucide-react";
|
|
2
|
+
import { Send, Save, AlertCircle } from "lucide-react";
|
|
3
3
|
import { StatusBadge, DueDateDisplay } from "../../common";
|
|
4
4
|
import { FileUploadZone } from "../../content";
|
|
5
5
|
import { Button } from "../../ui/button";
|
|
@@ -9,6 +9,9 @@ import { Separator } from "../../ui/separator";
|
|
|
9
9
|
import { Card, CardContent } from "../../ui/card";
|
|
10
10
|
import { Alert, AlertDescription } from "../../ui/alert";
|
|
11
11
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "../../ui/tabs";
|
|
12
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
13
|
+
import { EmptyState } from "../../common/empty-state";
|
|
14
|
+
import { cn } from "../../lib/utils";
|
|
12
15
|
import type { AssignmentSubmissionProps, SubmissionData } from "./types";
|
|
13
16
|
|
|
14
17
|
export function AssignmentSubmission({
|
|
@@ -25,6 +28,9 @@ export function AssignmentSubmission({
|
|
|
25
28
|
grade,
|
|
26
29
|
isSubmitting = false,
|
|
27
30
|
readOnly = false,
|
|
31
|
+
isLoading,
|
|
32
|
+
error,
|
|
33
|
+
onRetry,
|
|
28
34
|
className,
|
|
29
35
|
style,
|
|
30
36
|
}: AssignmentSubmissionProps) {
|
|
@@ -33,6 +39,36 @@ export function AssignmentSubmission({
|
|
|
33
39
|
const [url, setUrl] = useState(existingSubmission?.url ?? "");
|
|
34
40
|
const [activeTab, setActiveTab] = useState<"text" | "file" | "url">(submissionTypes[0]);
|
|
35
41
|
|
|
42
|
+
if (isLoading) {
|
|
43
|
+
return (
|
|
44
|
+
<div className={cn("space-y-4", className)} style={style}>
|
|
45
|
+
<Skeleton className="h-8 w-64" />
|
|
46
|
+
<Skeleton className="h-6 w-20" />
|
|
47
|
+
<Skeleton className="h-32 w-full" />
|
|
48
|
+
<Skeleton className="h-10 w-32" />
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (error) {
|
|
54
|
+
return (
|
|
55
|
+
<div className={cn("py-12", className)} style={style}>
|
|
56
|
+
<EmptyState
|
|
57
|
+
icon={<AlertCircle className="size-10 text-destructive" />}
|
|
58
|
+
title="Something went wrong"
|
|
59
|
+
description={error}
|
|
60
|
+
action={
|
|
61
|
+
onRetry ? (
|
|
62
|
+
<Button variant="outline" onClick={onRetry}>
|
|
63
|
+
Retry
|
|
64
|
+
</Button>
|
|
65
|
+
) : undefined
|
|
66
|
+
}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
36
72
|
const isEditable = !readOnly && !["submitted", "graded"].includes(status);
|
|
37
73
|
|
|
38
74
|
function getSubmissionData(): SubmissionData {
|
|
@@ -44,6 +44,12 @@ export interface AssignmentSubmissionProps {
|
|
|
44
44
|
isSubmitting?: boolean;
|
|
45
45
|
/** When true, disables all interactions */
|
|
46
46
|
readOnly?: boolean;
|
|
47
|
+
/** Render skeleton placeholders instead of content */
|
|
48
|
+
isLoading?: boolean;
|
|
49
|
+
/** Error message — renders an error state with optional retry */
|
|
50
|
+
error?: string | null;
|
|
51
|
+
/** Called when the user clicks retry in the error state */
|
|
52
|
+
onRetry?: () => void;
|
|
47
53
|
/** CSS class name for the root element */
|
|
48
54
|
className?: string;
|
|
49
55
|
/** Inline styles for the root element */
|