@hydralms/components 0.1.1 → 0.1.2

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.
Files changed (131) hide show
  1. package/package.json +3 -1
  2. package/src/__tests__/setup.ts +1 -0
  3. package/src/assessment-toolbar/assessment-toolbar.tsx +96 -0
  4. package/src/assessment-toolbar/index.ts +10 -0
  5. package/src/assessment-toolbar/question-navigator.tsx +86 -0
  6. package/src/assessment-toolbar/timer-display.tsx +73 -0
  7. package/src/assessment-toolbar/types.ts +92 -0
  8. package/src/assets/hydra-icon.png +0 -0
  9. package/src/assets/hydra-icon.svg +18 -0
  10. package/src/assets/hydra-lms-icon.png +0 -0
  11. package/src/assets/hydra-lms-icon.svg +9 -0
  12. package/src/common/confirm-dialog.tsx +60 -0
  13. package/src/common/due-date-display.tsx +64 -0
  14. package/src/common/empty-state.tsx +24 -0
  15. package/src/common/index.ts +12 -0
  16. package/src/common/search-input.tsx +68 -0
  17. package/src/common/status-badge.test.tsx +43 -0
  18. package/src/common/status-badge.tsx +81 -0
  19. package/src/common/types.ts +129 -0
  20. package/src/content/content-block.tsx +116 -0
  21. package/src/content/file-upload-zone.tsx +109 -0
  22. package/src/content/index.ts +7 -0
  23. package/src/content/types.ts +76 -0
  24. package/src/curriculum/curriculum-item.tsx +81 -0
  25. package/src/curriculum/curriculum-tree.tsx +69 -0
  26. package/src/curriculum/index.ts +11 -0
  27. package/src/curriculum/learning-object-icon.tsx +44 -0
  28. package/src/curriculum/types.ts +83 -0
  29. package/src/feedback/feedback-banner.tsx +46 -0
  30. package/src/feedback/index.ts +8 -0
  31. package/src/feedback/likert-scale.tsx +58 -0
  32. package/src/feedback/star-rating.tsx +65 -0
  33. package/src/feedback/types.ts +86 -0
  34. package/src/flashcards/flashcard-deck.tsx +130 -0
  35. package/src/flashcards/flashcard.tsx +108 -0
  36. package/src/flashcards/index.ts +3 -0
  37. package/src/flashcards/types.ts +60 -0
  38. package/src/index.ts +38 -0
  39. package/src/lib/utils.ts +6 -0
  40. package/src/modules/CoursePlayer/CoursePlayer.tsx +281 -0
  41. package/src/modules/CoursePlayer/types.ts +48 -0
  42. package/src/modules/FlashcardLab/FlashcardLab.tsx +275 -0
  43. package/src/modules/FlashcardLab/types.ts +58 -0
  44. package/src/modules/QuizModule/QuizModule.tsx +241 -0
  45. package/src/modules/QuizModule/types.ts +56 -0
  46. package/src/modules/index.ts +12 -0
  47. package/src/progress/grade-indicator.tsx +65 -0
  48. package/src/progress/index.ts +8 -0
  49. package/src/progress/progress-ring.tsx +56 -0
  50. package/src/progress/stat-card.tsx +42 -0
  51. package/src/progress/types.ts +73 -0
  52. package/src/provider/HydraProvider.tsx +26 -0
  53. package/src/provider/index.ts +2 -0
  54. package/src/questions/choice.tsx +90 -0
  55. package/src/questions/essay.tsx +59 -0
  56. package/src/questions/fill-in-the-blank.tsx +69 -0
  57. package/src/questions/index.ts +14 -0
  58. package/src/questions/multiple-choice.test.tsx +104 -0
  59. package/src/questions/multiple-choice.tsx +97 -0
  60. package/src/questions/question-renderer.tsx +37 -0
  61. package/src/questions/true-false.test.tsx +89 -0
  62. package/src/questions/true-false.tsx +90 -0
  63. package/src/questions/types.ts +53 -0
  64. package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +141 -0
  65. package/src/sections/AnnouncementFeed/types.ts +50 -0
  66. package/src/sections/AssessmentReview/AssessmentReview.tsx +148 -0
  67. package/src/sections/AssessmentReview/types.ts +61 -0
  68. package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +190 -0
  69. package/src/sections/AssignmentSubmission/types.ts +60 -0
  70. package/src/sections/CertificateViewer/CertificateViewer.tsx +117 -0
  71. package/src/sections/CertificateViewer/types.ts +45 -0
  72. package/src/sections/CourseOutline/CourseOutline.tsx +79 -0
  73. package/src/sections/CourseOutline/types.ts +53 -0
  74. package/src/sections/DiscussionThread/DiscussionThread.tsx +186 -0
  75. package/src/sections/DiscussionThread/types.ts +77 -0
  76. package/src/sections/ExamSession/ExamSession.tsx +182 -0
  77. package/src/sections/ExamSession/types.ts +64 -0
  78. package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +76 -0
  79. package/src/sections/FlashcardStudySession/types.ts +42 -0
  80. package/src/sections/GradebookTable/GradebookTable.tsx +229 -0
  81. package/src/sections/GradebookTable/types.ts +75 -0
  82. package/src/sections/LecturePlayer/LecturePlayer.tsx +60 -0
  83. package/src/sections/LecturePlayer/types.ts +48 -0
  84. package/src/sections/LessonPage/LessonPage.tsx +91 -0
  85. package/src/sections/LessonPage/types.ts +41 -0
  86. package/src/sections/PracticeQuiz/PracticeQuiz.tsx +199 -0
  87. package/src/sections/PracticeQuiz/types.ts +44 -0
  88. package/src/sections/ProgressDashboard/ProgressDashboard.tsx +140 -0
  89. package/src/sections/ProgressDashboard/types.ts +74 -0
  90. package/src/sections/QuizSession/QuizSession.tsx +113 -0
  91. package/src/sections/QuizSession/types.ts +47 -0
  92. package/src/sections/ResourceLibrary/ResourceLibrary.tsx +218 -0
  93. package/src/sections/ResourceLibrary/types.ts +57 -0
  94. package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +170 -0
  95. package/src/sections/ScrollableQuiz/types.ts +40 -0
  96. package/src/sections/SurveyForm/SurveyForm.tsx +180 -0
  97. package/src/sections/SurveyForm/types.ts +69 -0
  98. package/src/sections/index.ts +90 -0
  99. package/src/social/index.ts +3 -0
  100. package/src/social/post-card.tsx +91 -0
  101. package/src/social/types.ts +57 -0
  102. package/src/social/user-avatar.tsx +76 -0
  103. package/src/styles/globals.css +125 -0
  104. package/src/ui/alert-dialog.tsx +343 -0
  105. package/src/ui/alert.tsx +65 -0
  106. package/src/ui/avatar.tsx +52 -0
  107. package/src/ui/badge.tsx +53 -0
  108. package/src/ui/button.tsx +62 -0
  109. package/src/ui/card.tsx +92 -0
  110. package/src/ui/index.ts +44 -0
  111. package/src/ui/input.tsx +21 -0
  112. package/src/ui/progress.tsx +73 -0
  113. package/src/ui/separator.tsx +29 -0
  114. package/src/ui/skeleton.tsx +15 -0
  115. package/src/ui/slot.tsx +48 -0
  116. package/src/ui/table.tsx +108 -0
  117. package/src/ui/tabs.tsx +147 -0
  118. package/src/ui/textarea.tsx +20 -0
  119. package/src/ui/tooltip.tsx +177 -0
  120. package/src/utils/debounce.test.ts +59 -0
  121. package/src/utils/debounce.ts +10 -0
  122. package/src/utils/format-duration.test.ts +55 -0
  123. package/src/utils/format-duration.ts +30 -0
  124. package/src/video/index.ts +17 -0
  125. package/src/video/types.ts +216 -0
  126. package/src/video/video-bookmark.tsx +76 -0
  127. package/src/video/video-chapter-list.tsx +93 -0
  128. package/src/video/video-player.tsx +103 -0
  129. package/src/video/video-playlist-item.tsx +90 -0
  130. package/src/video/video-thumbnail-card.tsx +74 -0
  131. package/src/video/video-transcript.tsx +102 -0
