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);
};