From 44bc838f6aeb4ac5b1f7f67e47edb4fd10286733 Mon Sep 17 00:00:00 2001
From: qixing-jk <street-anime-olive@duck.com>
Date: Sun, 7 Sep 2025 22:06:28 +0800
Subject: feat(extension): Add theme and dynamic icon support (#1894)

* feat: add theme selection support to browser extension

- integrate theme settings with plugin settings storage
- add theme selector dropdown to options page
- implement custom ThemeProvider using plugin settings
- include new Select UI component for theme selection

* feat(extension): add dynamic icon theme switching (#1100)

Add updateIcon() function to dynamically change extension icon based on
selected theme (light/dark/system). Update icon on initial load and when
settings change to reflect current theme preference.

Closes #1100

* fix(extension): switch dark mode strategy from media to selector

This allows manual control over dark mode via class toggling rather
than relying on the OS/browser preference.

* fix(extension): move icon update logic to content script

The `window` object is inaccessible in the background script, causing
icon updates to fail. This change relocates the icon update logic to the
content script where `window.matchMedia` is available.

- Remove `updateIcon` function from background script
- Add icon update logic to `ThemeProvider` component
- Consolidate theme and icon updates in single effect

* feat(settings): make theme field required in settings schema

Remove optional flag from theme field to enforce presence in
settings validation schema.

* deps: Upgrade the extension deps

* minor fixes

---------

Co-authored-by: MohamedBassem <me@mbassem.com>
---
 apps/browser-extension/src/utils/ThemeProvider.tsx | 64 ++++++++++++----------
 1 file changed, 36 insertions(+), 28 deletions(-)

(limited to 'apps/browser-extension/src/utils/ThemeProvider.tsx')

diff --git a/apps/browser-extension/src/utils/ThemeProvider.tsx b/apps/browser-extension/src/utils/ThemeProvider.tsx
index 79a0b32f..20a928e1 100644
--- a/apps/browser-extension/src/utils/ThemeProvider.tsx
+++ b/apps/browser-extension/src/utils/ThemeProvider.tsx
@@ -1,11 +1,11 @@
-import { createContext, useContext, useEffect, useState } from "react";
+import { createContext, useContext, useEffect } from "react";
+
+import usePluginSettings from "./settings";
 
 type Theme = "dark" | "light" | "system";
 
 interface ThemeProviderProps {
   children: React.ReactNode;
-  defaultTheme?: Theme;
-  storageKey?: string;
 }
 
 interface ThemeProviderState {
@@ -20,39 +20,47 @@ const initialState: ThemeProviderState = {
 
 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,
-  );
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+  const { settings, setSettings } = usePluginSettings();
+  const theme = settings.theme;
 
   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);
+    const updateIcon = (useDarkModeIcons: boolean) => {
+      const iconSuffix = useDarkModeIcons ? "-darkmode.png" : ".png";
+
+      const iconPaths = {
+        "16": `logo-16${iconSuffix}`,
+        "48": `logo-48${iconSuffix}`,
+        "128": `logo-128${iconSuffix}`,
+      };
+      chrome.action.setIcon({ path: iconPaths });
+    };
+
+    const applyThemeAndIcon = () => {
+      root.classList.remove("light", "dark");
+
+      let currentTheme: "light" | "dark";
+      if (theme === "system") {
+        currentTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
+          ? "dark"
+          : "light";
+      } else {
+        currentTheme = theme;
+      }
+
+      root.classList.add(currentTheme);
+      updateIcon(currentTheme === "dark");
+    };
+
+    applyThemeAndIcon();
   }, [theme]);
 
   const value = {
     theme,
-    setTheme: (theme: Theme) => {
-      localStorage.setItem(storageKey, theme);
-      setTheme(theme);
+    setTheme: (newTheme: Theme) => {
+      setSettings((s) => ({ ...s, theme: newTheme }));
     },
   };
 
-- 
cgit v1.2.3-70-g09d2

