@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
|
@@ -1,14 +1,123 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
1
2
|
import { Award, Download, Printer } from "lucide-react";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
2
5
|
import { Button } from "../../ui/button";
|
|
3
|
-
import {
|
|
4
|
-
import type { CertificateViewerProps } from "./types";
|
|
6
|
+
import type { CertificateViewerProps, CertificateVariant } from "./types";
|
|
5
7
|
import { cn } from "../../lib/utils";
|
|
8
|
+
import { SectionShell } from "../_shared/section-shell";
|
|
9
|
+
import {
|
|
10
|
+
CORNER_CONFIGS,
|
|
11
|
+
CORNER_POSITIONS,
|
|
12
|
+
DIVIDER_CONFIGS,
|
|
13
|
+
BACKGROUND_GLOW,
|
|
14
|
+
ICON_COLORS,
|
|
15
|
+
HEADING_STYLES,
|
|
16
|
+
COURSE_TITLE_COLORS,
|
|
17
|
+
SERIF_VARIANTS,
|
|
18
|
+
} from "./certificate-variants";
|
|
6
19
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
20
|
+
// ─── Frame CVAs ────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const outerFrameVariants = cva("mx-auto max-w-4xl", {
|
|
23
|
+
variants: {
|
|
24
|
+
variant: {
|
|
25
|
+
classic:
|
|
26
|
+
"border-[3px] border-double border-warning/50 rounded-lg p-1.5",
|
|
27
|
+
modern: "rounded-lg",
|
|
28
|
+
elegant:
|
|
29
|
+
"border border-foreground/15 rounded-lg p-2.5 dark:border-foreground/10",
|
|
30
|
+
academic: "border-2 border-info/30 rounded-lg p-1.5",
|
|
31
|
+
minimal: "rounded-lg",
|
|
32
|
+
bold: "rounded-lg",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: { variant: "classic" },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const innerFrameVariants = cva(
|
|
39
|
+
"relative text-center p-8 sm:p-12 md:p-16 overflow-hidden",
|
|
40
|
+
{
|
|
41
|
+
variants: {
|
|
42
|
+
variant: {
|
|
43
|
+
classic: "border border-warning/25 rounded bg-warning/5",
|
|
44
|
+
modern:
|
|
45
|
+
"border border-primary/20 rounded-lg bg-linear-to-br from-primary/5 to-primary/8 border-t-[3px] border-t-primary/60",
|
|
46
|
+
elegant:
|
|
47
|
+
"border border-foreground/8 rounded bg-linear-to-b from-background to-muted/20 dark:border-foreground/5 dark:to-muted/10",
|
|
48
|
+
academic: "border border-info/15 rounded bg-info/3",
|
|
49
|
+
minimal: "border border-border rounded-lg bg-background",
|
|
50
|
+
bold: "border-[3px] border-primary rounded-lg bg-linear-to-br from-primary/8 to-palette-0/8",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
defaultVariants: { variant: "classic" },
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// ─── Sub-components ─────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
function CornerOrnaments({ variant }: { variant: CertificateVariant }) {
|
|
60
|
+
const config = CORNER_CONFIGS[variant];
|
|
61
|
+
if (!config) return null;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
{CORNER_POSITIONS.map(({ key, pos, transform }) => (
|
|
66
|
+
<svg
|
|
67
|
+
key={key}
|
|
68
|
+
className={cn("absolute pointer-events-none", pos, config.color)}
|
|
69
|
+
width={config.size}
|
|
70
|
+
height={config.size}
|
|
71
|
+
viewBox={`0 0 ${config.size} ${config.size}`}
|
|
72
|
+
fill="none"
|
|
73
|
+
stroke="currentColor"
|
|
74
|
+
style={transform ? { transform } : undefined}
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
>
|
|
77
|
+
{config.paths.map((p, i) => (
|
|
78
|
+
<path key={i} d={p.d} strokeWidth={p.strokeWidth} />
|
|
79
|
+
))}
|
|
80
|
+
{config.dots?.map((dot, i) => (
|
|
81
|
+
<circle
|
|
82
|
+
key={`d${i}`}
|
|
83
|
+
cx={dot.cx}
|
|
84
|
+
cy={dot.cy}
|
|
85
|
+
r={dot.r}
|
|
86
|
+
fill="currentColor"
|
|
87
|
+
stroke="none"
|
|
88
|
+
/>
|
|
89
|
+
))}
|
|
90
|
+
</svg>
|
|
91
|
+
))}
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function OrnamentalDivider({
|
|
97
|
+
variant,
|
|
98
|
+
className,
|
|
99
|
+
}: {
|
|
100
|
+
variant: CertificateVariant;
|
|
101
|
+
className?: string;
|
|
102
|
+
}) {
|
|
103
|
+
const config = DIVIDER_CONFIGS[variant];
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
className={cn(
|
|
107
|
+
"flex items-center justify-center gap-3 my-5 mx-auto max-w-70",
|
|
108
|
+
className,
|
|
109
|
+
)}
|
|
110
|
+
role="separator"
|
|
111
|
+
aria-hidden="true"
|
|
112
|
+
>
|
|
113
|
+
<span className={cn("flex-1 h-px", config.lineColor)} />
|
|
114
|
+
{config.motif}
|
|
115
|
+
<span className={cn("flex-1 h-px", config.lineColor)} />
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Main component ────────────────────────────────────────────
|
|
12
121
|
|
|
13
122
|
export function CertificateViewer({
|
|
14
123
|
recipientName,
|
|
@@ -22,10 +131,13 @@ export function CertificateViewer({
|
|
|
22
131
|
showActions = true,
|
|
23
132
|
onPrint,
|
|
24
133
|
onDownload,
|
|
134
|
+
isLoading,
|
|
135
|
+
error,
|
|
136
|
+
onRetry,
|
|
25
137
|
className,
|
|
26
138
|
style,
|
|
27
139
|
}: CertificateViewerProps) {
|
|
28
|
-
const formattedDate = (() => {
|
|
140
|
+
const formattedDate = useMemo(() => {
|
|
29
141
|
try {
|
|
30
142
|
return new Date(completionDate).toLocaleDateString("en-US", {
|
|
31
143
|
year: "numeric",
|
|
@@ -35,7 +147,7 @@ export function CertificateViewer({
|
|
|
35
147
|
} catch {
|
|
36
148
|
return completionDate;
|
|
37
149
|
}
|
|
38
|
-
})
|
|
150
|
+
}, [completionDate]);
|
|
39
151
|
|
|
40
152
|
function handlePrint() {
|
|
41
153
|
if (onPrint) {
|
|
@@ -45,63 +157,105 @@ export function CertificateViewer({
|
|
|
45
157
|
}
|
|
46
158
|
}
|
|
47
159
|
|
|
160
|
+
const glowStyle = BACKGROUND_GLOW[variant];
|
|
161
|
+
|
|
48
162
|
return (
|
|
163
|
+
<SectionShell
|
|
164
|
+
isLoading={isLoading}
|
|
165
|
+
error={error}
|
|
166
|
+
onRetry={onRetry}
|
|
167
|
+
className={className}
|
|
168
|
+
style={style}
|
|
169
|
+
skeleton={<Skeleton className="h-80 w-full rounded-lg border" />}
|
|
170
|
+
>
|
|
49
171
|
<div className={className} style={style}>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
{signatory && (
|
|
88
|
-
<div className="mt-4 mb-2">
|
|
89
|
-
<Separator className="my-2 mx-auto max-w-50" />
|
|
90
|
-
<p className="font-semibold text-sm text-foreground">{signatory.name}</p>
|
|
91
|
-
<p className="text-xs text-muted-foreground">{signatory.title}</p>
|
|
92
|
-
</div>
|
|
93
|
-
)}
|
|
172
|
+
{/* Outer certificate frame */}
|
|
173
|
+
<div className={outerFrameVariants({ variant })}>
|
|
174
|
+
{/* Inner certificate frame */}
|
|
175
|
+
<div className={innerFrameVariants({ variant })}>
|
|
176
|
+
<CornerOrnaments variant={variant} />
|
|
177
|
+
|
|
178
|
+
{glowStyle && (
|
|
179
|
+
<div
|
|
180
|
+
className="absolute inset-0 pointer-events-none rounded-[inherit]"
|
|
181
|
+
style={glowStyle}
|
|
182
|
+
aria-hidden="true"
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
<div className="relative">
|
|
187
|
+
{organizationLogo ? (
|
|
188
|
+
<img
|
|
189
|
+
src={organizationLogo}
|
|
190
|
+
alt={organizationName}
|
|
191
|
+
className="h-20 mb-4 mx-auto block"
|
|
192
|
+
/>
|
|
193
|
+
) : (
|
|
194
|
+
<Award
|
|
195
|
+
size={64}
|
|
196
|
+
className={cn("mx-auto mb-6", ICON_COLORS[variant])}
|
|
197
|
+
/>
|
|
198
|
+
)}
|
|
199
|
+
|
|
200
|
+
<p className={cn("mb-2", HEADING_STYLES[variant])}>
|
|
201
|
+
Certificate of Completion
|
|
202
|
+
</p>
|
|
203
|
+
|
|
204
|
+
<OrnamentalDivider variant={variant} />
|
|
205
|
+
|
|
206
|
+
<p className="text-base text-foreground/70 mb-2">
|
|
207
|
+
This is to certify that
|
|
208
|
+
</p>
|
|
94
209
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
210
|
+
<p
|
|
211
|
+
className={cn(
|
|
212
|
+
"text-4xl font-bold mb-4 text-foreground",
|
|
213
|
+
SERIF_VARIANTS.has(variant) && "font-serif",
|
|
214
|
+
)}
|
|
215
|
+
>
|
|
216
|
+
{recipientName}
|
|
217
|
+
</p>
|
|
218
|
+
|
|
219
|
+
<p className="text-base text-foreground/70 mb-2">
|
|
220
|
+
has successfully completed
|
|
221
|
+
</p>
|
|
222
|
+
|
|
223
|
+
<p
|
|
224
|
+
className={cn(
|
|
225
|
+
"text-2xl font-semibold mb-4",
|
|
226
|
+
COURSE_TITLE_COLORS[variant],
|
|
227
|
+
)}
|
|
228
|
+
>
|
|
229
|
+
{courseTitle}
|
|
230
|
+
</p>
|
|
231
|
+
|
|
232
|
+
<p className="text-base text-foreground/60 mb-6">
|
|
233
|
+
Issued by {organizationName} on {formattedDate}
|
|
234
|
+
</p>
|
|
235
|
+
|
|
236
|
+
{signatory && (
|
|
237
|
+
<div className="mt-6 mb-4">
|
|
238
|
+
<OrnamentalDivider variant={variant} />
|
|
239
|
+
<p className="font-semibold text-base text-foreground">
|
|
240
|
+
{signatory.name}
|
|
241
|
+
</p>
|
|
242
|
+
<p className="text-sm text-muted-foreground">
|
|
243
|
+
{signatory.title}
|
|
244
|
+
</p>
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
|
|
248
|
+
{certificateId && (
|
|
249
|
+
<span className="block text-xs text-muted-foreground mt-4">
|
|
250
|
+
Certificate ID: {certificateId}
|
|
251
|
+
</span>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
100
255
|
</div>
|
|
101
256
|
|
|
102
|
-
{/* Actions */}
|
|
103
257
|
{showActions && (
|
|
104
|
-
<div className="flex justify-center gap-
|
|
258
|
+
<div className="flex justify-center gap-3 mt-6">
|
|
105
259
|
<Button variant="outline" onClick={handlePrint}>
|
|
106
260
|
<Printer size={16} /> Print
|
|
107
261
|
</Button>
|
|
@@ -113,5 +267,6 @@ export function CertificateViewer({
|
|
|
113
267
|
</div>
|
|
114
268
|
)}
|
|
115
269
|
</div>
|
|
270
|
+
</SectionShell>
|
|
116
271
|
);
|
|
117
272
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { CertificateVariant } from "./types";
|
|
2
|
+
|
|
3
|
+
// ─── Corner ornaments ──────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export interface CornerConfig {
|
|
6
|
+
size: number;
|
|
7
|
+
paths: { d: string; strokeWidth: number }[];
|
|
8
|
+
dots?: { cx: number; cy: number; r: number }[];
|
|
9
|
+
color: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const CORNER_CONFIGS: Record<CertificateVariant, CornerConfig | null> = {
|
|
13
|
+
classic: {
|
|
14
|
+
size: 40,
|
|
15
|
+
paths: [
|
|
16
|
+
{ d: "M 2 38 L 2 10 Q 2 2 10 2 L 38 2", strokeWidth: 1.5 },
|
|
17
|
+
{ d: "M 7 38 L 7 13 Q 7 7 13 7 L 38 7", strokeWidth: 0.75 },
|
|
18
|
+
],
|
|
19
|
+
color: "text-warning/50",
|
|
20
|
+
},
|
|
21
|
+
modern: null,
|
|
22
|
+
elegant: {
|
|
23
|
+
size: 32,
|
|
24
|
+
paths: [{ d: "M 0 30 L 0 0 L 30 0", strokeWidth: 0.5 }],
|
|
25
|
+
dots: [{ cx: 2.5, cy: 2.5, r: 1.5 }],
|
|
26
|
+
color: "text-foreground/20",
|
|
27
|
+
},
|
|
28
|
+
academic: {
|
|
29
|
+
size: 36,
|
|
30
|
+
paths: [
|
|
31
|
+
{ d: "M 0 36 L 0 0 L 36 0", strokeWidth: 2 },
|
|
32
|
+
{ d: "M 5 36 L 5 5 L 36 5", strokeWidth: 1 },
|
|
33
|
+
],
|
|
34
|
+
color: "text-info/35",
|
|
35
|
+
},
|
|
36
|
+
minimal: null,
|
|
37
|
+
bold: {
|
|
38
|
+
size: 20,
|
|
39
|
+
paths: [],
|
|
40
|
+
dots: [{ cx: 5, cy: 5, r: 5 }],
|
|
41
|
+
color: "text-primary/40",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const CORNER_POSITIONS = [
|
|
46
|
+
{ key: "tl", pos: "top-3 left-3", transform: undefined },
|
|
47
|
+
{ key: "tr", pos: "top-3 right-3", transform: "scaleX(-1)" },
|
|
48
|
+
{ key: "bl", pos: "bottom-3 left-3", transform: "scaleY(-1)" },
|
|
49
|
+
{ key: "br", pos: "bottom-3 right-3", transform: "scale(-1)" },
|
|
50
|
+
] as const;
|
|
51
|
+
|
|
52
|
+
// ─── Divider configs ────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
export interface DividerConfig {
|
|
55
|
+
lineColor: string;
|
|
56
|
+
motif: React.ReactNode;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const DIVIDER_CONFIGS: Record<CertificateVariant, DividerConfig> = {
|
|
60
|
+
classic: {
|
|
61
|
+
lineColor: "bg-warning/30",
|
|
62
|
+
motif: (
|
|
63
|
+
<svg width="12" height="12" viewBox="0 0 12 12" className="text-warning/50 shrink-0" aria-hidden="true">
|
|
64
|
+
<path d="M6 0 L12 6 L6 12 L0 6 Z" fill="currentColor" />
|
|
65
|
+
</svg>
|
|
66
|
+
),
|
|
67
|
+
},
|
|
68
|
+
modern: {
|
|
69
|
+
lineColor: "bg-primary/20",
|
|
70
|
+
motif: (
|
|
71
|
+
<svg width="8" height="8" viewBox="0 0 8 8" className="text-primary/40 shrink-0" aria-hidden="true">
|
|
72
|
+
<circle cx="4" cy="4" r="3" fill="currentColor" />
|
|
73
|
+
</svg>
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
elegant: {
|
|
77
|
+
lineColor: "bg-foreground/10",
|
|
78
|
+
motif: (
|
|
79
|
+
<svg width="10" height="10" viewBox="0 0 10 10" className="text-foreground/15 shrink-0" aria-hidden="true">
|
|
80
|
+
<path d="M5 0 L6 4 L10 5 L6 6 L5 10 L4 6 L0 5 L4 4 Z" fill="currentColor" />
|
|
81
|
+
</svg>
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
academic: {
|
|
85
|
+
lineColor: "bg-info/25",
|
|
86
|
+
motif: (
|
|
87
|
+
<svg width="10" height="10" viewBox="0 0 10 10" className="text-info/40 shrink-0" aria-hidden="true">
|
|
88
|
+
<path d="M4 0 L6 0 L6 4 L10 4 L10 6 L6 6 L6 10 L4 10 L4 6 L0 6 L0 4 L4 4 Z" fill="currentColor" />
|
|
89
|
+
</svg>
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
minimal: {
|
|
93
|
+
lineColor: "bg-border",
|
|
94
|
+
motif: null,
|
|
95
|
+
},
|
|
96
|
+
bold: {
|
|
97
|
+
lineColor: "bg-primary/40",
|
|
98
|
+
motif: (
|
|
99
|
+
<svg width="10" height="10" viewBox="0 0 10 10" className="text-primary/50 shrink-0" aria-hidden="true">
|
|
100
|
+
<rect x="1" y="1" width="8" height="8" fill="currentColor" />
|
|
101
|
+
</svg>
|
|
102
|
+
),
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// ─── Background glow ────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
export const BACKGROUND_GLOW: Record<CertificateVariant, React.CSSProperties | null> = {
|
|
109
|
+
classic: {
|
|
110
|
+
background:
|
|
111
|
+
"radial-gradient(ellipse at center, var(--color-warning) 0%, transparent 65%)",
|
|
112
|
+
opacity: 0.06,
|
|
113
|
+
},
|
|
114
|
+
modern: null,
|
|
115
|
+
elegant: {
|
|
116
|
+
background:
|
|
117
|
+
"radial-gradient(ellipse at center, var(--color-foreground) 0%, transparent 70%)",
|
|
118
|
+
opacity: 0.03,
|
|
119
|
+
},
|
|
120
|
+
academic: {
|
|
121
|
+
background:
|
|
122
|
+
"radial-gradient(ellipse at center, var(--color-info) 0%, transparent 65%)",
|
|
123
|
+
opacity: 0.05,
|
|
124
|
+
},
|
|
125
|
+
minimal: null,
|
|
126
|
+
bold: {
|
|
127
|
+
background:
|
|
128
|
+
"radial-gradient(ellipse at 30% 50%, var(--color-primary) 0%, transparent 50%), radial-gradient(ellipse at 70% 50%, var(--color-palette-0) 0%, transparent 50%)",
|
|
129
|
+
opacity: 0.06,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ─── Typography maps ────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
export const ICON_COLORS: Record<CertificateVariant, string> = {
|
|
136
|
+
classic: "text-warning",
|
|
137
|
+
modern: "text-primary",
|
|
138
|
+
elegant: "text-foreground/60",
|
|
139
|
+
academic: "text-info",
|
|
140
|
+
minimal: "text-muted-foreground",
|
|
141
|
+
bold: "text-primary",
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const HEADING_STYLES: Record<CertificateVariant, string> = {
|
|
145
|
+
classic:
|
|
146
|
+
"uppercase tracking-[4px] text-base font-medium text-foreground/80 font-serif",
|
|
147
|
+
modern: "uppercase tracking-[3px] text-base font-medium text-primary/80",
|
|
148
|
+
elegant:
|
|
149
|
+
"uppercase tracking-[5px] text-sm font-light text-foreground/50",
|
|
150
|
+
academic:
|
|
151
|
+
"uppercase tracking-[3px] text-base font-semibold text-info/80",
|
|
152
|
+
minimal:
|
|
153
|
+
"uppercase tracking-[2px] text-sm font-normal text-muted-foreground",
|
|
154
|
+
bold: "uppercase tracking-[3px] text-lg font-black text-primary",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const COURSE_TITLE_COLORS: Record<CertificateVariant, string> = {
|
|
158
|
+
classic: "text-warning",
|
|
159
|
+
modern: "text-primary",
|
|
160
|
+
elegant: "text-foreground/80",
|
|
161
|
+
academic: "text-info",
|
|
162
|
+
minimal: "text-foreground",
|
|
163
|
+
bold: "text-primary",
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const SERIF_VARIANTS: ReadonlySet<CertificateVariant> = new Set([
|
|
167
|
+
"classic",
|
|
168
|
+
"elegant",
|
|
169
|
+
"academic",
|
|
170
|
+
]);
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
/** Available certificate visual styles */
|
|
2
|
+
export type CertificateVariant =
|
|
3
|
+
| "classic"
|
|
4
|
+
| "modern"
|
|
5
|
+
| "elegant"
|
|
6
|
+
| "academic"
|
|
7
|
+
| "minimal"
|
|
8
|
+
| "bold";
|
|
1
9
|
|
|
2
10
|
/**
|
|
3
11
|
* CertificateViewer section — a printable completion certificate.
|
|
4
12
|
*
|
|
5
13
|
* Displays a certificate with recipient details, course information,
|
|
6
|
-
* signatory, and verification ID. Supports
|
|
7
|
-
* classic, modern, and
|
|
14
|
+
* signatory, and verification ID. Supports six visual variants:
|
|
15
|
+
* classic, modern, elegant, academic, minimal, and bold.
|
|
8
16
|
*
|
|
9
17
|
* @example
|
|
10
18
|
* <CertificateViewer
|
|
@@ -12,7 +20,7 @@
|
|
|
12
20
|
* courseTitle="Advanced React"
|
|
13
21
|
* completionDate="2025-03-01"
|
|
14
22
|
* organizationName="HydraLMS Academy"
|
|
15
|
-
* variant="
|
|
23
|
+
* variant="elegant"
|
|
16
24
|
* />
|
|
17
25
|
*/
|
|
18
26
|
export interface CertificateViewerProps {
|
|
@@ -30,14 +38,20 @@ export interface CertificateViewerProps {
|
|
|
30
38
|
signatory?: { name: string; title: string };
|
|
31
39
|
/** Unique certificate ID */
|
|
32
40
|
certificateId?: string;
|
|
33
|
-
/** Certificate template variant */
|
|
34
|
-
variant?:
|
|
41
|
+
/** Certificate template variant @default "classic" */
|
|
42
|
+
variant?: CertificateVariant;
|
|
35
43
|
/** Whether to show print/download actions */
|
|
36
44
|
showActions?: boolean;
|
|
37
45
|
/** Called when print is triggered */
|
|
38
46
|
onPrint?: () => void;
|
|
39
47
|
/** Called when download is triggered */
|
|
40
48
|
onDownload?: () => void;
|
|
49
|
+
/** Render skeleton placeholders instead of content */
|
|
50
|
+
isLoading?: boolean;
|
|
51
|
+
/** Error message — renders an error state with optional retry */
|
|
52
|
+
error?: string | null;
|
|
53
|
+
/** Called when the user clicks retry in the error state */
|
|
54
|
+
onRetry?: () => void;
|
|
41
55
|
/** CSS class name for the root element */
|
|
42
56
|
className?: string;
|
|
43
57
|
/** Inline styles for the root element */
|