@hydralms/components 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/StudentProfile-BPsZBaJj.cjs +1 -0
- package/dist/StudentProfile-Cw2p-RZn.js +3273 -0
- package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
- package/dist/assessment-toolbar/timer-display.d.ts +1 -1
- package/dist/common/index.d.ts +2 -1
- package/dist/common/pagination.d.ts +26 -0
- package/dist/common/types.d.ts +1 -0
- package/dist/components.css +1 -1
- package/dist/content/audio-player.d.ts +22 -0
- package/dist/content/code-block.d.ts +30 -0
- package/dist/content/embed-block.d.ts +28 -0
- package/dist/content/index.d.ts +6 -0
- package/dist/content/types.d.ts +24 -0
- package/dist/curriculum/course-card.d.ts +51 -0
- package/dist/curriculum/index.d.ts +2 -0
- package/dist/curriculum/types.d.ts +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +495 -439
- package/dist/license/HydraContext.d.ts +16 -0
- package/dist/license/ProBadge.d.ts +6 -0
- package/dist/license/index.d.ts +7 -0
- package/dist/license/tiers.d.ts +6 -0
- package/dist/license/useHydraLicense.d.ts +6 -0
- package/dist/license/validate.d.ts +13 -0
- package/dist/license/withProGate.d.ts +6 -0
- package/dist/modules/AssignmentModule/AssignmentModule.d.ts +4 -7
- package/dist/modules/AssignmentModule/types.d.ts +5 -1
- package/dist/modules/CertificateModule/CertificateModule.d.ts +4 -8
- package/dist/modules/CertificateModule/types.d.ts +6 -4
- package/dist/modules/CourseCatalogModule/CourseCatalogModule.d.ts +5 -0
- package/dist/modules/CourseCatalogModule/types.d.ts +43 -0
- package/dist/modules/CoursePlayer/CoursePlayer.d.ts +4 -1
- package/dist/modules/DiscussionModule/DiscussionModule.d.ts +4 -7
- package/dist/modules/ExamModule/ExamModule.d.ts +4 -7
- package/dist/modules/ExamModule/types.d.ts +5 -14
- package/dist/modules/FlashcardLab/FlashcardLab.d.ts +4 -1
- package/dist/modules/FlashcardLab/types.d.ts +2 -0
- package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +4 -8
- package/dist/modules/GradeCenterModule/types.d.ts +2 -0
- package/dist/modules/QuizModule/QuizModule.d.ts +4 -1
- package/dist/modules/QuizModule/types.d.ts +5 -14
- package/dist/modules/StudentDashboardModule/StudentDashboardModule.d.ts +5 -0
- package/dist/modules/StudentDashboardModule/types.d.ts +54 -0
- package/dist/modules/StudentProfileModule/StudentProfileModule.d.ts +5 -0
- package/dist/modules/StudentProfileModule/types.d.ts +43 -0
- package/dist/modules/SurveyModule/SurveyModule.d.ts +4 -6
- package/dist/modules/SurveyModule/types.d.ts +2 -0
- package/dist/modules/_shared/assessment-intro.d.ts +16 -0
- package/dist/modules/_shared/assessment-results.d.ts +23 -0
- package/dist/modules/_shared/types.d.ts +10 -0
- package/dist/modules/_shared/use-timer.d.ts +9 -0
- package/dist/modules/index.d.ts +6 -0
- package/dist/modules.cjs +1 -1
- package/dist/modules.js +1267 -854
- package/dist/progress/types.d.ts +2 -0
- package/dist/provider/HydraProvider.d.ts +5 -1
- package/dist/questions/choice.d.ts +1 -1
- package/dist/questions/confidence-indicator.d.ts +37 -0
- package/dist/questions/essay.d.ts +1 -1
- package/dist/questions/fill-in-the-blank.d.ts +1 -1
- package/dist/questions/hotspot.d.ts +1 -1
- package/dist/questions/index.d.ts +2 -0
- package/dist/questions/inline-choice.d.ts +1 -1
- package/dist/questions/matching.d.ts +1 -1
- package/dist/questions/multiple-choice.d.ts +1 -1
- package/dist/questions/numeric.d.ts +1 -1
- package/dist/questions/ordering.d.ts +1 -1
- package/dist/questions/question-renderer.d.ts +1 -1
- package/dist/questions/scenario.d.ts +1 -1
- package/dist/questions/spreadsheet.d.ts +1 -1
- package/dist/questions/true-false.d.ts +1 -1
- package/dist/sections/AdaptiveLearningPath/AdaptiveLearningPath.d.ts +5 -0
- package/dist/sections/AdaptiveLearningPath/path-connector.d.ts +8 -0
- package/dist/sections/AdaptiveLearningPath/path-milestone-marker.d.ts +7 -0
- package/dist/sections/AdaptiveLearningPath/path-node-card.d.ts +10 -0
- package/dist/sections/AdaptiveLearningPath/path-skill-bar.d.ts +8 -0
- package/dist/sections/AdaptiveLearningPath/types.d.ts +136 -0
- package/dist/sections/AnnouncementFeed/AnnouncementFeed.d.ts +1 -1
- package/dist/sections/AnnouncementFeed/types.d.ts +15 -1
- package/dist/sections/AssessmentReview/AssessmentReview.d.ts +1 -1
- package/dist/sections/AssessmentReview/types.d.ts +6 -0
- package/dist/sections/AssignmentSubmission/AssignmentSubmission.d.ts +1 -1
- package/dist/sections/AssignmentSubmission/types.d.ts +6 -0
- package/dist/sections/CertificateViewer/CertificateViewer.d.ts +1 -1
- package/dist/sections/CertificateViewer/certificate-variants.d.ts +42 -0
- package/dist/sections/CertificateViewer/types.d.ts +6 -0
- package/dist/sections/ContentAuthoringStudio/ContentAuthoringStudio.d.ts +5 -0
- package/dist/sections/ContentAuthoringStudio/block-editor-item.d.ts +14 -0
- package/dist/sections/ContentAuthoringStudio/block-type-picker.d.ts +12 -0
- package/dist/sections/ContentAuthoringStudio/types.d.ts +67 -0
- package/dist/sections/CourseCatalog/CourseCatalog.d.ts +2 -0
- package/dist/sections/CourseCatalog/types.d.ts +80 -0
- package/dist/sections/CourseOutline/CourseOutline.d.ts +1 -1
- package/dist/sections/CourseOutline/types.d.ts +6 -0
- package/dist/sections/DiscussionThread/DiscussionThread.d.ts +1 -1
- package/dist/sections/DiscussionThread/types.d.ts +6 -0
- package/dist/sections/EnrollmentWizard/EnrollmentWizard.d.ts +2 -0
- package/dist/sections/EnrollmentWizard/types.d.ts +66 -0
- package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
- package/dist/sections/ExamSession/types.d.ts +6 -0
- package/dist/sections/FlashcardStudySession/FlashcardStudySession.d.ts +1 -1
- package/dist/sections/FlashcardStudySession/types.d.ts +6 -0
- package/dist/sections/ForumBoard/ForumBoard.d.ts +1 -1
- package/dist/sections/ForumBoard/types.d.ts +14 -0
- package/dist/sections/GradebookTable/GradebookTable.d.ts +1 -1
- package/dist/sections/GradebookTable/types.d.ts +14 -0
- package/dist/sections/LecturePlayer/LecturePlayer.d.ts +1 -1
- package/dist/sections/LecturePlayer/types.d.ts +8 -0
- package/dist/sections/LessonPage/LessonPage.d.ts +1 -1
- package/dist/sections/LessonPage/types.d.ts +6 -0
- package/dist/sections/PracticeQuiz/PracticeQuiz.d.ts +1 -1
- package/dist/sections/PracticeQuiz/types.d.ts +6 -0
- package/dist/sections/ProgressDashboard/ProgressDashboard.d.ts +1 -1
- package/dist/sections/ProgressDashboard/types.d.ts +6 -0
- package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
- package/dist/sections/QuizSession/types.d.ts +6 -0
- package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +1 -1
- package/dist/sections/RequirementsChecklist/types.d.ts +6 -0
- package/dist/sections/ResourceLibrary/ResourceLibrary.d.ts +1 -1
- package/dist/sections/ResourceLibrary/types.d.ts +15 -1
- package/dist/sections/RubricView/RubricView.d.ts +1 -1
- package/dist/sections/RubricView/types.d.ts +6 -0
- package/dist/sections/ScrollableQuiz/ScrollableQuiz.d.ts +1 -1
- package/dist/sections/ScrollableQuiz/types.d.ts +6 -0
- package/dist/sections/StudentProfile/StudentProfile.d.ts +2 -0
- package/dist/sections/StudentProfile/types.d.ts +98 -0
- package/dist/sections/SurveyForm/SurveyForm.d.ts +1 -1
- package/dist/sections/SurveyForm/types.d.ts +6 -0
- package/dist/sections/_shared/merge-answers.d.ts +9 -0
- package/dist/sections/_shared/section-shell.d.ts +20 -0
- package/dist/sections/_shared/use-assessment-session.d.ts +30 -0
- package/dist/sections/index.d.ts +10 -0
- package/dist/sections.cjs +1 -1
- package/dist/sections.js +1361 -307
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/progress.d.ts +1 -1
- package/dist/ui/rich-text-editor.d.ts +3 -1
- package/dist/ui/toast.d.ts +43 -0
- package/dist/utils/debounce.d.ts +5 -1
- package/dist/utils/pick-palette-color.d.ts +19 -0
- package/dist/video/types.d.ts +15 -0
- package/dist/video/video-player.d.ts +1 -1
- package/dist/withProGate-BJdu1T9Y.cjs +2 -0
- package/dist/withProGate-BvFc7Jwy.js +4975 -0
- package/package.json +57 -226
- package/src/assessment-toolbar/question-navigator.tsx +10 -5
- package/src/assessment-toolbar/timer-display.tsx +4 -3
- package/src/assessment-toolbar/use-countdown.ts +1 -1
- package/src/common/empty-state.tsx +1 -0
- package/src/common/index.ts +2 -0
- package/src/common/pagination.tsx +135 -0
- package/src/common/search-input.tsx +2 -1
- package/src/common/types.ts +2 -0
- package/src/content/attachment-list.tsx +2 -0
- package/src/content/audio-player.tsx +196 -0
- package/src/content/code-block.tsx +113 -0
- package/src/content/content-block.tsx +64 -0
- package/src/content/embed-block.tsx +78 -0
- package/src/content/file-upload-zone.tsx +10 -0
- package/src/content/index.ts +6 -0
- package/src/content/types.ts +5 -0
- package/src/curriculum/course-card.tsx +199 -0
- package/src/curriculum/curriculum-item.tsx +3 -3
- package/src/curriculum/curriculum-tree.tsx +20 -13
- package/src/curriculum/index.ts +2 -0
- package/src/curriculum/types.ts +2 -2
- package/src/flashcards/flashcard.tsx +28 -8
- package/src/index.ts +3 -0
- package/src/license/HydraContext.tsx +62 -0
- package/src/license/ProBadge.tsx +43 -0
- package/src/license/index.ts +7 -0
- package/src/license/tiers.ts +34 -0
- package/src/license/useHydraLicense.ts +10 -0
- package/src/license/validate.ts +90 -0
- package/src/license/withProGate.tsx +21 -0
- package/src/modules/AssignmentModule/AssignmentModule.tsx +17 -8
- package/src/modules/AssignmentModule/types.ts +5 -1
- package/src/modules/CertificateModule/CertificateModule.tsx +21 -9
- package/src/modules/CertificateModule/types.ts +6 -4
- package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
- package/src/modules/CourseCatalogModule/types.ts +47 -0
- package/src/modules/CoursePlayer/CoursePlayer.tsx +39 -22
- package/src/modules/DiscussionModule/DiscussionModule.tsx +57 -22
- package/src/modules/ExamModule/ExamModule.tsx +64 -198
- package/src/modules/ExamModule/types.ts +5 -14
- package/src/modules/FlashcardLab/FlashcardLab.tsx +10 -5
- package/src/modules/FlashcardLab/types.ts +2 -0
- package/src/modules/GradeCenterModule/GradeCenterModule.tsx +7 -2
- package/src/modules/GradeCenterModule/types.ts +2 -0
- package/src/modules/QuizModule/QuizModule.tsx +49 -169
- package/src/modules/QuizModule/types.ts +5 -15
- package/src/modules/StudentDashboardModule/StudentDashboardModule.tsx +117 -0
- package/src/modules/StudentDashboardModule/types.ts +56 -0
- package/src/modules/StudentProfileModule/StudentProfileModule.tsx +289 -0
- package/src/modules/StudentProfileModule/types.ts +45 -0
- package/src/modules/SurveyModule/SurveyModule.tsx +9 -4
- package/src/modules/SurveyModule/types.ts +2 -0
- package/src/modules/_shared/assessment-intro.tsx +75 -0
- package/src/modules/_shared/assessment-results.tsx +133 -0
- package/src/modules/_shared/types.ts +11 -0
- package/src/modules/_shared/use-timer.ts +49 -0
- package/src/modules/index.ts +9 -0
- package/src/progress/achievement-badge.tsx +3 -3
- package/src/progress/grade-indicator.tsx +9 -1
- package/src/progress/progress-ring.tsx +2 -1
- package/src/progress/stat-card.tsx +14 -2
- package/src/progress/types.ts +2 -0
- package/src/provider/HydraProvider.tsx +15 -6
- package/src/questions/choice.tsx +13 -6
- package/src/questions/confidence-indicator.tsx +107 -0
- package/src/questions/essay.tsx +6 -4
- package/src/questions/fill-in-the-blank.tsx +8 -4
- package/src/questions/hotspot.tsx +4 -4
- package/src/questions/index.ts +2 -0
- package/src/questions/inline-choice.tsx +5 -4
- package/src/questions/matching.tsx +5 -4
- package/src/questions/multiple-choice.tsx +13 -6
- package/src/questions/numeric.tsx +8 -4
- package/src/questions/ordering.tsx +12 -4
- package/src/questions/question-renderer.tsx +3 -2
- package/src/questions/scenario.tsx +4 -4
- package/src/questions/spreadsheet.tsx +5 -4
- package/src/questions/true-false.tsx +13 -6
- package/src/sections/AdaptiveLearningPath/AdaptiveLearningPath.tsx +251 -0
- package/src/sections/AdaptiveLearningPath/path-connector.tsx +27 -0
- package/src/sections/AdaptiveLearningPath/path-milestone-marker.tsx +50 -0
- package/src/sections/AdaptiveLearningPath/path-node-card.tsx +166 -0
- package/src/sections/AdaptiveLearningPath/path-skill-bar.tsx +49 -0
- package/src/sections/AdaptiveLearningPath/types.ts +159 -0
- package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +64 -8
- package/src/sections/AnnouncementFeed/types.ts +15 -1
- package/src/sections/AssessmentReview/AssessmentReview.tsx +37 -0
- package/src/sections/AssessmentReview/types.ts +6 -0
- package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +37 -1
- package/src/sections/AssignmentSubmission/types.ts +6 -0
- package/src/sections/CertificateViewer/CertificateViewer.tsx +29 -227
- package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
- package/src/sections/CertificateViewer/types.ts +6 -0
- package/src/sections/ContentAuthoringStudio/ContentAuthoringStudio.tsx +289 -0
- package/src/sections/ContentAuthoringStudio/block-editor-item.tsx +487 -0
- package/src/sections/ContentAuthoringStudio/block-type-picker.tsx +123 -0
- package/src/sections/ContentAuthoringStudio/types.ts +67 -0
- package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
- package/src/sections/CourseCatalog/types.ts +76 -0
- package/src/sections/CourseOutline/CourseOutline.tsx +41 -0
- package/src/sections/CourseOutline/types.ts +6 -0
- package/src/sections/DiscussionThread/DiscussionThread.tsx +42 -1
- package/src/sections/DiscussionThread/types.ts +6 -0
- package/src/sections/EnrollmentWizard/EnrollmentWizard.tsx +343 -0
- package/src/sections/EnrollmentWizard/types.ts +65 -0
- package/src/sections/ExamSession/ExamSession.tsx +100 -94
- package/src/sections/ExamSession/types.ts +6 -0
- package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
- package/src/sections/FlashcardStudySession/types.ts +6 -0
- package/src/sections/ForumBoard/ForumBoard.tsx +67 -7
- package/src/sections/ForumBoard/types.ts +14 -0
- package/src/sections/GradebookTable/GradebookTable.tsx +54 -1
- package/src/sections/GradebookTable/types.ts +14 -0
- package/src/sections/LecturePlayer/LecturePlayer.tsx +63 -37
- package/src/sections/LecturePlayer/types.ts +8 -0
- package/src/sections/LessonPage/LessonPage.tsx +34 -6
- package/src/sections/LessonPage/types.ts +6 -0
- package/src/sections/PracticeQuiz/PracticeQuiz.tsx +106 -74
- package/src/sections/PracticeQuiz/types.ts +6 -0
- package/src/sections/ProgressDashboard/ProgressDashboard.tsx +64 -10
- package/src/sections/ProgressDashboard/types.ts +6 -0
- package/src/sections/QuizSession/QuizSession.tsx +71 -82
- package/src/sections/QuizSession/types.ts +6 -0
- package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +41 -1
- package/src/sections/RequirementsChecklist/types.ts +6 -0
- package/src/sections/ResourceLibrary/ResourceLibrary.tsx +64 -8
- package/src/sections/ResourceLibrary/types.ts +15 -1
- package/src/sections/RubricView/RubricView.tsx +37 -1
- package/src/sections/RubricView/types.ts +6 -0
- package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +36 -15
- package/src/sections/ScrollableQuiz/types.ts +6 -0
- package/src/sections/StudentProfile/StudentProfile.tsx +279 -0
- package/src/sections/StudentProfile/types.ts +99 -0
- package/src/sections/SurveyForm/SurveyForm.tsx +32 -5
- package/src/sections/SurveyForm/types.ts +6 -0
- package/src/sections/_shared/merge-answers.ts +22 -0
- package/src/sections/_shared/section-shell.tsx +64 -0
- package/src/sections/_shared/use-assessment-session.ts +125 -0
- package/src/sections/index.ts +40 -0
- package/src/social/user-avatar.tsx +9 -5
- package/src/styles/globals.css +39 -41
- package/src/ui/badge.tsx +8 -0
- package/src/ui/index.ts +2 -0
- package/src/ui/progress.tsx +4 -0
- package/src/ui/rich-text-editor.tsx +10 -0
- package/src/ui/rich-text-toolbar.tsx +2 -1
- package/src/ui/toast.tsx +170 -0
- package/src/utils/debounce.ts +8 -2
- package/src/utils/pick-palette-color.ts +33 -0
- package/src/video/types.ts +16 -0
- package/src/video/video-player.tsx +27 -6
- package/dist/ForumBoard-CHXU3mjC.js +0 -2207
- package/dist/ForumBoard-d1w5-r6n.cjs +0 -1
- package/dist/tabs-DRM2Iq_J.cjs +0 -172
- package/dist/tabs-Wf3h_Cx3.js +0 -21580
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
1
2
|
import { Award, Download, Printer } from "lucide-react";
|
|
2
3
|
import { cva } from "class-variance-authority";
|
|
4
|
+
import { Skeleton } from "../../ui/skeleton";
|
|
3
5
|
import { Button } from "../../ui/button";
|
|
4
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
20
|
// ─── Frame CVAs ────────────────────────────────────────────────
|
|
8
|
-
// Double-frame variants (classic, elegant, academic) use outer + inner borders
|
|
9
|
-
// with a gap between them. Single-frame variants collapse the outer.
|
|
10
21
|
|
|
11
22
|
const outerFrameVariants = cva("mx-auto max-w-4xl", {
|
|
12
23
|
variants: {
|
|
@@ -36,61 +47,14 @@ const innerFrameVariants = cva(
|
|
|
36
47
|
"border border-foreground/8 rounded bg-linear-to-b from-background to-muted/20 dark:border-foreground/5 dark:to-muted/10",
|
|
37
48
|
academic: "border border-info/15 rounded bg-info/3",
|
|
38
49
|
minimal: "border border-border rounded-lg bg-background",
|
|
39
|
-
bold: "border-[3px] border-primary rounded-lg bg-linear-to-br from-primary/8 to-
|
|
50
|
+
bold: "border-[3px] border-primary rounded-lg bg-linear-to-br from-primary/8 to-palette-0/8",
|
|
40
51
|
},
|
|
41
52
|
},
|
|
42
53
|
defaultVariants: { variant: "classic" },
|
|
43
54
|
},
|
|
44
55
|
);
|
|
45
56
|
|
|
46
|
-
// ───
|
|
47
|
-
|
|
48
|
-
interface CornerConfig {
|
|
49
|
-
size: number;
|
|
50
|
-
paths: { d: string; strokeWidth: number }[];
|
|
51
|
-
dots?: { cx: number; cy: number; r: number }[];
|
|
52
|
-
color: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const CORNER_CONFIGS: Record<CertificateVariant, CornerConfig | null> = {
|
|
56
|
-
classic: {
|
|
57
|
-
size: 40,
|
|
58
|
-
paths: [
|
|
59
|
-
{ d: "M 2 38 L 2 10 Q 2 2 10 2 L 38 2", strokeWidth: 1.5 },
|
|
60
|
-
{ d: "M 7 38 L 7 13 Q 7 7 13 7 L 38 7", strokeWidth: 0.75 },
|
|
61
|
-
],
|
|
62
|
-
color: "text-warning/50",
|
|
63
|
-
},
|
|
64
|
-
modern: null,
|
|
65
|
-
elegant: {
|
|
66
|
-
size: 32,
|
|
67
|
-
paths: [{ d: "M 0 30 L 0 0 L 30 0", strokeWidth: 0.5 }],
|
|
68
|
-
dots: [{ cx: 2.5, cy: 2.5, r: 1.5 }],
|
|
69
|
-
color: "text-foreground/20",
|
|
70
|
-
},
|
|
71
|
-
academic: {
|
|
72
|
-
size: 36,
|
|
73
|
-
paths: [
|
|
74
|
-
{ d: "M 0 36 L 0 0 L 36 0", strokeWidth: 2 },
|
|
75
|
-
{ d: "M 5 36 L 5 5 L 36 5", strokeWidth: 1 },
|
|
76
|
-
],
|
|
77
|
-
color: "text-info/35",
|
|
78
|
-
},
|
|
79
|
-
minimal: null,
|
|
80
|
-
bold: {
|
|
81
|
-
size: 20,
|
|
82
|
-
paths: [],
|
|
83
|
-
dots: [{ cx: 5, cy: 5, r: 5 }],
|
|
84
|
-
color: "text-primary/40",
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const CORNER_POSITIONS = [
|
|
89
|
-
{ key: "tl", pos: "top-3 left-3", transform: undefined },
|
|
90
|
-
{ key: "tr", pos: "top-3 right-3", transform: "scaleX(-1)" },
|
|
91
|
-
{ key: "bl", pos: "bottom-3 left-3", transform: "scaleY(-1)" },
|
|
92
|
-
{ key: "br", pos: "bottom-3 right-3", transform: "scale(-1)" },
|
|
93
|
-
] as const;
|
|
57
|
+
// ─── Sub-components ─────────────────────────────────────────────
|
|
94
58
|
|
|
95
59
|
function CornerOrnaments({ variant }: { variant: CertificateVariant }) {
|
|
96
60
|
const config = CORNER_CONFIGS[variant];
|
|
@@ -129,96 +93,6 @@ function CornerOrnaments({ variant }: { variant: CertificateVariant }) {
|
|
|
129
93
|
);
|
|
130
94
|
}
|
|
131
95
|
|
|
132
|
-
// ─── Ornamental dividers ───────────────────────────────────────
|
|
133
|
-
|
|
134
|
-
interface DividerConfig {
|
|
135
|
-
lineColor: string;
|
|
136
|
-
motif: React.ReactNode;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const DIVIDER_CONFIGS: Record<CertificateVariant, DividerConfig> = {
|
|
140
|
-
classic: {
|
|
141
|
-
lineColor: "bg-warning/30",
|
|
142
|
-
motif: (
|
|
143
|
-
<svg
|
|
144
|
-
width="12"
|
|
145
|
-
height="12"
|
|
146
|
-
viewBox="0 0 12 12"
|
|
147
|
-
className="text-warning/50 shrink-0"
|
|
148
|
-
aria-hidden="true"
|
|
149
|
-
>
|
|
150
|
-
<path d="M6 0 L12 6 L6 12 L0 6 Z" fill="currentColor" />
|
|
151
|
-
</svg>
|
|
152
|
-
),
|
|
153
|
-
},
|
|
154
|
-
modern: {
|
|
155
|
-
lineColor: "bg-primary/20",
|
|
156
|
-
motif: (
|
|
157
|
-
<svg
|
|
158
|
-
width="8"
|
|
159
|
-
height="8"
|
|
160
|
-
viewBox="0 0 8 8"
|
|
161
|
-
className="text-primary/40 shrink-0"
|
|
162
|
-
aria-hidden="true"
|
|
163
|
-
>
|
|
164
|
-
<circle cx="4" cy="4" r="3" fill="currentColor" />
|
|
165
|
-
</svg>
|
|
166
|
-
),
|
|
167
|
-
},
|
|
168
|
-
elegant: {
|
|
169
|
-
lineColor: "bg-foreground/10",
|
|
170
|
-
motif: (
|
|
171
|
-
<svg
|
|
172
|
-
width="10"
|
|
173
|
-
height="10"
|
|
174
|
-
viewBox="0 0 10 10"
|
|
175
|
-
className="text-foreground/15 shrink-0"
|
|
176
|
-
aria-hidden="true"
|
|
177
|
-
>
|
|
178
|
-
<path
|
|
179
|
-
d="M5 0 L6 4 L10 5 L6 6 L5 10 L4 6 L0 5 L4 4 Z"
|
|
180
|
-
fill="currentColor"
|
|
181
|
-
/>
|
|
182
|
-
</svg>
|
|
183
|
-
),
|
|
184
|
-
},
|
|
185
|
-
academic: {
|
|
186
|
-
lineColor: "bg-info/25",
|
|
187
|
-
motif: (
|
|
188
|
-
<svg
|
|
189
|
-
width="10"
|
|
190
|
-
height="10"
|
|
191
|
-
viewBox="0 0 10 10"
|
|
192
|
-
className="text-info/40 shrink-0"
|
|
193
|
-
aria-hidden="true"
|
|
194
|
-
>
|
|
195
|
-
<path
|
|
196
|
-
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"
|
|
197
|
-
fill="currentColor"
|
|
198
|
-
/>
|
|
199
|
-
</svg>
|
|
200
|
-
),
|
|
201
|
-
},
|
|
202
|
-
minimal: {
|
|
203
|
-
lineColor: "bg-border",
|
|
204
|
-
motif: null,
|
|
205
|
-
},
|
|
206
|
-
bold: {
|
|
207
|
-
lineColor: "bg-primary/40",
|
|
208
|
-
motif: (
|
|
209
|
-
<svg
|
|
210
|
-
width="10"
|
|
211
|
-
height="10"
|
|
212
|
-
viewBox="0 0 10 10"
|
|
213
|
-
className="text-primary/50 shrink-0"
|
|
214
|
-
aria-hidden="true"
|
|
215
|
-
>
|
|
216
|
-
<rect x="1" y="1" width="8" height="8" fill="currentColor" />
|
|
217
|
-
</svg>
|
|
218
|
-
),
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
|
|
222
96
|
function OrnamentalDivider({
|
|
223
97
|
variant,
|
|
224
98
|
className,
|
|
@@ -243,76 +117,6 @@ function OrnamentalDivider({
|
|
|
243
117
|
);
|
|
244
118
|
}
|
|
245
119
|
|
|
246
|
-
// ─── Background glow ───────────────────────────────────────────
|
|
247
|
-
// Subtle radial glows using CSS vars for dark mode compat
|
|
248
|
-
|
|
249
|
-
const BACKGROUND_GLOW: Record<
|
|
250
|
-
CertificateVariant,
|
|
251
|
-
React.CSSProperties | null
|
|
252
|
-
> = {
|
|
253
|
-
classic: {
|
|
254
|
-
background:
|
|
255
|
-
"radial-gradient(ellipse at center, var(--color-warning) 0%, transparent 65%)",
|
|
256
|
-
opacity: 0.06,
|
|
257
|
-
},
|
|
258
|
-
modern: null,
|
|
259
|
-
elegant: {
|
|
260
|
-
background:
|
|
261
|
-
"radial-gradient(ellipse at center, var(--color-foreground) 0%, transparent 70%)",
|
|
262
|
-
opacity: 0.03,
|
|
263
|
-
},
|
|
264
|
-
academic: {
|
|
265
|
-
background:
|
|
266
|
-
"radial-gradient(ellipse at center, var(--color-info) 0%, transparent 65%)",
|
|
267
|
-
opacity: 0.05,
|
|
268
|
-
},
|
|
269
|
-
minimal: null,
|
|
270
|
-
bold: {
|
|
271
|
-
background:
|
|
272
|
-
"radial-gradient(ellipse at 30% 50%, var(--color-primary) 0%, transparent 50%), radial-gradient(ellipse at 70% 50%, var(--color-purple) 0%, transparent 50%)",
|
|
273
|
-
opacity: 0.06,
|
|
274
|
-
},
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
// ─── Typography maps ───────────────────────────────────────────
|
|
278
|
-
|
|
279
|
-
const ICON_COLORS: Record<CertificateVariant, string> = {
|
|
280
|
-
classic: "text-warning",
|
|
281
|
-
modern: "text-primary",
|
|
282
|
-
elegant: "text-foreground/60",
|
|
283
|
-
academic: "text-info",
|
|
284
|
-
minimal: "text-muted-foreground",
|
|
285
|
-
bold: "text-primary",
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const HEADING_STYLES: Record<CertificateVariant, string> = {
|
|
289
|
-
classic:
|
|
290
|
-
"uppercase tracking-[4px] text-base font-medium text-foreground/80 font-serif",
|
|
291
|
-
modern: "uppercase tracking-[3px] text-base font-medium text-primary/80",
|
|
292
|
-
elegant:
|
|
293
|
-
"uppercase tracking-[5px] text-sm font-light text-foreground/50",
|
|
294
|
-
academic:
|
|
295
|
-
"uppercase tracking-[3px] text-base font-semibold text-info/80",
|
|
296
|
-
minimal:
|
|
297
|
-
"uppercase tracking-[2px] text-sm font-normal text-muted-foreground",
|
|
298
|
-
bold: "uppercase tracking-[3px] text-lg font-black text-primary",
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
const COURSE_TITLE_COLORS: Record<CertificateVariant, string> = {
|
|
302
|
-
classic: "text-warning",
|
|
303
|
-
modern: "text-primary",
|
|
304
|
-
elegant: "text-foreground/80",
|
|
305
|
-
academic: "text-info",
|
|
306
|
-
minimal: "text-foreground",
|
|
307
|
-
bold: "text-primary",
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const SERIF_VARIANTS: ReadonlySet<CertificateVariant> = new Set([
|
|
311
|
-
"classic",
|
|
312
|
-
"elegant",
|
|
313
|
-
"academic",
|
|
314
|
-
]);
|
|
315
|
-
|
|
316
120
|
// ─── Main component ────────────────────────────────────────────
|
|
317
121
|
|
|
318
122
|
export function CertificateViewer({
|
|
@@ -327,10 +131,13 @@ export function CertificateViewer({
|
|
|
327
131
|
showActions = true,
|
|
328
132
|
onPrint,
|
|
329
133
|
onDownload,
|
|
134
|
+
isLoading,
|
|
135
|
+
error,
|
|
136
|
+
onRetry,
|
|
330
137
|
className,
|
|
331
138
|
style,
|
|
332
139
|
}: CertificateViewerProps) {
|
|
333
|
-
const formattedDate = (() => {
|
|
140
|
+
const formattedDate = useMemo(() => {
|
|
334
141
|
try {
|
|
335
142
|
return new Date(completionDate).toLocaleDateString("en-US", {
|
|
336
143
|
year: "numeric",
|
|
@@ -340,7 +147,7 @@ export function CertificateViewer({
|
|
|
340
147
|
} catch {
|
|
341
148
|
return completionDate;
|
|
342
149
|
}
|
|
343
|
-
})
|
|
150
|
+
}, [completionDate]);
|
|
344
151
|
|
|
345
152
|
function handlePrint() {
|
|
346
153
|
if (onPrint) {
|
|
@@ -353,15 +160,21 @@ export function CertificateViewer({
|
|
|
353
160
|
const glowStyle = BACKGROUND_GLOW[variant];
|
|
354
161
|
|
|
355
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
|
+
>
|
|
356
171
|
<div className={className} style={style}>
|
|
357
172
|
{/* Outer certificate frame */}
|
|
358
173
|
<div className={outerFrameVariants({ variant })}>
|
|
359
174
|
{/* Inner certificate frame */}
|
|
360
175
|
<div className={innerFrameVariants({ variant })}>
|
|
361
|
-
{/* Corner ornaments */}
|
|
362
176
|
<CornerOrnaments variant={variant} />
|
|
363
177
|
|
|
364
|
-
{/* Background radial glow */}
|
|
365
178
|
{glowStyle && (
|
|
366
179
|
<div
|
|
367
180
|
className="absolute inset-0 pointer-events-none rounded-[inherit]"
|
|
@@ -370,9 +183,7 @@ export function CertificateViewer({
|
|
|
370
183
|
/>
|
|
371
184
|
)}
|
|
372
185
|
|
|
373
|
-
{/* Content */}
|
|
374
186
|
<div className="relative">
|
|
375
|
-
{/* Logo / Icon */}
|
|
376
187
|
{organizationLogo ? (
|
|
377
188
|
<img
|
|
378
189
|
src={organizationLogo}
|
|
@@ -386,20 +197,16 @@ export function CertificateViewer({
|
|
|
386
197
|
/>
|
|
387
198
|
)}
|
|
388
199
|
|
|
389
|
-
{/* Certificate heading */}
|
|
390
200
|
<p className={cn("mb-2", HEADING_STYLES[variant])}>
|
|
391
201
|
Certificate of Completion
|
|
392
202
|
</p>
|
|
393
203
|
|
|
394
|
-
{/* Top ornamental divider */}
|
|
395
204
|
<OrnamentalDivider variant={variant} />
|
|
396
205
|
|
|
397
|
-
{/* Certify text */}
|
|
398
206
|
<p className="text-base text-foreground/70 mb-2">
|
|
399
207
|
This is to certify that
|
|
400
208
|
</p>
|
|
401
209
|
|
|
402
|
-
{/* Recipient name */}
|
|
403
210
|
<p
|
|
404
211
|
className={cn(
|
|
405
212
|
"text-4xl font-bold mb-4 text-foreground",
|
|
@@ -409,12 +216,10 @@ export function CertificateViewer({
|
|
|
409
216
|
{recipientName}
|
|
410
217
|
</p>
|
|
411
218
|
|
|
412
|
-
{/* Completion text */}
|
|
413
219
|
<p className="text-base text-foreground/70 mb-2">
|
|
414
220
|
has successfully completed
|
|
415
221
|
</p>
|
|
416
222
|
|
|
417
|
-
{/* Course title */}
|
|
418
223
|
<p
|
|
419
224
|
className={cn(
|
|
420
225
|
"text-2xl font-semibold mb-4",
|
|
@@ -424,12 +229,10 @@ export function CertificateViewer({
|
|
|
424
229
|
{courseTitle}
|
|
425
230
|
</p>
|
|
426
231
|
|
|
427
|
-
{/* Issuance line */}
|
|
428
232
|
<p className="text-base text-foreground/60 mb-6">
|
|
429
233
|
Issued by {organizationName} on {formattedDate}
|
|
430
234
|
</p>
|
|
431
235
|
|
|
432
|
-
{/* Signatory */}
|
|
433
236
|
{signatory && (
|
|
434
237
|
<div className="mt-6 mb-4">
|
|
435
238
|
<OrnamentalDivider variant={variant} />
|
|
@@ -442,7 +245,6 @@ export function CertificateViewer({
|
|
|
442
245
|
</div>
|
|
443
246
|
)}
|
|
444
247
|
|
|
445
|
-
{/* Certificate ID */}
|
|
446
248
|
{certificateId && (
|
|
447
249
|
<span className="block text-xs text-muted-foreground mt-4">
|
|
448
250
|
Certificate ID: {certificateId}
|
|
@@ -452,7 +254,6 @@ export function CertificateViewer({
|
|
|
452
254
|
</div>
|
|
453
255
|
</div>
|
|
454
256
|
|
|
455
|
-
{/* Actions */}
|
|
456
257
|
{showActions && (
|
|
457
258
|
<div className="flex justify-center gap-3 mt-6">
|
|
458
259
|
<Button variant="outline" onClick={handlePrint}>
|
|
@@ -466,5 +267,6 @@ export function CertificateViewer({
|
|
|
466
267
|
</div>
|
|
467
268
|
)}
|
|
468
269
|
</div>
|
|
270
|
+
</SectionShell>
|
|
469
271
|
);
|
|
470
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
|
+
]);
|
|
@@ -46,6 +46,12 @@ export interface CertificateViewerProps {
|
|
|
46
46
|
onPrint?: () => void;
|
|
47
47
|
/** Called when download is triggered */
|
|
48
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;
|
|
49
55
|
/** CSS class name for the root element */
|
|
50
56
|
className?: string;
|
|
51
57
|
/** Inline styles for the root element */
|