aboutsummaryrefslogtreecommitdiffstats
path: root/apps/browser-extension/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'apps/browser-extension/src/utils')
-rw-r--r--apps/browser-extension/src/utils/ThemeProvider.tsx73
-rw-r--r--apps/browser-extension/src/utils/css.ts7
-rw-r--r--apps/browser-extension/src/utils/providers.tsx9
3 files changed, 88 insertions, 1 deletions
diff --git a/apps/browser-extension/src/utils/ThemeProvider.tsx b/apps/browser-extension/src/utils/ThemeProvider.tsx
new file mode 100644
index 00000000..79a0b32f
--- /dev/null
+++ b/apps/browser-extension/src/utils/ThemeProvider.tsx
@@ -0,0 +1,73 @@
+import { createContext, useContext, useEffect, useState } from "react";
+
+type Theme = "dark" | "light" | "system";
+
+interface ThemeProviderProps {
+ children: React.ReactNode;
+ defaultTheme?: Theme;
+ storageKey?: string;
+}
+
+interface ThemeProviderState {
+ theme: Theme;
+ setTheme: (theme: Theme) => void;
+}
+
+const initialState: ThemeProviderState = {
+ theme: "system",
+ setTheme: () => null,
+};
+
+const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
+
+export function ThemeProvider({
+ children,
+ defaultTheme = "system",
+ storageKey = "vite-ui-theme",
+ ...props
+}: ThemeProviderProps) {
+ const [theme, setTheme] = useState<Theme>(
+ () => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
+ );
+
+ useEffect(() => {
+ const root = window.document.documentElement;
+
+ root.classList.remove("light", "dark");
+
+ if (theme === "system") {
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
+ .matches
+ ? "dark"
+ : "light";
+
+ root.classList.add(systemTheme);
+ return;
+ }
+
+ root.classList.add(theme);
+ }, [theme]);
+
+ const value = {
+ theme,
+ setTheme: (theme: Theme) => {
+ localStorage.setItem(storageKey, theme);
+ setTheme(theme);
+ },
+ };
+
+ return (
+ <ThemeProviderContext.Provider {...props} value={value}>
+ {children}
+ </ThemeProviderContext.Provider>
+ );
+}
+
+export const useTheme = () => {
+ const context = useContext(ThemeProviderContext);
+
+ if (context === undefined)
+ throw new Error("useTheme must be used within a ThemeProvider");
+
+ return context;
+};
diff --git a/apps/browser-extension/src/utils/css.ts b/apps/browser-extension/src/utils/css.ts
new file mode 100644
index 00000000..88283f01
--- /dev/null
+++ b/apps/browser-extension/src/utils/css.ts
@@ -0,0 +1,7 @@
+import type { ClassValue } from "clsx";
+import { clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/apps/browser-extension/src/utils/providers.tsx b/apps/browser-extension/src/utils/providers.tsx
index 4ca17016..4b571254 100644
--- a/apps/browser-extension/src/utils/providers.tsx
+++ b/apps/browser-extension/src/utils/providers.tsx
@@ -1,9 +1,16 @@
import { TRPCProvider } from "@hoarder/shared-react/providers/trpc-provider";
import usePluginSettings from "./settings";
+import { ThemeProvider } from "./ThemeProvider";
export function Providers({ children }: { children: React.ReactNode }) {
const { settings } = usePluginSettings();
- return <TRPCProvider settings={settings}>{children}</TRPCProvider>;
+ return (
+ <TRPCProvider settings={settings}>
+ <ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
+ {children}
+ </ThemeProvider>
+ </TRPCProvider>
+ );
}