Commit
•
55d7c10
1
Parent(s):
6ea0cab
add shadcn components and add search modal
Browse files- bun.lockb +0 -0
- components.json +17 -0
- package.json +10 -1
- src/components/UserSearchDialog.tsx +157 -0
- src/components/ui/avatar.tsx +48 -0
- src/components/ui/button.tsx +56 -0
- src/components/ui/dialog.tsx +120 -0
- src/components/ui/input.tsx +25 -0
- src/components/ui/tooltip.tsx +28 -0
- src/lib/utils.ts +6 -0
- src/pages/index.tsx +52 -41
- src/styles/globals.css +68 -11
- tailwind.config.ts +73 -13
bun.lockb
CHANGED
Binary files a/bun.lockb and b/bun.lockb differ
|
|
components.json
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"$schema": "https://ui.shadcn.com/schema.json",
|
3 |
+
"style": "default",
|
4 |
+
"rsc": false,
|
5 |
+
"tsx": true,
|
6 |
+
"tailwind": {
|
7 |
+
"config": "tailwind.config.ts",
|
8 |
+
"css": "src/styles/globals.css",
|
9 |
+
"baseColor": "slate",
|
10 |
+
"cssVariables": true,
|
11 |
+
"prefix": ""
|
12 |
+
},
|
13 |
+
"aliases": {
|
14 |
+
"components": "@/components",
|
15 |
+
"utils": "@/lib/utils"
|
16 |
+
}
|
17 |
+
}
|
package.json
CHANGED
@@ -12,10 +12,19 @@
|
|
12 |
"@emotion/react": "^11.13.0",
|
13 |
"@emotion/styled": "^11.13.0",
|
14 |
"@mui/material": "^5.16.6",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
"next": "14.2.5",
|
16 |
"react": "^18",
|
17 |
"react-activity-calendar": "^2.2.11",
|
18 |
-
"react-dom": "^18"
|
|
|
|
|
19 |
},
|
20 |
"devDependencies": {
|
21 |
"typescript": "^5",
|
|
|
12 |
"@emotion/react": "^11.13.0",
|
13 |
"@emotion/styled": "^11.13.0",
|
14 |
"@mui/material": "^5.16.6",
|
15 |
+
"@radix-ui/react-avatar": "^1.1.0",
|
16 |
+
"@radix-ui/react-dialog": "^1.1.1",
|
17 |
+
"@radix-ui/react-slot": "^1.1.0",
|
18 |
+
"@radix-ui/react-tooltip": "^1.1.2",
|
19 |
+
"class-variance-authority": "^0.7.0",
|
20 |
+
"clsx": "^2.1.1",
|
21 |
+
"lucide-react": "^0.427.0",
|
22 |
"next": "14.2.5",
|
23 |
"react": "^18",
|
24 |
"react-activity-calendar": "^2.2.11",
|
25 |
+
"react-dom": "^18",
|
26 |
+
"tailwind-merge": "^2.4.0",
|
27 |
+
"tailwindcss-animate": "^1.0.7"
|
28 |
},
|
29 |
"devDependencies": {
|
30 |
"typescript": "^5",
|
src/components/UserSearchDialog.tsx
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState, useMemo } from "react";
|
2 |
+
import { Input } from "./ui/input";
|
3 |
+
import {
|
4 |
+
Dialog,
|
5 |
+
DialogContent,
|
6 |
+
DialogHeader,
|
7 |
+
DialogTitle,
|
8 |
+
DialogTrigger,
|
9 |
+
} from "./ui/dialog";
|
10 |
+
import { Button } from "./ui/button";
|
11 |
+
import { fetchAllAuthorsData } from "../utils/authors";
|
12 |
+
import { generateCalendarData } from "../utils/calendar";
|
13 |
+
import Heatmap from "./Heatmap";
|
14 |
+
import { ModelData } from "../types/heatmap";
|
15 |
+
import { fetchAuthorData, fetchUserData } from "@/utils/authors";
|
16 |
+
|
17 |
+
const UserSearchDialog = () => {
|
18 |
+
const [isOpen, setIsOpen] = useState(false);
|
19 |
+
const [isLoading, setIsLoading] = useState(false);
|
20 |
+
const [searchInput, setSearchInput] = useState("");
|
21 |
+
const [searchedData, setSearchedData] = useState<ModelData[] | null>(null);
|
22 |
+
const [isCopied, setIsCopied] = useState(false);
|
23 |
+
const [currentSearchTerm, setCurrentSearchTerm] = useState("");
|
24 |
+
const [userInfo, setUserInfo] = useState<{ fullName: string; avatarUrl: string | null } | null>(null);
|
25 |
+
|
26 |
+
const handleSearch = async () => {
|
27 |
+
if (searchInput.trim()) {
|
28 |
+
setIsLoading(true);
|
29 |
+
try {
|
30 |
+
const authorData = await fetchAllAuthorsData([searchInput.trim()]);
|
31 |
+
const authorInfo = await fetchUserData([searchInput.trim()]);
|
32 |
+
setSearchedData(authorData);
|
33 |
+
setUserInfo(authorInfo);
|
34 |
+
setCurrentSearchTerm(searchInput.trim());
|
35 |
+
} catch (error) {
|
36 |
+
console.error("Error fetching data for searched user:", error);
|
37 |
+
setSearchedData(null);
|
38 |
+
setUserInfo(null);
|
39 |
+
setCurrentSearchTerm("");
|
40 |
+
}
|
41 |
+
setIsLoading(false);
|
42 |
+
} else {
|
43 |
+
setSearchedData(null);
|
44 |
+
setUserInfo(null);
|
45 |
+
setCurrentSearchTerm("");
|
46 |
+
}
|
47 |
+
};
|
48 |
+
|
49 |
+
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
50 |
+
if (e.key === 'Enter') {
|
51 |
+
handleSearch();
|
52 |
+
}
|
53 |
+
};
|
54 |
+
|
55 |
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
56 |
+
setSearchInput(e.target.value);
|
57 |
+
};
|
58 |
+
|
59 |
+
const getIframeCode = (username: string) => {
|
60 |
+
return `<iframe
|
61 |
+
src="https://cfahlgren1-model-release-heatmap.hf.space/${username}"
|
62 |
+
class="w-full h-[300px]"
|
63 |
+
frameborder="0"
|
64 |
+
></iframe>`;
|
65 |
+
};
|
66 |
+
|
67 |
+
const handleCopyCode = () => {
|
68 |
+
const iframeCode = getIframeCode(searchInput);
|
69 |
+
navigator.clipboard.writeText(iframeCode).then(() => {
|
70 |
+
setIsCopied(true);
|
71 |
+
setTimeout(() => setIsCopied(false), 2000);
|
72 |
+
});
|
73 |
+
};
|
74 |
+
|
75 |
+
const searchedHeatmapData = useMemo(() => {
|
76 |
+
if (searchedData && searchedData.length > 0 && userInfo) {
|
77 |
+
return generateCalendarData(searchedData, [{
|
78 |
+
authors: [currentSearchTerm],
|
79 |
+
color: "#0088cc",
|
80 |
+
fullName: userInfo.fullName,
|
81 |
+
avatarUrl: userInfo.avatarUrl
|
82 |
+
}])[currentSearchTerm];
|
83 |
+
}
|
84 |
+
return null;
|
85 |
+
}, [searchedData, currentSearchTerm, userInfo]);
|
86 |
+
|
87 |
+
const handleDialogOpenChange = (open: boolean) => {
|
88 |
+
setIsOpen(open);
|
89 |
+
if (!open) {
|
90 |
+
setSearchInput("");
|
91 |
+
setSearchedData(null);
|
92 |
+
setCurrentSearchTerm("");
|
93 |
+
setIsLoading(false);
|
94 |
+
setIsCopied(false);
|
95 |
+
setUserInfo(null);
|
96 |
+
}
|
97 |
+
};
|
98 |
+
|
99 |
+
return (
|
100 |
+
<Dialog open={isOpen} onOpenChange={handleDialogOpenChange}>
|
101 |
+
<DialogTrigger asChild>
|
102 |
+
<Button variant="outline">Search</Button>
|
103 |
+
</DialogTrigger>
|
104 |
+
<DialogContent className="w-full max-w-[95vw] sm:max-w-4xl p-4 sm:p-6 max-h-[90vh] flex flex-col">
|
105 |
+
<DialogHeader>
|
106 |
+
<DialogTitle className="text-lg sm:text-xl mb-4">Get your Hugging Face Heatmap</DialogTitle>
|
107 |
+
</DialogHeader>
|
108 |
+
<div className="flex-grow overflow-y-auto">
|
109 |
+
<div className="grid gap-4 py-4">
|
110 |
+
<div className="flex items-center space-x-2 p-2 bg-background rounded-md">
|
111 |
+
<Input
|
112 |
+
type="text"
|
113 |
+
placeholder="Enter username"
|
114 |
+
value={searchInput}
|
115 |
+
onChange={handleInputChange}
|
116 |
+
onKeyDown={handleKeyPress}
|
117 |
+
className="flex-grow"
|
118 |
+
/>
|
119 |
+
</div>
|
120 |
+
{isLoading ? (
|
121 |
+
<p className="text-center">Loading...</p>
|
122 |
+
) : searchedHeatmapData && userInfo ? (
|
123 |
+
<div className="mt-4 space-y-4">
|
124 |
+
<div className="overflow-x-auto pb-2">
|
125 |
+
<Heatmap
|
126 |
+
data={searchedHeatmapData}
|
127 |
+
color="#FF9D00"
|
128 |
+
providerName={currentSearchTerm}
|
129 |
+
fullName={userInfo.fullName}
|
130 |
+
avatarUrl={userInfo.avatarUrl || ''}
|
131 |
+
/>
|
132 |
+
</div>
|
133 |
+
<div>
|
134 |
+
<div className="flex justify-between items-center mb-2">
|
135 |
+
<h3 className="font-semibold text-sm sm:text-base">Embed in iFrame</h3>
|
136 |
+
<Button onClick={handleCopyCode} variant="link" size="sm">
|
137 |
+
{isCopied ? "Copied!" : "Copy"}
|
138 |
+
</Button>
|
139 |
+
</div>
|
140 |
+
<div className="overflow-x-auto">
|
141 |
+
<pre className="bg-secondary p-2 rounded text-xs whitespace-pre-wrap break-all">
|
142 |
+
<code>{getIframeCode(searchInput)}</code>
|
143 |
+
</pre>
|
144 |
+
</div>
|
145 |
+
</div>
|
146 |
+
</div>
|
147 |
+
) : searchedData !== null && searchedData.length === 0 ? (
|
148 |
+
<p className="text-center text-slate-500 text-sm italic">User or Organization not found</p>
|
149 |
+
) : null}
|
150 |
+
</div>
|
151 |
+
</div>
|
152 |
+
</DialogContent>
|
153 |
+
</Dialog>
|
154 |
+
);
|
155 |
+
};
|
156 |
+
|
157 |
+
export default UserSearchDialog;
|
src/components/ui/avatar.tsx
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
|
6 |
+
const Avatar = React.forwardRef<
|
7 |
+
React.ElementRef<typeof AvatarPrimitive.Root>,
|
8 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
9 |
+
>(({ className, ...props }, ref) => (
|
10 |
+
<AvatarPrimitive.Root
|
11 |
+
ref={ref}
|
12 |
+
className={cn(
|
13 |
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
14 |
+
className
|
15 |
+
)}
|
16 |
+
{...props}
|
17 |
+
/>
|
18 |
+
))
|
19 |
+
Avatar.displayName = AvatarPrimitive.Root.displayName
|
20 |
+
|
21 |
+
const AvatarImage = React.forwardRef<
|
22 |
+
React.ElementRef<typeof AvatarPrimitive.Image>,
|
23 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
24 |
+
>(({ className, ...props }, ref) => (
|
25 |
+
<AvatarPrimitive.Image
|
26 |
+
ref={ref}
|
27 |
+
className={cn("aspect-square h-full w-full", className)}
|
28 |
+
{...props}
|
29 |
+
/>
|
30 |
+
))
|
31 |
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
32 |
+
|
33 |
+
const AvatarFallback = React.forwardRef<
|
34 |
+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
35 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
36 |
+
>(({ className, ...props }, ref) => (
|
37 |
+
<AvatarPrimitive.Fallback
|
38 |
+
ref={ref}
|
39 |
+
className={cn(
|
40 |
+
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
41 |
+
className
|
42 |
+
)}
|
43 |
+
{...props}
|
44 |
+
/>
|
45 |
+
))
|
46 |
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
47 |
+
|
48 |
+
export { Avatar, AvatarImage, AvatarFallback }
|
src/components/ui/button.tsx
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { Slot } from "@radix-ui/react-slot"
|
3 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const buttonVariants = cva(
|
8 |
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
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",
|
15 |
+
outline:
|
16 |
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
17 |
+
secondary:
|
18 |
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
19 |
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
20 |
+
link: "text-primary underline-offset-4 hover:underline",
|
21 |
+
},
|
22 |
+
size: {
|
23 |
+
default: "h-10 px-4 py-2",
|
24 |
+
sm: "h-9 rounded-md px-3",
|
25 |
+
lg: "h-11 rounded-md px-8",
|
26 |
+
icon: "h-10 w-10",
|
27 |
+
},
|
28 |
+
},
|
29 |
+
defaultVariants: {
|
30 |
+
variant: "default",
|
31 |
+
size: "default",
|
32 |
+
},
|
33 |
+
}
|
34 |
+
)
|
35 |
+
|
36 |
+
export interface ButtonProps
|
37 |
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
38 |
+
VariantProps<typeof buttonVariants> {
|
39 |
+
asChild?: boolean
|
40 |
+
}
|
41 |
+
|
42 |
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
43 |
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
44 |
+
const Comp = asChild ? Slot : "button"
|
45 |
+
return (
|
46 |
+
<Comp
|
47 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
48 |
+
ref={ref}
|
49 |
+
{...props}
|
50 |
+
/>
|
51 |
+
)
|
52 |
+
}
|
53 |
+
)
|
54 |
+
Button.displayName = "Button"
|
55 |
+
|
56 |
+
export { Button, buttonVariants }
|
src/components/ui/dialog.tsx
ADDED
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
3 |
+
import { X } from "lucide-react"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const Dialog = DialogPrimitive.Root
|
8 |
+
|
9 |
+
const DialogTrigger = DialogPrimitive.Trigger
|
10 |
+
|
11 |
+
const DialogPortal = DialogPrimitive.Portal
|
12 |
+
|
13 |
+
const DialogClose = DialogPrimitive.Close
|
14 |
+
|
15 |
+
const DialogOverlay = React.forwardRef<
|
16 |
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
17 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
18 |
+
>(({ className, ...props }, ref) => (
|
19 |
+
<DialogPrimitive.Overlay
|
20 |
+
ref={ref}
|
21 |
+
className={cn(
|
22 |
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
23 |
+
className
|
24 |
+
)}
|
25 |
+
{...props}
|
26 |
+
/>
|
27 |
+
))
|
28 |
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
29 |
+
|
30 |
+
const DialogContent = React.forwardRef<
|
31 |
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
32 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
33 |
+
>(({ className, children, ...props }, ref) => (
|
34 |
+
<DialogPortal>
|
35 |
+
<DialogOverlay />
|
36 |
+
<DialogPrimitive.Content
|
37 |
+
ref={ref}
|
38 |
+
className={cn(
|
39 |
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
40 |
+
className
|
41 |
+
)}
|
42 |
+
{...props}
|
43 |
+
>
|
44 |
+
{children}
|
45 |
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
46 |
+
<X className="h-4 w-4" />
|
47 |
+
<span className="sr-only">Close</span>
|
48 |
+
</DialogPrimitive.Close>
|
49 |
+
</DialogPrimitive.Content>
|
50 |
+
</DialogPortal>
|
51 |
+
))
|
52 |
+
DialogContent.displayName = DialogPrimitive.Content.displayName
|
53 |
+
|
54 |
+
const DialogHeader = ({
|
55 |
+
className,
|
56 |
+
...props
|
57 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
58 |
+
<div
|
59 |
+
className={cn(
|
60 |
+
"flex flex-col space-y-1.5 text-center sm:text-left",
|
61 |
+
className
|
62 |
+
)}
|
63 |
+
{...props}
|
64 |
+
/>
|
65 |
+
)
|
66 |
+
DialogHeader.displayName = "DialogHeader"
|
67 |
+
|
68 |
+
const DialogFooter = ({
|
69 |
+
className,
|
70 |
+
...props
|
71 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
72 |
+
<div
|
73 |
+
className={cn(
|
74 |
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
75 |
+
className
|
76 |
+
)}
|
77 |
+
{...props}
|
78 |
+
/>
|
79 |
+
)
|
80 |
+
DialogFooter.displayName = "DialogFooter"
|
81 |
+
|
82 |
+
const DialogTitle = React.forwardRef<
|
83 |
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
84 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
85 |
+
>(({ className, ...props }, ref) => (
|
86 |
+
<DialogPrimitive.Title
|
87 |
+
ref={ref}
|
88 |
+
className={cn(
|
89 |
+
"text-lg font-semibold leading-none tracking-tight",
|
90 |
+
className
|
91 |
+
)}
|
92 |
+
{...props}
|
93 |
+
/>
|
94 |
+
))
|
95 |
+
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
96 |
+
|
97 |
+
const DialogDescription = React.forwardRef<
|
98 |
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
99 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
100 |
+
>(({ className, ...props }, ref) => (
|
101 |
+
<DialogPrimitive.Description
|
102 |
+
ref={ref}
|
103 |
+
className={cn("text-sm text-muted-foreground", className)}
|
104 |
+
{...props}
|
105 |
+
/>
|
106 |
+
))
|
107 |
+
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
108 |
+
|
109 |
+
export {
|
110 |
+
Dialog,
|
111 |
+
DialogPortal,
|
112 |
+
DialogOverlay,
|
113 |
+
DialogClose,
|
114 |
+
DialogTrigger,
|
115 |
+
DialogContent,
|
116 |
+
DialogHeader,
|
117 |
+
DialogFooter,
|
118 |
+
DialogTitle,
|
119 |
+
DialogDescription,
|
120 |
+
}
|
src/components/ui/input.tsx
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
|
3 |
+
import { cn } from "@/lib/utils"
|
4 |
+
|
5 |
+
export interface InputProps
|
6 |
+
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
7 |
+
|
8 |
+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
9 |
+
({ className, type, ...props }, ref) => {
|
10 |
+
return (
|
11 |
+
<input
|
12 |
+
type={type}
|
13 |
+
className={cn(
|
14 |
+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
15 |
+
className
|
16 |
+
)}
|
17 |
+
ref={ref}
|
18 |
+
{...props}
|
19 |
+
/>
|
20 |
+
)
|
21 |
+
}
|
22 |
+
)
|
23 |
+
Input.displayName = "Input"
|
24 |
+
|
25 |
+
export { Input }
|
src/components/ui/tooltip.tsx
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
|
6 |
+
const TooltipProvider = TooltipPrimitive.Provider
|
7 |
+
|
8 |
+
const Tooltip = TooltipPrimitive.Root
|
9 |
+
|
10 |
+
const TooltipTrigger = TooltipPrimitive.Trigger
|
11 |
+
|
12 |
+
const TooltipContent = React.forwardRef<
|
13 |
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
14 |
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
15 |
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
16 |
+
<TooltipPrimitive.Content
|
17 |
+
ref={ref}
|
18 |
+
sideOffset={sideOffset}
|
19 |
+
className={cn(
|
20 |
+
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
21 |
+
className
|
22 |
+
)}
|
23 |
+
{...props}
|
24 |
+
/>
|
25 |
+
))
|
26 |
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
27 |
+
|
28 |
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
src/lib/utils.ts
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { type ClassValue, clsx } from "clsx"
|
2 |
+
import { twMerge } from "tailwind-merge"
|
3 |
+
|
4 |
+
export function cn(...inputs: ClassValue[]) {
|
5 |
+
return twMerge(clsx(inputs))
|
6 |
+
}
|
src/pages/index.tsx
CHANGED
@@ -1,12 +1,14 @@
|
|
1 |
-
import React, { useState, useEffect } from "react";
|
2 |
import { generateCalendarData } from "../utils/calendar";
|
3 |
import {
|
4 |
OpenSourceHeatmapProps,
|
5 |
ProviderInfo,
|
6 |
ModelData,
|
|
|
7 |
} from "../types/heatmap";
|
8 |
import Heatmap from "../components/Heatmap";
|
9 |
import { fetchAllProvidersData, fetchAllAuthorsData } from "../utils/authors";
|
|
|
10 |
|
11 |
const PROVIDERS: ProviderInfo[] = [
|
12 |
{ color: "#ff7000", authors: ["mistralai"] },
|
@@ -54,6 +56,21 @@ export async function getStaticProps() {
|
|
54 |
}
|
55 |
}
|
56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
|
58 |
calendarData,
|
59 |
providers,
|
@@ -66,57 +83,51 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
|
|
66 |
}
|
67 |
}, [calendarData]);
|
68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
return (
|
70 |
-
<div className="w-full max-w-screen-lg mx-auto p-4">
|
71 |
<h1 className="text-3xl lg:text-5xl mt-16 font-bold text-center mb-2">
|
72 |
Hugging Face Heatmap 🤗
|
73 |
</h1>
|
74 |
-
<
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
.
|
86 |
-
</p>
|
87 |
{isLoading ? (
|
88 |
<p className="text-center">Loading...</p>
|
89 |
) : (
|
90 |
<div className="space-y-16">
|
91 |
-
{
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
(sum, day) => sum + day.count,
|
99 |
-
0
|
100 |
-
)
|
101 |
-
)
|
102 |
-
.map((provider) => {
|
103 |
-
const providerName = provider.fullName || provider.authors[0];
|
104 |
-
return (
|
105 |
-
<div key={providerName} className="flex flex-col items-center">
|
106 |
-
<Heatmap
|
107 |
-
data={calendarData[providerName]}
|
108 |
-
color={provider.color}
|
109 |
-
providerName={providerName}
|
110 |
-
fullName={provider.fullName ?? providerName}
|
111 |
-
avatarUrl={provider.avatarUrl ?? ''}
|
112 |
-
/>
|
113 |
-
</div>
|
114 |
-
);
|
115 |
-
})}
|
116 |
</div>
|
117 |
)}
|
118 |
</div>
|
119 |
);
|
120 |
};
|
121 |
|
122 |
-
export default OpenSourceHeatmap;
|
|
|
1 |
+
import React, { useState, useEffect, useMemo } from "react";
|
2 |
import { generateCalendarData } from "../utils/calendar";
|
3 |
import {
|
4 |
OpenSourceHeatmapProps,
|
5 |
ProviderInfo,
|
6 |
ModelData,
|
7 |
+
CalendarData,
|
8 |
} from "../types/heatmap";
|
9 |
import Heatmap from "../components/Heatmap";
|
10 |
import { fetchAllProvidersData, fetchAllAuthorsData } from "../utils/authors";
|
11 |
+
import UserSearchDialog from "../components/UserSearchDialog";
|
12 |
|
13 |
const PROVIDERS: ProviderInfo[] = [
|
14 |
{ color: "#ff7000", authors: ["mistralai"] },
|
|
|
56 |
}
|
57 |
}
|
58 |
|
59 |
+
const ProviderHeatmap = React.memo(({ provider, calendarData }: { provider: ProviderInfo, calendarData: CalendarData }) => {
|
60 |
+
const providerName = provider.fullName || provider.authors[0];
|
61 |
+
return (
|
62 |
+
<div key={providerName} className="flex flex-col items-center">
|
63 |
+
<Heatmap
|
64 |
+
data={calendarData[providerName]}
|
65 |
+
color={provider.color}
|
66 |
+
providerName={providerName}
|
67 |
+
fullName={provider.fullName ?? providerName}
|
68 |
+
avatarUrl={provider.avatarUrl ?? ''}
|
69 |
+
/>
|
70 |
+
</div>
|
71 |
+
);
|
72 |
+
});
|
73 |
+
|
74 |
const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
|
75 |
calendarData,
|
76 |
providers,
|
|
|
83 |
}
|
84 |
}, [calendarData]);
|
85 |
|
86 |
+
const sortedProviders = useMemo(() =>
|
87 |
+
providers.sort((a, b) =>
|
88 |
+
calendarData[b.fullName || b.authors[0]].reduce(
|
89 |
+
(sum, day) => sum + day.count,
|
90 |
+
0
|
91 |
+
) -
|
92 |
+
calendarData[a.fullName || a.authors[0]].reduce(
|
93 |
+
(sum, day) => sum + day.count,
|
94 |
+
0
|
95 |
+
)
|
96 |
+
),
|
97 |
+
[providers, calendarData]
|
98 |
+
);
|
99 |
+
|
100 |
return (
|
101 |
+
<div className="w-full max-w-screen-lg mx-auto p-4 py-16">
|
102 |
<h1 className="text-3xl lg:text-5xl mt-16 font-bold text-center mb-2">
|
103 |
Hugging Face Heatmap 🤗
|
104 |
</h1>
|
105 |
+
<div className="text-center text-sm my-8 space-y-4">
|
106 |
+
<p>
|
107 |
+
Models, Datasets, and Spaces from the top AI labs.
|
108 |
+
</p>
|
109 |
+
<div className="flex justify-center space-x-4">
|
110 |
+
<UserSearchDialog />
|
111 |
+
</div>
|
112 |
+
</div>
|
113 |
+
|
114 |
+
<div className="h-px max-w-lg mx-auto bg-gray-200 my-16" />
|
115 |
+
|
|
|
|
|
116 |
{isLoading ? (
|
117 |
<p className="text-center">Loading...</p>
|
118 |
) : (
|
119 |
<div className="space-y-16">
|
120 |
+
{sortedProviders.map((provider) => (
|
121 |
+
<ProviderHeatmap
|
122 |
+
key={provider.fullName || provider.authors[0]}
|
123 |
+
provider={provider}
|
124 |
+
calendarData={calendarData}
|
125 |
+
/>
|
126 |
+
))}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
</div>
|
128 |
)}
|
129 |
</div>
|
130 |
);
|
131 |
};
|
132 |
|
133 |
+
export default React.memo(OpenSourceHeatmap);
|
src/styles/globals.css
CHANGED
@@ -2,21 +2,78 @@
|
|
2 |
@tailwind components;
|
3 |
@tailwind utilities;
|
4 |
|
5 |
-
|
6 |
-
--foreground-rgb: 75, 75, 75;
|
7 |
-
--background-rgb: 255, 255, 255;
|
8 |
-
}
|
9 |
-
|
10 |
-
@media (prefers-color-scheme: dark) {
|
11 |
:root {
|
12 |
-
--
|
13 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
}
|
15 |
}
|
16 |
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
}
|
21 |
|
22 |
@layer utilities {
|
|
|
2 |
@tailwind components;
|
3 |
@tailwind utilities;
|
4 |
|
5 |
+
@layer base {
|
|
|
|
|
|
|
|
|
|
|
6 |
:root {
|
7 |
+
--background: 0 0% 100%;
|
8 |
+
--foreground: 222.2 84% 4.9%;
|
9 |
+
--card: 0 0% 100%;
|
10 |
+
--card-foreground: 222.2 84% 4.9%;
|
11 |
+
--popover: 0 0% 100%;
|
12 |
+
--popover-foreground: 222.2 84% 4.9%;
|
13 |
+
--primary: 222.2 47.4% 11.2%;
|
14 |
+
--primary-foreground: 210 40% 98%;
|
15 |
+
--secondary: 210 40% 96.1%;
|
16 |
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
17 |
+
--muted: 210 40% 96.1%;
|
18 |
+
--muted-foreground: 215.4 16.3% 46.9%;
|
19 |
+
--accent: 210 40% 96.1%;
|
20 |
+
--accent-foreground: 222.2 47.4% 11.2%;
|
21 |
+
--destructive: 0 84.2% 60.2%;
|
22 |
+
--destructive-foreground: 210 40% 98%;
|
23 |
+
--border: 214.3 31.8% 91.4%;
|
24 |
+
--input: 214.3 31.8% 91.4%;
|
25 |
+
--ring: 222.2 84% 4.9%;
|
26 |
+
--radius: 0.5rem;
|
27 |
+
--chart-1: 12 76% 61%;
|
28 |
+
--chart-2: 173 58% 39%;
|
29 |
+
--chart-3: 197 37% 24%;
|
30 |
+
--chart-4: 43 74% 66%;
|
31 |
+
--chart-5: 27 87% 67%;
|
32 |
+
--foreground-rgb: 75, 75, 75;
|
33 |
+
--background-rgb: 255, 255, 255;
|
34 |
+
}
|
35 |
+
|
36 |
+
@media (prefers-color-scheme: dark) {
|
37 |
+
:root {
|
38 |
+
--foreground-rgb: 255, 255, 255;
|
39 |
+
--background-rgb: 0, 0, 0;
|
40 |
+
--background: 222.2 84% 4.9%;
|
41 |
+
--foreground: 210 40% 98%;
|
42 |
+
--card: 222.2 84% 4.9%;
|
43 |
+
--card-foreground: 210 40% 98%;
|
44 |
+
--popover: 222.2 84% 4.9%;
|
45 |
+
--popover-foreground: 210 40% 98%;
|
46 |
+
--primary: 210 40% 98%;
|
47 |
+
--primary-foreground: 222.2 47.4% 11.2%;
|
48 |
+
--secondary: 217.2 32.6% 17.5%;
|
49 |
+
--secondary-foreground: 210 40% 98%;
|
50 |
+
--muted: 217.2 32.6% 17.5%;
|
51 |
+
--muted-foreground: 215 20.2% 65.1%;
|
52 |
+
--accent: 217.2 32.6% 17.5%;
|
53 |
+
--accent-foreground: 210 40% 98%;
|
54 |
+
--destructive: 0 62.8% 30.6%;
|
55 |
+
--destructive-foreground: 210 40% 98%;
|
56 |
+
--border: 217.2 32.6% 17.5%;
|
57 |
+
--input: 217.2 32.6% 17.5%;
|
58 |
+
--ring: 212.7 26.8% 83.9%;
|
59 |
+
--chart-1: 220 70% 50%;
|
60 |
+
--chart-2: 160 60% 45%;
|
61 |
+
--chart-3: 30 80% 55%;
|
62 |
+
--chart-4: 280 65% 60%;
|
63 |
+
--chart-5: 340 75% 55%;
|
64 |
+
}
|
65 |
}
|
66 |
}
|
67 |
|
68 |
+
@layer base {
|
69 |
+
* {
|
70 |
+
@apply border-border;
|
71 |
+
}
|
72 |
+
body {
|
73 |
+
color: rgb(var(--foreground-rgb));
|
74 |
+
background: rgb(var(--background-rgb));
|
75 |
+
@apply text-foreground;
|
76 |
+
}
|
77 |
}
|
78 |
|
79 |
@layer utilities {
|
tailwind.config.ts
CHANGED
@@ -1,20 +1,80 @@
|
|
1 |
-
import type { Config } from "tailwindcss"
|
2 |
|
3 |
-
const config
|
|
|
4 |
content: [
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
|
|
|
|
9 |
theme: {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
extend: {
|
11 |
-
|
12 |
-
|
13 |
-
"
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
},
|
16 |
},
|
17 |
},
|
18 |
-
plugins: [],
|
19 |
-
}
|
20 |
-
|
|
|
|
1 |
+
import type { Config } from "tailwindcss"
|
2 |
|
3 |
+
const config = {
|
4 |
+
darkMode: ["class"],
|
5 |
content: [
|
6 |
+
'./pages/**/*.{ts,tsx}',
|
7 |
+
'./components/**/*.{ts,tsx}',
|
8 |
+
'./app/**/*.{ts,tsx}',
|
9 |
+
'./src/**/*.{ts,tsx}',
|
10 |
+
],
|
11 |
+
prefix: "",
|
12 |
theme: {
|
13 |
+
container: {
|
14 |
+
center: true,
|
15 |
+
padding: "2rem",
|
16 |
+
screens: {
|
17 |
+
"2xl": "1400px",
|
18 |
+
},
|
19 |
+
},
|
20 |
extend: {
|
21 |
+
colors: {
|
22 |
+
border: "hsl(var(--border))",
|
23 |
+
input: "hsl(var(--input))",
|
24 |
+
ring: "hsl(var(--ring))",
|
25 |
+
background: "hsl(var(--background))",
|
26 |
+
foreground: "hsl(var(--foreground))",
|
27 |
+
primary: {
|
28 |
+
DEFAULT: "hsl(var(--primary))",
|
29 |
+
foreground: "hsl(var(--primary-foreground))",
|
30 |
+
},
|
31 |
+
secondary: {
|
32 |
+
DEFAULT: "hsl(var(--secondary))",
|
33 |
+
foreground: "hsl(var(--secondary-foreground))",
|
34 |
+
},
|
35 |
+
destructive: {
|
36 |
+
DEFAULT: "hsl(var(--destructive))",
|
37 |
+
foreground: "hsl(var(--destructive-foreground))",
|
38 |
+
},
|
39 |
+
muted: {
|
40 |
+
DEFAULT: "hsl(var(--muted))",
|
41 |
+
foreground: "hsl(var(--muted-foreground))",
|
42 |
+
},
|
43 |
+
accent: {
|
44 |
+
DEFAULT: "hsl(var(--accent))",
|
45 |
+
foreground: "hsl(var(--accent-foreground))",
|
46 |
+
},
|
47 |
+
popover: {
|
48 |
+
DEFAULT: "hsl(var(--popover))",
|
49 |
+
foreground: "hsl(var(--popover-foreground))",
|
50 |
+
},
|
51 |
+
card: {
|
52 |
+
DEFAULT: "hsl(var(--card))",
|
53 |
+
foreground: "hsl(var(--card-foreground))",
|
54 |
+
},
|
55 |
+
},
|
56 |
+
borderRadius: {
|
57 |
+
lg: "var(--radius)",
|
58 |
+
md: "calc(var(--radius) - 2px)",
|
59 |
+
sm: "calc(var(--radius) - 4px)",
|
60 |
+
},
|
61 |
+
keyframes: {
|
62 |
+
"accordion-down": {
|
63 |
+
from: { height: "0" },
|
64 |
+
to: { height: "var(--radix-accordion-content-height)" },
|
65 |
+
},
|
66 |
+
"accordion-up": {
|
67 |
+
from: { height: "var(--radix-accordion-content-height)" },
|
68 |
+
to: { height: "0" },
|
69 |
+
},
|
70 |
+
},
|
71 |
+
animation: {
|
72 |
+
"accordion-down": "accordion-down 0.2s ease-out",
|
73 |
+
"accordion-up": "accordion-up 0.2s ease-out",
|
74 |
},
|
75 |
},
|
76 |
},
|
77 |
+
plugins: [require("tailwindcss-animate")],
|
78 |
+
} satisfies Config
|
79 |
+
|
80 |
+
export default config
|