diff --git a/packages/trpc-panel/src/react-app/Root.tsx b/packages/trpc-panel/src/react-app/Root.tsx
index a2823f2..946e8fe 100644
--- a/packages/trpc-panel/src/react-app/Root.tsx
+++ b/packages/trpc-panel/src/react-app/Root.tsx
@@ -8,6 +8,7 @@ import {
HeadersContextProvider,
useHeaders,
} from "@src/react-app/components/contexts/HeadersContext";
+import { useLocalStorage } from "@src/react-app/components/hooks/useLocalStorage";
import { HeadersPopup } from "@src/react-app/components/HeadersPopup";
import { Toaster } from "react-hot-toast";
import { SiteNavigationContextProvider } from "@src/react-app/components/contexts/SiteNavigationContext";
@@ -80,7 +81,10 @@ function ClientProviders({
}
function AppInnards({ rootRouter }: { rootRouter: ParsedRouter }) {
- const [sidebarOpen, setSidebarOpen] = useState(true);
+ const [sidebarOpen, setSidebarOpen] = useLocalStorage(
+ "trpc-panel.show-minimap",
+ true
+ );
return (
diff --git a/packages/trpc-panel/src/react-app/components/hooks/useLocalStorage.tsx b/packages/trpc-panel/src/react-app/components/hooks/useLocalStorage.tsx
new file mode 100644
index 0000000..d84b4c9
--- /dev/null
+++ b/packages/trpc-panel/src/react-app/components/hooks/useLocalStorage.tsx
@@ -0,0 +1,88 @@
+// a stripped down version of https://usehooks-ts.com/react-hook/use-local-storage
+import {
+ useCallback,
+ useEffect,
+ useState,
+ type Dispatch,
+ type SetStateAction,
+} from "react";
+
+type SetValue = Dispatch>;
+
+export function useLocalStorage(
+ key: string,
+ initialValue: T
+): [T, SetValue] {
+ // Get from local storage then
+ // parse stored json or return initialValue
+ const readValue = useCallback((): T => {
+ // Prevent build error "window is undefined" but keeps working
+ if (typeof window === "undefined") {
+ return initialValue;
+ }
+
+ try {
+ const item = window.localStorage.getItem(key);
+ return item ? (parseJSON(item) as T) : initialValue;
+ } catch (error) {
+ console.warn(
+ `tRPC-Panel.useLocalStorage: Error reading localStorage key “${key}”:`,
+ error
+ );
+ return initialValue;
+ }
+ }, [initialValue, key]);
+
+ // State to store our value
+ // Pass initial state function to useState so logic is only executed once
+ const [storedValue, setStoredValue] = useState(readValue);
+
+ // Return a wrapped version of useState's setter function that ...
+ // ... persists the new value to localStorage.
+ const setValue: SetValue = useCallback(
+ (value) => {
+ // Prevent build error "window is undefined" but keeps working
+ if (typeof window === "undefined") {
+ console.warn(
+ `tRPC-Panel.useLocalStorage: Tried setting localStorage key “${key}” even though environment is not a client`
+ );
+ }
+
+ try {
+ // Allow value to be a function so we have the same API as useState
+ const newValue = value instanceof Function ? value(storedValue) : value;
+
+ // Save to local storage
+ window.localStorage.setItem(key, JSON.stringify(newValue));
+
+ // Save state
+ setStoredValue(newValue);
+ } catch (error) {
+ console.warn(
+ `tRPC-Panel.useLocalStorage: Error setting localStorage key “${key}”:`,
+ error
+ );
+ }
+ },
+ [storedValue]
+ );
+
+ useEffect(() => {
+ setStoredValue(readValue());
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return [storedValue, setValue];
+}
+
+// A wrapper for "JSON.parse()"" to support "undefined" value
+function parseJSON(value: string | null): T | undefined {
+ try {
+ return value === "undefined" ? undefined : JSON.parse(value ?? "");
+ } catch {
+ console.log("tRPC-Panel.useLocalStorage.parseJSON: parsing error on", {
+ value,
+ });
+ return undefined;
+ }
+}