Merge pull request #73 from modelcontextprotocol/ashwin/darkmodetoggle

make theme selectable in UI, store setting in localstorage
This commit is contained in:
ashwin-ant
2024-11-25 06:24:59 -08:00
committed by GitHub
4 changed files with 79 additions and 37 deletions

View File

@@ -29,7 +29,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import {
Notification,
StdErrNotification,
StdErrNotificationSchema
StdErrNotificationSchema,
} from "./lib/notificationTypes";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -54,7 +54,6 @@ import RootsTab from "./components/RootsTab";
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
import Sidebar from "./components/Sidebar";
import ToolsTab from "./components/ToolsTab";
import useDarkModeSync from "./lib/useDarkModeSync";
const DEFAULT_REQUEST_TIMEOUT_MSEC = 10000;
@@ -144,8 +143,6 @@ const App = () => {
const dragStartY = useRef<number>(0);
const dragStartHeight = useRef<number>(0);
useDarkModeSync();
const handleDragStart = useCallback(
(e: React.MouseEvent) => {
setIsDragging(true);

View File

@@ -1,6 +1,6 @@
import { useState } from "react";
import { Play, ChevronDown, ChevronRight, Settings } from "lucide-react";
import { Play, ChevronDown, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
@@ -12,6 +12,8 @@ import {
} from "@/components/ui/select";
import { StdErrNotification } from "@/lib/notificationTypes";
import useTheme from "../lib/useTheme";
interface SidebarProps {
connectionStatus: "disconnected" | "connected" | "error";
transportType: "stdio" | "sse";
@@ -43,13 +45,15 @@ const Sidebar = ({
onConnect,
stdErrNotifications,
}: SidebarProps) => {
const [theme, setTheme] = useTheme();
const [showEnvVars, setShowEnvVars] = useState(false);
return (
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
<div className="flex items-center p-4 border-b border-gray-200">
<Settings className="w-6 h-6 text-gray-500" />
<h1 className="ml-2 text-lg font-semibold">MCP Inspector</h1>
<div className="flex items-center justify-between p-4 border-b border-gray-200">
<div className="flex items-center">
<h1 className="ml-2 text-lg font-semibold">MCP Inspector</h1>
</div>
</div>
<div className="p-4 flex-1 overflow-auto">
@@ -212,6 +216,25 @@ const Sidebar = ({
</div>
</div>
</div>
<div className="p-4 border-t">
<div className="flex items-center space-x-2">
<Select
value={theme}
onValueChange={(value: string) =>
setTheme(value as "system" | "light" | "dark")
}
>
<SelectTrigger className="w-[120px]" id="theme-select">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="system">System</SelectItem>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
);
};

View File

@@ -1,29 +0,0 @@
import { useEffect } from "react";
// Listen for changes to the user's preferred color scheme
const useDarkModeSync = () => {
useEffect(() => {
const darkModeMediaQuery = window.matchMedia(
"(prefers-color-scheme: dark)",
);
const handleDarkModeChange = (e: MediaQueryListEvent) => {
if (e.matches) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
};
if (darkModeMediaQuery.matches) {
document.documentElement.classList.add("dark");
}
darkModeMediaQuery.addEventListener("change", handleDarkModeChange);
return () => {
darkModeMediaQuery.removeEventListener("change", handleDarkModeChange);
};
}, []);
};
export default useDarkModeSync;

View File

@@ -0,0 +1,51 @@
import { useCallback, useEffect, useState } from "react";
type Theme = "light" | "dark" | "system";
const useTheme = (): [Theme, (mode: Theme) => void] => {
const [theme, setTheme] = useState<Theme>(() => {
const savedTheme = localStorage.getItem("theme") as Theme;
return savedTheme || "system";
});
useEffect(() => {
const darkModeMediaQuery = window.matchMedia(
"(prefers-color-scheme: dark)",
);
const handleDarkModeChange = (e: MediaQueryListEvent) => {
if (theme === "system") {
updateDocumentTheme(e.matches ? "dark" : "light");
}
};
const updateDocumentTheme = (newTheme: "light" | "dark") => {
document.documentElement.classList.toggle("dark", newTheme === "dark");
};
// Set initial theme based on current mode
if (theme === "system") {
updateDocumentTheme(darkModeMediaQuery.matches ? "dark" : "light");
} else {
updateDocumentTheme(theme);
}
darkModeMediaQuery.addEventListener("change", handleDarkModeChange);
return () => {
darkModeMediaQuery.removeEventListener("change", handleDarkModeChange);
};
}, [theme]);
return [
theme,
useCallback((newTheme: Theme) => {
setTheme(newTheme);
localStorage.setItem("theme", newTheme);
if (newTheme !== "system") {
document.documentElement.classList.toggle("dark", newTheme === "dark");
}
}, []),
];
};
export default useTheme;