diff --git a/web/src/App.tsx b/web/src/App.tsx index 7fc607993..0e1bf90d8 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -5,7 +5,7 @@ import { Outlet } from "react-router-dom"; import { getSystemColorScheme } from "./helpers/utils"; import useNavigateTo from "./hooks/useNavigateTo"; import { userStore, workspaceStore } from "./store/v2"; -import { loadTheme, validateTheme, getStoredTheme, loadStoredThemeSync } from "./utils/theme"; +import { loadTheme } from "./utils/theme"; const App = observer(() => { const { i18n } = useTranslation(); @@ -15,11 +15,6 @@ const App = observer(() => { const userSetting = userStore.state.userSetting; const workspaceGeneralSetting = workspaceStore.state.generalSetting; - // Load theme immediately from localStorage to prevent flickering - useEffect(() => { - loadStoredThemeSync(); - }, []); - // Redirect to sign up page if no instance owner. useEffect(() => { if (!workspaceProfile.owner) { @@ -111,16 +106,7 @@ const App = observer(() => { // Load theme when workspace setting changes, validate API response useEffect(() => { - const theme = workspaceGeneralSetting.theme; - if (theme) { - // Validate theme. - const validatedTheme = validateTheme(theme); - // Only load if different from current stored theme - const currentTheme = getStoredTheme(); - if (validatedTheme !== currentTheme) { - loadTheme(validatedTheme); - } - } + loadTheme(workspaceGeneralSetting.theme); }, [workspaceGeneralSetting.theme]); return ; diff --git a/web/src/components/ui/dropdown-menu.tsx b/web/src/components/ui/dropdown-menu.tsx index bae8522fa..8be5ffa51 100644 --- a/web/src/components/ui/dropdown-menu.tsx +++ b/web/src/components/ui/dropdown-menu.tsx @@ -6,7 +6,8 @@ import { cn } from "@/lib/utils"; const DropdownMenu = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ ...props }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars +>(({ ...props }, _ref) => { return ; }); DropdownMenu.displayName = "DropdownMenu"; diff --git a/web/src/components/ui/popover.tsx b/web/src/components/ui/popover.tsx index 2c0ca459a..a65233f46 100644 --- a/web/src/components/ui/popover.tsx +++ b/web/src/components/ui/popover.tsx @@ -5,7 +5,8 @@ import { cn } from "@/lib/utils"; const Popover = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ ...props }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars +>(({ ...props }, _ref) => { return ; }); Popover.displayName = "Popover"; diff --git a/web/src/components/ui/sheet.tsx b/web/src/components/ui/sheet.tsx index 1e30cbbc7..4d98bc41d 100644 --- a/web/src/components/ui/sheet.tsx +++ b/web/src/components/ui/sheet.tsx @@ -4,7 +4,8 @@ import * as React from "react"; import { cn } from "@/lib/utils"; const Sheet = React.forwardRef, React.ComponentPropsWithoutRef>( - ({ ...props }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ({ ...props }, _ref) => { return ; }, ); diff --git a/web/src/components/ui/tooltip.tsx b/web/src/components/ui/tooltip.tsx index e75d2b58e..85578e17c 100644 --- a/web/src/components/ui/tooltip.tsx +++ b/web/src/components/ui/tooltip.tsx @@ -5,7 +5,8 @@ import { cn } from "@/lib/utils"; const TooltipProvider = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ delayDuration = 0, ...props }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars +>(({ delayDuration = 0, ...props }, _ref) => { return ; }); TooltipProvider.displayName = "TooltipProvider"; @@ -13,7 +14,8 @@ TooltipProvider.displayName = "TooltipProvider"; const Tooltip = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ ...props }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars +>(({ ...props }, _ref) => { return ( diff --git a/web/src/index.css b/web/src/index.css index 7b6a845a1..eb777ebcc 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -1,5 +1,6 @@ @import "tailwindcss"; @import "tw-animate-css"; +@import "./themes/default.css"; @custom-variant dark (&: is(.dark *)); diff --git a/web/src/theme/index.ts b/web/src/theme/index.ts deleted file mode 100644 index 77b70ca57..000000000 --- a/web/src/theme/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Theme configuration for Tailwind CSS v4 -// This file is kept for compatibility but no longer used for MUI theme configuration -// All styling is now handled by Tailwind CSS - -export default {}; diff --git a/web/public/themes/default.css b/web/src/themes/default.css similarity index 100% rename from web/public/themes/default.css rename to web/src/themes/default.css diff --git a/web/public/themes/paper.css b/web/src/themes/paper.css similarity index 100% rename from web/public/themes/paper.css rename to web/src/themes/paper.css diff --git a/web/src/utils/theme.ts b/web/src/utils/theme.ts index 684f4e631..e0325ea2d 100644 --- a/web/src/utils/theme.ts +++ b/web/src/utils/theme.ts @@ -1,74 +1,42 @@ +import paperThemeContent from "../themes/paper.css?raw"; + const VALID_THEMES = ["default", "paper"] as const; type ValidTheme = (typeof VALID_THEMES)[number]; -// Validate theme name and return default if invalid -export const validateTheme = (theme: string): ValidTheme => { - return VALID_THEMES.includes(theme as ValidTheme) ? (theme as ValidTheme) : "default"; +const THEME_CONTENT: Record = { + default: null, + paper: paperThemeContent, }; -// Get theme from localStorage -export const getStoredTheme = (): ValidTheme => { - try { - const stored = localStorage.getItem("workspace-theme"); - return stored ? validateTheme(stored) : "default"; - } catch { - return "default"; - } -}; - -// Save theme to localStorage -export const storeTheme = (theme: string): void => { - try { - const validTheme = validateTheme(theme); - localStorage.setItem("workspace-theme", validTheme); - } catch { - // Silently fail if localStorage is not available - } +const validateTheme = (theme: string): ValidTheme => { + return VALID_THEMES.includes(theme as ValidTheme) ? (theme as ValidTheme) : "default"; }; -// Load theme immediately from localStorage to prevent flickering -export const loadStoredThemeSync = (): void => { - const theme = getStoredTheme(); - // Apply theme synchronously to prevent flash - document.documentElement.setAttribute("data-theme", theme); +export const getStoredTheme = (): ValidTheme => { + const stored = localStorage.getItem("workspace-theme"); + return stored ? validateTheme(stored) : "default"; }; -// Async theme loader -export const loadTheme = async (themeName: string): Promise => { +export const loadTheme = (themeName: string): void => { const validTheme = validateTheme(themeName); - // Store theme for next page load - storeTheme(validTheme); + // Store theme + localStorage.setItem("workspace-theme", validTheme); // Remove existing theme - const existingTheme = document.getElementById("workspace-theme"); - if (existingTheme) { - existingTheme.remove(); - } - - try { - // Load theme CSS - const response = await fetch(`/themes/${validTheme}.css`); - if (!response.ok) { - throw new Error(`Failed to load theme: ${response.status}`); - } - - const css = await response.text(); - - // Apply theme - const styleElement = document.createElement("style"); - styleElement.id = "workspace-theme"; - styleElement.textContent = css; - document.head.appendChild(styleElement); - - // Update data attribute - document.documentElement.setAttribute("data-theme", validTheme); - } catch (error) { - console.error("Failed to load theme:", error); - - // Fallback to default theme if current theme fails - if (validTheme !== "default") { - await loadTheme("default"); + document.getElementById("workspace-theme")?.remove(); + + // Apply theme (skip for default) + if (validTheme !== "default") { + const css = THEME_CONTENT[validTheme]; + if (css) { + const style = document.createElement("style"); + style.id = "workspace-theme"; + style.textContent = css; + document.head.appendChild(style); } } + + // Set data attribute + document.documentElement.setAttribute("data-theme", validTheme); };