@hydralms/components 0.1.0 → 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.
- package/dist/components.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +442 -110
- package/dist/modules/CoursePlayer/CoursePlayer.d.ts +2 -0
- package/dist/modules/CoursePlayer/types.d.ts +59 -0
- package/dist/modules/FlashcardLab/FlashcardLab.d.ts +2 -0
- package/dist/modules/FlashcardLab/types.d.ts +55 -0
- package/dist/modules/QuizModule/QuizModule.d.ts +2 -0
- package/dist/modules/QuizModule/types.d.ts +54 -0
- package/dist/modules/index.d.ts +6 -0
- package/dist/provider/HydraProvider.d.ts +1 -1
- package/dist/sections.cjs +1 -1
- package/dist/sections.js +261 -291
- package/dist/table-BrS5cDQu.js +2510 -0
- package/dist/table-D6AkBBEo.cjs +1 -0
- package/dist/ui/alert-dialog.d.ts +14 -8
- package/dist/ui/button.d.ts +1 -1
- package/dist/ui/tabs.d.ts +15 -5
- package/dist/ui/tooltip.d.ts +12 -5
- package/dist/video/index.d.ts +6 -1
- package/dist/video/types.d.ts +167 -0
- package/dist/video/video-bookmark.d.ts +2 -0
- package/dist/video/video-chapter-list.d.ts +2 -0
- package/dist/video/video-playlist-item.d.ts +2 -0
- package/dist/video/video-thumbnail-card.d.ts +2 -0
- package/dist/video/video-transcript.d.ts +2 -0
- package/package.json +135 -24
- package/src/__tests__/setup.ts +1 -0
- package/src/assessment-toolbar/assessment-toolbar.tsx +96 -0
- package/src/assessment-toolbar/index.ts +10 -0
- package/src/assessment-toolbar/question-navigator.tsx +86 -0
- package/src/assessment-toolbar/timer-display.tsx +73 -0
- package/src/assessment-toolbar/types.ts +92 -0
- package/src/assets/hydra-icon.png +0 -0
- package/src/assets/hydra-icon.svg +18 -0
- package/src/assets/hydra-lms-icon.png +0 -0
- package/src/assets/hydra-lms-icon.svg +9 -0
- package/src/common/confirm-dialog.tsx +60 -0
- package/src/common/due-date-display.tsx +64 -0
- package/src/common/empty-state.tsx +24 -0
- package/src/common/index.ts +12 -0
- package/src/common/search-input.tsx +68 -0
- package/src/common/status-badge.test.tsx +43 -0
- package/src/common/status-badge.tsx +81 -0
- package/src/common/types.ts +129 -0
- package/src/content/content-block.tsx +116 -0
- package/src/content/file-upload-zone.tsx +109 -0
- package/src/content/index.ts +7 -0
- package/src/content/types.ts +76 -0
- package/src/curriculum/curriculum-item.tsx +81 -0
- package/src/curriculum/curriculum-tree.tsx +69 -0
- package/src/curriculum/index.ts +11 -0
- package/src/curriculum/learning-object-icon.tsx +44 -0
- package/src/curriculum/types.ts +83 -0
- package/src/feedback/feedback-banner.tsx +46 -0
- package/src/feedback/index.ts +8 -0
- package/src/feedback/likert-scale.tsx +58 -0
- package/src/feedback/star-rating.tsx +65 -0
- package/src/feedback/types.ts +86 -0
- package/src/flashcards/flashcard-deck.tsx +130 -0
- package/src/flashcards/flashcard.tsx +108 -0
- package/src/flashcards/index.ts +3 -0
- package/src/flashcards/types.ts +60 -0
- package/src/index.ts +38 -0
- package/src/lib/utils.ts +6 -0
- package/src/modules/CoursePlayer/CoursePlayer.tsx +281 -0
- package/src/modules/CoursePlayer/types.ts +48 -0
- package/src/modules/FlashcardLab/FlashcardLab.tsx +275 -0
- package/src/modules/FlashcardLab/types.ts +58 -0
- package/src/modules/QuizModule/QuizModule.tsx +241 -0
- package/src/modules/QuizModule/types.ts +56 -0
- package/src/modules/index.ts +12 -0
- package/src/progress/grade-indicator.tsx +65 -0
- package/src/progress/index.ts +8 -0
- package/src/progress/progress-ring.tsx +56 -0
- package/src/progress/stat-card.tsx +42 -0
- package/src/progress/types.ts +73 -0
- package/src/provider/HydraProvider.tsx +26 -0
- package/src/provider/index.ts +2 -0
- package/src/questions/choice.tsx +90 -0
- package/src/questions/essay.tsx +59 -0
- package/src/questions/fill-in-the-blank.tsx +69 -0
- package/src/questions/index.ts +14 -0
- package/src/questions/multiple-choice.test.tsx +104 -0
- package/src/questions/multiple-choice.tsx +97 -0
- package/src/questions/question-renderer.tsx +37 -0
- package/src/questions/true-false.test.tsx +89 -0
- package/src/questions/true-false.tsx +90 -0
- package/src/questions/types.ts +53 -0
- package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +141 -0
- package/src/sections/AnnouncementFeed/types.ts +50 -0
- package/src/sections/AssessmentReview/AssessmentReview.tsx +148 -0
- package/src/sections/AssessmentReview/types.ts +61 -0
- package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +190 -0
- package/src/sections/AssignmentSubmission/types.ts +60 -0
- package/src/sections/CertificateViewer/CertificateViewer.tsx +117 -0
- package/src/sections/CertificateViewer/types.ts +45 -0
- package/src/sections/CourseOutline/CourseOutline.tsx +79 -0
- package/src/sections/CourseOutline/types.ts +53 -0
- package/src/sections/DiscussionThread/DiscussionThread.tsx +186 -0
- package/src/sections/DiscussionThread/types.ts +77 -0
- package/src/sections/ExamSession/ExamSession.tsx +182 -0
- package/src/sections/ExamSession/types.ts +64 -0
- package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +76 -0
- package/src/sections/FlashcardStudySession/types.ts +42 -0
- package/src/sections/GradebookTable/GradebookTable.tsx +229 -0
- package/src/sections/GradebookTable/types.ts +75 -0
- package/src/sections/LecturePlayer/LecturePlayer.tsx +60 -0
- package/src/sections/LecturePlayer/types.ts +48 -0
- package/src/sections/LessonPage/LessonPage.tsx +91 -0
- package/src/sections/LessonPage/types.ts +41 -0
- package/src/sections/PracticeQuiz/PracticeQuiz.tsx +199 -0
- package/src/sections/PracticeQuiz/types.ts +44 -0
- package/src/sections/ProgressDashboard/ProgressDashboard.tsx +140 -0
- package/src/sections/ProgressDashboard/types.ts +74 -0
- package/src/sections/QuizSession/QuizSession.tsx +113 -0
- package/src/sections/QuizSession/types.ts +47 -0
- package/src/sections/ResourceLibrary/ResourceLibrary.tsx +218 -0
- package/src/sections/ResourceLibrary/types.ts +57 -0
- package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +170 -0
- package/src/sections/ScrollableQuiz/types.ts +40 -0
- package/src/sections/SurveyForm/SurveyForm.tsx +180 -0
- package/src/sections/SurveyForm/types.ts +69 -0
- package/src/sections/index.ts +90 -0
- package/src/social/index.ts +3 -0
- package/src/social/post-card.tsx +91 -0
- package/src/social/types.ts +57 -0
- package/src/social/user-avatar.tsx +76 -0
- package/src/styles/globals.css +125 -0
- package/src/ui/alert-dialog.tsx +343 -0
- package/src/ui/alert.tsx +65 -0
- package/src/ui/avatar.tsx +52 -0
- package/src/ui/badge.tsx +53 -0
- package/src/ui/button.tsx +62 -0
- package/src/ui/card.tsx +92 -0
- package/src/ui/index.ts +44 -0
- package/src/ui/input.tsx +21 -0
- package/src/ui/progress.tsx +73 -0
- package/src/ui/separator.tsx +29 -0
- package/src/ui/skeleton.tsx +15 -0
- package/src/ui/slot.tsx +48 -0
- package/src/ui/table.tsx +108 -0
- package/src/ui/tabs.tsx +147 -0
- package/src/ui/textarea.tsx +20 -0
- package/src/ui/tooltip.tsx +177 -0
- package/src/utils/debounce.test.ts +59 -0
- package/src/utils/debounce.ts +10 -0
- package/src/utils/format-duration.test.ts +55 -0
- package/src/utils/format-duration.ts +30 -0
- package/src/video/index.ts +17 -0
- package/src/video/types.ts +216 -0
- package/src/video/video-bookmark.tsx +76 -0
- package/src/video/video-chapter-list.tsx +93 -0
- package/src/video/video-player.tsx +103 -0
- package/src/video/video-playlist-item.tsx +90 -0
- package/src/video/video-thumbnail-card.tsx +74 -0
- package/src/video/video-transcript.tsx +102 -0
- package/dist/table-CW4_BYny.js +0 -9869
- package/dist/table-DSBBqb9X.cjs +0 -56
package/src/ui/card.tsx
ADDED
|
@@ -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
|
+
};
|
package/src/ui/index.ts
ADDED
|
@@ -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";
|
package/src/ui/input.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
6
|
+
return (
|
|
7
|
+
<input
|
|
8
|
+
type={type}
|
|
9
|
+
data-slot="input"
|
|
10
|
+
className={cn(
|
|
11
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-sm shadow-xs outline-none transition-[color,box-shadow] file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:opacity-50",
|
|
12
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
13
|
+
"aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { Input };
|
|
@@ -0,0 +1,73 @@
|
|
|
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 progressVariants = cva("h-full rounded-full transition-all duration-300", {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
default: "bg-primary",
|
|
10
|
+
success: "bg-success",
|
|
11
|
+
warning: "bg-warning",
|
|
12
|
+
info: "bg-info",
|
|
13
|
+
},
|
|
14
|
+
size: {
|
|
15
|
+
sm: "",
|
|
16
|
+
default: "",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: "default",
|
|
21
|
+
size: "default",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const trackVariants = cva("bg-muted overflow-hidden rounded-full w-full", {
|
|
26
|
+
variants: {
|
|
27
|
+
size: {
|
|
28
|
+
sm: "h-1.5",
|
|
29
|
+
default: "h-2.5",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
size: "default",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
interface ProgressProps
|
|
38
|
+
extends Omit<React.ComponentProps<"div">, "children">,
|
|
39
|
+
VariantProps<typeof progressVariants> {
|
|
40
|
+
value?: number;
|
|
41
|
+
max?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function Progress({
|
|
45
|
+
className,
|
|
46
|
+
value = 0,
|
|
47
|
+
max = 100,
|
|
48
|
+
variant,
|
|
49
|
+
size,
|
|
50
|
+
...props
|
|
51
|
+
}: ProgressProps) {
|
|
52
|
+
const percentage = Math.min(100, Math.max(0, (value / max) * 100));
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
data-slot="progress"
|
|
57
|
+
role="progressbar"
|
|
58
|
+
aria-valuenow={value}
|
|
59
|
+
aria-valuemin={0}
|
|
60
|
+
aria-valuemax={max}
|
|
61
|
+
className={cn(trackVariants({ size }), className)}
|
|
62
|
+
{...props}
|
|
63
|
+
>
|
|
64
|
+
<div
|
|
65
|
+
className={progressVariants({ variant, size })}
|
|
66
|
+
style={{ width: `${percentage}%` }}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { Progress, progressVariants };
|
|
73
|
+
export type { ProgressProps };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Separator({
|
|
6
|
+
className,
|
|
7
|
+
orientation = "horizontal",
|
|
8
|
+
decorative = true,
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<"div"> & {
|
|
11
|
+
orientation?: "horizontal" | "vertical";
|
|
12
|
+
decorative?: boolean;
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
data-slot="separator"
|
|
17
|
+
role={decorative ? "none" : "separator"}
|
|
18
|
+
aria-orientation={!decorative ? orientation : undefined}
|
|
19
|
+
className={cn(
|
|
20
|
+
"bg-border shrink-0",
|
|
21
|
+
orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
|
|
22
|
+
className,
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { Separator };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="skeleton"
|
|
9
|
+
className={cn("bg-muted animate-pulse rounded-md", className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { Skeleton };
|
package/src/ui/slot.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal Slot implementation for the asChild pattern.
|
|
5
|
+
* Merges parent props onto a single React element child.
|
|
6
|
+
*/
|
|
7
|
+
export function Slot({
|
|
8
|
+
children,
|
|
9
|
+
...props
|
|
10
|
+
}: React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }) {
|
|
11
|
+
if (React.isValidElement(children)) {
|
|
12
|
+
return React.cloneElement(children, {
|
|
13
|
+
...mergeProps(props, children.props as Record<string, unknown>),
|
|
14
|
+
ref: (children as React.ReactElement & { ref?: React.Ref<unknown> }).ref,
|
|
15
|
+
} as Record<string, unknown>);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (React.Children.count(children) > 1) {
|
|
19
|
+
React.Children.only(null);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mergeProps(
|
|
26
|
+
slotProps: Record<string, unknown>,
|
|
27
|
+
childProps: Record<string, unknown>,
|
|
28
|
+
) {
|
|
29
|
+
const overrideProps: Record<string, unknown> = { ...childProps };
|
|
30
|
+
|
|
31
|
+
for (const propName in childProps) {
|
|
32
|
+
const slotVal = slotProps[propName];
|
|
33
|
+
const childVal = childProps[propName];
|
|
34
|
+
|
|
35
|
+
if (propName === "style") {
|
|
36
|
+
overrideProps[propName] = { ...(slotVal as object), ...(childVal as object) };
|
|
37
|
+
} else if (propName === "className") {
|
|
38
|
+
overrideProps[propName] = [slotVal, childVal].filter(Boolean).join(" ");
|
|
39
|
+
} else if (typeof slotVal === "function" && typeof childVal === "function") {
|
|
40
|
+
overrideProps[propName] = (...args: unknown[]) => {
|
|
41
|
+
childVal(...args);
|
|
42
|
+
slotVal(...args);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { ...slotProps, ...overrideProps };
|
|
48
|
+
}
|
package/src/ui/table.tsx
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
6
|
+
return (
|
|
7
|
+
<div data-slot="table-container" className="relative w-full overflow-auto">
|
|
8
|
+
<table
|
|
9
|
+
data-slot="table"
|
|
10
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
18
|
+
return (
|
|
19
|
+
<thead
|
|
20
|
+
data-slot="table-header"
|
|
21
|
+
className={cn("[&_tr]:border-b", className)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
28
|
+
return (
|
|
29
|
+
<tbody
|
|
30
|
+
data-slot="table-body"
|
|
31
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
38
|
+
return (
|
|
39
|
+
<tfoot
|
|
40
|
+
data-slot="table-footer"
|
|
41
|
+
className={cn(
|
|
42
|
+
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
51
|
+
return (
|
|
52
|
+
<tr
|
|
53
|
+
data-slot="table-row"
|
|
54
|
+
className={cn(
|
|
55
|
+
"border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
64
|
+
return (
|
|
65
|
+
<th
|
|
66
|
+
data-slot="table-head"
|
|
67
|
+
className={cn(
|
|
68
|
+
"text-muted-foreground h-10 px-3 text-left align-middle font-semibold whitespace-nowrap text-sm [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
69
|
+
className,
|
|
70
|
+
)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
77
|
+
return (
|
|
78
|
+
<td
|
|
79
|
+
data-slot="table-cell"
|
|
80
|
+
className={cn(
|
|
81
|
+
"px-3 py-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
82
|
+
className,
|
|
83
|
+
)}
|
|
84
|
+
{...props}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
|
|
90
|
+
return (
|
|
91
|
+
<caption
|
|
92
|
+
data-slot="table-caption"
|
|
93
|
+
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
Table,
|
|
101
|
+
TableHeader,
|
|
102
|
+
TableBody,
|
|
103
|
+
TableFooter,
|
|
104
|
+
TableRow,
|
|
105
|
+
TableHead,
|
|
106
|
+
TableCell,
|
|
107
|
+
TableCaption,
|
|
108
|
+
};
|
package/src/ui/tabs.tsx
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { createContext, useContext, useState, useRef, useCallback } from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
/* --------------------------------- Context -------------------------------- */
|
|
7
|
+
|
|
8
|
+
interface TabsContextValue {
|
|
9
|
+
selectedValue: string;
|
|
10
|
+
onSelect: (value: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TabsContext = createContext<TabsContextValue | null>(null);
|
|
14
|
+
|
|
15
|
+
function useTabsContext() {
|
|
16
|
+
const ctx = useContext(TabsContext);
|
|
17
|
+
if (!ctx) throw new Error("Tabs compound components must be used within <Tabs>");
|
|
18
|
+
return ctx;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* ---------------------------------- Tabs ---------------------------------- */
|
|
22
|
+
|
|
23
|
+
interface TabsProps extends React.ComponentProps<"div"> {
|
|
24
|
+
value?: string;
|
|
25
|
+
defaultValue?: string;
|
|
26
|
+
onValueChange?: (value: string) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Tabs({
|
|
30
|
+
value,
|
|
31
|
+
defaultValue,
|
|
32
|
+
onValueChange,
|
|
33
|
+
className,
|
|
34
|
+
...props
|
|
35
|
+
}: TabsProps) {
|
|
36
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? "");
|
|
37
|
+
const selectedValue = value ?? internalValue;
|
|
38
|
+
|
|
39
|
+
const onSelect = useCallback(
|
|
40
|
+
(v: string) => {
|
|
41
|
+
if (value === undefined) setInternalValue(v);
|
|
42
|
+
onValueChange?.(v);
|
|
43
|
+
},
|
|
44
|
+
[value, onValueChange],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<TabsContext.Provider value={{ selectedValue, onSelect }}>
|
|
49
|
+
<div data-slot="tabs" className={className} {...props} />
|
|
50
|
+
</TabsContext.Provider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* -------------------------------- TabsList -------------------------------- */
|
|
55
|
+
|
|
56
|
+
function TabsList({
|
|
57
|
+
className,
|
|
58
|
+
children,
|
|
59
|
+
...props
|
|
60
|
+
}: React.ComponentProps<"div">) {
|
|
61
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
62
|
+
|
|
63
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
64
|
+
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
65
|
+
|
|
66
|
+
const list = listRef.current;
|
|
67
|
+
if (!list) return;
|
|
68
|
+
|
|
69
|
+
const tabs = Array.from(
|
|
70
|
+
list.querySelectorAll<HTMLButtonElement>('[role="tab"]:not([disabled])'),
|
|
71
|
+
);
|
|
72
|
+
const current = tabs.indexOf(document.activeElement as HTMLButtonElement);
|
|
73
|
+
if (current === -1) return;
|
|
74
|
+
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
const next =
|
|
77
|
+
e.key === "ArrowRight"
|
|
78
|
+
? (current + 1) % tabs.length
|
|
79
|
+
: (current - 1 + tabs.length) % tabs.length;
|
|
80
|
+
tabs[next].focus();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
ref={listRef}
|
|
86
|
+
role="tablist"
|
|
87
|
+
data-slot="tabs-list"
|
|
88
|
+
className={cn(
|
|
89
|
+
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
|
90
|
+
className,
|
|
91
|
+
)}
|
|
92
|
+
onKeyDown={handleKeyDown}
|
|
93
|
+
{...props}
|
|
94
|
+
>
|
|
95
|
+
{children}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ------------------------------- TabsTrigger ------------------------------ */
|
|
101
|
+
|
|
102
|
+
interface TabsTriggerProps extends React.ComponentProps<"button"> {
|
|
103
|
+
value: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function TabsTrigger({ value, className, ...props }: TabsTriggerProps) {
|
|
107
|
+
const { selectedValue, onSelect } = useTabsContext();
|
|
108
|
+
const isSelected = selectedValue === value;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<button
|
|
112
|
+
role="tab"
|
|
113
|
+
type="button"
|
|
114
|
+
data-slot="tabs-trigger"
|
|
115
|
+
aria-selected={isSelected}
|
|
116
|
+
{...(isSelected ? { "data-selected": "" } : {})}
|
|
117
|
+
className={cn(
|
|
118
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 data-selected:bg-background data-selected:text-foreground data-selected:shadow-sm",
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
121
|
+
onClick={() => onSelect(value)}
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ------------------------------- TabsContent ------------------------------ */
|
|
128
|
+
|
|
129
|
+
interface TabsContentProps extends React.ComponentProps<"div"> {
|
|
130
|
+
value: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function TabsContent({ value, className, ...props }: TabsContentProps) {
|
|
134
|
+
const { selectedValue } = useTabsContext();
|
|
135
|
+
if (selectedValue !== value) return null;
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div
|
|
139
|
+
role="tabpanel"
|
|
140
|
+
data-slot="tabs-content"
|
|
141
|
+
className={cn("pt-2", className)}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
6
|
+
return (
|
|
7
|
+
<textarea
|
|
8
|
+
data-slot="textarea"
|
|
9
|
+
className={cn(
|
|
10
|
+
"placeholder:text-muted-foreground border-input flex min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs outline-none transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50",
|
|
11
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
12
|
+
"aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
|
|
13
|
+
className,
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { Textarea };
|