Skip to content

Commit da73ea7

Browse files
committedJan 21, 2024
Added dark mode and better loading spinner.
1 parent 55d0399 commit da73ea7

14 files changed

+315
-99
lines changed
 

‎index.html

+1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
<body>
1111
<div id="root"></div>
1212
<script type="module" src="/src/main.tsx"></script>
13+
<div id="loading"></div>
1314
</body>
1415
</html>

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@radix-ui/react-dialog": "^1.0.5",
1414
"@radix-ui/react-dropdown-menu": "^2.0.6",
1515
"@radix-ui/react-icons": "^1.3.0",
16+
"@radix-ui/react-scroll-area": "^1.0.5",
1617
"@radix-ui/react-slot": "^1.0.2",
1718
"axios": "^1.6.5",
1819
"chart.js": "^4.4.1",

‎pnpm-lock.yaml

+38
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/App.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@ import './App.css'
22
import Header from "@/components/layout/header.tsx";
33
import {Outlet} from "react-router";
44
import Sidebar from "@/components/layout/sidebar.tsx";
5+
import {useEffect} from "react";
6+
import {initTheme} from "@/components/layout/ThemeToggle/theme-toggle.tsx";
57

68
const App = ()=>{
9+
10+
useEffect(() => {
11+
initTheme()
12+
}, []);
13+
714
return (
815
<>
916
<Header />
10-
<div className="flex h-screen overflow-hidden">
17+
<div className="flex h-screen">
1118
<Sidebar />
12-
<main className="w-full pt-16 overflow-auto">
19+
<main className="w-full mt-20">
1320
{<Outlet/>}
1421
</main>
1522
</div>

‎src/components/layout/ThemeToggle/theme-toggle.tsx

+23-9
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,30 @@ import {
99
DropdownMenuTrigger,
1010
} from "@/components/ui/dropdown-menu";
1111
type CompProps = {};
12-
export default function ThemeToggle({}: CompProps) {
13-
const setTheme = (theme: string) => {
14-
if (theme === "system") {
15-
theme = window.matchMedia("(prefers-color-scheme: dark)").matches
16-
? "dark"
17-
: "light";
18-
}
19-
document.documentElement.setAttribute("data-theme", theme);
20-
localStorage.setItem("theme", theme);
12+
13+
export const setTheme = (theme: string) => {
14+
if (theme === "system") {
15+
theme = window.matchMedia("(prefers-color-scheme: dark)").matches
16+
? "dark"
17+
: "light";
18+
}
19+
document.documentElement.setAttribute("class", theme);
20+
localStorage.setItem("theme", theme);
21+
}
22+
23+
export const initTheme = () => {
24+
const theme = localStorage.getItem("theme");
25+
if (!theme) {
26+
window.matchMedia("(prefers-color-scheme: dark)").matches
27+
? setTheme("dark")
28+
: setTheme("light");
29+
} else {
30+
setTheme(theme);
2131
}
32+
}
33+
34+
export default function ThemeToggle({}: CompProps) {
35+
2236

2337

2438
return (

‎src/components/ui/Spinner.tsx

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {SVGProps} from "react";
2+
import {cn} from "@/lib/utils.ts";
3+
import {createPortal} from "react-dom";
4+
5+
export interface ISVGProps extends SVGProps<SVGSVGElement> {
6+
size?: number;
7+
className?: string;
8+
}
9+
10+
export const LoadingSpinner = ({
11+
size = 24,
12+
className,
13+
...props
14+
}: ISVGProps) => {
15+
return createPortal(
16+
<div className="flex bg-gray-900 pl-5 pr-5 pt-2 pb-2 rounded" id="loading-inner">
17+
<svg
18+
xmlns="http://www.w3.org/2000/svg"
19+
width={size}
20+
height={size}
21+
{...props}
22+
viewBox="0 0 24 24"
23+
fill="none"
24+
stroke="currentColor"
25+
strokeWidth="2"
26+
strokeLinecap="round"
27+
strokeLinejoin="round"
28+
className={cn("animate-spin", className)}
29+
>
30+
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
31+
</svg>
32+
<span className="ml-2 loading">Loading<span>.</span><span>.</span><span>.</span></span>
33+
34+
</div>, document.getElementById('loading')!)
35+
36+
};

‎src/components/ui/input.tsx

+25
Original file line numberDiff line numberDiff line change
@@ -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-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring 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/scroll-area.tsx

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as React from "react"
2+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
3+
4+
import { cn } from "@/lib/utils"
5+
6+
const ScrollArea = React.forwardRef<
7+
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
8+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
9+
>(({ className, children, ...props }, ref) => (
10+
<ScrollAreaPrimitive.Root
11+
ref={ref}
12+
className={cn("relative overflow-hidden", className)}
13+
{...props}
14+
>
15+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
16+
{children}
17+
</ScrollAreaPrimitive.Viewport>
18+
<ScrollBar />
19+
<ScrollAreaPrimitive.Corner />
20+
</ScrollAreaPrimitive.Root>
21+
))
22+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
23+
24+
const ScrollBar = React.forwardRef<
25+
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
26+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
27+
>(({ className, orientation = "vertical", ...props }, ref) => (
28+
<ScrollAreaPrimitive.ScrollAreaScrollbar
29+
ref={ref}
30+
orientation={orientation}
31+
className={cn(
32+
"flex touch-none select-none transition-colors",
33+
orientation === "vertical" &&
34+
"h-full w-2.5 border-l border-l-transparent p-[1px]",
35+
orientation === "horizontal" &&
36+
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
37+
className
38+
)}
39+
{...props}
40+
>
41+
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
42+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
43+
))
44+
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
45+
46+
export { ScrollArea, ScrollBar }

‎src/index.css

+36
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,39 @@
8989
body {
9090
overflow: hidden;
9191
}
92+
93+
#loading-inner {
94+
position: fixed;
95+
top: 0;
96+
left: 0;
97+
right: 0;
98+
bottom: 0;
99+
display: flex;
100+
align-items: center;
101+
justify-content: center;
102+
}
103+
104+
@keyframes loading {
105+
0% { opacity: 0; }
106+
50% { opacity: 1; }
107+
100% { opacity: 0; }
108+
}
109+
110+
.loading span {
111+
margin-left: 5px;
112+
animation-name: loading;
113+
animation-duration: 2s;
114+
animation-iteration-count: infinite;
115+
}
116+
117+
.loading span:nth-child(1) {
118+
animation-delay: 0.2s;
119+
}
120+
121+
.loading span:nth-child(2) {
122+
animation-delay: 0.4s;
123+
}
124+
125+
.loading span:nth-child(3) {
126+
animation-delay: 0.6s;
127+
}

‎src/pages/apiVersions.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {FC, useMemo} from "react";
33
import {Bar} from "react-chartjs-2";
44
import "chart.js/auto";
55
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card.tsx";
6+
import {LoadingSpinner} from "@/components/ui/Spinner.tsx";
67

78
type ApiVersionsProps = {
89
stats: StatsResponse
@@ -23,7 +24,7 @@ export const ApiVersions: FC<ApiVersionsProps> = ({stats}) => {
2324
},
2425
[stats])
2526

26-
if(!labels||!data) return <div>Loading...</div>
27+
if(!labels||!data) return <LoadingSpinner/>
2728

2829
return <Card>
2930
<CardHeader>

0 commit comments

Comments
 (0)
Please sign in to comment.