cfahlgren1 HF staff commited on
Commit
55d7c10
1 Parent(s): 6ea0cab

add shadcn components and add search modal

Browse files
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
- <p className="text-center text-sm my-8">
75
- Models, Datasets, and Spaces from the top AI labs. <br />
76
- Request more heatmaps by{" "}
77
- <a
78
- href="https://huggingface.co/spaces/cfahlgren1/model-release-heatmap/discussions/new"
79
- target="_blank"
80
- rel="noopener noreferrer"
81
- className="text-blue-500 hover:underline"
82
- >
83
- opening a discussion
84
- </a>
85
- .
86
- </p>
87
  {isLoading ? (
88
  <p className="text-center">Loading...</p>
89
  ) : (
90
  <div className="space-y-16">
91
- {providers
92
- .sort((a, b) =>
93
- calendarData[b.fullName || b.authors[0]].reduce(
94
- (sum, day) => sum + day.count,
95
- 0
96
- ) -
97
- calendarData[a.fullName || a.authors[0]].reduce(
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
- :root {
6
- --foreground-rgb: 75, 75, 75;
7
- --background-rgb: 255, 255, 255;
8
- }
9
-
10
- @media (prefers-color-scheme: dark) {
11
  :root {
12
- --foreground-rgb: 255, 255, 255;
13
- --background-rgb: 0, 0, 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
  }
16
 
17
- body {
18
- color: rgb(var(--foreground-rgb));
19
- background: rgb(var(--background-rgb));
 
 
 
 
 
 
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: Config = {
 
4
  content: [
5
- "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
6
- "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
7
- "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
8
- ],
 
 
9
  theme: {
 
 
 
 
 
 
 
10
  extend: {
11
- backgroundImage: {
12
- "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13
- "gradient-conic":
14
- "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  },
16
  },
17
  },
18
- plugins: [],
19
- };
20
- export default config;
 
 
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