@@ -0,0 +1,74 @@
1
+ import { Play, Video } from "lucide-react";
2
+ import type { VideoThumbnailCardProps } from "./types";
3
+ import { cn } from "../lib/utils";
4
+ import { formatDuration } from "../utils/format-duration";
5
+ import { Progress } from "../ui/progress";
6
+
7
+ export const VideoThumbnailCard = ({
8
+ poster,
9
+ title,
10
+ duration,
11
+ progress,
12
+ onClick,
13
+ className,
14
+ style,
15
+ }: VideoThumbnailCardProps) => {
16
+ return (
17
+ <div
18
+ className={cn(
19
+ "group overflow-hidden rounded-lg border border-border transition-colors",
20
+ onClick && "cursor-pointer hover:border-primary/50 hover:shadow-sm",
21
+ className,
22
+ )}
23
+ style={style}
24
+ onClick={onClick}
25
+ >
26
+ {/* Poster area */}
27
+ <div className="relative overflow-hidden" style={{ aspectRatio: "16/9" }}>
28
+ {poster ? (
29
+ <img
30
+ src={poster}
31
+ alt={title}
32
+ className="size-full object-cover transition-transform duration-300 group-hover:scale-105"
33
+ />
34
+ ) : (
35
+ <div className="flex size-full items-center justify-center bg-muted">
36
+ <Video size={32} className="text-muted-foreground" />
37
+ </div>
38
+ )}
39
+
40
+ {/* Play icon overlay */}
41
+ <div className="absolute inset-0 flex items-center justify-center bg-black/0 transition-colors group-hover:bg-black/20">
42
+ <div className="flex size-10 items-center justify-center rounded-full bg-black/60 opacity-0 transition-opacity group-hover:opacity-100">
43
+ <Play size={20} className="ml-0.5 text-white" />
44
+ </div>
45
+ </div>
46
+
47
+ {/* Duration badge */}
48
+ {duration != null && duration > 0 && (
49
+ <span className="absolute bottom-2 right-2 rounded bg-black/75 px-1.5 py-0.5 text-xs font-medium tabular-nums text-white">
50
+ {formatDuration(duration)}
51
+ </span>
52
+ )}
53
+ </div>
54
+
55
+ {/* Title */}
56
+ <div className="p-3">
57
+ <h4 className="truncate text-sm font-medium text-foreground">
58
+ {title}
59
+ </h4>
60
+ </div>
61
+
62
+ {/* Optional progress bar */}
63
+ {progress != null && progress >= 0 && (
64
+ <div className="px-3 pb-3">
65
+ <Progress
66
+ value={progress}
67
+ size="sm"
68
+ variant={progress >= 100 ? "success" : "default"}
69
+ />
70
+ </div>
71
+ )}
72
+ </div>
73
+ );
74
+ };
@@ -0,0 +1,102 @@
1
+ import { useRef, useEffect } from "react";
2
+ import { Clock } from "lucide-react";
3
+ import type { VideoTranscriptProps } from "./types";
4
+ import { cn } from "../lib/utils";
5
+ import { formatTimer } from "../utils/format-duration";
6
+
7
+ export const VideoTranscript = ({
8
+ entries,
9
+ currentTime = 0,
10
+ onSeek,
11
+ readOnly = false,
12
+ maxHeight = "400px",
13
+ className,
14
+ style,
15
+ }: VideoTranscriptProps) => {
16
+ const containerRef = useRef<HTMLDivElement>(null);
17
+ const activeRef = useRef<HTMLDivElement>(null);
18
+
19
+ // Last entry whose time <= currentTime
20
+ const activeIndex = entries.reduce<number>(
21
+ (acc, entry, i) => (entry.time <= currentTime ? i : acc),
22
+ -1,
23
+ );
24
+
25
+ useEffect(() => {
26
+ if (activeRef.current && containerRef.current) {
27
+ activeRef.current.scrollIntoView({
28
+ behavior: "smooth",
29
+ block: "nearest",
30
+ });
31
+ }
32
+ }, [activeIndex]);
33
+
34
+ if (entries.length === 0) {
35
+ return (
36
+ <div
37
+ className={cn(
38
+ "flex flex-col items-center justify-center gap-2 rounded-md border border-border bg-muted p-6",
39
+ className,
40
+ )}
41
+ style={style}
42
+ >
43
+ <Clock size={20} className="text-muted-foreground" />
44
+ <span className="text-sm text-muted-foreground">
45
+ No transcript available
46
+ </span>
47
+ </div>
48
+ );
49
+ }
50
+
51
+ return (
52
+ <div
53
+ ref={containerRef}
54
+ className={cn(
55
+ "overflow-y-auto rounded-md border border-border",
56
+ className,
57
+ )}
58
+ style={{ maxHeight, ...style }}
59
+ >
60
+ {entries.map((entry, i) => {
61
+ const isActive = i === activeIndex;
62
+ return (
63
+ <div
64
+ key={i}
65
+ ref={isActive ? activeRef : undefined}
66
+ className={cn(
67
+ "flex gap-3 px-3 py-2 text-sm transition-colors",
68
+ isActive && "bg-primary/10",
69
+ !readOnly && onSeek && "cursor-pointer hover:bg-muted",
70
+ )}
71
+ onClick={() => !readOnly && onSeek?.(entry.time)}
72
+ >
73
+ <span
74
+ className={cn(
75
+ "shrink-0 font-mono text-xs tabular-nums pt-0.5",
76
+ isActive
77
+ ? "text-primary font-medium"
78
+ : "text-muted-foreground",
79
+ )}
80
+ >
81
+ {formatTimer(Math.floor(entry.time))}
82
+ </span>
83
+ <div className="min-w-0">
84
+ {entry.speaker && (
85
+ <span className="mr-1 font-semibold text-foreground">
86
+ {entry.speaker}:
87
+ </span>
88
+ )}
89
+ <span
90
+ className={
91
+ isActive ? "text-foreground" : "text-muted-foreground"
92
+ }
93
+ >
94
+ {entry.text}
95
+ </span>
96
+ </div>
97
+ </div>
98
+ );
99
+ })}
100
+ </div>
101
+ );
102
+ };