@hydralms/components 0.1.1 → 0.1.3

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 +52 -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,343 @@
1
+ import * as React from "react";
2
+ import {
3
+ createContext,
4
+ useContext,
5
+ useEffect,
6
+ useRef,
7
+ useId,
8
+ useCallback,
9
+ } from "react";
10
+ import { createPortal } from "react-dom";
11
+
12
+ import { cn } from "../lib/utils";
13
+ import { buttonVariants } from "./button";
14
+
15
+ /* --------------------------------- Context -------------------------------- */
16
+
17
+ interface AlertDialogContextValue {
18
+ open: boolean;
19
+ onOpenChange: (open: boolean) => void;
20
+ titleId: string;
21
+ descriptionId: string;
22
+ }
23
+
24
+ const AlertDialogContext = createContext<AlertDialogContextValue | null>(null);
25
+
26
+ function useAlertDialogContext() {
27
+ const ctx = useContext(AlertDialogContext);
28
+ if (!ctx)
29
+ throw new Error(
30
+ "AlertDialog compound components must be used within <AlertDialog>",
31
+ );
32
+ return ctx;
33
+ }
34
+
35
+ /* ------------------------------- Focus Trap ------------------------------- */
36
+
37
+ const FOCUSABLE_SELECTOR = [
38
+ 'a[href]',
39
+ 'button:not([disabled])',
40
+ 'input:not([disabled])',
41
+ 'select:not([disabled])',
42
+ 'textarea:not([disabled])',
43
+ '[tabindex]:not([tabindex="-1"])',
44
+ ].join(", ");
45
+
46
+ function useFocusTrap(containerRef: React.RefObject<HTMLElement | null>, active: boolean) {
47
+ const previousFocusRef = useRef<Element | null>(null);
48
+
49
+ useEffect(() => {
50
+ if (!active || !containerRef.current) return;
51
+
52
+ // Save previously focused element
53
+ previousFocusRef.current = document.activeElement;
54
+
55
+ // Focus first focusable element inside the dialog
56
+ const focusable = containerRef.current.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR);
57
+ if (focusable.length > 0) {
58
+ focusable[0].focus();
59
+ }
60
+
61
+ return () => {
62
+ // Restore focus on cleanup
63
+ if (previousFocusRef.current instanceof HTMLElement) {
64
+ previousFocusRef.current.focus();
65
+ }
66
+ };
67
+ }, [active, containerRef]);
68
+
69
+ const handleKeyDown = useCallback(
70
+ (e: React.KeyboardEvent) => {
71
+ if (e.key !== "Tab" || !containerRef.current) return;
72
+
73
+ const focusable = Array.from(
74
+ containerRef.current.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
75
+ );
76
+ if (focusable.length === 0) return;
77
+
78
+ const first = focusable[0];
79
+ const last = focusable[focusable.length - 1];
80
+
81
+ if (e.shiftKey && document.activeElement === first) {
82
+ e.preventDefault();
83
+ last.focus();
84
+ } else if (!e.shiftKey && document.activeElement === last) {
85
+ e.preventDefault();
86
+ first.focus();
87
+ }
88
+ },
89
+ [containerRef],
90
+ );
91
+
92
+ return handleKeyDown;
93
+ }
94
+
95
+ /* ------------------------------- Scroll Lock ------------------------------ */
96
+
97
+ function useScrollLock(active: boolean) {
98
+ useEffect(() => {
99
+ if (!active) return;
100
+
101
+ const original = document.body.style.overflow;
102
+ document.body.style.overflow = "hidden";
103
+
104
+ return () => {
105
+ document.body.style.overflow = original;
106
+ };
107
+ }, [active]);
108
+ }
109
+
110
+ /* ------------------------------ AlertDialog ------------------------------ */
111
+
112
+ interface AlertDialogProps {
113
+ children: React.ReactNode;
114
+ open: boolean;
115
+ onOpenChange: (open: boolean) => void;
116
+ }
117
+
118
+ function AlertDialog({ children, open, onOpenChange }: AlertDialogProps) {
119
+ const titleId = useId();
120
+ const descriptionId = useId();
121
+
122
+ return (
123
+ <AlertDialogContext.Provider
124
+ value={{ open, onOpenChange, titleId, descriptionId }}
125
+ >
126
+ {children}
127
+ </AlertDialogContext.Provider>
128
+ );
129
+ }
130
+
131
+ /* --------------------------- AlertDialogTrigger --------------------------- */
132
+
133
+ function AlertDialogTrigger({
134
+ className,
135
+ onClick,
136
+ ...props
137
+ }: React.ComponentProps<"button">) {
138
+ const { onOpenChange } = useAlertDialogContext();
139
+
140
+ return (
141
+ <button
142
+ type="button"
143
+ data-slot="alert-dialog-trigger"
144
+ className={className}
145
+ onClick={(e) => {
146
+ onOpenChange(true);
147
+ onClick?.(e);
148
+ }}
149
+ {...props}
150
+ />
151
+ );
152
+ }
153
+
154
+ /* ----------------------------- AlertDialogPortal ----------------------------- */
155
+
156
+ function AlertDialogPortal({ children }: { children: React.ReactNode }) {
157
+ return createPortal(children, document.body);
158
+ }
159
+
160
+ /* --------------------------- AlertDialogBackdrop -------------------------- */
161
+
162
+ function AlertDialogBackdrop({
163
+ className,
164
+ ...props
165
+ }: React.ComponentProps<"div">) {
166
+ const { onOpenChange } = useAlertDialogContext();
167
+
168
+ return (
169
+ <div
170
+ data-slot="alert-dialog-backdrop"
171
+ className={cn(
172
+ "fixed inset-0 z-50 bg-black/50 transition-opacity data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
173
+ className,
174
+ )}
175
+ data-state="open"
176
+ onClick={() => onOpenChange(false)}
177
+ {...props}
178
+ />
179
+ );
180
+ }
181
+
182
+ /* --------------------------- AlertDialogContent --------------------------- */
183
+
184
+ function AlertDialogContent({
185
+ className,
186
+ children,
187
+ ...props
188
+ }: React.ComponentProps<"div">) {
189
+ const { open, onOpenChange, titleId, descriptionId } =
190
+ useAlertDialogContext();
191
+ const contentRef = useRef<HTMLDivElement>(null);
192
+
193
+ // Focus trap
194
+ const handleFocusTrap = useFocusTrap(contentRef, open);
195
+
196
+ // Scroll lock
197
+ useScrollLock(open);
198
+
199
+ // Escape key
200
+ useEffect(() => {
201
+ if (!open) return;
202
+ const handler = (e: KeyboardEvent) => {
203
+ if (e.key === "Escape") onOpenChange(false);
204
+ };
205
+ document.addEventListener("keydown", handler);
206
+ return () => document.removeEventListener("keydown", handler);
207
+ }, [open, onOpenChange]);
208
+
209
+ if (!open) return null;
210
+
211
+ return (
212
+ <AlertDialogPortal>
213
+ <AlertDialogBackdrop />
214
+ <div
215
+ ref={contentRef}
216
+ role="alertdialog"
217
+ aria-modal="true"
218
+ aria-labelledby={titleId}
219
+ aria-describedby={descriptionId}
220
+ data-slot="alert-dialog-content"
221
+ data-state="open"
222
+ className={cn(
223
+ "bg-background fixed top-1/2 left-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border p-6 shadow-lg transition-all data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
224
+ className,
225
+ )}
226
+ onKeyDown={handleFocusTrap}
227
+ {...props}
228
+ >
229
+ {children}
230
+ </div>
231
+ </AlertDialogPortal>
232
+ );
233
+ }
234
+
235
+ /* -------------------------------- Layout --------------------------------- */
236
+
237
+ function AlertDialogHeader({
238
+ className,
239
+ ...props
240
+ }: React.ComponentProps<"div">) {
241
+ return (
242
+ <div
243
+ data-slot="alert-dialog-header"
244
+ className={cn(
245
+ "flex flex-col gap-2 text-center sm:text-left",
246
+ className,
247
+ )}
248
+ {...props}
249
+ />
250
+ );
251
+ }
252
+
253
+ function AlertDialogFooter({
254
+ className,
255
+ ...props
256
+ }: React.ComponentProps<"div">) {
257
+ return (
258
+ <div
259
+ data-slot="alert-dialog-footer"
260
+ className={cn(
261
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
262
+ className,
263
+ )}
264
+ {...props}
265
+ />
266
+ );
267
+ }
268
+
269
+ /* ------------------------------- Title/Desc ------------------------------ */
270
+
271
+ function AlertDialogTitle({
272
+ className,
273
+ ...props
274
+ }: React.ComponentProps<"h2">) {
275
+ const { titleId } = useAlertDialogContext();
276
+
277
+ return (
278
+ <h2
279
+ id={titleId}
280
+ data-slot="alert-dialog-title"
281
+ className={cn("text-lg font-semibold", className)}
282
+ {...props}
283
+ />
284
+ );
285
+ }
286
+
287
+ function AlertDialogDescription({
288
+ className,
289
+ ...props
290
+ }: React.ComponentProps<"p">) {
291
+ const { descriptionId } = useAlertDialogContext();
292
+
293
+ return (
294
+ <p
295
+ id={descriptionId}
296
+ data-slot="alert-dialog-description"
297
+ className={cn("text-muted-foreground text-sm", className)}
298
+ {...props}
299
+ />
300
+ );
301
+ }
302
+
303
+ /* -------------------------------- Actions -------------------------------- */
304
+
305
+ function AlertDialogAction({
306
+ className,
307
+ ...props
308
+ }: React.ComponentProps<"button">) {
309
+ return (
310
+ <button
311
+ type="button"
312
+ className={cn(buttonVariants(), className)}
313
+ {...props}
314
+ />
315
+ );
316
+ }
317
+
318
+ function AlertDialogCancel({
319
+ className,
320
+ ...props
321
+ }: React.ComponentProps<"button">) {
322
+ return (
323
+ <button
324
+ type="button"
325
+ className={cn(buttonVariants({ variant: "outline" }), className)}
326
+ {...props}
327
+ />
328
+ );
329
+ }
330
+
331
+ export {
332
+ AlertDialog,
333
+ AlertDialogTrigger,
334
+ AlertDialogPortal,
335
+ AlertDialogBackdrop,
336
+ AlertDialogContent,
337
+ AlertDialogHeader,
338
+ AlertDialogFooter,
339
+ AlertDialogTitle,
340
+ AlertDialogDescription,
341
+ AlertDialogAction,
342
+ AlertDialogCancel,
343
+ };
@@ -0,0 +1,65 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "../lib/utils";
5
+
6
+ const alertVariants = cva(
7
+ "relative flex w-full items-start gap-3 rounded-lg border p-4 text-sm [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:translate-y-0.5",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-card text-foreground",
12
+ destructive:
13
+ "border-destructive/50 bg-destructive/5 text-destructive [&>svg]:text-destructive",
14
+ success:
15
+ "border-success/50 bg-success/5 text-success [&>svg]:text-success",
16
+ warning:
17
+ "border-warning/50 bg-warning/5 text-warning [&>svg]:text-warning",
18
+ info: "border-info/50 bg-info/5 text-info [&>svg]:text-info",
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ variant: "default",
23
+ },
24
+ },
25
+ );
26
+
27
+ function Alert({
28
+ className,
29
+ variant,
30
+ ...props
31
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
32
+ return (
33
+ <div
34
+ data-slot="alert"
35
+ role="alert"
36
+ className={cn(alertVariants({ variant }), className)}
37
+ {...props}
38
+ />
39
+ );
40
+ }
41
+
42
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
43
+ return (
44
+ <div
45
+ data-slot="alert-title"
46
+ className={cn("font-medium leading-normal tracking-tight", className)}
47
+ {...props}
48
+ />
49
+ );
50
+ }
51
+
52
+ function AlertDescription({
53
+ className,
54
+ ...props
55
+ }: React.ComponentProps<"div">) {
56
+ return (
57
+ <div
58
+ data-slot="alert-description"
59
+ className={cn("text-foreground text-sm [&_p]:leading-relaxed", className)}
60
+ {...props}
61
+ />
62
+ );
63
+ }
64
+
65
+ export { Alert, AlertTitle, AlertDescription, alertVariants };
@@ -0,0 +1,52 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../lib/utils";
4
+
5
+ function Avatar({
6
+ className,
7
+ ...props
8
+ }: React.ComponentProps<"span">) {
9
+ return (
10
+ <span
11
+ data-slot="avatar"
12
+ className={cn(
13
+ "relative flex size-9 shrink-0 overflow-hidden rounded-full",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ function AvatarImage({
22
+ className,
23
+ onError,
24
+ ...props
25
+ }: React.ComponentProps<"img">) {
26
+ return (
27
+ <img
28
+ data-slot="avatar-image"
29
+ className={cn("aspect-square size-full object-cover", className)}
30
+ onError={onError}
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+
36
+ function AvatarFallback({
37
+ className,
38
+ ...props
39
+ }: React.ComponentProps<"span">) {
40
+ return (
41
+ <span
42
+ data-slot="avatar-fallback"
43
+ className={cn(
44
+ "bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-xs font-medium",
45
+ className,
46
+ )}
47
+ {...props}
48
+ />
49
+ );
50
+ }
51
+
52
+ export { Avatar, AvatarImage, AvatarFallback };
@@ -0,0 +1,53 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "../lib/utils";
5
+ import { Slot } from "./slot";
6
+
7
+ const badgeVariants = cva(
8
+ "inline-flex items-center justify-center rounded-full border px-2.5 py-0.5 text-xs font-medium whitespace-nowrap transition-colors [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "border-transparent bg-primary text-primary-foreground",
14
+ secondary:
15
+ "border-transparent bg-secondary text-secondary-foreground",
16
+ destructive:
17
+ "border-transparent bg-destructive text-destructive-foreground",
18
+ outline: "text-foreground",
19
+ success:
20
+ "border-transparent bg-success/15 text-success",
21
+ warning:
22
+ "border-transparent bg-warning/15 text-warning",
23
+ info:
24
+ "border-transparent bg-info/15 text-info",
25
+ muted:
26
+ "border-transparent bg-muted text-muted-foreground",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ },
32
+ },
33
+ );
34
+
35
+ function Badge({
36
+ className,
37
+ variant,
38
+ asChild = false,
39
+ ...props
40
+ }: React.ComponentProps<"span"> &
41
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
42
+ const Comp = asChild ? Slot : "span";
43
+
44
+ return (
45
+ <Comp
46
+ data-slot="badge"
47
+ className={cn(badgeVariants({ variant }), className)}
48
+ {...props}
49
+ />
50
+ );
51
+ }
52
+
53
+ export { Badge, badgeVariants };
@@ -0,0 +1,62 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "../lib/utils";
5
+ import { Slot } from "./slot";
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
15
+ outline:
16
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost:
20
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
25
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
26
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
27
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28
+ icon: "size-9",
29
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
30
+ "icon-sm": "size-8",
31
+ "icon-lg": "size-10",
32
+ },
33
+ },
34
+ defaultVariants: {
35
+ variant: "default",
36
+ size: "default",
37
+ },
38
+ },
39
+ );
40
+
41
+ function Button({
42
+ className,
43
+ variant = "default",
44
+ size = "default",
45
+ asChild = false,
46
+ ...props
47
+ }: React.ComponentProps<"button"> &
48
+ VariantProps<typeof buttonVariants> & {
49
+ asChild?: boolean;
50
+ }) {
51
+ const Comp = asChild ? Slot : "button";
52
+
53
+ return (
54
+ <Comp
55
+ data-slot="button"
56
+ className={cn(buttonVariants({ variant, size, className }))}
57
+ {...props}
58
+ />
59
+ );
60
+ }
61
+
62
+ export { Button, buttonVariants };
@@ -0,0 +1,92 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../lib/utils";
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "bg-card text-card-foreground rounded-xl border shadow-sm",
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn(
23
+ "flex flex-col gap-1.5 px-6 pt-6 has-data-[slot=card-action]:flex-row has-data-[slot=card-action]:items-center has-data-[slot=card-action]:justify-between",
24
+ className,
25
+ )}
26
+ {...props}
27
+ />
28
+ );
29
+ }
30
+
31
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32
+ return (
33
+ <div
34
+ data-slot="card-title"
35
+ className={cn("leading-none font-semibold", className)}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="card-description"
45
+ className={cn("text-muted-foreground text-sm", className)}
46
+ {...props}
47
+ />
48
+ );
49
+ }
50
+
51
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52
+ return (
53
+ <div
54
+ data-slot="card-action"
55
+ className={cn(
56
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
57
+ className,
58
+ )}
59
+ {...props}
60
+ />
61
+ );
62
+ }
63
+
64
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65
+ return (
66
+ <div
67
+ data-slot="card-content"
68
+ className={cn("px-6 pb-6 pt-0", className)}
69
+ {...props}
70
+ />
71
+ );
72
+ }
73
+
74
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75
+ return (
76
+ <div
77
+ data-slot="card-footer"
78
+ className={cn("flex items-center px-6 pb-6 pt-0", className)}
79
+ {...props}
80
+ />
81
+ );
82
+ }
83
+
84
+ export {
85
+ Card,
86
+ CardHeader,
87
+ CardFooter,
88
+ CardTitle,
89
+ CardAction,
90
+ CardDescription,
91
+ CardContent,
92
+ };
@@ -0,0 +1,44 @@
1
+ export { Button, buttonVariants } from "./button";
2
+ export { Input } from "./input";
3
+ export { Textarea } from "./textarea";
4
+ export { Badge, badgeVariants } from "./badge";
5
+ export { Alert, AlertTitle, AlertDescription, alertVariants } from "./alert";
6
+ export { Separator } from "./separator";
7
+ export {
8
+ AlertDialog,
9
+ AlertDialogTrigger,
10
+ AlertDialogPortal,
11
+ AlertDialogBackdrop,
12
+ AlertDialogContent,
13
+ AlertDialogHeader,
14
+ AlertDialogFooter,
15
+ AlertDialogTitle,
16
+ AlertDialogDescription,
17
+ AlertDialogAction,
18
+ AlertDialogCancel,
19
+ } from "./alert-dialog";
20
+ export { Avatar, AvatarImage, AvatarFallback } from "./avatar";
21
+ export {
22
+ Card,
23
+ CardHeader,
24
+ CardFooter,
25
+ CardTitle,
26
+ CardAction,
27
+ CardDescription,
28
+ CardContent,
29
+ } from "./card";
30
+ export { Tabs, TabsList, TabsTrigger, TabsContent } from "./tabs";
31
+ export { Progress, progressVariants } from "./progress";
32
+ export type { ProgressProps } from "./progress";
33
+ export {
34
+ Table,
35
+ TableHeader,
36
+ TableBody,
37
+ TableFooter,
38
+ TableRow,
39
+ TableHead,
40
+ TableCell,
41
+ TableCaption,
42
+ } from "./table";
43
+ export { Skeleton } from "./skeleton";
44
+ export { Tooltip, TooltipTrigger, TooltipContent } from "./tooltip";